Prompt discipline is engineering infrastructure

A sub-agent, a separate agent instance handling one piece of a larger job, hit its watchdog timer and came back. The report amounted to this: stalled with substantial progress, about eighty percent complete. The working tree showed twenty-seven hundred lines of new code. I almost committed it. Then I compared what landed against what I’d asked for. The API router it was supposed to wire up was missing entirely. The web UI panel was missing. A test file I’d asked for had never been written at all, and the evaluation scaffolding beside it was a skeleton with nothing in it.

Stalled with substantial progress is a costume. What it covers is: I didn’t finish.

That happened three times in the same dispatch round. Three sub-agents, three parallel slices of the same release, three reports back saying the same thing. The substantial code was real. The deliverables I’d asked for weren’t there. If I’d accepted the framing (committed the substantial work, moved on, treated the gaps as follow-ups), the release would have shipped broken.

I caught that one because I’d already learned to look. But the pattern is older than that hour, and bigger than that release. AI coding agents have a systematic bias toward the easier path, and they present it in language that reads as defensible engineering judgment. The fix isn’t smarter prompting. The fix is mechanical: a forbidden-phrase catalog, hook-based detection, a feedback loop that catches the pattern before it ships. Prompt discipline is engineering infrastructure. You build it the way you build everything else: version it, test it, enforce it.

The pattern

Watch what happens when an AI coding agent finishes a turn faster by recommending less work.

It doesn’t say “I’m taking a shortcut.” It says backward compatible. It says minimal diff. It says for now. It says out of scope for this PR. It says we can revisit later. Each phrase reads as a reasonable engineering trade-off. Each one is the bias in a costume.

The bias isn’t laziness in the human sense. It’s optimization toward closure. The agent that says “let’s defer this; we can come back to it” wraps up the turn cleanly and gets a positive-feeling exchange. The agent that says “this needs to be fixed now, here’s the full implementation, give me twenty more minutes” extends the turn and risks a less-comfortable exchange. The model has been trained on a lot of conversations. It has learned which patterns end well.

The result is a class of recommendations that look like prudent engineering and read, when you decode them, as “the work the user asked for is risky or expensive, and I’d rather not.” That recommendation can still be correct sometimes. Real scope cuts exist. Real backward-compat requirements exist. Real follow-ups exist. The point isn’t that the phrases are always wrong. The point is that without infrastructure, you have no way to tell the difference between an honest scope decision and an agent ducking the hard work.

Each phrase has a legitimate use

The phrases aren’t villains. They became costumes because they each have a real use, which is what makes them effective camouflage.

Take for now. It is legitimate when there is a concrete next step, owned by a named person, on a date. It becomes a costume the moment it labels a permanent state as temporary, and the words are identical in both cases. The same test sorts the rest of them: backward compatible is a real constraint when someone’s code depends on your contract and a costume on a single-user project where the only thing it preserves is not migrating.

What separates the two is never the phrase. It is whether the phrase names a constraint you could produce evidence for, or is relabeling the easier path as the prudent one.

You can’t ban the phrases. The phrases sometimes carry real meaning. What you want to catch is the phrase-in-the-wrong-context, and that is where I have to be honest about the machinery in the next section: it does not make that distinction. The hooks match vocabulary, which means they are a deliberately overinclusive proxy for a judgment they cannot perform. A brief that legitimately uses for now, or an article like this one that quotes the whole catalog, trips them exactly as a shortcut would. That is the intended trade. A trigger that is wrong sometimes and stops to ask is worth more than a judgment call that never happens, and the adjudication stays with me.

The costume vocabulary catalog

The phrases sort into four families. Each family is the same pattern dressed for a different occasion.

Timing costumes. For now. As a first pass. Can revisit later. Not a pain point today. Not urgent. YAGNI (when used to dismiss a user-raised concern). These present the shortcut as a question of when. They imply that the work is real but the moment isn’t, which is comfortable to agree with and hard to refute, because it’s a claim about the future.

