Guide 05 / Build — deterministic replacement
Turn advice into enforcement
Every team has the instruction somewhere — a CLAUDE.md line, a skill, a system prompt: "never edit files under src/generated/." It works until the model forgets, and the model is allowed to forget. This build replaces that advice with about twenty lines of deterministic code the host must wait for.
01Advice vs enforcement
"Never edit generated files."
- Advises the model.
- Competes with everything else in context.
- Can be forgotten, summarized away, or overridden.
- Compliance is probabilistic.
if path.startswith("src/generated/"): deny
- Decides while the host is blocked waiting.
- Runs outside the model's context entirely.
- Same input, same answer, every time.
- Compliance is structural.
Keep the skill if you like — it makes the agent cooperative. The Controller makes the rule true either way.
02See it on the bench
The deny is not an error — the agent receives the reason as the blocked tool result and can route around it (edit the schema, regenerate) instead of fighting it.
03Scaffold from the shell-policy template
pitot init --template shell-policy --language python --dir guarded-files
a runnable Controller project plus one tenant fragment under .pitot/conf.d/
The template ships allowing everything and denying a canary string. You replace the canary check with your path rule.
04The Controller: your rule, as code
A Controller reads one correlated request and returns exactly one response. The decision logic is the part you own — here it is a path prefix check:
# guarded-files/main.py — your code, sketched import json, sys PROTECTED = ("src/generated/", "dist/") for line in sys.stdin: request = json.loads(line) # type: control.requested command = request.get("data", {}).get("command", "") hit = next((p for p in PROTECTED if p in command), None) print(json.dumps({ "pitot_version": "1", "type": "control.response", "controller_id": "guarded-files", "action_id": request["action_id"], "outcome": "deny" if hit else "allow", "message": f"{hit} is generated — edit the source schema and regenerate." if hit else "", }), flush=True)
Pitot validates the controller identity, action id, deadline, schema, and single-response rule before carrying the answer back — your code only has to be right about the rule.
05The fragment: fail closed
# .pitot/conf.d/guarded-files.yaml controllers: shell: id: guarded-files command: ["python3", "main.py"] dir: "guarded-files" deadline_ms: 2000 on_timeout: deny on_unavailable: deny
on_timeout: deny and on_unavailable: deny are the enforcement guarantee: if your Controller crashes or stalls, protected actions do not silently sail through. A request kind has one owner, so if another tool already claims shell, discovery fails loudly naming both files — merge your rules into one Controller instead of racing.
06What the agent sees
Run any supported agent behind it and watch the decisions:
pitot dev --host kimi -- kimi -p "Update src/generated/api.ts"
the deny reason is returned to the agent as the blocked tool result — it reads your message and adjusts course
This guards the host's blocking shell boundary — the path every supervised agent's tool execution crosses. It is a workflow rail for cooperative agents, not a filesystem sandbox; defense in depth still belongs at the OS layer.