← All posts
By The agentjail team

Why we use policy, not prompts

Most agent safety relies on system prompts or LLM-based evaluation. We chose deterministic Rego policy instead. Here is what that buys you.

Two paths compared: system prompts produce probabilistic results in 2 seconds, while Rego policy produces deterministic verdicts in ~8 ms median.
System prompts are probabilistic. Policy is deterministic. You want both -- but you need the second.

There are two ways to make a coding agent safer.

One is to ask the model to be careful. Add instructions to the system prompt: “do not delete important files,” “ask before running destructive commands,” “never access credentials.” This is what most agent frameworks do today. It is easy to implement and it sort of works.

The other is to enforce policy at the boundary, outside the model, on the actual tool calls. This is what agentjail does.

We think the second approach is fundamentally better. Here is why.

The model is not the right enforcement point

A system prompt is a suggestion. A well-crafted one, backed by RLHF and careful training, but a suggestion nonetheless. The model can be jailbroken. It can hallucinate compliance while violating the spirit of the instruction. It can interpret “do not delete important files” differently than you would. It can decide that a file is not important.

More subtly, model-based safety degrades under load. Long conversations accumulate context that pushes safety instructions further from the attention window. Complex multi-step tasks create situations the system prompt never anticipated. And every model update can silently change how instructions are interpreted.

None of this is the model’s fault. It is the wrong tool for the job. Access control should not be probabilistic.

What deterministic policy looks like

An agentjail rule is a Rego expression that evaluates a tool call and returns a verdict: allow, deny, or ask. The evaluation is deterministic. The same input produces the same output every time, regardless of conversation length, model version, or phase of the moon.

deny[msg] {
  input.tool == "Bash"
  input.command_binaries[_] == "curl"
  not startswith(input.tool_input.command, "curl -fsSL https://")
  msg := "Blocked: curl command does not use safe flags"
}

This rule fires or it does not. There is no “usually” or “depending on context.” There is no temperature parameter that affects whether your credentials get exposed. The rule is a few lines of plain text that anyone on the team can read, review, and version control.

Five things policy gives you that prompts cannot

1. Auditability. When agentjail blocks a call, the log shows exactly which rule fired, what it matched, and why. Try auditing a system prompt rejection. The model said no. Why? “Based on my guidelines.” Which guideline? Silence.

{"ts":"2026-06-16T14:23:01Z","verdict":"deny","rule":"library/no-curl-unsafe-flags","tool":"Bash","command":"curl http://169.254.169.254/latest/meta-data/iam/","matched_binary":"curl","session":"abc123"}

2. Testability. agentjail rules have unit tests. You write a tool call input, assert the expected verdict, run the suite. A rule that should block rm -rf / but does not is a failing test, caught before deployment. How do you unit test a system prompt? You do not. You run the model and hope.

test_blocks_imds_curl {
  result := deny with input as {
    "tool": "Bash",
    "tool_input": {"command": "curl http://169.254.169.254/latest/meta-data/"},
    "command_binaries": ["curl"]
  }
  count(result) > 0
}

test_allows_registry_curl {
  result := deny with input as {
    "tool": "Bash",
    "tool_input": {"command": "curl -fsSL https://registry.npmjs.org/"},
    "command_binaries": ["curl"]
  }
  count(result) == 0
}

Run agentjail policy test. A failing test means a broken rule — caught before any agent runs it.

3. Composability. Rules stack. A team deploying to AWS might layer library/no-aws-destructive on top of the defaults, disable no-curl-unsafe-flags because their agents legitimately run curl pipelines, and add a custom rule that blocks force-pushes outside business hours. That configuration is three lines in a policy file. A system prompt is a monolith. Change one sentence and the interaction effects are unpredictable.

4. Speed. OPA evaluates Rego rules in microseconds. agentjail’s median is ~8 ms on warm evaluations for the full decision pipeline (parse, evaluate, log, respond). An LLM-based safety check adds seconds of latency per tool call. Multiply by the hundreds of tool calls in a typical agent session and you have added minutes of wall-clock time to every task.

5. Consistency across models. The same agentjail policy works identically whether the underlying agent is Claude, GPT, Gemini, or a local model. You write the rules once. Switch models and nothing changes. System prompts need to be re-tuned per model, per version, sometimes per task.

“But policy is rigid”

The most common objection: deterministic rules cannot handle nuance. The model “understands” that rm -rf node_modules is safe but rm -rf / is not. A regex rule blocks both or neither.

This is true of bad rules. It is not true of policy in general.

agentjail’s structured command parsing (added in v0.2.0) gives rules access to parsed binary names and command structure. A rule can allow rm when the target is inside the project directory and deny it otherwise. It can allow curl to known package registries and block it everywhere else. It can allow git push to the current branch and deny force-pushes to main.

deny[msg] {
  input.tool == "Bash"
  input.command_binaries[_] == "git"
  contains(input.tool_input.command, "push")
  contains(input.tool_input.command, "--force")
  msg := "Blocked: force push requires explicit approval"
}

The expressiveness ceiling of Rego is high. You can match on file paths, environment variables, command structure, network targets, and combinations of all of them. The rules we ship cover the obvious cases. The rules you write cover your specific cases.

And for the genuinely ambiguous calls — the ones that depend on intent, not structure — agentjail has the “ask” verdict. The call is paused, the user is prompted, and their decision is logged. This is not the model guessing whether you would approve. It is you actually approving.

The layering argument

We are not saying system prompts are useless. They are a good first layer. An agent that has been instructed to be careful will attempt fewer dangerous things, which means fewer tool calls for agentjail to evaluate, which means fewer interruptions for the user.

But the system prompt is the seatbelt. agentjail is the guardrail. You want both. You do not want to rely on the seatbelt to keep the car on the road.

What this costs

Deterministic policy has real costs:

Rules need maintenance. New tools, new agents, new attack surfaces mean new rules. Someone has to write them. We ship default packs and will keep expanding them, but your team’s specific constraints are yours to encode.

False positives are your problem. A model can use context to distinguish a safe rm from a dangerous one. A rule matches or it does not. The structured parsing in v0.2.0 fixed the class of false positives caused by matching binary names in file paths, but they still happen. When they do, you adjust the rule.

There is a learning curve. Rego is not hard, but it is another thing to learn. We have tried to make the defaults good enough that most users never write a rule, but power users will need to learn the syntax.

We think these costs are worth paying. A model that “usually” blocks credential access is not safe. It is lucky.

The alternative is trusting a probabilistic system with deterministic consequences. For personal projects, that might be fine. For anything that touches production systems, credentials, or other people’s code, “usually” is not a safety posture. It is an optimistic default.

$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

  agentjail DENY  library/no-curl-unsafe-flags
  rule:  curl to non-https or without safe flags
  cmd:   curl http://169.254.169.254/...
  took:  2.1ms

Tool call blocked. The agent was notified.
curl -fsSL https://agentjail.io/install.sh | sh

Policy docs | Write your first rule