← All docs

The policy model

How agentjail policies are written, matched against tool calls, and evaluated with candidate→resolver→decision semantics.

agentjail policies are written in Rego, the same language used by Open Policy Agent. Each policy file contributes candidate verdict entries that are resolved to a single decision by a central resolver.

The shape of a tool call

Every evaluation receives a structured input describing the call the agent wants to make:

{
  "hook_event": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf ~/.ssh/" },
  "session_id": "abc123",
  "cwd": "/home/user/project"
}

Note: The hook bridge sends hook_event_name in its stdin payload; the daemon maps this to hook_event in the OPA input document. Use input.hook_event in your Rego rules.

Candidate → resolver → decision

Every policy file (core, library, and custom) contributes entries to a shared partial rule set called candidate. A file in resolver.rego is the sole producer of data.agentjail.decision: it reads all candidates, builds an effective_candidate set (filtering out any disabled_rules), and picks the most restrictive:

deny > ask > allow

Within the same priority tier, the candidate with the lexicographically smallest rule_id wins, making evaluation deterministic for cache hits and audit replay.

When no candidate fires at all, resolver.rego returns a safe default of ask (fail-safe, not silent allow).

A rule that fires

A candidate rule matches when its body holds. Each candidate entry is an object with action, rule_id, and reason:

package agentjail

import future.keywords.if
import future.keywords.contains

candidate contains r if {
  input.tool_name == "Bash"
  contains(input.tool_input.command, "/.ssh/")
  r := {
    "action":  "deny",
    "rule_id": "command_policy/no-ssh-access",
    "reason":  "Blocked: command targets sensitive path ~/.ssh/",
    "impact":  "would expose SSH private keys",
  }
}

Breaking that down:

  • package agentjail: all hook-path rules must use this package.
  • candidate contains r if { ... }: the partial rule entry pattern. Never declare decision = ... in your own files; resolver.rego owns that.
  • r is an object with a mandatory action, rule_id, and reason (plus an optional impact).
  • The body is a conjunction: every line must hold for the candidate to be added.

Three-way verdicts

The resolver produces one of three verdicts:

VerdictMeaning
allowTool call proceeds immediately.
askThe agent is prompted to confirm before continuing.
denyTool call is blocked; the agent receives a rejection message.

The fail-safe default (no candidates fire) is ask, not allow. This means an unknown or unexpected tool call escalates to the user rather than silently proceeding.

Rule IDs and namespacing

Every rule carries a namespaced rule_id:

PrefixUsed by
file_policy/…Built-in file path rules
command_policy/…Built-in shell command rules
mcp_policy/…Built-in MCP server rules
library/…Opt-in library rules
custom/<stem>/…Your own rules (enforced by agentjail policy add)

The custom/<stem>/<rule> namespace is reserved for user-authored rules installed via agentjail policy add. For example, a file named my_rule.rego must emit only IDs like custom/my_rule/no-something.

Disabled rules

Any rule except the locked self-protection set can be suppressed by adding its rule_id to disabled_rules in ~/.agentjail/policy.yaml, or by running agentjail policy disable <rule_id>. The resolver drops disabled candidates before resolving the verdict. See Configuration for the full schema.

Config overlay

The daemon loads ~/.agentjail/policy.yaml, merges it over built-in defaults, and injects it into OPA as data.agentjail.config. Rules read allowlists, blocklists, and tuning values from there. The config is re-injected on SIGHUP (the daemon hot-reloads without restart), and the decision cache is invalidated on every reload.

Evaluation semantics

  • Rules are evaluated locally: there is no network call at decision time.
  • resolver.rego collects all candidate entries from all loaded policy files and picks the most restrictive.
  • The default when nothing fires is ask (fail-safe).
  • Policies are plain text you can read, diff, and version-control alongside the rest of your project.

See Installation to get a working policy bundle on your machine, and Writing your first rule to author your own candidate rules.