A little while back I left a comment on a post by Dr. Maryam Miradi about spec-driven AI engineering — the practice of writing down, in real detail, what an agent is supposed to build before it writes a single line of code. I meant every word of that comment. Writing the spec first stopped being optional a while ago. It's the difference between directing an engineer and hoping a competent one shows up in the diff.
But somewhere over the last year, Claude Code stopped being something I reviewed line by line and became something that ran for hours at a stretch — reading a spec, breaking it into tasks, writing the code, running its own tests, deciding for itself whether to move on to the next step. Once that happened, I noticed the spec was answering the wrong question for the failure modes I was actually seeing. A spec is very good at telling a model what to build. It has almost nothing to say about how the model should behave while it's building it — what it's allowed to skip, what "done" is allowed to mean, whether it gets to grade its own homework.
So I wrote instructions for that too. Never commit straight to the main branch. Never mark a test as passing without actually running it. Never skip a step because it looks trivial this time. All reasonable rules, all correct — and all, in the end, just sentences in a markdown file. A model reads them, agrees with them, and then, deep into a long autonomous run, quietly stops acting like it ever read them. Not out of defiance. Attention isn't memory, and a rule that isn't in front of the model right now has to compete for space with everything that is.
"An instruction like ‘never do X’ in a markdown file is still just an instruction."
That's the part I think gets missed in most of the spec-driven conversation. A better-written spec does not fix an attention problem. You can phrase "never do X" as clearly as language allows, and it is still a sentence the model has to remember to apply, every single time, for the rest of the session. If I really mean never, wanting isn't a mechanism 😄. So at some point I stopped trying to word the instruction better and started building the thing that would enforce it whether the model remembered or not.
In practice that turned into four pieces of process wrapped around the model — not inside its prompt, around its work.
A skill is how I tell the model how a specific kind of work gets done, not just what the end result should look like. A database change follows a different discipline than a security review, which follows a different discipline again from a UI change. A skill writes that discipline down once and loads it automatically when the task matches, so the model isn't improvising a process it should already know cold.
Hooks are where "never" actually becomes never. A hook doesn't live in the model's context window — it runs in code, entirely outside the conversation, and it can block an action before it happens: a destructive command, a deploy that skips a required check, a commit that touches something it shouldn't. The model doesn't have to remember the rule, because the model was never the one enforcing it. An instruction competes for attention. A hook has no attention to lose.
A gate is a checkpoint between phases of work that doesn't open on the model's say-so. A phase isn't done because the model says it's done — it's done when whatever the gate checks for is actually true: the artifact exists, the test genuinely ran, the criteria are met in fact, not in a status update. No stub passes as finished. No skipping ahead because a step looked easy this time.
The last piece: the model that wrote the code is never the model that approves it. Review goes to a different model entirely. A model checking its own output has every incentive — structural, not moral — to see what it meant to do rather than what it actually did. A second model with no stake in the first draft doesn't carry that blind spot.
Put together, that's a small closed loop, and it's worth drawing out properly, because the loop is the actual point: