Pitot
Guides / 05 — Build: guarded files

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

A skill / prompt rule

"Never edit generated files."

  • Advises the model.
  • Competes with everything else in context.
  • Can be forgotten, summarized away, or overridden.
  • Compliance is probabilistic.
A Pitot Controller

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

Bench demo / guarded filesSimulated · illustrative

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

Start
01 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:

Live run
01 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

Honest scope

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.