The recurring failure
Three separate incidents in two days, all the same shape — a prompt string asserting something
about the agent that its resolved capabilities contradict:
|
What the prompt said |
What was true |
| #254 |
"NEVER claim you personally ran commands… you do not drive the engine" |
start_work was in BASE; the agent had just used it |
| #255 |
"attach a repository in the Repo tab" |
the agent declares surfaces: ["coding"] — it has no Repo tab |
| #247 |
Coder docs described a tmux pane |
the engine is a child process; tmux was removed in June |
Each was fixed individually. The class is untouched, and the codebase already diagnosed it — from
toolBlurbFor:
Describing tools the agent doesn't have is not cosmetic: it tells the model a story about itself
that its actual tool set contradicts, and the model believes the story.
That comment was written after Local Repo Chat refused to read a repo it could already read. It
is the same lesson, and it keeps being learned one incident at a time.
Why it keeps happening
Capability is data — toolNamesFor(capabilities), capabilities.surfaces,
SELF_WRITABLE_FIELDS. Claims about capability are hardcoded English in agent-think.ts.
Prose cannot be type-checked, so it drifts the moment the data changes, and the drift is invisible
until an agent says something wrong to a user.
There are currently 8+ prompt lines naming UI locations ("the Coding tab", "the Repo tab") that
are not derived from capabilities.surfaces. None are wrong today. That is exactly what was true
of the tmux lines in June.
Proposal: a prompt-drift check, in the shape the repo already uses
scripts/docs-drift.mjs does this for documentation — 8 checks, including "MCP tool table: 124
registered, 124 documented, exact match". instance-config.test.ts does it for a code pattern,
scanning the tree for raw UPDATE agent_instances SET config because "a reviewer cannot be
expected to catch the seventeenth by eye".
Same idea for prompts. Build the system prompt for a set of representative capability fixtures and
assert:
Fixtures worth pinning: a Repo Coder (coding, drive:false), Repo Chat (repo), a cloud-only
chat agent (no surfaces, no runtime), and the legacy Coder.
Why this is worth a ticket rather than more care
Care was applied. Every one of those three lines was written by someone who understood the system,
and #254's was written as a correctness fix. The lines went stale because capability moved, not
because anyone was careless — and that is precisely the situation a mechanical check exists for.
Verification
- Adding a tool to
BASE without updating the prompt fails the check with the contradicting line
named.
- Renaming or removing a console surface fails any prompt that still names it.
- The check runs in CI next to
docs:drift.
The recurring failure
Three separate incidents in two days, all the same shape — a prompt string asserting something
about the agent that its resolved capabilities contradict:
start_workwas inBASE; the agent had just used itsurfaces: ["coding"]— it has no Repo tabEach was fixed individually. The class is untouched, and the codebase already diagnosed it — from
toolBlurbFor:That comment was written after Local Repo Chat refused to read a repo it could already read. It
is the same lesson, and it keeps being learned one incident at a time.
Why it keeps happening
Capability is data —
toolNamesFor(capabilities),capabilities.surfaces,SELF_WRITABLE_FIELDS. Claims about capability are hardcoded English inagent-think.ts.Prose cannot be type-checked, so it drifts the moment the data changes, and the drift is invisible
until an agent says something wrong to a user.
There are currently 8+ prompt lines naming UI locations ("the Coding tab", "the Repo tab") that
are not derived from
capabilities.surfaces. None are wrong today. That is exactly what was trueof the tmux lines in June.
Proposal: a prompt-drift check, in the shape the repo already uses
scripts/docs-drift.mjsdoes this for documentation — 8 checks, including "MCP tool table: 124registered, 124 documented, exact match".
instance-config.test.tsdoes it for a code pattern,scanning the tree for raw
UPDATE agent_instances SET configbecause "a reviewer cannot beexpected to catch the seventeenth by eye".
Same idea for prompts. Build the system prompt for a set of representative capability fixtures and
assert:
["coding"]agent's prompt must nevercontain "Repo tab". This alone would have caught [bug] A Repo Coder is not told it owns a repo — ownership lives in a memory string, and it cites tabs it does not have #255.
toolNamesFordid not grant — and, the inverse that bit us, no denial ofa tool that WAS granted (
start_workpresent ⇒ the prompt must not say it cannot act). [bug] The prompt still forbids what start_work does — a Coder denied real work and told the user to redo it #254 wasthe inverse direction, which is the one nobody thinks to check.
docs-driftalready makes for/docs/<slug>/links.Fixtures worth pinning: a Repo Coder (
coding,drive:false), Repo Chat (repo), a cloud-onlychat agent (no surfaces, no runtime), and the legacy Coder.
Why this is worth a ticket rather than more care
Care was applied. Every one of those three lines was written by someone who understood the system,
and #254's was written as a correctness fix. The lines went stale because capability moved, not
because anyone was careless — and that is precisely the situation a mechanical check exists for.
Verification
BASEwithout updating the prompt fails the check with the contradicting linenamed.
docs:drift.