From e200f05d97c3003cbf29b8968438ed9a20f7a4d5 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 18:40:54 -0500 Subject: [PATCH 1/2] docs(korus): make a stale dispatched brief something the receiver checks for (BACKLOG #1448) A chip is a snapshot of the tree at spawn. Once the receiver has started it there is no path to correct it: dismiss_task withdraws only a chip nobody has acted on yet. Measured 2026-09-04. A chip cut under #1443 named three drift sites; minutes later the spawner took item 3 itself and pushed c2f549f42 on PR 837. PR 859 avoided rewriting the same two comment blocks only because it read that diff at hunk granularity before touching anything. Two of the brief's other items also failed a read of their sources, and the three faults have three different causes. Section 5 already says the brief is disposable. That covers a brief being thrown away, not one that is wrong by the time it is read. Two bullets: the receiver verifies against the tree, and whichever seat dispatched mails what it took back. #1391 is cited rather than extended. Its brief is wrong at generation; this one goes wrong after dispatch, and neither candidate fix touches the other row. Checks: pre-commit all hooks pass, five doc lint scripts clean, 134 doc tests pass. No code changed, so ruff, mypy and simplify have nothing to read. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 19 +++++++++++++++ docs/BACKLOG.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 23797a554..11ebd6404 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -335,6 +335,18 @@ went red, and no workflow reads that label back. So the Console finds both by as at spawn, because no hook will invent a goal, by design: a machine that invents one writes a record that looks declared and says nothing. +6. **A dispatched brief can go stale between spawn and read, and nothing will tell you.** Verify it + against the tree before you act on it: read the diff of **every PR it names**, at hunk + granularity, and re-locate every line number by symbol. **Where the brief and the tree disagree, + the tree wins.** Measured 2026-09-04: a chip named three drift sites, and minutes later the + spawner took item 3 itself and pushed it as `c2f549f42` on PR 837. The receiver read that diff + before touching anything, saw both hunks already rewritten, and skipped it. Trusting the brief + would have put two PRs on the same two comment blocks, to meet at merge with the Lander + resolving prose by hand. **Two of the same brief's other three items also failed to survive a + read of their sources**, so one confirmed drift is a reason to re-check the rest, not to correct + that line and carry on. ***"The brief is disposable" above says it may be thrown away; it does + not say it was true when written.*** BACKLOG #1448, same family as #1391. + ### The Console plans, spawns, and holds the owner's attention - **Plan first, then spawn.** For anything past a trivial change the Console produces a plan and @@ -346,6 +358,13 @@ went red, and no workflow reads that label back. So the Console finds both by as - One brief per Builder. After about two failed attempts at the same problem, spawn a fresh Builder with a better brief rather than reuse a poisoned context. A Builder cannot do this. When you are stuck after two attempts, push what is green and say in the PR body that the brief needs re-cutting. +- **If you take back part of a brief you already dispatched, mail the receiver, because you cannot + update the chip.** `dismiss_task` withdraws only a chip the user has **not** acted on, so a + started one stays live and frozen around your stale text, and no channel carries the correction. + Say which item is already done and where it landed. **This binds whichever seat dispatched, not + only the Console:** any seat can raise a chip, and in the 2026-09-04 case above the spawner was + the session that then pushed the fix. It corrected its own BACKLOG item in the same change and + still could not reach the chip, which is the whole shape of the defect -- BACKLOG #1448. - Give each session its own git worktree (`scripts/worktree/new.ps1 -Name `, cleanup with `remove.ps1`). Each gets an isolated checkout, branch and `.venv` on the same remote and the same PR flow. See [`docs/WORKTREES.md`](docs/WORKTREES.md). The AI project memory is shared across diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 5a070ff8b..70a44a48b 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -22066,3 +22066,64 @@ So `test_the_script_prefers_its_own_repo_over_an_earlier_path_entry` supplies th **Verification:** 8 passed in `tests/test_webconsole_seam_snapshot.py`. Mutation check run rather than argued -- with the `sys.path.insert` line deleted, the decoy test reds naming the decoy import, and the by-path digest test **stays green**, which is the luck described above measured rather than predicted. Anchor restored, 8 passed again. **Adjacent and NOT fixed here, named rather than numbered.** `docs/WEBCONSOLE-PACKAGE.md`'s seam-refresh procedure is stale in three steps left behind by #1220: it says to bump `ENGINE_UI_SEAM` by hand (`1` to `2`) when the value is a derived digest, it says to update curated lists in this script that #1220 retired, and its step 5 prescribes `python scripts/webconsole_seam_snapshot.py > tests/golden/...`, the shell redirect this script's own docstring forbids because PowerShell's `>` writes UTF-16LE with a BOM into a file the test reads as UTF-8. That is doc drift with its own cause and it wants its own item; folding a documentation rewrite into a `sys.path` fix would make both harder to review. + +--- + +## 1448. a dispatched brief is frozen at spawn: the chip cannot be corrected and nothing tells the receiver it has drifted + +> 🔢 **FILED 2026-09-04 by the session that read the stale brief.** Not started. A **fleet-process** defect with no engine, PHI or deployment axis (sec. 0) -- nothing here reaches a running instance, because there are none. What it would cost is duplicated Builder turns and a prose merge conflict a human then resolves by hand. + +**The defect in one sentence: a chip is a snapshot of the tree at spawn, and once the receiver has started it there is no path to correct it and no signal that it needs correcting.** + +`CLAUDE.md` sec. 5 already says *"The brief is disposable. The BACKLOG item is the record."* That covers a brief being **thrown away**. It does not cover a brief being **wrong** by the time it is read, and those are different failures with opposite remedies: a disposable brief you may ignore, a stale one you must first detect. + +### The measured incident, 2026-09-04 + +**#1443 and #1445 are allocated and open in PRs 837 and 859; neither is on `main` yet**, so `dangling_citation_check.py` reports both unresolved until they land. #1439 is on main. Cited anyway, per the rule in `CLAUDE.md` sec. 5 that an allocated-but-unlanded number is cited and said so. + +A session working **#1443** raised a task chip naming three drift sites. Item 3 was a docstring in `scripts/webconsole_seam_snapshot.py`. + +Minutes later the author of that line -- working **#1439**, and declining to open a competing branch over two comment blocks -- handed the same fix to the spawning session directly. It took it and pushed `c2f549f42` on **PR 837**. **That was the correct call**: the session already in the file is the cheapest place for the change. + +**The chip was already dispatched, and the spawner then had nowhere to put that fact.** `dismiss_task` withdraws only a chip the user has **not** yet acted on; this one had been started, so it stayed live, frozen, and wrong. + +***THE SHARPEST PART OF THIS ROW IS THAT THE RECORD WAS UPDATED AND ONLY THE CHIP WAS NOT.*** #1443's own body carries the handover in the same change, under the heading *"ONE MORE SIBLING WAS TAKEN, ON A HANDOVER"*. So the spawner did exactly what sec. 5 prescribes -- the BACKLOG item is the record, and it corrected the record -- **and the receiver was reading the other artifact, the one with no update path.** + +**The receiver -- PR 859, working #1445 -- avoided the duplicate for a reason that does not generalise.** It read PR 837's diff at hunk granularity before touching anything and saw both comment blocks already rewritten. Had it trusted the brief, two PRs would have rewritten the same two hunks and met at merge, with the Lander resolving prose by hand. + +### Two more of the same brief's items failed to survive a read of their sources, and they are three different faults + +**A brief that drifts once has usually drifted more than once**, because the tree moves and the brief does not, so the count of bad items tracks elapsed time rather than the author's care. But the three faults below have three different causes, and a fix aimed at one will not touch the others. + +| item | what the brief said | what the source says | fault | +|---|---|---|---| +| 3 | fix the docstring in `scripts/webconsole_seam_snapshot.py` | already fixed and pushed as `c2f549f42` | **stale** -- correct at spawn, overtaken | +| 1 | delete one line, `messagefoundry_webconsole/__init__.py:29` | the deletion makes the file **worse**. Verified by reading: `:29` is a truncated fragment ending on the word "the"; removing it alone puts the pre-#279 range framing on `:27-28` (*"A pair outside this set"*, *"PEP 508 compat range"*) directly against its contradiction on `:30-31` (*"EXACTLY the engine seam it was built against -- deliberately a single value, not a range"*). The correct fix is a three-line rewrite. | **wrong when written** -- never true, not stale | +| adjacency | `docs/adr/0143-...md` is "adjacent, probably leave alone" | #1443's carve-out **does** list it, among *"Three more, all in the #1220 class and all deliberately out of this diff"* -- **and the same sentence argues against fixing it**, that *"an accepted ADR records what was true when written and is better left dated than edited"* | **a compression of a source that says two things.** The brief is defensible here; what it is not is a substitute for reading the carve-out | + +***THE THIRD ROW IS RECORDED AGAINST THIS ITEM'S OWN INTEREST, AND DELIBERATELY.*** It was reported to this session as a plain staleness -- the brief deferring a site the carve-out wanted fixed -- and the source does not support that reading. A row asserting the brief was wrong there would be a stale citation of exactly the kind this item exists to describe. **It still supports the rule**, because the receiver's recovery is the same in all three cases: go read the source, not the brief's one-line summary of it. + +### THE DEFECT IS THE MECHANISM, NOT THE AUTHOR + +Handing item 3 to the session already in that file was right, and re-cutting the chip is not an option anyone declined -- **it does not exist**. Read this row as an argument about the channel: **a dispatch artifact with no update path and no staleness signal will eventually be acted on stale, and the only thing standing between that and a hand-resolved merge conflict is a receiver who happens to verify.** + +### Family, and why this is NOT folded into #1391 + +**#1391 is the same family and is cited rather than extended** -- both are a dispatch artifact that is wrong by the time it is used. The mechanisms and the fixes are disjoint: + +| | #1391 | this row | +|---|---|---| +| when it goes wrong | **at generation.** `respawn.ps1:1339`/`:1426` emit a relative command rooted at the predecessor's checkout, so the brief is wrong the instant it is written | **after dispatch.** The brief was correct at spawn and the tree moved underneath it | +| candidate fixes | an absolute path in the brief, or `seat.ps1` keying on the session rather than the invoking tree | a chip that cites the record instead of restating it, or a head-SHA stamp the receiver can diff from | + +Neither fix does anything for the other row. **#1391's own body warns that "a fix aimed at the wrong half will look like it worked on the seats that were never affected"** -- folding these together would build that trap on purpose. + +### What a fix looks like, smallest first + +1. **Prose, and it is already written.** `CLAUDE.md` sec. 5 now tells the receiver to verify a brief against the tree before acting, and tells whichever seat dispatched to mail the receiver when it takes an item back. **That is a workaround, not a mechanism.** It costs the receiver a diff read per named PR, and it fails silently the first time somebody skips it. +2. **A chip that cites the record rather than restating it.** Sec. 5 already names the BACKLOG item as the record, and the incident above shows the record staying accurate while the chip did not. A chip reduced to a citation would let the spawner correct the item and have the receiver read the correction. **Unmeasured:** nobody has checked whether a citation alone carries enough for a cold receiver, which is the whole reason briefs restate. +3. **A staleness signal.** Stamp the chip with the head SHA of every branch it names, so the receiver can diff from a known point rather than guessing what moved. + +**Do not start at 3.** The receiver already recovers by reading diffs, so a signal telling it to do what it already does buys the least of the three. **Measure first how often a chip outlives its own accuracy** -- this row is a population of one, and one incident cannot tell a rare race from a routine one. + +**Cluster:** Fleet coordination. **Priority:** unscored -- filed after the 2026-09-03 scoring pass, so it carries no row in the ranking table above. **Verdict:** build (small), pending that measurement. **Severity:** no engine effect, no PHI axis, no deployment axis (sec. 0). It costs duplicated Builder turns and prose conflicts resolved by hand. From b67edbf237a86cfc9cf363310dc1fab9fe78ecba Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 4 Sep 2026 18:48:02 -0500 Subject: [PATCH 2/2] docs(korus): widen the stale-brief rule past the window it was first scoped to (BACKLOG #1448) The rule shipped saying a brief goes stale "between spawn and read". Two Manager seats answered within the hour that the dominant window is filed-to-dispatch: the brief is written stale because the ITEM it was cut from is stale, over weeks rather than minutes. One reports six of eleven briefed items already answered at spawn on 2026-09-04, each by a different mechanism. That scoping was the defect this row exists to describe. A reader told the window is spawn-to-read checks how fresh the brief is and skips -- the SDS-3.4 shape the incident's own c2f549f42 was fixing, a warning whose stated reason licenses the failure it prevents. Also added: line numbers are navigation aids and never evidence (four anchors adrift by 50, 86, 581 and 593 lines in one day), and finding an item already answered is a good outcome to record and stop on, not a failed turn. The row records what the title cannot: a subagent CAN be corrected mid-flight (MANAGER.md:30), a chip and a claude -p session cannot, so the fix differs by seat. Peer measurements are attributed, not verified here. BUILDER.md and MANAGER.md checked for conflict before amending sec. 5; none found, reported as a null result. Checks: pre-commit green, doc lints clean, no new dangling citations (same 16 distinct numbers). Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 27 ++++++++++++------ docs/BACKLOG.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11ebd6404..dd3528a28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -335,17 +335,28 @@ went red, and no workflow reads that label back. So the Console finds both by as at spawn, because no hook will invent a goal, by design: a machine that invents one writes a record that looks declared and says nothing. -6. **A dispatched brief can go stale between spawn and read, and nothing will tell you.** Verify it - against the tree before you act on it: read the diff of **every PR it names**, at hunk - granularity, and re-locate every line number by symbol. **Where the brief and the tree disagree, - the tree wins.** Measured 2026-09-04: a chip named three drift sites, and minutes later the - spawner took item 3 itself and pushed it as `c2f549f42` on PR 837. The receiver read that diff - before touching anything, saw both hunks already rewritten, and skipped it. Trusting the brief - would have put two PRs on the same two comment blocks, to meet at merge with the Lander +6. **A brief can be wrong by the time you read it, and nothing will tell you.** Verify it against + the tree before you act on it: read the diff of **every PR it names**, at hunk granularity, and + re-locate every line number by symbol. **Where the brief and the tree disagree, the tree wins.** + ***Do not scope this check to how recently the brief was written.*** Two windows give the same + symptom and **the wider one dominates**: a brief goes stale AFTER dispatch, in minutes, and it is + written stale because the ITEM it was cut from is stale, over weeks. A Manager seat reported six + of eleven briefed items already answered at spawn on 2026-09-04 -- by an ADR accepted before the + brief, by work shipped under a different number, by a PR the item itself says not to rebuild. + **Attributed, not verified here.** The structural cause is that an item records its own research + and nothing records the work that ANSWERS it, so a settled row still reads as current. + **Line numbers are navigation aids and never evidence** -- the same seat measured four anchors + adrift by 50, 86, 581 and 593 lines in one day, one item with both of its anchors dead. + Measured here 2026-09-04, the after-dispatch window: a chip named three drift sites, and minutes + later the spawner took item 3 itself and pushed it as `c2f549f42` on PR 837. The receiver read + that diff before touching anything, saw both hunks already rewritten, and skipped it. Trusting + the brief would have put two PRs on the same two comment blocks, to meet at merge with the Lander resolving prose by hand. **Two of the same brief's other three items also failed to survive a read of their sources**, so one confirmed drift is a reason to re-check the rest, not to correct that line and carry on. ***"The brief is disposable" above says it may be thrown away; it does - not say it was true when written.*** BACKLOG #1448, same family as #1391. + not say it was true when written.*** **Finding an item already answered is a GOOD outcome** -- + record it with evidence and stop, rather than building it again. BACKLOG #1448, same family + as #1391. ### The Console plans, spawns, and holds the owner's attention diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 70a44a48b..84c69b696 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -22118,6 +22118,81 @@ Handing item 3 to the session already in that file was right, and re-cutting the Neither fix does anything for the other row. **#1391's own body warns that "a fix aimed at the wrong half will look like it worked on the seats that were never affected"** -- folding these together would build that trap on purpose. +### AMENDED SAME DAY: THE WINDOW THIS ROW FIRST NAMED IS THE SMALLER HALF + +**This item was filed scoped to spawn-to-read, and two Manager seats answered within the hour that +the dominant window is filed-to-dispatch.** Both reports are attributed, not verified here; the +correction is recorded because **the original scoping was the defect this row exists to describe.** +A rule reading *"a brief can go stale between spawn and read"* invites a reader to check how fresh +the brief is and skip -- which is the SDS-3.4 shape the incident's own `c2f549f42` commit was fixing, +a warning whose stated reason licenses the failure it exists to prevent. `CLAUDE.md` sec. 5 item 6 +was widened accordingly. + +**The reported population, from a seat that dispatched fourteen Builders that day: six of eleven +briefed items were already answered at spawn.** Each by a different mechanism, and none of them a +race: + +| item | how it was already answered | +|---|---| +| #1117 | founding premise died when ADR 0172 shipped the change **under a different item number** | +| #1317 | its open half decided by ADR 0180, **accepted five days before the brief**; the item never recorded it | +| #1139 | one of three named targets already built under #1248 | +| #1085 | marked fixed 2026-08-26, banner deliberately left open; the item said so, the ranking table did not | +| #1107 | shipped in PR 488, and the item's own body says **"DO NOT REBUILD IT"** | +| #1172 | both brief items shipped, in PR 603 and a 2026-09-03 gate change | + +***THE STRUCTURAL CAUSE, AND IT IS BIGGER THAN THE CHIP.*** **An item records its own research, and +nothing records the work that ANSWERS it.** A ruling or a PR that settles a question never touches +the row that asked it, so the row keeps pointing at a settled question and every dispatch screen +reads it as current. The frozen chip is one instance; a row that outlives its own answer is the +general case, and it is measured in weeks rather than minutes. + +**Anchor drift is the second half and is reported worse than the first.** Four anchors adrift by +**50, 86, 581 and 593 lines** in one day, one item with both of its anchors dead. Hence the flat +rule now in sec. 5: **line numbers are navigation aids and never evidence.** + +### THE CORRECTION PATH EXISTS FOR ONE WORKER TYPE AND NOT THE OTHER + +**This row's title says "cannot be corrected", and that is true of a chip and of a `claude -p` +session. It is NOT true of a subagent.** `MANAGER.md:30` carries the table: a Console's workers are +separate `claude -p` sessions, a Manager's are **subagents in the Manager's own process**. A Manager +reported sending mid-flight corrections to four running subagents on 2026-09-04; each landed at the +worker's next tool round and all four acted on it. + +**So "what to do about a stale brief" has two answers by seat, and a single rule that ignores that is +wrong for one of them.** A fix aimed only at the frozen-chip case would leave the Manager path +untouched, and a fix assuming corrections are deliverable would not reach a Console Builder at all. + +***AND THE OBVIOUS FIX PUSHES IN A DIRECTION ANOTHER RULE ALREADY WARNS ABOUT.*** "Send more +corrections" collides with `MANAGER.md` sec. 5, which says to hand down **readings, not +conclusions**, because a worker told your conclusion will apply it and its own correct evidence will +lose. **A correction must therefore carry the new MEASUREMENT and let the worker re-decide** -- never +the sender's new conclusion. + +### The mitigation that has evidence behind it, and it is two lines + +The same seat put two lines at the top of every brief, and reports **six Builders hitting a stale +item with none wasting its turn**; two produced better findings than the original item asked for: + +1. *Check it is not already answered. If it is, record that with evidence and STOP -- that is a good + outcome, not a failure.* +2. *Re-measure every line number this item cites.* + +**Line 1 is the load-bearing half**, and it is a permission rather than an instruction: a Builder +that believes finding-it-done is a failed turn will build it again rather than report it. + +### Where the authoritative text lives is itself unresolved, and this row does not settle it + +`CLAUDE.md` sec. 5 is no longer the only place seat rules live. The vault's `roles/` folder carries +`COMMON.md`, `MANAGER.md`, `BUILDER.md` and others, and `MANAGER.md`'s standing rules say a conflict +between a playbook and `COMMON` goes to the **owner** rather than being resolved by any seat. + +**Checked before amending sec. 5, and reported as a null result rather than skipped:** `BUILDER.md` +carries no rule about verifying a brief against the tree, and `MANAGER.md` carries none the new +bullets contradict. **So there is no conflict to escalate today** -- but sec. 5's roster does not +list a Manager seat at all, while `MANAGER.md` exists and is being followed, and **that gap is a +question for the owner, not for this row.** + ### What a fix looks like, smallest first 1. **Prose, and it is already written.** `CLAUDE.md` sec. 5 now tells the receiver to verify a brief against the tree before acting, and tells whichever seat dispatched to mail the receiver when it takes an item back. **That is a workaround, not a mechanism.** It costs the receiver a diff read per named PR, and it fails silently the first time somebody skips it.