chore(governance): one delivery cycle per session + a Bash-burn hook (ADR-G009) - #284
chore(governance): one delivery cycle per session + a Bash-burn hook (ADR-G009)#284aperim-agent wants to merge 3 commits into
Conversation
…er (RED) Both tests land before their subjects, per the R2 test-first rule. test-prefer-native-tools.mjs asserts three things about the PreToolUse hook: the repo's own contract commands still run, unbounded reads/searches are caught, and — the load-bearing one — no block ever names a tool this harness does not have. Grep and Glob do not exist here (verified 2026-07-27 by direct call), so a block recommending them would strand the agent with no action available. It also pins the fail-open contract on malformed payloads. lib/test.mjs pins wave partitioning and the DONE gate: unknown lanes coerce to single-writer, the wave cap defers rather than drops, DONE fails safe on every invalid input, and the dry-cycle requirement cannot be argued below 2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured over 7 days across 1,589 sessions: cache reads are 59.5% of true cost, and the 4% of sessions past 250 turns are 66% of spend — $0.47/session at 16-40 turns against $40.70 above 600. Cache read is prefix size x turn count, so the only lever that touches the 66% is ending the session. Instruction files are 0.4% of payload; shrinking documents cannot move this. Multiview had no Stop hook, no supervisor and no resume-directive. Its self-continuation was three lines: step 9 RESCHEDULE of the orchestrate skill and ADR-G007's Autonomy clause authorising a ScheduleWakeup-driven loop. Both are withdrawn. A cycle is now one session; tick.sh starts the next one in a fresh process under a single-owner lock, and continuity lives in GitHub. The orchestrate skill becomes short and repo-agnostic, reading orchestrate.config.json. Multiview's half — the lane map and the specific file that forces each lane serial, the class-scaled gate, the cross-vendor review and its never-merge-on-fallback rule, merge mechanics, salvage, build-cache discipline — moves to docs/runbooks/orchestrate.md rather than being deleted. Wave partitioning and the DONE gate become pure tested code: unknown lanes coerce to single-writer, and DONE fails safe on every invalid input. The Bash hook deviates from the sweep's supplied version, deliberately. That version redirected searches to Grep and Glob tools; neither exists in this harness, verified by direct call, including in the headless sessions tick.sh spawns. A block whose only remedy is an uncallable tool is worse than the burn it prevents, so searches must bound themselves (-l, -c, -m N, or | head) instead. Read does exist, so the file-read rules stand as written. Segments whose stdout is piped onward are exempt — those bytes never reach the transcript, which is why the gitleaks checksum verify still runs. Verified: 397 commands extracted from CI, the devcontainer, scripts/, xtask, web/package.json, the runbooks and agent docs; 394 pass. The 3 blocked were unbounded rg examples in working-in-this-monorepo.md, and that caller was fixed. Hook 53/53, partitioner 22/22, tick.sh guard paths exercised (STOP, DONE, live lock, stale-lock reclaim, lock release), links and inclusive language clean, cargo fmt and cargo check --workspace green. Ships disarmed: no ACTIVE sentinel, and the ready/blocked/loop-state labels do not exist yet. Arming is an explicit operator action. R2 (agent policy). No invariant touched; no test weakened; no Rust, CI, manifest or dependency file changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e map Three defects in the hook adaptation, all now pinned by tests: - Piped file reads were blocked while telling the agent to use Read, which cannot feed a pipe. `cat pkg.json | jq .version` and `head -100 build.log | grep error` had no available remedy — and the second is the very thing AGENTS.md now advises. The pipes-onward exemption ran after the file-read rules instead of before them, contradicting both the file header and ADR-G009. - ripgrep's -L is --follow, which WIDENS a search; it was counted as a bounding flag. It stays bounded for grep and ag, where -L really is files-without-match. - A repeated segment was resolved by first textual occurrence, so `rg foo src | head -5 && rg foo src` let the unbounded copy through, and `rg foo src > /tmp/a; rg foo src | head -5` was wrongly blocked. Segments now carry their own separators, and `||`/`&&` are recorded as control flow rather than pipes. Lane map: multiview-hal becomes its own lane. ADR-G007 split it across two territories — load.rs to engine, select.rs to gpu — but both are declared in one hal/src/lib.rs and share one Cargo.toml, so the split put a shared file in two concurrently-dispatchable lanes, which the partitioner cannot express. scopedLanes ships empty. gpu and audio would each parallelise safely per crate, but partition.mjs admits a second item into a scoped lane when the first declares no scope — it reserves the lane, not the scope namespace. That defect is upstream in a file this repo received verbatim, so it is reported and worked around rather than silently patched. Same for the two tick.sh lock defects (recycled pid stalls the loop; stale-lock reclaim is not atomic), which are documented with symptoms and recovery in the runbook. Restores two things the rewrite dropped: the board RECORD discipline (flip the Part-2 box, set Part-3 Status, red-to-green SHAs and PR number inline) and the proactive qdrant-store mandate. Hook 61/61, partitioner 22/22, links and inclusive language clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review record —
|
CI:
|
Draft deliberately — do not merge. The R2 cross-vendor review gate cannot run:
codex execfails 401, "Your access token could not be refreshed because your refresh token was revoked." Per ADR-G007 and the codex-review runbook, a fresh-context Claude pass is a labelledclaude-fallback, not cross-vendor, and this repo never merges on a fallback verdict. Undraft once Codex auth lands and a real cross-vendor pass is recorded here.Why
Measured over 7 days across 1,589 sessions: cache reads are 59.5% of true cost, and the 4% of sessions past 250 turns are 66% of spend — $0.47/session at 16–40 turns against $40.70 above 600. Cache read is prefix size × turn count, so the only lever that touches the 66% is ending the session. Instruction files are 0.4% of payload — shrinking documents cannot move this.
Multiview had no
Stophook, no supervisor, no resume-directive. Its self-continuation was three lines: step ⑨ RESCHEDULE of theorchestrateskill, and ADR-G007's Autonomy clause authorising aScheduleWakeup-driven loop.What changed
tick.shstarts the next in a fresh process under a single-owner lock; continuity lives in GitHub. ADR-G007's Autonomy clause + step ⑨ are superseded; its territory, sole-integrator, review and memory decisions stand.orchestrateskill is now repo-agnostic, readingorchestrate.config.json. Multiview's half — lane map, class-scaled gate, cross-vendor review, merge mechanics, salvage, build-cache discipline — moved todocs/runbooks/orchestrate.md, not deleted.PreToolUseBash hook stops unbounded reads/searches dumping into context.Deviation from the brief, and why
The supplied hook redirected searches to the
GrepandGlobtools. Neither exists in this harness — verified by direct call (No such tool available), including in the headlessclaude -psessionstick.shspawns. A block whose only remedy is an uncallable tool is worse than the burn it prevents.The intent is kept; the remedy changed to one an agent can actually take:
rg "pattern" src-l,-c,-m N, or `rg -l "pattern" src·rg -m5 …find . -name '*.ts'cat/head/tail/sed -n M,NpRead(it exists)Segments whose stdout is piped onward are exempt — those bytes never reach the transcript. That is why
grep "${tarball}" checksums.txt | sha256sum -c -ingitleaks.ymlstill runs untouched.Verification
scripts/,xtask,web/package.json, runbooks and agent docs, replayed through the hook: 394 pass. The 3 blocked were unboundedrgexamples inworking-in-this-monorepo.md— exactly the target — and that caller was fixed.node .claude/hooks/test-prefer-native-tools.mjs→ 53/53 (includes the fail-open contract and an assertion that no block ever namesGrep/Glob).node .claude/skills/orchestrate/lib/test.mjs→ 22/22.tick.shguard paths exercised: STOP, DONE, live lock respected, stale-pid lock reclaimed, lock released on exit.check-inclusive-language.shOK ·check-markdown-links.shOK ·cargo fmt --all -- --checkOK ·cargo check --workspaceOK.scripts/classify.sh→ R2. No Rust, CI, manifest or dependency file changed; clippy/test/deny were not re-run locally for that reason — CI runs them here.9628f429; subjects absent from that tree).Ships disarmed
No
ACTIVEsentinel. Before this loop can dispatch, an operator must (1) create thestatus: ready/status: blocked/loop-statelabels — none exist today; (2) open the loop-state issue; (3) decide the backlog substrate, since the backlog is still the 402 KBwork-schedule.mdboard rather than issues. All three are recorded in the runbook.🤖 Generated with Claude Code