← 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  (Claude Code)

      │  tool call

  agentjail-hook  (PreToolUse hook)

      │  Unix socket

  agentjail-daemon  (OPA / Rego, ~8 ms median)

      ├─ allow ─▶  shell / files / network
      ├─ 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 ~8 ms median, 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, Codex, and Cursor are all fully supported. agentjail install auto-detects whichever agents are present and wires the hook for each.

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.

Config overlay

The daemon loads ~/.agentjail/policy.yaml at startup and re-injects it into OPA as data.agentjail.config on every SIGHUP (no restart needed). The config carries MCP allowlists, extra path deny patterns, disabled rule IDs, and other tuning values that Rego rules read at evaluation time. The decision cache is invalidated on every reload, so policy changes take effect immediately.

Request paths and cwd are canonicalized (symlinks and .. resolved) before evaluation, so rules always operate on real absolute paths.

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.

The resolver picks the most restrictive candidate: deny > ask > allow. If no candidate fires at all, the default is ask (fail-safe: unknown calls escalate to the user rather than silently proceeding).

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.

Beyond hooks

The hook layer is cooperative: the agent must call the hook, and the hook must pattern-match the command. Shell tricks like variable expansion, eval, or non-shell interpreters can bypass hook-level protection. agentjail provides stronger isolation tiers for these cases:

  • OS-native sandbox: wraps the agent in the kernel sandbox so subprocess spawns and shell tricks are caught at the syscall level.
  • Isolation tiers: the full spectrum from hooks to containers to kernel-level enforcement.

Next steps