# Enforce before acting

Retrieval tells an agent what the policy is; `check_action` stops it from breaking it. Call it right
before a side-effectful step and branch on the verdict: proceed on `allowed`, stop and cite the rule
on `blocked`, escalate on `needs_human`. There are two natural places to wire it in. See
[how gnt works](/docs/concepts) for why enforcement has to sit outside the model's own reasoning.

## Claude Code PreToolUse hook

A `PreToolUse` hook runs before every tool call. `gnt` ships one: it checks the guarded,
world-changing tools against your rules and blocks the call on `blocked` or `needs_human`, so
enforcement doesn't depend on the model choosing to ask.

```bash
gnt hooks install
```

This writes a `PreToolUse` entry to `~/.claude/settings.json`, wired to `gnt hooks check-action`.
By default it guards `Bash`, `Write`, `Edit`, `NotebookEdit`, and `WebFetch` — reads (`Grep`,
`Glob`, `Read`, `WebSearch`) pass through untouched, since `check_action`'s retrieval + LLM-judge
round trip has real latency and there's nothing to enforce on a read. Widen or narrow the matcher
in `~/.claude/settings.json` after installing if you want to gate an installed connector's own
`mcp__*` tools too.

Most checks retrieve your rules and run a real model judgment against them, so expect a couple of
seconds on a genuine side-effectful call — that's the cost of grounding a verdict in your actual
rules rather than guessing. A small set of routine, read-only commands (a bare `ls`, `pwd`, `git
status`, `git diff`, `git log`, or `git branch` with no arguments) skip that round trip entirely and
return in milliseconds, still logged, still going through gnt — everything else, including the same
commands with flags or arguments, gets the full check.

If `check_action` can't complete at all (network error, gnt unreachable), the hook fails open by
default — the tool call proceeds rather than stalling your session. Pass `--fail-closed` at install
time to block instead whenever the check itself fails, not just on a real `blocked` verdict. A real
`blocked` verdict always blocks. `needs_human` follows the returned `enforcement_action`: Strict
requires a human, while Observe can return `proceed` after the uncertainty is durably audited.
Fail-open only governs what happens when gnt couldn't be reached to answer at all.

```bash
gnt hooks uninstall   # remove it
```

No harness besides Claude Code has a pre-execution hook point gnt can install into today. Everyone
else uses the system prompt pattern below.

## System prompt instruction

For agents without a hook layer, add a standing policy-check instruction to the system prompt. Pair
it with the hook above for defense in depth: the prompt guides the model, the hook enforces
regardless.

```text
Before any action that sends a message, moves money, deletes data, or is
otherwise hard to undo, first call the check_action tool with a plain-
English description of what you are about to do.

- enforcement_action "proceed": proceed unless verdict is "blocked".
- enforcement_action "block": do not proceed. Tell the user why, citing the rule.
- enforcement_action "require_human": stop and ask a human to approve.

Never treat a missing/unclear directive or a blocked verdict as permission.
```
