The Expensive Verb: Teaching an Agent to Stop Re-Running Everything
My agent's favorite diagnostic was a seven-minute full re-run, even when the answer was already sitting in a cached JSON file. Prose rules didn't stop it. A 148-line hook did.
View companion repoEvery project has one verb that costs more than all the others
On my video-detection project, that verb is detect. It decodes a screen recording frame by frame, runs OCR on every frame, computes structural similarity against a sliding window, and feeds the whole stream through two analysis phases. On a two-minute clip it takes three to seven minutes. On the full validation set it is most of an hour.
The agent loved it. Any question about the detector's behavior, and its first move was a fresh run. What does segment 12 look like? Run detection. Did my one-line filter change work? Run detection. What was on screen at frame 2790? Run detection. Each run answered the question, so from inside the session every run looked justified. From outside, most of them were waste: the answer was already on disk from the previous run, in a summary JSON the agent itself had written.
The session logs make the pattern hard to argue with. This one project has 494 session files totaling 3.2 GB in my Claude Code project store, spanning March 23 to July 1. The project's own rules file records the low point: on June 3, all six detection runs in a single session were the wrong verb. Six full re-runs, zero of which needed to happen, because every answer was in the cache. And an earlier note in the same conventions file remembers a runaway detection loop on February 9 that burned eight hours of API credits before anyone stopped it.
This post is about what finally fixed it. Not better prompting. A hook.
“Detection is the last verb, not the default.”
Why the agent defaults to the expensive verb
The reflex makes sense once you see the incentive structure. A full re-run is the one action that is guaranteed to produce a fresh, authoritative answer. Reading a cached artifact requires knowing the artifact exists, knowing which of several files answers this particular question, and trusting that the cache still reflects the current code. Re-running requires knowing one command. When you're an agent optimizing for "answer the user's question this turn," the expensive verb is the safe verb.
The cost asymmetry is invisible from inside the session. The agent doesn't feel seven minutes of wall clock. It doesn't see the API bill. It sees a tool call that returns the answer, same as any other tool call. Every economic signal that would teach a human engineer to stop doing this is absent.
I tried the obvious fix first: I wrote the rule down. The project's conventions file grew a section called "Detection Verb Hierarchy" with a table mapping each question type to the cheapest artifact that answers it. Segment counts and boundaries: read the cached summary.json. Per-frame signals like OCR text or similarity scores: read phase1_frames.json. What's actually on screen at frame N: open the pre-extracted JPEG in the frame library. The table ends with detection itself, labeled the way I now think about it on every project: detection is the last verb, not the default.
The June 3 session, the one with six wrong-verb runs, happened after that table existed. The agent had the rule in context and ran anyway. That's the lesson worth generalizing: prose describes the right behavior, but it doesn't bind the next action. When the model is mid-task and pattern-matching toward "run the thing," a paragraph it read forty turns ago loses.
The fix: make the run impossible without written reasoning
What worked was moving the rule out of prose and into the tool layer. Claude Code lets you register a PreToolUse hook: a script that sees every Bash command before it executes and can block it. I wrote one that intercepts any real invocation of the detector and refuses to let it run unless a justification marker exists on disk, written within the last ten minutes.
The marker isn't a flag. It's a form with four required fields:
Each field targets a specific failure mode. RUN-JUSTIFICATION forces the agent to state the question, because a run without a question is a reflex. CACHE-CANNOT-ANSWER-BECAUSE forces it to check the cache first, because the honest answer here is usually "actually, it can." TOUCHED forces it to say which phase of the pipeline changed, because a Phase-2-only change can be re-tested in seconds against cached Phase-1 data instead of minutes of re-collection. And CONSULTED forces it to name the project skill or subagent the run serves, with a blocklist rejecting evasions like "none" or "n/a", so the line has to be deliberate.
When the marker is missing or stale, the hook blocks the command and prints the decision table instead: here is where cached answers live, here is the marker format if you genuinely need a run. The ten-minute expiry matters more than I expected. A justification written for one question can't silently authorize a different run twenty minutes later; the reasoning has to be current.
The economics of that bottom-right path deserve a number. A full detect on a test clip re-collects everything: decode, OCR, similarity, three to seven minutes. But the detector can also accept a previously exported Phase-1 JSON and re-run only the decision layer against it. For a change to the filter pipeline, that turns a minutes-long feedback loop into seconds, with output I verified as tuple-identical to a full run. Half the value of the hook is that the TOUCHED field shoves the agent toward noticing this cheaper path exists.
The interception itself is a plain PreToolUse exchange. The part that took iteration was the exit-code semantics: exit 2 with the decision table on stderr, so the block message is the teaching moment.
What the hook can and cannot do
I want to be honest about the limits, because the hook's own source code is. A comment inside it notes that the CONSULTED field can force the agent to name a skill, but the hook cannot verify the skill was actually invoked. That verification gap is real and I haven't closed it. A determined agent could write a technically-complete marker that's substantively hollow. In practice it doesn't, because the act of filling in CACHE-CANNOT-ANSWER-BECAUSE truthfully is exactly the reasoning step that was being skipped, and once the reasoning happens the conclusion is usually "read the cache."
There's a second, softer enforcement layer behind the hook. The gate catches runs the agent attempts; it does nothing about cached answers the agent never looks for. So the decision table also lives in a project skill that routes each question type to its artifact, and the hook's block message repeats the table verbatim. The hook catches the calls; the skills catch the skips.
Has it worked? The honest evidence is directional rather than a clean A/B. Wrong-verb runs went from "six in one session" to rare enough that I notice them individually. The recent release-validation sessions for v1.9.1 and v1.9.2 ran detection only at the moments the process actually required it: re-earning the ground-truth gate after code changes, once per candidate build, with cached artifacts serving every intermediate question. The 791-commit history now includes multi-hour debugging sessions with zero detection runs in them, something that never happened before the gate.
The general pattern, because yours isn't a video detector
Strip the project specifics and the recipe is short:
- 01Identify your expensive verb. Full test suite, clean build, e2e run, container rebuild, deploy preview, data backfill. If you wince when the agent runs it twice in a row, that's the one.
- 02Make the cheap answers findable. The expensive verb almost always writes artifacts. Inventory them, then write the mapping from question type to artifact. Be concrete: this file answers counts, that file answers per-item detail. An agent will read a cache it can locate in one step; it will not go spelunking.
- 03Find the partial re-run. Most expensive verbs have a shape where stage one is collection and stage two is decision. If stage two can replay against stage one's saved output, you've turned minutes into seconds for the most common change type. This was the single most valuable discovery on my project, and it was sitting in the CLI flags the whole time.
- 04Gate the verb mechanically. A PreToolUse hook that demands written, current, structured justification. Not to create bureaucracy, but because the fields of the form are the reasoning the agent was skipping. Ten-minute expiry. Required fields with rejected evasions.
- 05Accept the verification gap. The hook binds the action, and the form binds the stated reasoning, but nothing yet binds the two together. Knowing where your enforcement ends is part of the design.
The deeper thing I keep re-learning is that agent behavior lives in the tool layer, and rules live wherever you enforce them. I spent months writing better and better prose about when to run detection, and the agent kept running detection. A 148-line Python script that returns exit code 2 changed the behavior in a day. If a rule matters enough to write down, it probably matters enough to enforce where the action happens.
The hook, the decision table, and the conventions file are all in the companion repo. The expensive verb on your project is different from mine. The reflex to reach for it is the same.
Continue the series
- 40SeriesShipping awesome.video: 2,365 Resources, Built Mostly by AgentsA curated directory of video-development resources is now live. It took 1,185 commits and thirteen months, and coding agents wrote or drove most of them. Here is the honest recap.
- 42SeriesThe Refutation Swarm: 842 Subagents That Default to Disbelieving Each OtherOne FileAuditAgent per source file, one RefutationAgent per claimed defect, and a standing order to disbelieve. Only 43 of 239 candidate defects survived the adversarial gate.
- 39SeriesThe Machine That Writes These PostsA content pipeline of 24 commands and 11 skills mines my sessions, drafts the post, and refuses to let any machine click Publish. This one was made by it.
- 43SeriesThe Agent Cannot Edit Its Own Answer Key: Structural Guardrails Against Reward HackingAn agent scored against ground-truth files has three shortcuts to a fake PASS: edit the answer key, tune a constant until green, or assert "verified" without running anything. I closed each one in the harness, where the model cannot rationalize past it.