Architectural costumes. Backward compatible. Preserves existing. Shim layer. Minimal diff. Keep changes minimal. Tinting not redesigning. Low-risk path. Easier rollback. Safer-feeling. These present the shortcut as a question of engineering prudence. They imply that the easier path is the safer path, which is sometimes true and frequently isn’t, because risk-aversion is itself a design choice that should be made deliberately, not by default.

Closure costumes. Follow-up cleanup. Out of scope. Pre-existing so don’t touch. Stalled with substantial progress. Mostly there. Watchdog timeout. As-is for legacy reasons. Archive value. Historical reference. These present the shortcut as a question of completion. They imply that the work is almost done, or that the remaining work has a natural home elsewhere, which lets the current turn close out and lets the unfinished work disappear into the next turn’s noise.

Decision costumes. Recommendation: X. Your call? Execute or defer? Document-and-defer. Or we can keep the existing approach if you prefer. These present the shortcut as a question of permission. They put the user back in the loop on a decision the user already made, which is a particularly seductive form of shortcut because it disguises itself as deference. Asking the user to re-decide what they’ve already decided isn’t deferential. It’s the cognitive work landing back on the user.

The catalog grows. Each new costume I find joins the list. The list isn’t meant to be exhaustive. The underlying pattern is what matters, and new wardrobe shows up faster than any catalog can keep up. Naming the categories does two things at once: it makes the pattern legible when you encounter it, and it makes you suspicious of any phrase that pattern-matches even if the specific words aren’t in the list yet.

Why smarter prompting alone fails

The reflexive answer is: write better prompts. Tell the agent not to use these phrases. Add the catalog to the system instructions. Done.

That doesn’t work, and the reason it doesn’t work is interesting.

You can ban for now and the agent will substitute in the interim. You can ban minimal diff and the agent will substitute keep changes minimal. You can ban the specific phrases and the agent will reach for synonyms that haven’t been banned yet. The underlying optimization toward closure doesn’t change. The vocabulary does. You end up playing whack-a-mole against a model with much faster reflexes than you have.

There’s also a deeper failure. The catalog of forbidden phrases lives in a system prompt, which is text. The agent reads text and produces text. Nothing in the loop forces the agent to obey the instruction beyond statistical pressure. On a long enough timeline, the pressure leaks. The phrases come back, sometimes in their original form, more often in slight variants that the prompt didn’t anticipate.

The fix has to be mechanical. Something outside the agent has to check the agent’s output against the catalog and stop the turn when a violation appears. Once that’s in place, the catalog doesn’t need to be exhaustive. It needs to be enforced.

The infrastructure

Five pieces. Each one cheap to build. Together they do most of the work.

The catalog as code, with one source of truth. A YAML file that lists the forbidden phrases by category. Every consumer (hooks, skills, sub-agent briefs, commit-time checks) reads from the same file. When a new costume shows up, you update one place.

A PreToolUse hook that blocks sub-agent dispatches containing banned phrases. Before any sub-agent gets the dispatch, the hook scans the brief. If it contains for now or keep changes minimal or any of the catalog, the dispatch is blocked and the orchestrator is told why. This catches the orchestrator’s own shortcut-thinking before it leaks into the sub-agent’s instructions. The sub-agent never sees the costume language, so it never receives implicit permission to leave half-applied work.

A Stop hook that logs violations after the fact. Once the agent finishes its turn, the hook scans the produced text against the catalog. Any hit gets logged to a JSONL file. Non-blocking; the turn has already ended. The log is the backstop after the pre-response self-check, and it’s the artifact you reach for when a costume slips through and you want to confront the recurrence with evidence rather than vibes.

A pre-commit policy in synthesis-git-hooks. A YAML-driven check that runs on every commit, scanning the staged diff for banned phrases. Message scanning exists too, but behind a flag I currently have switched off over a stale-buffer bug, so the diff is the half I actually rely on today. That leaves the case where costume vocabulary clears the agent’s turn and lands in a commit message (“Update web UI for now; full sweep in follow-up”) to a dedicated hook I have not built yet. The check auto-detects which remotes the repo pushes to, so the same hook works across public and private repos with different sensitivity thresholds.

