Skip to content

docs(rules): two multi-agent lessons that never landed, recovered from a merged branch - #447

Merged
ZacxDev merged 7 commits into
mainfrom
docs/worktree-verdict-and-two-rules
Aug 13, 2026
Merged

docs(rules): two multi-agent lessons that never landed, recovered from a merged branch#447
ZacxDev merged 7 commits into
mainfrom
docs/worktree-verdict-and-two-rules

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What

Two rules recovered from zach/rules-multiagent-lessons (ea82146), plus their archive
entries. Both were absent from claude/RULES.md and claude/RULES-ARCHIVE.md alike.

  • 🔴 cross-repo-worktreeisolation: "worktree" builds the worktree from the
    dispatching 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 status inside it is clean. It surfaces one
    indirection 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>/cmdline
    before 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 collected and
    exit 144 — both readable as code defects. /proc/<pid>/cwd is 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.

ea82146 is 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-control and count-tests anchors are
gone but their rule text is in main (count-testscount-not-exit-code), and
stale-gate-base survives compressed inside the merged-tree bullet as "check how far behind
main a PR is".

Size

RULES.md 31,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.md is not auto-loaded and
has no ceiling.

Verification

nix build .#checks.x86_64-linux.pytests on 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 own
stderr says it could not warm its isolated opencode config dir and needs to
npm-install @opencode-ai/plugin with network available. test_rules_size.py — the gate
that actually governs this change — passed.

A cached origin/main build shows failed=0, but that is not a control: cached means it ran
under whatever network conditions held then, versus my fresh run now. A forced --rebuild of
the 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

ZacxDev and others added 2 commits August 13, 2026 13:24
…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>
@ZacxDev

ZacxDev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Corrections to the PR body above — an audit found three claims wrong

Posting 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.
ea82146:claude/RULES.md carries 27 distinct → archive: anchors, of which 22 are
present in main and 5 are not. Of those 5, three survive as text under a changed or absent
anchor (cheap-controlclaude/RULES.md:55; count-testscount-not-exit-code at :61;
stale-gate-base compressed into the merged-tree bullet at :60), leaving the two this PR
recovers. So the conclusion — exactly two genuinely lost — is unchanged and independently
confirmed, but the arithmetic I quoted was not.

