← All docs

How it works

How agentjail intercepts tool calls, evaluates them offline, and returns a verdict before any command reaches the shell.

agentjail sits at the boundary between your coding agent and the tools it can call. It intercepts every outgoing tool call, evaluates it against your policy, and returns a verdict before the call is handed off to the shell, filesystem, or network.

The tool-call boundary

Coding agents expose a lifecycle hook called PreToolUse. This hook fires before a tool call executes, giving agentjail a chance to inspect it and either pass it through, block it, or ask the user to confirm.

  your agent ──tool call──▶  agentjail-hook ──▶  agentjail-daemon ──allow──▶  shell / files / network
 (Claude Code)               (PreToolUse hook)    (OPA / Rego)      └─ask───▶  user prompted
                                                                     └─deny──▶  blocked
                                                                                (e.g. rm -rf ~/.ssh)

The agentjail-hook binary (installed in ~/.agentjail/bin/) receives the tool-call payload and forwards it over a Unix socket to the persistent agentjail-daemon process. The daemon keeps OPA warm and evaluates your Rego policy in under 5 ms, then returns the verdict. The hook prints the decision to the agent and exits.

Nothing runs until agentjail has evaluated the call. If the call is denied, the command never reaches the shell.

Integration status: Claude Code is fully supported. Codex and Cursor integrations are not yet available.

What agentjail evaluates

Each tool call arrives as a structured object. The fields your policy can inspect include:

  • input.tool_name: the name of the tool being called (for example, "Bash").
  • input.tool_input: the arguments for that tool (for example, {"command": "rm -rf ~/.ssh/"}).
  • input.hook_event: the lifecycle hook name (always "PreToolUse").
  • input.session_id: the agent session identifier.
  • input.cwd: the agent’s current working directory.

For a Bash tool call the shell command is at input.tool_input.command. For Write and Edit calls the target file is at input.tool_input.file_path. MCP tools surface as input.tool_name values like mcp__server__tool.

Your policy rules inspect these fields and decide whether to allow, ask, or deny. See The policy model for how rules are written.

Offline evaluation

Evaluation runs entirely on the local machine. There is no network call at decision time, no external service, and no model in the decision loop. This keeps verdicts fast and deterministic: the same tool call produces the same verdict every time, regardless of network conditions or service availability.

Allow, ask, and deny

There are three possible verdicts:

  • allow — the call proceeds normally.
  • ask — the hook prompts the user to confirm before proceeding.
  • deny — the call is blocked; the command never reaches the shell.

A call is denied if any deny rule in your policy fires. If an ask rule fires (and no deny rule does), the user is prompted. If neither fires, the call is allowed.

See Verdicts for the full semantics and what the agent receives on each outcome.

What happens on a denial

When a call is denied, agentjail returns a structured block message to the agent and exits with status code 2 (the Claude fast-block convention). The agent receives the denial reason and stops rather than proceeding. The command is never handed to the shell.

You can test this manually while the daemon is running:

echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"rm -rf ~/.ssh/"}}' \
  | agentjail-hook

You can also unit-test Rego policies directly with opa test.

Why this approach

Enforcing policy at the tool boundary, offline, has a few practical consequences:

  • No false sense of safety from latency. There is no window between “agent decides to act” and “policy is checked.” The check is synchronous and happens before execution.
  • Auditable. Every rule is plain text you can read, diff, and version alongside your project.
  • Works without network. Useful in air-gapped environments, strict CI, or anywhere an outbound call would be blocked.

Next steps