Case Study: Governing a coding agent

Key facts

Role
Author and operator, July to August 2026.
What it is
A control layer around the coding agents I work with daily. Ten hooks that run inside the agent harness, a memory store built as a context budget, and one written contract shared by two different agents.
Form
Python hooks on PreToolUse, PostToolUse, SessionStart and UserPromptSubmit; a file-per-fact memory store with a routing index; a self-test that exercises every guard with synthetic payloads.
Design rule
Every hook exists because of a specific recorded failure. None was added speculatively.
Boundary
Hooks catch mechanically detectable mistakes only. They do not catch a wrong judgement, and this page ends with a worked example of one they missed.
Claimed usage
None. Personal working infrastructure, not a product, with no user, buyer, or adoption claim.

The problem

An agent that can edit files, commit, open a pull request and merge it is as safe as the checks around it, not as safe as the instructions given to it. Written rules degrade in a predictable way: they get longer, they contradict each other, and a long instruction file loses adherence exactly as it grows.

So my working rule is that a recurring mistake which a machine can detect belongs in a hook, not in another paragraph of prose. Prose is for judgement. Mechanisms are for the rest.

What each guard is for, and what created it

guard_git.py turns four written rules into mechanisms on PreToolUse. It blocks force-push, history rewrite and wide recursive deletes. It runs the repository's own validators before a commit and blocks on failure, after two CI breakages reached main. It scans commit messages for local paths, credentials and external references. And it refuses gh pr merge unless the checks are actually green, because --auto merges immediately when a repository has no branch protection and cannot be relied on to hold a pull request.

check_python_lines.py measures line length in characters after an edit. It exists because local flake8 on this machine silently fails to emit E501, so a change can pass local lint and fail the CI lint job. It caught that class of failure after a real one.

instructions_drift.py compares the two global instruction files that two different agents read. Neither imports the other, so a rule added to one is simply absent from the other. They drifted twice before the check existed, silently both times, and both times the drift was found by accident.

context_file_size.py holds instruction files under the documented limit for a file that is loaded whole. It exists because I split two oversized contracts by hand in July, and both regrew past the limit within three weeks. The first pass used a judgement — the file has grown — and had no number. The hook has the number.

memory_recall.py scores the prompt against every memory file and names the relevant ones before the answer starts. Measured before it existed: the store held 67 files and 283 KB, of which only the 11.9 KB index reached context automatically. The remaining 96% existed only if the agent happened to open it, and every index line described what a file contained while none said when to open it. What looked like forgetting was retrieval that never happened.

closure_needs_replacement.py refuses to let a decision to close something be written into memory alone, without naming what replaces it. The store recorded subtraction faithfully and addition almost never, so each session narrowed the world slightly and nothing widened it. The hook does not argue with the closure. It only requires the next door to be named, or the absence of one to be stated plainly, which is itself worth recording.

outreach_pulse.py reports, at the start of every session, the counter that only moves when I act outside the machine. Commit counts are visible in every repository, every pull request and every CI run. The number that matters commercially is visible nowhere unless something puts it there. The hook states it without asking anything, because the asking is what makes a person stop reading it.

The rule that makes the layer work

A hook failure is evidence. It is never routed around, and the hook is never disabled to get an action through — the cause gets fixed instead.

This sounds obvious and is the part that usually fails. A guard that can be switched off under deadline is decoration. Twice during the period covered here a guard blocked something I intended to do; both times the block was correct, and the fix was to satisfy the condition rather than to remove the check.

selftest.py exercises every guard with synthetic payloads on stdin, so no guarded command reaches a real shell during testing. It runs after any hook is edited.

Context as a budget

The memory store holds one fact per file, and the index that loads automatically carries one line per file saying when to open it rather than what is in it. The index has a hard ceiling: content past the limit is dropped with no error and no truncation notice, so an index that grows past it fails silently and looks exactly like an index that works.

Measured fill is tracked and stated. That is the entire point of a budget rather than a guideline.

What it does not do

Hooks catch a class of mistake that a machine can recognise. They do not catch a wrong judgement, and the difference matters.

A worked example from 20 August 2026. The agent found that five of eight deployed endpoints returned a failed task state for a minimal probe where three returned a completed one, and reported this as a defect worth fixing. No hook objected, because nothing mechanical was wrong. I asked what exactly was broken. Measurement against the scoring rubric that third parties actually apply showed the difference cost nothing: uptime scored full on all eight, and the grade tracked the number of declared skills rather than the task state. The recommendation was withdrawn and the memory record corrected.

The gap between an anomaly and a defect is a judgement, and a question closed it. That is the division of labour this layer is built around: mechanisms for what repeats, attention for what does not.

Reading it

The site's own instruction contract is public: AGENTS.md. It carries the same discipline at the repository level, including the rule that any score published here must trace to a documented generator — see the conformance report and the script that produces it.

The hooks themselves are personal working files and are not published; this page describes what they enforce and why, not what passes through them.