2. "recovered from ea82146" was accurate for the RULES.md bullets and NOT for the archive
sections.
Those were rewrites. They dropped Measured 2026-08-02, the repo pair
(civit/civitai dispatched from a cwd in civit/cli), the kill pattern
(chrome-headless-shell|vitest|steam-run, system-wide, ~15 PIDs), n=2, and the second failure
mode — i.e. precisely the evidence the archive exists to hold, while reading as though it were
that evidence. Two sentences were outright invention; one ("the loop can run several times
before anyone asks") is contradicted by the measurement it sat next to. Fixed in ff9589b:
both sections are now verbatim, and the recovered rule is no longer narrower than the original.

3. The gate failure's stated cause was the wrong mechanism. I attributed it to the npm/network
warning in the test's stderr. That warning is echoed because the test failed — pytest -q only
surfaces captured stderr on failure — so it is a symptom, not the cause. The failure is
subprocess.TimeoutExpired … timed out after 60 seconds, returncode: -9: a load flake in a
test that wraps a 60s timeout around an npm-shelling subprocess, on a box that had 3–4 concurrent
devrc-pytests builds in flight. Its absence from the passing logs proves nothing either way,
which makes that an empty result rather than a control.

The open item in the body is now closed, and it exonerates the PR

Baseline is sandwiched around the failure — same collected count, tree identified by the
RULES.md byte size in each derivation's source path:

derivation source tree result
wqppw4b… 31,726 B — main collected=9267 passed=9266 failed=0 · PASS
xy6iffw… 32,727 B — this PR collected=9267 passed=9265 failed=1 · FAIL
ia47nkq… byte-identical to origin/main collected=9267 passed=9266 failed=0 · PASS

Corroborated structurally: test_browser_agent.py never reads claude/, and this PR touches
only claude/RULES.md and claude/RULES-ARCHIVE.md.

Per this repo's own flake-vs-assertion rule a flaky test is fixable rather than re-runnable —
the 60s timeout is a real timing dependency on a shared box. Pre-existing and out of scope here;
worth its own issue.

Also fixed in ff9589b, from the same audit

  • /proc/<pid>/cwd was overstated. This harness nests agent worktrees at
    <dispatching-repo>/.claude/worktrees/agent-<id>, so the obvious prefix check against the repo
    root matches every sibling and discriminates nothing — compare the exact path. Worse, agents
    sharing the base clone (the read-only ones the adjacent rule says not to give a worktree) have
    identical cwds, so the discriminator does not exist for them — and the original incident's
    perpetrator was an auditor. Both caveats now stated.
  • The claim "confirming cmdline is not sufficient" was never measured — the auditor
    pattern-matched system-wide instead of running resolve-then-confirm. Sound inference, now
    labelled as inference.
  • cross-repo-worktree is an 11-day-old harness measurement re-landed without re-verification,
    now labelled, with the corroboration that does hold today (the Agent schema exposes no
    target-repo parameter).
  • New bullet reconciled with the "Mandatory" isolation rule above it; missing
    worktree-not-session TOC entry added; sibling-agent-kill moved to the TOC cluster its
    section actually lives in.

Size after the fix round: claude/RULES.md 32,727 → 33,289 B, against a 35,200 ceiling
requiring 900 headroom — 1,011 spare. All 5 tests in scripts/tests/test_rules_size.py pass,
including test_every_archive_pointer_resolves.

One thing I am not claiming to have fixed: the audit notes this repo's own size module says the
answer to the next real rule is consolidation, not another bump, and the scattered worktree
bullets it names just went from four to five. That is a fair criticism of this PR and I have left
it standing rather than bundle a consolidation into a recovery.

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>
@ZacxDev

ZacxDev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Correcting my own correction — the cwd caveat was wrong about its own incident

A delta re-audit of ff9589b found that the caveat I added in the previous round — and repeated
in the comment above — overstates its scope and contradicts the incident it describes. Fixed in
fe315e4.

What I got wrong. I wrote that the /proc/<pid>/cwd discriminator "does not exist" for
base-clone-sharing agents "— and the perpetrator above was an auditor", which reads as: cwd
filtering would not have prevented the recorded incident. It would have. The restored 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 it. What kills the
victim is the prefix check, which is precisely what caveat (a) diagnoses. My own caveat (a)
already contained the refutation of my caveat (b), two sentences apart.

So the real scope is narrower: cwd fails only between two agents that both sit in the base
clone. Also corrected the premise it leaned on — claude/RULES.md:94 says read-only agents
"don't need" a worktree, which is permissive; I had paraphrased it as a prohibition.

The consequence was worse than the overstatement. claude/RULES.md:104 ended up reading
"there, don't kill by pattern at all", aimed at read-only agents in the base clone — i.e. at
exactly the actor in the incident, whose stated need was "to clear its own hung browser/test
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 the one (b) was reaching for: kill only your own descendants — walk PPid in
/proc/<pid>/status back to your own pid, or track the pid you launched. Verified PPid is
populated on this host.

Size: claude/RULES.md 33,289 → 33,356 B; 944 spare against the 34,300 budget. All 5
tests in scripts/tests/test_rules_size.py pass — and the re-audit negative-controlled each of
them (forcing the ceiling below current size, the headroom above it, a missing file, a dangling
anchor), so the 5/5 is not a vacuous green.

The re-audit also independently reproduced the 27 → 22 present / 5 absent anchor count from my
previous comment under all four ways of counting, so that correction stands.

ZacxDev and others added 3 commits August 13, 2026 13:59
…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>
@ZacxDev

ZacxDev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Retracting the fix I recommended in the comment above

My previous comment endorsed "a filter that is deterministic and strictly stronger than cwd in
every case
… kill only your own descendants — walk PPid"
. That is measured false and has
been removed from the branch.
Correcting publicly, since it has been readable since I posted it.

Why it's wrong — two independent ways, both reproduced on this host:

  • A nix build an agent launches does its real work in a nix-daemon child of PID 1, not in
    the agent's descendant tree. All 9 live workers here have PPid=18717, and 18717 itself has
    PPid=1. So a PPid walk from the agent returns the empty set for exactly the hung-run case
    the rule is about.
  • Each Bash call is a fresh zsh -c (four calls → four distinct pids). A stray from an earlier
    tool call is not a descendant of the current one — and "clear its own hung run from earlier"
    is the incident's own stated need.

It also collided with claude/RULES.md:14, which mandates killing an orphan by resolved PID —
an orphan is PPid 1 by construction, so never anyone's descendant.

Net: the advice I removed (fail-safe: don't pattern-kill here) failed safe; the replacement I
recommended fails empty, while looking like it closed the gap. cwd and descendant filtering are
incomparable, not ordered. Fixed across 0679fe0 / fa1c54f: cwd is primary, a descendant walk
only narrows, orphan hunts are scoped to the deploy rule, and the fail-safe is restored.

Two more corrections from rounds 3–4

The trim cut a load-bearing qualifier. After trimming the bullet (to move my own error trail to
the archive) the core no longer said cwd cannot separate two agents that both sit in the base
clone — a configuration RULES.md:94 explicitly permits. That re-opened the original incident:
both agents match the exact-path check, sibling gets killed, and the fail-safe never fires because
the reader is confident. scripts/tests/test_rules_size.py:128-133 forbids exactly this — trimming
a "not just X" qualifier to buy bytes. Restored in fa1c54f.

"Including Chrome's zygote" was invention inside a "Measured" paragraph. Measured now: every
Brave --type=zygote process on this host has a real parent (2869800/28698022869781;
28698042869802); only chrome_crashpad sits at PPid 1. The zygote does not re-parent.
Struck, and the archive entry now records that the earlier assertion was wrong. The conclusion is
unaffected — the nix-daemon measurement and the per-tool-call mechanism each establish the empty
walk on their own, and it is the per-tool-call shell (not zygote re-parenting) that actually orphans
a chrome-headless-shell from an earlier call.

That is the fourth instance in this PR of one error class: stating an inference as an
observation. It is now logged in the archive entry itself, not just in a commit message.

State

claude/RULES.md 33,300 B — 1,000 spare against the 34,300 budget. All 5 tests in
scripts/tests/test_rules_size.py pass, negative-controlled on all five plus a positive control on
the pointer test, so the green is not vacuous. ea82146's recovered bodies remain byte-identical
through every round (cmp-verified). Gate green on 388ddac (re-run), ff9589b, fe315e4,
37dbbe8 — with main passing either side of the single early failure, which was a load flake in a
60s-timeout test.

…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>
@ZacxDev
ZacxDev merged commit 2f6d4ad into main Aug 13, 2026
@ZacxDev
ZacxDev deleted the docs/worktree-verdict-and-two-rules branch August 13, 2026 20:03
ZacxDev added a commit that referenced this pull request Aug 13, 2026
… 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>
ZacxDev added a commit that referenced this pull request Aug 13, 2026
…-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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant