docs(rules): two multi-agent lessons that never landed, recovered from a merged branch - #447
Conversation
…m a merged branch `zach/rules-multiagent-lessons` merged as #313, so it read as fully absorbed. It scores 1% when measured by added-lines-present-in-main -- which means nothing for RULES.md, a file that is continuously reworded. Measured by ARCHIVE ANCHOR instead, 20 of its 22 lessons are in main and two are in neither RULES.md nor RULES-ARCHIVE.md: - cross-repo-worktree: `isolation: "worktree"` builds the worktree from the dispatching session's CURRENT repo, not the repo the task names. Cross-repo dispatch silently gives every agent a worktree of the wrong repo, with no error. - sibling-agent-kill: under a parallel fan-out, confirming `/proc/<pid>/cmdline` is not sufficient before a kill -- a sibling agent running the same suite matches it legitimately. Filter by `/proc/<pid>/cwd` to your own worktree too. Found while diffing the 23 preserved worktrees for keep-or-drop (verdict table in claudedocs/handoff-subsystem-store.md, landed in #446). Every other worktree was absorbed or is deliberately open (#355); this was the single real loss, and the "superseded intermediate" guess would have dropped it. RULES.md 31,726 -> 32,727 bytes, against a 35,200 ceiling with 900 required headroom -- fits with 1,573 to spare, so no eviction was needed. Numbers read from scripts/tests/test_rules_size.py, which owns them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Audit of #447 found the two archive sections were REWRITES, not recoveries. They dropped exactly what an archive exists to carry: `Measured 2026-08-02`, the repo names (`civit/civitai` dispatched from `civit/cli`), the kill pattern (`chrome-headless-shell|vitest|steam-run`, system-wide, ~15 PIDs), and n=2. Both sections are now ea82146's text verbatim, plus a remedy paragraph and the cross-links. Two invented sentences that read like evidence are gone -- "the loop can run several times before anyone asks" is contradicted by the n=2 measurement (one agent refused, one self-recovered; there was no loop). Also restores the SILENT failure mode the paraphrase lost. cross-repo-worktree has two, and the first round taught only the loud one: an agent may instead self-recover by quietly building its own worktree in the right repo, leaving the dispatcher's model of where the work happened wrong. A rule recovered and then narrowed is worse than the rule it replaced. Corrections to the claims themselves, both measured on the workbench today and neither present in the 2026-08-02 write-up: - `/proc/<pid>/cwd` is not sufficient as stated. This harness nests agent worktrees at `<dispatching-repo>/.claude/worktrees/agent-<id>`, so a prefix check against the repo root matches every sibling and discriminates nothing -- compare the EXACT path. And agents that share the base clone (the read-only ones, which the adjacent rule tells you not to give a worktree) have identical cwds, so the discriminator does not exist for them at all -- which is what the perpetrator of the original incident was. - The inference "confirming `cmdline` is not sufficient" was never measured: the auditor pattern-matched system-wide rather than running resolve-then-confirm. The widening is sound but it is reasoning, and is now labelled as such. - cross-repo-worktree is an 11-day-old harness measurement re-landed without re-verification. Labelled, with the structural corroboration that does hold today: the Agent tool schema exposes no target-repo parameter. Reconciles the new bullet with the "Mandatory" isolation rule directly above it -- having the agent make its own worktree still satisfies that mandate, so it is not an exception. Adds the missing `worktree-not-session` TOC entry (pre-existing gap) and moves `sibling-agent-kill` to the git/worktree TOC cluster where its section actually lives. RULES.md 32,727 -> 33,289 against the 34,300 budget; 1,011 spare. All 5 tests in scripts/tests/test_rules_size.py pass, including test_every_archive_pointer_resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Corrections to the PR body above — an audit found three claims wrongPosting rather than silently editing, since the body has been readable since it was opened. 1. "20 of its 22 lessons are in main" is wrong — neither number reproduces. 2. "recovered from 3. The gate failure's stated cause was the wrong mechanism. I attributed it to the npm/network The open item in the body is now closed, and it exonerates the PRBaseline is sandwiched around the failure — same collected count, tree identified by the
Corroborated structurally: Per this repo's own Also fixed in
|
Delta re-audit of ff9589b caught an analytical error the previous fix round introduced -- not a restoration defect; the recovered text is faithful. Caveat (b) said the discriminator "does not exist" for base-clone-sharing agents "-- and the perpetrator above was an auditor", which reads as: cwd filtering would not have saved the recorded incident. That is false, and caveat (a) two sentences earlier already implied so. The retained ea82146 text says the victim died "in another worktree of the same repo". Perpetrator in the base clone, victim in a worktree -> the two cwds DIFFER -> exact-path comparison spares the victim. The prefix check is what kills it, which is exactly what (a) diagnoses. So (b)'s real scope is narrower than I stated: cwd fails only between two agents that BOTH sit in the base clone. Corrected, along with the premise it leaned on -- RULES.md:94 says read-only agents "don't need one", which is permissive, and I had paraphrased it as a prohibition. The consequence was worse than the overstatement. RULES.md:104 read "there, don't kill by pattern at all", aimed at read-only agents in the base clone -- i.e. at precisely the actor in the incident, whose stated need was to clear ITS OWN hung run. A prohibition with no substitute, for the one case the archive entry exists to describe. Replaced with a filter that is deterministic and strictly stronger than cwd in every case including that one: kill only your own descendants, walking PPid in /proc/<pid>/status back to your own pid, or track the pid you launched. Verified PPid is populated on this host. The same over-reach was published in the PR comment; a follow-up comment corrects it. RULES.md 33,289 -> 33,356 against the 34,300 budget; 944 spare. All 5 tests in scripts/tests/test_rules_size.py pass. Those tests were negative-controlled during the re-audit -- forcing the ceiling under the current size, the headroom above it, a missing file and a dangling anchor each turned the matching test red with its own message -- so the 5/5 is not vacuous. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correcting my own correction — the cwd caveat was wrong about its own incidentA delta re-audit of What I got wrong. I wrote that the So the real scope is narrower: cwd fails only between two agents that both sit in the base The consequence was worse than the overstatement. Replaced with a filter that is deterministic and strictly stronger than cwd in every case, Size: The re-audit also independently reproduced the 27 → 22 present / 5 absent anchor count from my |
…or the case it was for Round-3 delta audit found a 🔴 in fe315e4: I replaced a fail-safe prohibition with a procedure that does not reach its targets. Verified both decisive claims myself rather than taking the audit's word. - MEASURED: nix-daemon workers are children of nix-daemon (PID 18717, PPid 1). A `nix build` an agent launches does its real work in a process that is NOT the agent's descendant, so a PPid walk returns the empty set for exactly the hung-run case sibling-agent-kill is about. Same for anything that daemonizes or re-parents -- including Chrome's zygote, and the original incident's pattern was `chrome-headless-shell`. A walk is also per-tool-call: each Bash call is a fresh `zsh -c`, so an earlier call's strays are not descendants of the current one. - MEASURED: RULES.md:14 mandates killing an orphan "by resolved PID". An orphan is PPid 1 by construction, so it is never your descendant -- line 104's "kill only your own descendants" contradicted it outright. So "strictly stronger in every case" was wrong twice over. cwd and descendant are INCOMPARABLE filters, not ordered. cwd is now primary (it demonstrably separates the recorded pair); the PPid walk only NARROWS a cwd-filtered set and can never build one; orphan hunts are explicitly scoped out to the deploy rule. Restores a fail-safe, which is what the previous round actually removed: if no filter leaves a confident set, kill nothing and hand it to the operator. An empty descendant set cannot distinguish "no strays" from "the walk cannot see them" -- the archive's own empty-result trap, wearing a procedure. Also strikes "the perpetrator sat in the base clone" (RULES-ARCHIVE.md:659). The original never places the perpetrator; that was my inference stated as fact, three sentences above the same paragraph's "it is reasoning, not an observation". The victim-side fact -- the victim was in another worktree -- carries the argument alone. My control last round ("verified PPid is populated") validated that the FIELD exists, not that the procedure reaches the targets. PPid is always populated on Linux. That is the instrument-validation rule's negative control missing: the check needed was whether a hung run from an earlier tool call is reachable by the walk. It is not. ea82146's recovered bodies remain byte-identical (cmp-verified). RULES.md 33,356 -> 33,596 against the 34,300 budget; 704 spare. All 5 tests in scripts/tests/test_rules_size.py pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review with Zach: the sibling-agent-kill bullet had accreted a REFUTATION of an idea no reader has had yet -- descendant/PPid filtering -- which exists in the core only because I proposed that fix in fe315e4 and had to walk it back in 0679fe0. A reader arriving fresh needs the imperative, not my error trail. The core now carries the three actionable facts: filter by the EXACT worktree path (a prefix matches every sibling, because agent worktrees nest under the base repo); a box-wide pattern reaches a sibling agent's processes, with the incident; and if no filter leaves a confident set, kill nothing and hand it over. Everything evicted is preserved in the archive entry, where it costs nothing until recalled -- that the two filters are incomparable rather than ordered, the measured nix-daemon/PPid-1 re-parenting that makes a descendant walk return empty, the per-tool-call shell, and the scoping of orphan hunts to the deploy rule. Verified `nix-daemon`, `PPid` and `incomparable` now appear in RULES-ARCHIVE.md and NOT in RULES.md. RULES.md 33,596 -> 33,145 against the 34,300 budget; spare goes 704 -> 1,155. All 5 tests in scripts/tests/test_rules_size.py pass. Not addressed here, deliberately: the size module's own comment says the answer to the next real rule is consolidation, not another bump, and this PR takes the worktree-related bullets from four to five. Zach's call was to land the recovery and consolidate separately rather than entangle a refactor with a salvage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rying scope Round-4 delta audit found a 🔴 in 37dbbe8 -- a regression from the eviction I proposed. Verified both findings against the files and live /proc before acting. 1. SCOPE LOSS (blocking). Trimming the bullet dropped every mention of the case cwd CANNOT answer. `grep "base clone"` on bullet 104 returned 0. The core then read as an unqualified "confirm /proc/<pid>/cwd is your OWN worktree -- the EXACT path", framing exact-match as THE fix for the sibling problem. Two read-only agents in the base clone -- a configuration RULES.md:94 explicitly permits -- both match, and the auditor kills the sibling. That is the 2026-08-02 incident verbatim, re-opened. The retained fail-safe does not catch it: "if no filter leaves a set you are confident in" never fires, because the reader IS confident -- the exact-path check matched and nothing left in the core says a match can still be a sibling. This is precisely what scripts/tests/test_rules_size.py:128-133 forbids in bold: trimming a "not just X" qualifier to buy bytes reintroduces the failure mode. The qualifier lived in the archive, which is not auto-loaded. Restored, ~150 B against 1,155 spare. Also restores the 🔴 marker the trim dropped from the fail-safe (:142 lists the priority marker under what stays). 2. UNMEASURED CLAIM INSIDE A "MEASURED" PARAGRAPH (again). The archive asserted a descendant walk misses "anything that daemonizes or re-parents -- including Chrome's zygote". Measured on this host: all Brave --type=zygote processes have real parents (2869800/2869802 <- 2869781; 2869804 <- 2869802); only chrome_crashpad sits at PPid 1. The zygote does NOT re-parent. Struck, and the entry now says so explicitly, because the wrong version was in a paragraph headed "Measured 2026-08-13" and in 0679fe0's commit message under "MEASURED:". The conclusion is unaffected -- the nix-daemon measurement and the per-tool-call mechanism each independently establish the empty walk, and both reproduce. It is the per-tool-call shell, not zygote re-parenting, that actually orphans a chrome-headless-shell from an earlier call; the entry now says that instead. Fourth recurrence in this PR of one error class: stating an inference as an observation. Logged in the entry itself rather than only in a commit message. 3. The archive's `Supports:` line still quoted core text 37dbbe8 had deleted. Requoted. No test covers archive->core quote fidelity (test_every_archive_pointer _resolves only checks core->anchor), so this one is eyes-only. RULES.md 33,145 -> 33,300 against the 34,300 budget; 1,000 spare. All 5 tests in scripts/tests/test_rules_size.py pass, negative-controlled by the auditor on all five plus a positive control on the pointer test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Retracting the fix I recommended in the comment aboveMy previous comment endorsed "a filter that is deterministic and strictly stronger than cwd in Why it's wrong — two independent ways, both reproduced on this host:
It also collided with Net: the advice I removed (fail-safe: don't pattern-kill here) failed safe; the replacement I Two more corrections from rounds 3–4The trim cut a load-bearing qualifier. After trimming the bullet (to move my own error trail to "Including Chrome's zygote" was invention inside a "Measured" paragraph. Measured now: every That is the fourth instance in this PR of one error class: stating an inference as an State
|
…wed it was not Round-5 delta audit was CLEAN on 37dbbe8..fa1c54f -- first clean round in five -- but found this pre-existing 🟡, introduced back in 0679fe0 and untouched since. Verified live before acting. The paragraph said a `nix build`'s real work runs in a nix-daemon child of PID 1, "so a PPid walk from the agent returns the EMPTY SET". The premise is true; the conclusion does not follow, and measurement refutes it. Sampled while this very gate ran: nix 368350 <- 366150(zsh) <- 328364(.claude-wrapped) <- the CLIENT is a descendant nix-daemon 368389 <- 18717 <- 1 <- the BUILDER is not So the walk returns something -- the client stub -- it just never reaches the process doing the work. Killing what it returns does not stop the build. Emptiness DOES hold, but via the other ground the paragraph already gave: the per-tool-call shell. Measured seven sequential Bash calls, seven distinct shell pids, and an actual orphaned stray from an earlier call (`nix 2085612 <- 1`). The entry now separates the two grounds and states which one yields which conclusion, and records the retraction inline -- because the wrong version sat inside a paragraph headed "Measured 2026-08-13", which is the whole failure mode being catalogued. Fifth instance in this PR of one class: stating a conclusion as measured when only its premise was. The imperative is unchanged and is now better supported than by the reason originally given: use a descendant walk to NARROW a cwd-filtered set, never to build one. Archive-only; RULES.md untouched at 33,300 B, 1,000 spare. All 5 tests in scripts/tests/test_rules_size.py pass. ea82146's recovered body still cmp-identical at 885 bytes. 🔴 Committed from a dedicated worktree at fa1c54f, NOT the base clone: another session checked ~/workspace/devrc out onto main and fast-forwarded it to b2fb680 mid-session. The gate I had just launched "on fa1c54f" resolved its derivation from that working tree and therefore measured MAIN -- collected=9429 against 9267 on every prior run, which was the tell I read past. That result is retracted; this commit's gate runs against the real branch tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… were (#454) The canonical handoff on main still read "DIFFED AND DECIDED -- NOT EXECUTED ... the drops have NOT been run -- 24 worktrees were still present". All 22 drops executed 2026-08-13; the count is 10. A /resume off this doc would have acted on a picture that stopped being true hours earlier -- the stale-memory hazard the rules exist for, in the file a session reads FIRST. Records what the sweep actually did, because the safety properties are the part worth carrying forward, not the count: - the removal script re-verified each worktree's HEAD and cleanliness AT the moment of removal rather than trusting the survey that motivated it. That earned its keep -- five worktrees from concurrent sessions had appeared in the interval and were left alone because they were not in the adjudicated set. - six drops were blocked by an untracked one-line `use opencode` .envrc. Each was checked to hold ONLY that, the file was removed, and the PLAIN (non---force) remove was retried, so any genuine surprise would still have blocked. - the residue is #355 (deliberately kept, DO-NOT-MERGE), this base clone, and worktrees created after the survey. Corrects the tag-preservation note from an instruction into a record, and adds the lesson that note was missing: A LOCAL TAG IS NOT A BACKUP. The 8 preserved/* tags sat local-only for the first hour after the drops, so the reversibility this section promised did not exist -- one disk failure and 22 "superseded" calls become unfalsifiable. They are now on origin, verified by reading them back: `git ls-remote --tags origin 'preserved/*'` returns 8. ls-remote is the check; `git tag -l` answers a different question. Also carries forward the #447 finding worth having in the handoff: four audit rounds, four real defects, and every defect after round 1 was in a caveat the FIX rounds added, never in the recovered material -- ea82146's bodies have been cmp-identical since ff9589b. Recurring error class, four times: an inference stated as an observation inside a paragraph headed "Measured". The doc now names the stopping rule that follows from that pattern. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-failure frame (#193) (#458) * refactor(rules): five worktree bullets each re-stated the same silent-failure frame (#193) `scripts/tests/test_rules_size.py` said eviction had been run to completion and that the answer to the next real rule was consolidation, not another ceiling bump — naming "the four scattered worktree bullets" as the candidate. PR #447 then made it five. This does the consolidation it asked for. The cluster stated three things more than once: - the `git worktree add … -b <branch> origin/<main-branch>` recipe, written out twice (sync-a-branch, and again for cross-repo). Now once, in the `git -C <repo>` form that covers both, referenced by the cross-repo rule. - "never two file-modifying agents in one checkout", stated as a hazard and again as a prohibition inside the same bullet. Now once as each. - the silent-failure framing ("no error, the worktree is created successfully"), spelled out separately by the cross-repo, session and .envrc bullets. Now hoisted once. Structurally: three separate "a worktree does not isolate X" rules became one frame — **a worktree isolates a working DIRECTORY only** — with three enumerated unisolated surfaces, the REPO it is built from, the SESSION, and the ENVIRONMENT. Each keeps its own 🔴, its own imperative, its own failure shape and its own `→ archive:` pointer, so none of them depends on the frame to carry its scope, and a fourth surface can join the list without re-stating it. The frame is also a correctness fix: the old text asserted "A worktree isolates the REPO, not the SESSION" while the bullet directly below it said the worktree is built from the wrong repo. Both cannot be true. "isolates a working DIRECTORY only" is the archive's own wording and is wider than either. claude/RULES.md 33,300 -> 33,229 B (-71). That is a small number and it is the finding, not a shortfall: every surviving clause is scope-bearing, so the only bytes available were genuine restatement. Recorded in the gate module so nobody reads this as evidence that another -71 B is sitting there. Nothing moved to the archive — there was no un-evicted narrative left in the cluster to move, which is the same "eviction has been run to completion" the module already recorded. The three archive sections had their `*Supports:*` quote lines updated to match the new core wording. Qualifiers considered for trimming and deliberately KEPT (each would have let the shortened rule pass while the hazard survived in a shape the original covered): - "Two failure modes, only one loud" + both modes. The point of the pair is that you would not necessarily notice; neither mode alone carries it. - "that still satisfies the isolation mandate, so this is not an exception to it" — without it, "do NOT pass the flag" reads as licence to skip isolation. - "file-modifying" and "in one checkout" on the never-two prohibition. A bare "never two" was written, then reverted: it silently dropped both qualifiers. - "Read-only agents (audits/research/exploration) don't need one" — the parenthetical is the routing surface that makes "read-only" operational. - "working in a tree where its brief makes no sense" — arguably subsumed by the two failure modes, which is exactly the reasoning the #447 trim used. - the `../<repo>-<topic>` path convention, dropped when the recipe was unified and restored once a cold read caught it. MAX_BYTES deliberately NOT ratcheted down. The headroom predates this change, and tightening the gate is a policy call that belongs in its own commit. Verification: all 5 functions in test_rules_size.py pass, and each was negative-controlled — ceiling forced below current size, headroom forced above the gap, RULES_MD and ARCHIVE_MD pointed at a missing file, and a bogus `→ archive:` anchor injected — each going red with its own message. A separate claim-coverage checker asserts all 56 distinct claims from the original block are still present and records the 7 deliberate rewordings with their reasons; it was itself controlled (empty file -> all 56 reported missing; five single-claim deletions -> red; unmutated -> green). Authoritative gate: TOTAL collected=9429 passed=9428 failed=0, RESULT: PASS (exit=0). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(rules): the frame over-generalised the three surfaces it introduced Audit of #458 found no lost claims -- the consolidation itself is sound -- but two 🟡s in the NEW frame text, both of the "a frame is a claim too" class this file keeps getting bitten by. Fixed here rather than in a follow-up, because the frame is what a skimming agent retains. 1. The verb fitted one of its three surfaces and INVERTED the sign on another. "three surfaces it does NOT isolate" is right for SESSION (scratchpad and branch namespace leak in). For REPO the failure is TARGETING, not isolation -- the worktree is perfectly isolated, just of the wrong repo. For ENVIRONMENT it read backwards: "does not isolate the environment" implies the environment is SHARED, when the hazard is that it is ABSENT (.envrc never comes with the checkout). That false implication is precisely the belief the .envrc rule exists to kill. Now "the surfaces it does NOT hand you", which covers all three. 2. "each failing SILENTLY" was hoisted from ONE bullet and asserted of three. Only the cross-repo bullet ever claimed it; grep of origin/main confirms neither the SESSION nor the .envrc bullet contains `no error`, `created successfully` or `silently`. Worse, the ENVIRONMENT sub-bullet documents two LOUD manifestations in its own text -- a toolchain binary "missing", and an un-`direnv allow`ed .envrc erroring on every `cd`. The claim that IS true of all three is that `worktree add` succeeds without warning you, so that is what it now says. 3. "three surfaces" read as a CLOSED enumeration. A fourth already exists eleven lines later -- the machine/process surface at the pgrep/pkill bullet, where a box-wide pattern reaches a sibling agent's processes (-> archive: sibling-agent-kill); the archive's own wording is "not a private repo and not a private machine". The frame now says the list is not closed, and the gate module records why. Also corrects three comment claims in scripts/tests/test_rules_size.py, since a comment is a claim too and this module is where the next maintainer will look: - "five bullets became one recipe + one frame" was 5 -> 2; it is 5 -> 3 (the base-clone re-sync bullet stayed top-level). - the ~250 B saving is an ESTIMATE of a hypothetical, not a measurement, and is now labelled as one. - the comment restated MIN_HEADROOM_BYTES as a literal ("~1,970 vs the 900 floor"), which the module's own docstring forbids for drift reasons. Replaced with a pointer to the constants. And requotes the three `*Supports:*` lines in RULES-ARCHIVE.md: two of them did not match the core verbatim (ASCII `--`/`->` for `—`/`→`, and a capitalisation), so an archive->core grep missed. All four quoted substrings now grep to exactly 1 hit. Nothing enforces this direction -- test_every_archive_pointer_resolves only checks core->anchor -- so it is eyes-only. RULES.md 33,229 -> 33,264 B (+35) against the ceiling and headroom floor the module owns; all 5 tests pass. MAX_BYTES and MIN_HEADROOM_BYTES untouched, no assert changed -- the audit proved the gate module's AST identical to main's before this commit, and this commit keeps it comment-only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(rules): the comment-accuracy fix left its own byte figures stale Delta audit of 7eada94..09e78ab found the fix round sound on every claim -- and one regression it introduced, inside the very comment block whose purpose was comment accuracy. The block still read "Result: 33,300 -> 33,229 B" and "That -71 B is the honest yield" after the frame reword had spent 35 B back. A reader at main measures 33,264 against a comment asserting 33,229, with the headline yield overstated roughly 2x. Now records both numbers and why they differ: the consolidation landed 33,229, the audit found the new frame verb described only one of its three surfaces correctly, and the reword cost 35 B. Both ship in one merge, so -36 B is what the tree sees. The audit also settled the question I raised against my own fix -- whether changing the frame verb to suit REPO and ENVIRONMENT had broken SESSION. It had not: "does NOT hand you" is better for REPO (the failure is targeting, not isolation) and fixes the sign inversion on ENVIRONMENT, and while the old verb was marginally more direct for SESSION, no claim is lost -- the sub-bullet still spells the leakage out verbatim. Silence is preserved and arguably strengthened: "a SUCCESSFUL `worktree add`" names the moment the silence occurs, which is what defuses the contradiction with ENVIRONMENT's two loud downstream tells. Verified after: RULES.md 33,264 B, all 5 tests pass, and the gate module's ast.dump SHA is unchanged from origin/main (a5c3e59bea8d9b87) -- still provably comment-only, no assert or constant touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
What
Two rules recovered from
zach/rules-multiagent-lessons(ea82146), plus their archiveentries. Both were absent from
claude/RULES.mdandclaude/RULES-ARCHIVE.mdalike.cross-repo-worktree—isolation: "worktree"builds the worktree from thedispatching session's current repo, not the repo the task names. Dispatch a fan-out at
repo B while sitting in repo A and every agent gets a worktree of A, with no error: the
worktree is created successfully and
git statusinside it is clean. It surfaces oneindirection away, as an agent reporting that a file named in its own brief does not exist.
sibling-agent-kill— under a parallel fan-out, confirming/proc/<pid>/cmdlinebefore a kill is not sufficient: a sibling agent running the same suite matches it
legitimately. One auditor cleaning up its own hung run killed ~15 PIDs and destroyed another
agent's in-flight test run, whose next attempt collapsed with
0 files collectedandexit 144— both readable as code defects./proc/<pid>/cwdis the discriminator.How these were found, and why they were nearly dropped
While diffing the 23 preserved worktrees for keep-or-drop. The verdict table landed separately
in #446 (
claudedocs/handoff-subsystem-store.md): 22 DROP, 1 KEEP (#355), 1 salvage —this one.
ea82146is the branch of #313, which merged. It therefore looks fully absorbed, and the"superseded intermediate" reading would have dropped it.
The measurement that classified the other 22 — what fraction of the lines the branch itself
added are present in main today — scores this branch at 1%, which means nothing for
RULES.md, a file that is continuously reworded. Re-measured by archive anchor instead,20 of its 22 lessons are in main and exactly these two are not.
Deliberately not salvaged, having checked:
cheap-controlandcount-testsanchors aregone but their rule text is in main (
count-tests→count-not-exit-code), andstale-gate-basesurvives compressed inside themerged-treebullet as "check how far behindmain a PR is".
Size
RULES.md31,726 → 32,727 bytes against a 35,200 ceiling requiring 900 headroom —1,573 bytes to spare, so no eviction was needed. Numbers read from
scripts/tests/test_rules_size.py, which owns them.RULES-ARCHIVE.mdis not auto-loaded andhas no ceiling.
Verification
nix build .#checks.x86_64-linux.pytestson this tree:TOTAL collected=9267 passed=9265 skipped=1 failed=1.🔴 Read that honestly: the suite is not clean, and I am not claiming my change is why. The
one failure is
test_agent_def_denies_bash_allows_only_custom_tool(
scripts/browser-bridge/tests/test_browser_agent.py), a 60s subprocess timeout whose ownstderr says it could not warm its isolated opencode config dir and needs to
npm-install @opencode-ai/pluginwith network available.test_rules_size.py— the gatethat actually governs this change — passed.
A cached
origin/mainbuild showsfailed=0, but that is not a control: cached means it ranunder whatever network conditions held then, versus my fresh run now. A forced
--rebuildofthe baseline drv is running to make the comparison discriminating; result appended below
before this merges. Three markdown files cannot plausibly reach a browser-agent subprocess,
but a theory that explains a failure is not evidence for it.
🤖 Generated with Claude Code