Two layers, different authority

An instruction written into a rules file is followed when the model understands it and remembers it. A long session, a misread, or a confidently wrong turn can wear that down. A permission set in the tool's own configuration is different: it is checked by the harness before the command runs, not by the model, so no amount of reasoning gets around it. The written rules carry intent everywhere. The gate holds the line on the few actions where a single slip is expensive.

Reads that are refused, not prompted

Some files are denied outright rather than prompted for: .env files, private keys in .pem, .pfx, and .p12, SSH private keys, .git-credentials, and .netrc. The agent cannot open them at all. A secret it never reads is one it cannot paste into a commit, print to a log, or hand to a network call, no matter what a fetched web page tells it to do.

Actions that stop for a yes

A second set is not blocked but paused. Every git or GitHub change that alters a repository (commit, merge, push, tag, reset), every command that reaches another machine (ssh, scp, sftp, rsync), and every destructive local command aimed at one named target (deleting a single file, dd, mkfs) waits for an explicit yes. These are the moves that are hard to undo, and pausing turns each into a decision instead of a side effect. A yes to one is not a yes to the next; each stop is its own.

Deletes that are refused, not paused

A delete that can reach more than its named target is refused outright, with no prompt to approve: a recursive delete, a delete fed a list of paths by another command, a delete aimed at a path held in a variable, and git clean. In each of these the command text does not show what will actually be removed, so a person approving it is approving a guess.

Folders are deleted only through one small tool that takes exact paths and checks each one on disk before touching anything. It removes a folder only if it sits inside the agent's own session temp area, or if it is a build or cache folder (such as build, dist, or __pycache__) inside a repository that git already ignores. It refuses everything else, including a repository's top folder, anything holding a .git folder, and links that point somewhere else.

This rule came from a real loss. A cleanup command meant for two cache folders matched every folder in a repository and deleted them. The gate flagged it, the prompt appeared, and I approved it, because the command read correctly. Nothing in its text showed that the shell would ignore the filter. A prompt can only show what a command says, not what it will do, so for deletes that can reach past their named target, a refusal and a tool that checks every path on disk replace the prompt. The field note has the full account.

The gap in the gate, and the hook that narrows it

The built-in matcher checks the start of a command, so a chained call slips through. cd project; git push does not begin with git push, so a name-based gate would wave it along. A small script runs before every shell command and reads the whole line rather than its first word. It refuses a bulk delete anywhere in the chain, then asks the moment any other gated command appears. It fails loud: if it cannot read the input it is handed, it refuses the call, and if it hits an error while checking a command that looks like a delete, it refuses that too.

What it still cannot see is a command that is not on its list. A network call folded into a one-line python -c never names curl or git, and a delete inside a script file never appears in the command line at all, so nothing catches either here. That is the boundary where the written rules take over, the Trifecta Rule in particular, because they cover the cases no gate can name.

A ceiling on delegation

A helper model can be given a task, but it is capped so it cannot spawn helpers of its own. One layer of delegation is a tool. Unbounded nesting is a way to lose track of what is running, on which branch, against whose approval, so the depth is fixed at one on purpose.