# Your first rule

> Anatomy of a deny rule, how to write one that blocks a command, where to put it, and how to test it.

A policy is a Rego file with one or more `deny` rules. Each rule inspects an
incoming tool call and fires when its conditions hold. If any rule fires, the
call is blocked and the message is returned to the agent.

This page walks through writing a single rule from scratch, loading it, and
confirming it works.

## Anatomy of a deny rule

```rego
deny[msg] {
  input.tool_name == "Bash"
  contains(input.tool_input.command, "/.ssh/")
  msg := "Blocked: command targets sensitive path ~/.ssh/"
}
```

Breaking that down:

- `deny[msg]` declares that this is a deny rule that produces a message.
- The body is a conjunction: every line must hold for the rule to fire.
- `input.tool_name` is the name of the tool the agent is trying to call.
- `input.tool_input` holds the tool's arguments. For a Bash call, `input.tool_input.command` is the shell command string.
- `msg` is the string returned to the agent when the rule fires.

If any condition does not hold, the rule does not fire. agentjail only blocks
the call if at least one rule produces a message.

See [The input schema](/docs/policies/input-schema) for the full shape of
`input` and how fields vary by tool.

## Write a rule

Place policy files in `~/.agentjail/rules/`. The daemon loads every `*.rego`
file in that directory (non-recursive) on startup and on reload.

Create a file called `~/.agentjail/rules/my_policy.rego`:

```rego
package my_policy

deny[msg] {
  input.tool_name == "Bash"
  contains(input.tool_input.command, ".env")
  msg := "Blocked: command reads or modifies .env file"
}
```

The `package` name should match the file name by convention. The rule fires
whenever a Bash command string contains `.env`.

## Load and list policies

After saving the file, confirm agentjail sees it:

```sh
agentjail policy list
```

You should see `my_policy` alongside the built-in rules.

No daemon restart is needed. Running `agentjail policy enable <name>` or
`agentjail policy disable <name>` sends SIGHUP and the daemon hot-reloads and
recompiles all rules atomically. If the daemon was not running when you saved
the file, it will pick it up on next start.

## Test it

The fastest end-to-end test is to pipe a PreToolUse JSON payload directly to
the hook (the daemon must be running):

**Deny case:** confirm the rule fires.

```sh
echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"cat .env"}}' \
  | agentjail-hook
```

Expected output:

```json
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Blocked: command reads or modifies .env file"}}
```

Exit code is `2` on deny.

**Allow case:** confirm a harmless call passes through.

```sh
echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo hello"}}' \
  | agentjail-hook
```

Expected: exit code `0`, `permissionDecision` is `"allow"`.

For more on writing test cases, see [Testing policies](/docs/policies/testing).
For the full set of fields available in `input`, see [The input schema](/docs/policies/input-schema).
For the evaluation model (allow vs. deny semantics), see [The policy model](/docs/concepts/policy-model).
