The Warning Your Agent Never Saw: Instruction Budgets, Invisible Hooks, and a Sync That Ate Its Own Output
One day of running Coograph on real projects: 120,000 tokens of instructions before a single line of code, guardrail warnings the model was never shown, and a sync that overwrote the fixes its own retro had proposed. What we found, how we proved it, and what ships in 1.8.5.
Most of what we ship comes from running Coograph on our own projects and watching it fail. This release is unusually concentrated: one issue, filed against one monorepo, turned into four findings in a single day, and two of them were the kind you only find by asking the model directly what it saw.
120,000 tokens before the first line of code
The issue came with numbers. A monorepo set up with Coograph about ten days earlier (a web app, a native app, a shared core and a set of cloud functions) loaded around 120,000 tokens of instruction files on a typical web task, before the agent read any code. One workspace map, the file that tells an agent how the web app is laid out, was 91,645 tokens on its own.
Nobody wrote a 90,000-token file on purpose. It grew. Every change appended a dated section (“Login, see openspec 2026-09-01”) so the current behaviour of one screen was spread over many sections and the agent had to read all of them. Under a web and native parity rule one feature produced three to five sections across the maps. The hard rules were copied into three root files and paid for three times. And a single 6,400-character table cell made the formatter pad every row of a wide table: 228 KB of that 363 KB file was spaces.
Retro, Coograph’s loop that measures how the guardrails hold up, was supposed to catch this. It did not, because it measured the wrong files. Its token budget counted the root files and .github/instructions/, and nothing else: not the workspace maps, not anything pulled in with an @ import. The 91,000-token map was invisible to the one tool whose job was to notice it.
So 1.8.0 gave instruction files a shape and a ruler. There are three tiers: always-loaded root files (9,000 tokens together, imports followed), a short router per workspace (2,000), and one feature doc per product area (8,000), read only when the agent needs it. Gotchas get their own file and are never loaded up front; a hook shows an entry at the moment the agent edits a file under its paths or runs the command it is about. layout.py --budget measures all of it, and a CI check fails a pull request that changes code a feature doc covers without touching the doc.
For files already too far gone, /coograph-docs-restructure writes an OpenSpec with every fact from the old maps listed by source, then migrates one area per agent, checking each fact against the code. On the monorepo it brought the always-loaded set from 30,745 to 7,914 tokens and the 91,645-token web map down to 591; no workspace map is over 700 now. The template Coograph ships now measures 5,510 of its 9,000-token budget; before, its own three root files each repeated the same hard rules in full.
The warning nobody read
Building the gotcha hook meant checking how a hook gets text in front of the model, and that check turned up the uncomfortable finding.
Coograph has four warning hooks. One warns when an edit falls outside the approved change, one when a second source file is edited with no proposal, one when a dependency is added, one when a commit lands with no review. All four did the same thing: print a message to stderr and exit with code 1. The message showed up in the transcript, so we believed it worked.
We tested it the only way that settles it. We started a nested session in a throwaway project, had it make an edit that the scope hook must warn about, then asked the model, with no other tool available, to quote every piece of text it had received after that edit containing the word “scope” or “OpenSpec”. It answered: none. The hook had fired; the transcript held the text, labelled as a non-blocking hook error. The model never received it.
The gotcha hook, written the same afternoon, used a different channel, additionalContext, and in the same probe the model quoted its entry word for word, including a token it could not have guessed. So the fix was clear. All four warning hooks now speak through additionalContext (for the model) and systemMessage (for the terminal) and exit 0. We re-ran the probe on the fixed hook: the model quoted the scope warning verbatim.
One more catch came out of that re-run. The first attempt failed again, and the transcript showed why: the installed plugin’s own copy of the hook, still the old version, had claimed the event before the project’s copy. Coograph lets exactly one copy of a hook act per event, which is right, but it means a fix is only live once both copies have it. The plugin now installs from a GitHub marketplace, so claude plugin update reaches it.
A guardrail that escalated on nothing
The invisible warnings had a second-order cost, and it is the one that worried us most.
Retro escalates a rule from a warning to a block when the evidence says warnings are being ignored: the rule fires, nothing fixes it, it fires again. On the monorepo, a retro had done exactly that. It read “ignored rate 0.8 over 5 outcomes” for the no-new-dependencies rule and moved it to a hard block on installs. Reasonable on its face. But every one of those five outcomes followed a warning the agent had never been shown. The block worked; the reason for it did not exist.
So Retro now counts a rule’s outcomes only from its outcomes_since date, the last time the hook’s behaviour changed. Outcomes from before the channel fix describe warnings nobody read, and they can no longer push a rule toward blocking. On the monorepo the rule went back to a warning, which the agent now actually sees, and the next retro will decide again on honest evidence. last_changed, which a simple rewording also sets, no longer touches the evidence at all; a first draft of this fix used it, and our review caught that a retro editing a sentence would have wiped a rule’s history.
The sync that deleted its own output
The last finding was ours to own. Merging these changes runs Coograph’s sync, which copies the template files into every registered project. That evening the monorepo’s own commits were reverted in its working tree: a retro’s hook upgrade, the precision fixes to the scope hook, a guard option in the layout checker. Sync had copied every template file over the project’s, and Retro proposes edits to exactly those files. The tool was deleting its own feedback loop’s output.
Sync now never silently overwrites a local edit. Each file it would replace is compared with what it last wrote and with every version Coograph has ever shipped. A file that matches neither is kept, the new upstream version is parked in .coograph/upstream/, and the log says KEPT <path> (local edit). On the monorepo it kept exactly the fifteen files with real local changes and updated the rest. The general fixes among those local changes were pulled upstream in the same release, so they now reach every project.
Also in this release
- OpenCode is a first-class tool. Sync generates an
opencode.jsonwith the code-graph server and the skills path. Verified end to end: OpenCode connected to the server and an agent call tograph_statsread the real graph. We also tried making that file portable with project-relative paths; it worked when OpenCode started at the project root and failed when it started in a subfolder, so the file keeps this machine’s absolute paths and is not meant to be committed. - A launcher that survives locked-down Windows. On machines with Smart App Control, uv’s own Python downloads could be blocked from loading, and the code-graph server died at start. The launch line now pins uv to a system Python 3.10 or newer.
- The OpenSpec gate warning names the rule, not a file, because in the new layout that rule lives in
AGENTS.md, notCLAUDE.md.
What this does not solve
A budget only tells you a file is too big; it does not tell you which facts are still true. The restructure skill checks every fact against the code, but facts the code cannot confirm, a deploy step or a console setting, are kept and marked rather than verified. The docs check is coarse on purpose: a feature doc covers paths, and a change under those paths must touch the doc, which says nothing about whether the edit was right. And the warning hooks are now visible, which also makes them noisier; whether that noise is worth it is exactly what the next few retros will measure.
The general shape
Three of these four findings share a cause: something worked, recorded itself, and was never seen by the one reader that mattered. A budget that skipped the biggest file. A warning written to a channel the model does not read. An escalation built on outcomes of that unread warning. The transcript looked fine each time.
The fix each time was to stop trusting the record and ask the reader. Measure the files the agent actually loads. Ask the model what it received. Count only the evidence the agent could have acted on.
All of this ships in Coograph 1.8.5. The docs are at /docs/layout/ for instruction budgets and gotchas, and /docs/retro/ for how the evidence works.
Cut your AI coding bill 67–78%. Coograph is MIT-licensed and free forever. Pro is bespoke services.