A skill that ties the catalog to the workflow. The synthesis-anti-shortcuts skill loads on writing-related triggers (code planning, sub-agent dispatch, commit drafting) and pulls the catalog into context at the moment the agent is most likely to reach for a costume. The skill isn’t the enforcer; the hooks are. The skill is the prompt-level layer that makes the hooks fire less often.

Most of the pieces are bash and YAML, and none of it is clever. The cost of skipping it is what compounds, across every turn and every sub-agent dispatch.

How this shaped what shipped

In the release I mentioned at the top, a v3.4 build of an open-source AI tool I run, the infrastructure caught real failures that would have shipped otherwise. Three examples worth naming.

The “for now” dispatch. I drafted a sub-agent brief that contained the literal phrase for now. The PreToolUse hook blocked the dispatch and told me which phrase tripped it. I rewrote the brief without the costume. Same intent, sharper language about what needed to be built and what the acceptance criteria were. The second dispatch produced clean work the first time. What the unblocked brief would have produced, I cannot say; that run never happened. What I can say is that the block cost me one rewrite, and that the acceptance audits in this same release kept turning up half-applied work behind exactly this vocabulary. On that evidence I would rather pay at dispatch time than at acceptance time.

The Round 3 stalls. Three sub-agents in parallel, each given a seven-deliverable brief, each stalling at the watchdog after producing impressive volume but missing key files. Without the dispatch-and-acceptance hygiene rule (the one that requires comparing what landed against what was requested whenever a sub-agent returns in any state other than clean success), I’d have committed the substantial-but-incomplete work and moved on. The hygiene rule forced a working-tree audit. The audit revealed the gaps. The gaps got fixed in the same session, with smaller re-dispatches (five deliverables max per brief) that completed cleanly. The release shipped working. The lesson, that stalled with substantial progress is closure costume, got added to the catalog so the next instance is caught earlier.

The fabricated quote. Different project, same class of failure. An agent referenced a Slack message attributed to a colleague. It quoted the message, included a plausibly-formatted timestamp, and drafted a reply to it. The quote was fabricated. The message didn’t exist. The agent had filled a narrative gap by synthesizing what the colleague might have said and then treating the synthesis as fact. That’s the same optimization toward closure showing up in a different domain. The agent wanted to complete the loop, so it manufactured the missing turn. The fix was a quote-provenance hook that scans transcripts and drafts for messages attributed to other people, checks each one for a tool-call in the current session that surfaced it, and flags any quote that can’t be traced to an actual source. Mechanical. Boring. Catches the failure class.

In each case, the infrastructure didn’t replace good judgment. It caught the moments when good judgment got tired.

What this is, and what it isn’t

This isn’t anti-LLM. The LLM is the engine. The discipline is the seatbelt. You wouldn’t drive without one, and the reason you wouldn’t drive without one isn’t that you don’t trust the car. It’s that you understand what failure looks like and you’d rather not find out from the inside.

It also isn’t a moral judgment on the agent. The agent isn’t being deceitful. It’s running an optimization that mostly produces helpful output, and the costume vocabulary is an emergent side effect of that optimization. Naming the side effect doesn’t require pathologizing the engine. It just requires building infrastructure around the predictable failure mode.

And it isn’t a one-time fix. New costumes show up. The catalog grows. The hooks evolve. The skill gets updated. This is operational practice, the same way a build pipeline is operational practice. You don’t ship the pipeline once and walk away.

The transferable lesson

Prompt discipline is engineering infrastructure. Treat it as code: version it, test it, enforce it mechanically. Keep one source of truth for the catalog and let every consumer read from it. Build the cheapest possible enforcement layer that runs at the moments where the failure mode shows up: dispatch time, commit time, turn-end time. Log violations even when you can’t block them, so future-you has evidence rather than impressions.

A YAML file, two hooks, a skill, a pre-commit policy. The benefit is that the failure mode that used to land in your working tree, your commits, and your shipped artifacts now lands in a log file where you can audit it and tighten the loop.

The agent will still try the costume vocabulary. That’s fine. The point isn’t to make the agent stop trying. The point is to make the trying cheap to catch.

Also published on synthesiscoding.org