Keep Neo out of somewhere
Block reads or writes under a directory, against a host, or on a particular tool — whichever route it would take.
Ask me first
Hold a call until you approve it, for the operations where being wrong is expensive.
Give every task a standing fact
Inject context at the start of every run, so nobody has to remember to mention it.
Do not finish until…
Send Neo back when it tries to end a task before something you require has happened.
How a rule runs
Take one concrete call: Neo runscat vendor/config.yaml through run_command.
1
The event fires
Neo calls
run_command with cat vendor/config.yaml. This raises a PreToolUse event carrying the tool name and its arguments.2
The matcher is checked
Every enabled PreToolUse hook is checked against the call. This one matches on path:
3
The action is applied
The matcher matched, so its action runs:
4
Neo continues
The reason comes back as the tool’s own result, not as a crash. Neo works around it: it tries a different approach, asks you, or moves on to something else.
Creating a rule
Ask Neo in plain language. It proposes the rule, and every create, enable, disable, and delete waits for your explicit approval before it takes effect — a standing rule is not something an agent gets to set for you on its own say-so.Listing your rules is the one action that does not need approval. Reading policy changes nothing.
Events
Choosing a matcher
The matcher decides whether a rule holds. This is where rules most often go wrong: one that looks right can quietly cover nothing, or cover one route while leaving three others open. The same action reaches a file through several tools, under several argument names —read_file carries path, write_file carries file_path, and run_command hides it inside command.
A
path ending in a slash means that directory and everything beneath it. vendor/ covers vendor, vendor/lib/x.go, and rm -rf vendor alike. Write vendor/** if you want the contents but not the directory itself.
An absent matcher field matches anything; an empty matcher ({}) matches everything within its scope.
PreToolUse
Good for blocking a call before it runs, or sending it back to be retried with different arguments. You say:never touch anything under the vendor directoryRule created:
PostToolUse
Good for attaching guidance to a result Neo just produced. You say:after any dependency install, remind me to check the lockfile diffRule created:
PostToolUseFailure
Good for adding guidance when a call fails, the same way PostToolUse does for a success. You say:when a deploy fails, remind me to check rollout status before retryingRule created:
PermissionRequest
Good for holding a call until a person approves it. You say:always ask before writing to productionRule created:
deny nor allow. To block a call outright, use a deny on PreToolUse. To stop being asked about something, narrow the rule’s matcher rather than adding a second rule to waive it — a waiver cannot override an approval another rule already requires.
SessionStart
Good for giving every task in a workspace, or for a user, a standing fact to start from. You say:remember that our payments and billing services are internet facingRule created:
SubagentStop
Good for sending a subagent back to finish something before it hands control back. You say:don’t let a subagent stop without writing a summary of what changedRule created:
Stop
Good for holding the task open until something you require has happened. You say:don’t let a task finish without running the test suiteRule created:
Scopes
Scopes are additive: every matching hook is evaluated, and any single deny wins. A narrower scope can never loosen a rule set at a broader one, so a user or thread rule cannot reopen something an org rule denies.
Repository rules are enforced like any other, but cannot be created or changed by asking Neo during a task. The repository a task is working on is context, not proof of write access to it — so ask for a user or org rule instead.
Actions
An action paired with an event that does not read it is refused when you create it, and ignored if one is already stored — a rule that could not decide anything should never look like it is enforcing something.
How update_input actually behaves
How update_input actually behaves
It does not silently rewrite arguments. The call is blocked and Neo is handed the corrected arguments with an instruction to call the tool again using them; that retry then goes through validation and approval like any other call.This matters for approval: arguments are approved before a rule could rewrite them, so a silent rewrite would execute something you never saw. The cost is one extra model round trip, and Neo may choose to do something else instead of retrying.
What judge does today
What judge does today
judge hands the decision to an external policy evaluator. Until one is configured it does not evaluate: an ordinary rule using it is skipped, and a locked organisation rule using it fails closed — it blocks, rather than quietly permitting something an administrator meant to gate.Limits that keep a rule from running away
Limits that keep a rule from running away
- A
StoporSubagentStopdenial is bounded bymaxDenials, and by a global ceiling across all rules, so no rule can hang a task indefinitely. Each run gets its own budget. - Added context is capped in total, shared between SessionStart and post-tool text, so policy cannot crowd out the task.
- A tool argument too large or too deeply nested to inspect fully is reported rather than silently skipped.
- Patterns are matched without regular expressions, so no rule can be written that is expensive to evaluate.
Managing rules
Ask Neo directly, the same way you created the rule.- “what rules do I have set?” lists them, scoped to what you can see.
- “disable the vendor rule” turns one off without deleting it.
- “delete that rule” removes it for good.
When a rule does not fire
It matched one tool but not another
It matched one tool but not another
The matcher is probably an
args condition on a field only one tool uses. Move it to path, which is checked against every string in the call.It never matches anything
It never matches anything
A
path with no wildcard matches only that exact string. Name the directory with a trailing slash (vendor/) to cover it and everything beneath it.It was created but does nothing
It was created but does nothing
Check the event and action pair. Actions only work on the events that read them — an
ask belongs on PermissionRequest, additional_context on SessionStart or the post-tool events.The rule is right but a broader one wins
The rule is right but a broader one wins
Scopes are additive and deny wins. An organisation rule cannot be loosened by a user or thread rule.
Good to know
- A denied tool call does not fail the task. Neo reads the reason back as the tool’s own result and works around it.
- Hooks apply inside subagents too, not just the main agent, and survive suspend, approval, and resume.
- A rule set for
chatdoes not apply to a task that arrived from Slack or GitHub unless you say so — usesourcedeliberately.

