@tyroneross/build-loop 0.34.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/AGENTS.md +71 -18
  6. package/README.md +208 -299
  7. package/agents/advisor.md +2 -0
  8. package/agents/alignment-checker.md +2 -0
  9. package/agents/api-assessor.md +3 -1
  10. package/agents/architecture-scout.md +2 -0
  11. package/agents/assessment-orchestrator.md +2 -0
  12. package/agents/build-orchestrator.md +13 -8
  13. package/agents/database-assessor.md +107 -3
  14. package/agents/design-contract-specialist.md +2 -0
  15. package/agents/fact-checker.md +2 -0
  16. package/agents/fix-critique.md +26 -5
  17. package/agents/frontend-assessor.md +3 -1
  18. package/agents/implementer.md +2 -0
  19. package/agents/independent-auditor.md +2 -0
  20. package/agents/mock-scanner.md +2 -0
  21. package/agents/optimize-runner.md +2 -0
  22. package/agents/overfitting-reviewer.md +2 -0
  23. package/agents/performance-assessor.md +3 -1
  24. package/agents/plan-critic.md +5 -2
  25. package/agents/promotion-reviewer.md +2 -0
  26. package/agents/recurring-pattern-detector.md +4 -1
  27. package/agents/retrospective-synthesizer.md +14 -0
  28. package/agents/root-cause-investigator.md +39 -2
  29. package/agents/scope-auditor.md +2 -0
  30. package/agents/security-reviewer.md +3 -0
  31. package/agents/self-improvement-architect.md +2 -0
  32. package/agents/synthesis-critic.md +2 -0
  33. package/agents/transcript-pattern-miner.md +2 -0
  34. package/agents/ui-validator.md +2 -0
  35. package/assets/build-loop-plugin-icon.png +0 -0
  36. package/bin/build-loop-install.js +234 -0
  37. package/commands/run.md +1 -1
  38. package/hooks/git/pre-commit +40 -0
  39. package/hooks/git/pre-push +35 -10
  40. package/hooks/hooks.json +42 -5
  41. package/hooks/pre-edit-architecture.sh +2 -2
  42. package/hooks/route-guard.sh +15 -0
  43. package/hooks/route_guard.py +221 -0
  44. package/hooks/session-start-extensions.sh +10 -0
  45. package/hooks/session-start-git-hooks.sh +6 -1
  46. package/hooks/session-start-rally-point.sh +23 -1
  47. package/hooks/test_session_start_rally_version.sh +123 -0
  48. package/package.json +7 -2
  49. package/plugin-artifacts/codex/.codex-plugin/plugin.json +1 -1
  50. package/plugin-artifacts/codex/AGENTS.md +77 -16
  51. package/plugin-artifacts/codex/README.md +208 -299
  52. package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
  53. package/plugin-artifacts/codex/references/agent-role-taxonomy.md +34 -0
  54. package/plugin-artifacts/codex/references/backlog-system.md +285 -0
  55. package/plugin-artifacts/codex/references/capability-routing.md +49 -0
  56. package/plugin-artifacts/codex/references/coordination-file-template.md +2 -2
  57. package/plugin-artifacts/codex/references/coordination-rules.md +275 -6
  58. package/plugin-artifacts/codex/references/memory-systems.md +25 -2
  59. package/plugin-artifacts/codex/references/memory.md +16 -0
  60. package/plugin-artifacts/codex/references/model-tier-mapping.md +45 -6
  61. package/plugin-artifacts/codex/references/phase-1-assess.md +19 -1
  62. package/plugin-artifacts/codex/references/phase-2-plan.md +10 -1
  63. package/plugin-artifacts/codex/references/phase-3-execute.md +5 -3
  64. package/plugin-artifacts/codex/references/phase-4-review.md +22 -1
  65. package/plugin-artifacts/codex/references/phase-5-iterate.md +4 -3
  66. package/plugin-artifacts/codex/references/phase-6-learn.md +3 -1
  67. package/plugin-artifacts/codex/references/verify-dispatch.md +85 -0
  68. package/plugin-artifacts/codex/skills/build-loop/SKILL.md +19 -10
  69. package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +1 -1
  70. package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +49 -0
  71. package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +101 -0
  72. package/plugin-artifacts/codex/skills/build-loop/references/memory.md +16 -0
  73. package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +98 -5
  74. package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +19 -1
  75. package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +10 -1
  76. package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +5 -3
  77. package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +22 -1
  78. package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +4 -3
  79. package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +3 -1
  80. package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +85 -0
  81. package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +4 -0
  82. package/scripts/build_codex_plugin_artifact.py +7 -0
  83. package/scripts/embed_backend.py +17 -13
  84. package/scripts/lessons_index/query.py +1 -1
  85. package/scripts/memory_graph/__init__.py +2 -2
  86. package/scripts/sync_navgator_lessons.py +4 -4
  87. package/skills/api-registry-bridge/SKILL.md +1 -1
  88. package/skills/auto-decision-capture/SKILL.md +4 -3
  89. package/skills/auto-finding-capture/SKILL.md +119 -0
  90. package/skills/build-loop/SKILL.md +19 -10
  91. package/skills/build-loop/fallbacks.md +1 -1
  92. package/skills/build-loop/references/capability-routing.md +49 -0
  93. package/skills/build-loop/references/experiment-results-template.md +101 -0
  94. package/skills/build-loop/references/memory.md +14 -0
  95. package/skills/build-loop/references/output-style.md +98 -5
  96. package/skills/build-loop/references/phase-1-assess.md +19 -1
  97. package/skills/build-loop/references/phase-2-plan.md +10 -1
  98. package/skills/build-loop/references/phase-3-execute.md +5 -3
  99. package/skills/build-loop/references/phase-4-review.md +20 -1
  100. package/skills/build-loop/references/phase-5-iterate.md +4 -3
  101. package/skills/build-loop/references/phase-6-learn.md +2 -0
  102. package/skills/build-loop/references/verify-dispatch.md +85 -0
  103. package/skills/build-loop/templates/codex-worker-prompt.md +4 -0
  104. package/skills/debug-loop/SKILL.md +4 -2
  105. package/skills/debugging-memory/SKILL.md +24 -12
  106. package/skills/{debugging/assess/SKILL.md → debugging-memory/references/assess.md} +6 -13
  107. package/skills/{debugging/memory/SKILL.md → debugging-memory/references/search.md} +10 -17
  108. package/skills/{debugging/store/SKILL.md → debugging-memory/references/store.md} +5 -13
  109. package/skills/debugging-memory/references/subagent-integration.md +3 -3
  110. package/skills/defenseclaw-bridge/SKILL.md +1 -1
  111. package/skills/{loop-builder → focused-loop-builder}/SKILL.md +6 -6
  112. package/skills/ibr-bridge/SKILL.md +1 -1
  113. package/skills/knowledge/SKILL.md +13 -2
  114. package/skills/{knowledge-review/SKILL.md → knowledge/references/review-mode.md} +8 -20
  115. package/skills/logging-tracer/SKILL.md +20 -4
  116. package/skills/model-bakeoff/SKILL.md +44 -0
  117. package/skills/model-tiering/SKILL.md +55 -20
  118. package/skills/optimize/SKILL.md +2 -2
  119. package/skills/prd-bridge/SKILL.md +1 -1
  120. package/skills/recursive-retrospective/SKILL.md +46 -0
  121. package/skills/recursive-retrospective/references/01-retrospective.md +136 -0
  122. package/skills/recursive-retrospective/references/02-learning-capture.md +72 -0
  123. package/skills/recursive-retrospective/references/03-judge.md +45 -0
  124. package/skills/research/SKILL.md +1 -1
  125. package/skills/root-cause-analysis/SKILL.md +42 -0
  126. package/skills/security-methodology/SKILL.md +10 -1
  127. package/skills/security-scan/SKILL.md +55 -0
  128. package/skills/spec-writing/SKILL.md +42 -1
  129. package/skills/sync-skills/SKILL.md +8 -7
  130. package/commands/assess.md +0 -41
  131. package/commands/compose-handoff.md +0 -113
  132. package/commands/debug.md +0 -20
  133. package/commands/debugger-detail.md +0 -34
  134. package/commands/debugger-scan.md +0 -15
  135. package/commands/debugger-status.md +0 -20
  136. package/commands/debugger.md +0 -32
  137. package/commands/optimize-run.md +0 -31
  138. package/commands/promote-experiment.md +0 -129
  139. package/commands/rally-point.md +0 -301
  140. package/commands/research-run.md +0 -22
  141. package/commands/review-knowledge.md +0 -16
  142. package/commands/self-review.md +0 -51
  143. package/commands/setup-memory.md +0 -32
  144. package/commands/start-prd.md +0 -57
  145. package/commands/test.md +0 -34
  146. package/commands/verify-plan.md +0 -39
  147. package/skills/debugging/debug-loop/SKILL.md +0 -231
  148. package/skills/logging-tracer-bridge/SKILL.md +0 -70
  149. /package/skills/{loop-builder → focused-loop-builder}/presets/active-project-evidence.yaml +0 -0
  150. /package/skills/{loop-builder → focused-loop-builder}/presets/generic-artifact-loop.yaml +0 -0
  151. /package/skills/{loop-builder → focused-loop-builder}/presets/presentation-audit.yaml +0 -0
  152. /package/skills/{loop-builder → focused-loop-builder}/presets/research-synthesis.yaml +0 -0
  153. /package/skills/{loop-builder → focused-loop-builder}/presets/source-ingestion-raw-data-audit.yaml +0 -0
  154. /package/skills/{loop-builder → focused-loop-builder}/references/spec-format.md +0 -0
  155. /package/skills/{loop-builder → focused-loop-builder}/scripts/loop_builder.py +0 -0
@@ -51,6 +51,8 @@ post(
51
51
 
52
52
  **Channel scope (worktree- and clone-independent):** resolve the channel through `scripts/rally_point/discovery_bridge.resolve(workdir)`. Native `agent-rally-point` discovery returns the canonical shared channel (currently `~/.agent-rally-point/apps/<repo-id>/`). The embedded build-loop fallback also defaults to `~/.agent-rally-point/apps/<slug>/`, where `slug` comes from `git rev-parse --git-common-dir` via `scripts/rally_point/channel_paths.app_slug(cwd)`. The main checkout, every worktree, and every clone of the same canonical repo share ONE channel. Different canonical repos get different channel directories (cross-repo isolation).
53
53
 
54
+ **Rally's REAL CLI surface (the only commands build-loop shells out to).** Rally exposes `enter`, `say <kind>`, `whoami`, `room`, `next`, `recent`, `stop <session|name|tool>`, `sessions [--reap]`, `migrate-legacy`, `mission`, `version`, and `check`. It has NO `setup`, NO `post`, NO `start`, and NO `replay` — those were anticipated but never shipped, and a discovery tier that gated on them was dead code (removed). A write goes out as `rally say <kind> --json …` (build-loop's `post()` helper translates to this); identity/channel info comes from `rally whoami --json` (`repo_root`/`repo_id`/`worktree`/`build_id`/`cwd`), NOT a `setup` probe. The surface-acceptance check (`discovery_bridge._rally_binary_supports_required_surface`) is pinned to a real rally binary's `--help` by `scripts/test_discovery_bridge.py::RequiredSurfacePinnedToRealRallyTests`, so it can never silently drift back to a phantom surface. When adding a rally call, read the binary's real `--help` first; do not assume a command exists.
55
+
54
56
  **Anti-pattern (silent no-op):**
55
57
 
56
58
  ```python
@@ -77,6 +79,27 @@ Threats this model does NOT cover (out of scope by design): a hostile process ru
77
79
 
78
80
  ---
79
81
 
82
+ ## Evidence boundary (Rally is not a verifier)
83
+
84
+ Rally records are peer-authored coordination metadata. They can tell an agent
85
+ what another agent claimed, handed off, reviewed, blocked, or released, and they
86
+ can point to artifacts worth inspecting. They do not prove the artifact, code,
87
+ package, tag, release, or remote state is correct.
88
+
89
+ Before making a factual claim about repo or release state, check the authoritative
90
+ surface directly:
91
+
92
+ - Code and docs: working tree, `git diff`, file contents, and tests.
93
+ - Package/version surface: manifests, package tests, dry-run pack/publish output,
94
+ and release-surface verifier scripts.
95
+ - Remote release state: GitHub/npm/GitHub Packages API or public pages, not a
96
+ Rally `release` or `artifact` record.
97
+
98
+ A Rally `artifact`, `release`, `resolve`, `review_artifact`, or `next` record is
99
+ a routing signal. It may identify what to inspect; it is never the inspection.
100
+
101
+ ---
102
+
80
103
  ## Cheap detection at step boundaries
81
104
 
82
105
  **Poll `coordination_status.py` BEFORE any step-boundary decision.** Costs ~100 tokens; prevents stale-state recommendations that cost full plan rewrites (~5K tokens).
@@ -150,9 +173,255 @@ any of the above. Memory citation:
150
173
 
151
174
  **Orphaned-lane absorption:** when a lane assigned to an idle peer is **local and reversible** (commits, doc/agent edits, dead-code or dead-key trims, version bumps, test updates), the live agent **absorbs it** — does the work itself, then records in the report `absorbed <peer>'s idle lane: <what> [<evidence>]`. Do **not** block a release, a finish, or "done" on an idle peer's local lane — that is the same manufactured wait as a turn-length stop (see `skills/build-loop/SKILL.md` §"Keep going until done"). Only surface/hold a lane that is genuinely **peer-exclusive**: needs the other vendor's model (true cross-vendor review), the peer's environment/credentials, or an irreversible action only that peer is authorized to take. Coordination is cooperative, not a dependency that can deadlock the live agent.
152
175
 
176
+ ## Recency decay & size-scaled lead/ownership auto-reclaim
177
+
178
+ A single coordination policy governs message aging and stale-claim reclaim,
179
+ mirrored from the canonical Rust implementation (agent-rally-point). Tunables
180
+ live under `coordinationPolicy` in `.build-loop/config.json` (defaults shown):
181
+
182
+ ```json
183
+ {
184
+ "coordinationPolicy": {
185
+ "half_life_hours": 48,
186
+ "archive_floor_weight": 0.05,
187
+ "reclaim_small_minutes": 30,
188
+ "reclaim_large_minutes": 120
189
+ }
190
+ }
191
+ ```
192
+
193
+ - **Recency decay (listing order + archive).** Every coordination change gets a
194
+ weight `0.5 ** (age_hours / half_life_hours)` (default half-life 48h). The
195
+ status / recent-changes listing orders fresh-first by weight and EXCLUDES any
196
+ change whose weight has fallen below the archive floor (default `0.05`, ≈14d).
197
+ Archived changes are losslessly retrievable with `--include-archived`
198
+ (`coordination_status.py --include-archived`), which also folds back any
199
+ physically-rotated `changes.jsonl.<date>` logs. Decay applies only to the
200
+ historical change stream — never to the live direct-message inbox or to active
201
+ state. Fails OPEN: a change with a malformed `ts` is treated as fresh.
202
+ - **Size-scaled lead/ownership auto-reclaim.** A lead lease (`rally/lead.json`)
203
+ whose `lease_until` has passed is auto-reclaimable by the next `claim_lead`.
204
+ The lease WINDOW scales with the claimed work size: a small (single-file /
205
+ effort XS·S) claim expires after `reclaim_small_minutes` (default 30m); a
206
+ large (multi-file / coarse / effort M·L·XL) claim after `reclaim_large_minutes`
207
+ (default 2h). Pass `work_size`/`effort`/`owns` to `claim_lead`; with NO size
208
+ signal the lease window stays the historical `renew_every_minutes` cadence
209
+ (backward-compatible). An auto-reclaim posts a durable `lead-reclaim` record
210
+ naming who reclaimed, the prior owner, and the reason (`stale-by-timeout`).
211
+ - **Preserved invariants.** Reclaim stays race-safe (the `rally/lead.lock`
212
+ fcntl lock is untouched) and FAIL-CLOSED: a present incumbent lease whose
213
+ `lease_until` is unparseable is NEVER auto-reclaimed (we refuse rather than
214
+ reclaim on a timestamp we cannot trust). An empty seat is still freely
215
+ claimable.
216
+ - **Policy is Rust-only; the Python math is an in-process helper.** Reclaim and
217
+ reap DECISIONS are made by the Rust binary (facade in `reaper.py` /
218
+ `leadership.py`, fail-loud below full capability). `decay.py` survives only as
219
+ pure in-process math the lead-lease window sizing needs (`recency_weight`,
220
+ `reclaim_timeout_seconds`, `classify_work_size`); it is NOT a behavioral mirror
221
+ of `decay.rs` and is no longer double-pinned against a cross-repo golden
222
+ fixture. The status/recent-changes LISTING still applies the decay weight in
223
+ Python over build-loop's own change-log so an old on-PATH binary cannot
224
+ un-decay the listing (pinned by `scripts/test_coordination_decay_invariant.py`);
225
+ that is a presentation concern, distinct from the now-Rust-only reclaim/reap
226
+ actuation.
227
+
228
+ This complements (does not replace) the >10-minute idle absorption rule above:
229
+ idle-absorption handles local reversible lanes a quiet peer left open; the lease
230
+ timeout governs the formal lead/ownership role handover.
231
+
232
+ ## In-room stale-state reaper (actuator) — RUST-ONLY via a fail-loud facade
233
+
234
+ The sections above define WHEN records become stale. Physically removing them
235
+ (reaping) is **Rust-only**. There is no Python reaper sweep — the prior Python
236
+ parity mirror (presence/claim-index/lead deletion, double-pinned against golden
237
+ fixtures) was RETIRED in the Rust-rally facade migration. A Python process
238
+ deleting coordination records the Rust binary owns is the exact shadow
239
+ implementation that is worse than no coordination.
240
+
241
+ **Canonical actuator (Rust):** the `rally` binary's reaper (`rally sessions
242
+ --reap`, or `rally doctor --reap-stale` on newer builds). Dry-run by default;
243
+ `--reap`/`--apply` physically removes over-TTL presence, claims, and leads.
244
+
245
+ **Facade (`scripts/rally_point/reaper.py`).** `reap_channel(channel, workdir,
246
+ apply=…)` resolves coordination capability via `discovery_bridge` and then:
247
+ - **full capability** (a real binary owns the channel — `repo-local-rally-cli`
248
+ / `fetched-binary` / env-override / path-binary / python-import) → shells
249
+ `rally sessions --reap` and surfaces the result (`capability_level: full`,
250
+ `deferred_to_rust: false`).
251
+ - **below full** (degraded-breadcrumb or unavailable) → REFUSES. It reaps
252
+ nothing and returns a capability-marked report (`deferred_to_rust: true`,
253
+ `coordination_unavailable: <reason>`). A degraded session must never reap a
254
+ peer it cannot prove is dead.
255
+
256
+ CLI: `python3 scripts/rally_point/reaper.py --workdir <path> [--apply] [--json]`.
257
+
258
+ **Capability field on every coordination envelope.** Every facade return and
259
+ `DiscoveryEnvelope` carries `capability_level` (`full` / `degraded-breadcrumb` /
260
+ `unavailable`) + a `coordination_unavailable` reason. The single source of truth
261
+ is `scripts/rally_point/capability.py`; `FULL_ONLY_OPERATIONS`
262
+ (claim/release/reclaim/lead/reap/liveness/before_write/checkpoint) are permitted
263
+ only at full capability.
264
+
265
+ **Degraded breadcrumb path (the ONLY thing a sub-full session may write).** When
266
+ no binary is available but the host is supported, a session may write
267
+ capability-marked presence/handoff *breadcrumb* facts so a later full-capability
268
+ peer (or a human) sees it existed. It must NOT — and structurally cannot — claim
269
+ ownership, reclaim, infer liveness, reap, or imply before-write protection.
270
+
271
+ **Unsupported-host = loud no-coordination.** A host with no fetchable pinned-
272
+ binary asset (Intel macOS, musl/Alpine, exotic arch) resolves to
273
+ `capability_level: unavailable` (`coordination_unavailable: unsupported_host`).
274
+ The facade is a loud no-op there — NEVER a policy mirror.
275
+
276
+ **Remaining Python guards** (the destructive paths that survive as in-process
277
+ guards, all FAIL-CLOSED): `presence.reap_stale` physically unlinks only at full
278
+ capability; `leadership` reclaim (taking a peer's lease) is full-only, while
279
+ seeding an EMPTY lead seat and self-relinquishing one's OWN seat stay breadcrumb-
280
+ class. The in-process `decay.py` / `liveness.py` are now pure window/weight math
281
+ helpers (lead-lease sizing, adaptive presence window), not behavioral mirrors.
282
+
283
+ **Codex parity.** A codex session emits the same presence record claude does, via
284
+ the `.codex/hooks.json` `SessionStart` hook that calls `session_probe.py --tool
285
+ codex`, so it ages and decays identically — now enforced by the single Rust
286
+ reaper both tools share, not by a cross-language golden fixture.
287
+
288
+ **Session-end self-release (primary).** Both tool hooks release the agent's Rally
289
+ file-claims at turn completion (`Stop` event) so peers immediately see the agent's
290
+ absence and the claims do not leak past Stop. The two hosts reach this differently
291
+ because their session models differ:
292
+
293
+ - **Codex** emits `rally stop codex` (`.codex/hooks.json` Stop). `codex` resolves to
294
+ Codex's managed `rally-*` tmux session, so that one call both self-kills the
295
+ session (it can never become a detached orphan) AND releases the session's claims.
296
+ - **Claude Code** is normally NOT a `rally run`-managed session, so `rally stop
297
+ claude_code` fails (`unknown managed session`). The Claude Stop hook therefore
298
+ releases claims via the portable primitive instead: `scripts/stop_closeout.py`
299
+ (`release_my_claims`, called from `hooks/closeout.sh stop`) enumerates this tool's
300
+ open claims with `rally room --tool claude_code --json` and releases each by event
301
+ id with `rally say release --tool claude_code --ref <event-id>`. It fires on EVERY
302
+ Stop (a stopped session is no longer editing), is advisory + fail-open (`command -v
303
+ rally` guarded; swallows all errors; exit 0 always), and is capped per Stop so a
304
+ backlog can't make the hook run long. History: before this, the Claude hook
305
+ released only the run IDENTITY, never the file claims, so `claude_code` auto-claims
306
+ accreted unbounded (112→127 observed). Added 2026-06-29.
307
+
308
+ **Reaper = backstop, not primary (NON-DEFAULT).** The reaper that expires abandoned
309
+ claims/presence (`rally sessions --reap`, faced by `scripts/rally_point/reaper.py`)
310
+ is Rust-only and dry-run/manual by default — it is NOT turned on. Self-release above
311
+ is the primary mechanism. The reaper exists ONLY to catch the dead: sessions that
312
+ crash (SIGKILL / 529 / network drop) before their Stop hook fires, so self-release
313
+ never ran. Do not enable it as a default-on sweep; run it deliberately as the
314
+ explicit backstop for crash-orphaned claims.
315
+
316
+ ## Adaptive multi-signal liveness (squad-projection decay + tmux orphan reaper)
317
+
318
+ Fixed staleness cutoffs are replaced by liveness that ADAPTS to each session's
319
+ planned heartbeat cadence and weighs four signals. The liveness DECISION (reap /
320
+ self-exit) is Rust-only via the facade; `scripts/rally_point/liveness.py` retains
321
+ only the in-process window/verdict MATH the presence squad-projection uses,
322
+ verified by its own inline unit tests (`test_liveness.py`). The cross-repo golden
323
+ fixture `liveness_vectors.json` and its `_provenance.json` drift entry were
324
+ RETIRED in the Rust-rally migration — there is no longer a byte-identical parity
325
+ contract to maintain across the two codebases.
326
+
327
+ **Adaptive cadence.** A session declares its beat via `planned_heartbeat_secs`
328
+ (presence record) or `renew_every_minutes` (lead.json); undeclared → the default
329
+ cadence. Staleness is RELATIVE: `window = planned_interval * MISS_MULTIPLIER + GRACE`.
330
+ Defaults `DEFAULT_CADENCE_SECS=300`, `MISS_MULTIPLIER=6`, `GRACE_SECS=60` →
331
+ a 5-min cadence is stale at ~31 min (≈6 missed beats); a 5-hour cadence not until
332
+ ~30 h. Tunable via `.rally/config.json` `coordination{}` (Rust) /
333
+ `.build-loop/config.json` `coordinationPolicy{}` (Python):
334
+ `default_cadence_secs`, `miss_multiplier`, `grace_secs` (+ `RALLY_*` env in Rust).
335
+ Legacy `heartbeat_minutes` is honored as a cadence source for backward compat.
336
+
337
+ **Four signals — LIVE if ANY is fresh within the adaptive window:**
338
+ (a) heartbeat/presence `last_seen`; (b) inject/ack (a `receipt`/`wake`/`handoff`
339
+ naming the session); (c) forward code progress (the session's worktree branch HEAD
340
+ MOVED since the last poll — Rust compares the two newest presence facts' shas, the
341
+ Python reaper compares a cached `branch_head_sha`); (d) declared active work (a
342
+ live claim or authored mission/handoff).
343
+
344
+ **Two fail-directions, each on the safe side:**
345
+ - **Squad VISIBILITY projection** (Rust `snapshot_from_facts_with_policy`) is
346
+ FAIL-OPEN. A squad whose four signals are ALL provably stale is DROPPED from the
347
+ default `rally room` view; `--include-archived` restores it (mirrors the message
348
+ archive model). A `Live` OR `Unknown` (any absent/unparseable signal) verdict
349
+ KEEPS the squad visible — hiding a still-alive peer could cause the very
350
+ write-collision this system prevents.
351
+ - **Reaper REMOVAL** (presence-file unlink, claim/lead) stays FAIL-CLOSED — never
352
+ remove on a signal it cannot trust.
353
+
354
+ **tmux orphan reaper.** `rally sessions --reap` also detects DETACHED `rally-*`
355
+ tmux sessions whose last activity is past the adaptive window and which are not
356
+ tracked as managed sessions, kills them, and tombstones the reap (closing the gap
357
+ where `--reap` saw 0 of the real detached orphans). Attached sessions (a human is
358
+ looking) are never killed.
359
+
360
+ ## Zombie-tmux prevention — three layers over ONE liveness model
361
+
362
+ Root cause of accreted zombie tmux sessions: rally `exec`s the agent, so a session
363
+ auto-closes when its agent EXITS, but agents that never exit (a disabled autonomy
364
+ poller, idle detached panes) leave the session forever — tmux has no native
365
+ idle/lifetime timeout. The fix is three layers, all reusing the single
366
+ `liveness::is_live` 4-signal model and the adaptive window above. NONE adds a
367
+ fixed/brute-force idle clock; lifetime follows real liveness/ownership.
368
+
369
+ **Layer 1 — completion-scoped self-exit (prevent at source).** `rally
370
+ self-exit-check --tool <self>` is a stateless re-check: a task-scoped session that
371
+ holds NO active claims AND for which `rally next` is non-actionable for a SUSTAINED
372
+ streak self-kills its own `rally-*` tmux session, so `exec` auto-closes it. The
373
+ streak (default 2 consecutive empty re-checks, `liveness::DEFAULT_SELF_EXIT_STREAK`)
374
+ is persisted in the session's OWN tmux env (`RALLY_SELFEXIT_STREAK`, dies with the
375
+ session — no new filesystem surface) so a brief lull between claims never exits
376
+ mid-task. **Opt-out:** `--persistent` short-circuits to "never self-exit" for a
377
+ deliberately-long-lived session. The existing `rally stop` self-kill remains the
378
+ explicit-completion path; Layer 1 adds the implicit "work done" path. Decision is
379
+ the shared `liveness::completion_self_exit_eligible(work_resolved,
380
+ next_empty_streak, required_streak, persistent_optout)`.
381
+
382
+ **Layer 2 — event-driven liveness-lease safety net.** `rally enter` (a new agent
383
+ joining) opportunistically sweeps detached `rally-*` orphan tmux sessions via the
384
+ SAME reaper Layer-3 logic, in addition to `rally sessions --reap`. Best-effort and
385
+ fail-open: it runs AFTER presence (so the entering agent's own session is in the
386
+ guard set), never blocks the enter path, and never raises. A live / parent-alive
387
+ session is never reaped. No daemon/cron (those would themselves need worktree
388
+ isolation). Both the enter sweep and `sessions --reap` call ONE shared actuator
389
+ (`sweep_orphan_tmux`).
390
+
391
+ **Layer 3 — parent-lifecycle binding.** At launch (`tmux_start_command`) the new
392
+ session's env is stamped with `RALLY_PARENT_PID=<launcher pid>` in the SAME atomic
393
+ `tmux new-session -e` call. The reaper reads it back (`show-environment`), probes
394
+ `kill -0 <pid>` (no new crate dependency — the repo keeps a zero-extra-dep
395
+ contract), and feeds the result to the shared `liveness::reapable(liveness,
396
+ parent_alive)`. This targets the exact failure mode here (autonomy poller died →
397
+ its child sessions orphaned).
398
+
399
+ **The single reaper-eligibility authority** is `liveness::reapable` (mirrored
400
+ Rust↔Python, asserted by the byte-identical `liveness_vectors.json` `reapable_cases`):
401
+
402
+ | liveness | parent_alive | reapable | rationale |
403
+ |----------|--------------|----------|-----------|
404
+ | Live | any | NO | any of 4 signals fresh → independently live |
405
+ | Unknown | any | NO | fail-closed: untrustworthy signals |
406
+ | Stale | alive | NO | stale by signals but a live parent may re-drive it (conservative) |
407
+ | Stale | dead | YES | the Layer-3 orphan target |
408
+ | Stale | none (no info)| YES | window criterion ALONE — fail-safe degradation |
409
+
410
+ **Fail-safe directions (binding):**
411
+ - A session making code progress / heartbeating on cadence / recently injected /
412
+ holding a live plan is NEVER reaped — that's `Live` → not reapable, regardless of
413
+ parent state.
414
+ - The control NEVER reaps on the parent criterion ALONE: parent-dead reaps only a
415
+ session that is ALSO `Stale` by the 4-signal liveness.
416
+ - Missing/unparseable parent info (`parent_alive = None`) degrades to the
417
+ liveness-window criterion alone (`Stale → reap`), preserving the pre-Layer-3
418
+ orphan-window behavior exactly — never reaped *because* the parent is unknown.
419
+ - `kill -0` failing for any reason other than "no such process" (e.g. EPERM) reads
420
+ ALIVE (a live-but-unsignalable process is never treated as dead).
421
+
153
422
  ## Idle-agent self-selection (rally facilitates, the agent decides)
154
423
 
155
- **Rally is a facilitator, not an orchestrator.** It exposes room state (`rally room` / `rally next`), file-level deconfliction (`rally check before-write --path P`), and claims/handoffs. It does **not** assign or pick work. A waiting agent runs this decision tree itself and chooses — the agent's LLM reasons over rally's surfaced state. This keeps coordination decentralized: no single point that hands out tasks (which would be a failure site and a bottleneck).
424
+ **Rally is a facilitator, not an orchestrator or verifier.** It exposes room state (`rally room` / `rally next`), file-level deconfliction (`rally check before-write --path P`), and claims/handoffs. It does **not** assign work, pick work, or verify code/release truth. A waiting agent runs this decision tree itself and chooses — the agent's LLM reasons over Rally's surfaced coordination records. This keeps coordination decentralized: no single point that hands out tasks (which would be a failure site and a bottleneck).
156
425
 
157
426
  When an agent is idle and `rally next` returns no actionable item, walk the tree top-down, stop at the first match:
158
427
 
@@ -162,7 +431,7 @@ When an agent is idle and `rally next` returns no actionable item, walk the tree
162
431
  4. **All coding candidates are claimed or conflicted** → do read-only research or assessment that helps and has zero file conflict (simplification scans of untouched areas, duplication/test-gap audits, docs the room needs).
163
432
  5. **Nothing fits, or the only work left is risky/deferred/peer-exclusive** → stay idle and say so; do not start risky/deferred work, do not touch another session's claimed paths.
164
433
 
165
- The tree is the guideline; rally supplies the facts (claims, collisions, pending items) each branch needs. Two same-tool agents running it independently land on different work because claim-first + `check before-write` makes the first claimant win and the second re-select — no central referee required.
434
+ The tree is the guideline; Rally supplies coordination records (claims, collisions, pending items) each branch needs. Two same-tool agents running it independently land on different work because claim-first + `check before-write` makes the first claimant win and the second re-select — no central referee required.
166
435
 
167
436
  ## Coordination reliability (verify the room before trusting it)
168
437
 
@@ -184,9 +453,9 @@ Named failure (2026-06-09, agent-rally-point): two orchestrator runs hit the sam
184
453
 
185
454
  ---
186
455
 
187
- ## MECE Packets (every write-handoff requires all six)
456
+ ## MECE Packets (briefs require all seven; rally packets six + optional 7th)
188
457
 
189
- **Every implementation handoff to a peer MUST spell out six elements: `owns / does-not-own / interface-contract / integration-checkpoint / allowed-tools / denied-tools`.** Anything less is "informational handoff" — produces drift, two writers on the same file, ambiguous "done" definitions.
458
+ **Every implementation handoff to a peer MUST spell out seven elements: `owns / does-not-own / interface-contract / integration-checkpoint / allowed-tools / denied-tools / acceptance-criteria`. The hard seven-field lint applies to dispatch BRIEFS (`brief_mece_validator.py`); rally `kind=handoff` ownership packets require the six structural fields (`mece_gate.py`) and validate `acceptance_criteria` when present — bootstrap/presence posts are not delegations and may omit it.** Anything less is "informational handoff" — produces drift, two writers on the same file, ambiguous "done" definitions.
190
459
 
191
460
  | Element | What it answers | Example |
192
461
  |---|---|---|
@@ -199,9 +468,9 @@ Named failure (2026-06-09, agent-rally-point): two orchestrator runs hit the sam
199
468
 
200
469
  Both `allowed-tools` and `denied-tools` MUST be present on every `kind=handoff` post; either MAY be an empty list. An empty `allowed_tools` is a valid explicit "no lateral limits" declaration — only a missing or non-list field is rejected by `mece_gate.validate_handoff`. These fields are the G2 lateral-limits feature (`feat(rally): tool-level lateral limits on handoff packets`, 2026-05-22).
201
470
 
202
- **Enforcement:** `python3 scripts/brief_mece_validator.py --brief-file <path> --json`. Exit 0 → all six present. Exit 1 → at least one missing; orchestrator surfaces a `[warn]` and may still dispatch (C-FLOW pattern — non-blocking lint). The orchestrator wires this lint into every `Agent(subagent_type=..., ...)` dispatch site for peer-handoff briefs.
471
+ **Enforcement:** `python3 scripts/brief_mece_validator.py --brief-file <path> --json`. Exit 0 → all seven present (briefs). Exit 1 → at least one missing; orchestrator surfaces a `[warn]` and may still dispatch (C-FLOW pattern — non-blocking lint). The orchestrator wires this lint into every `Agent(subagent_type=..., ...)` dispatch site for peer-handoff briefs.
203
472
 
204
- **Carve-out:** pure-read handoffs ("go look at this and tell me what you find") skip MECE. All write-handoffs need all six. Memory citation: `feedback_handoffs_require_mece_packets`.
473
+ **Carve-out:** pure-read handoffs ("go look at this and tell me what you find") skip MECE. All write-handoffs need all seven. Memory citation: `feedback_handoffs_require_mece_packets`.
205
474
 
206
475
  ---
207
476
 
@@ -4,6 +4,29 @@
4
4
 
5
5
  Build-loop reads/writes four memory stores. Loaded on demand at Phase 1 Assess and Phase 4 Review sub-step F.
6
6
 
7
+ ## Substrate vs memory policy
8
+
9
+ The database is not memory. Databases, files, object stores, vector indexes,
10
+ keyword indexes, caches, and event logs are storage/retrieval substrates. Memory
11
+ is the policy layer that decides what should be remembered, forgotten,
12
+ superseded, trusted, refreshed, retrieved, and injected into the model context.
13
+
14
+ When changing build-loop memory, keep these layers explicit:
15
+
16
+ | Layer | Build-loop examples | Rule |
17
+ |---|---|---|
18
+ | Canonical truth | `<memory-root>/projects/<project>/decisions/*.md`, project lessons, `.build-loop/state.json.runs[]` | Durable facts must be evidenced, scoped, and supersedable. |
19
+ | Raw artifacts | archived plans, source docs, run artifacts, attachments | Preserve evidence separately from promoted memory. |
20
+ | Derived indexes | `INDEX.md`, `INDEX.jsonl`, semantic/Postgres rows, SQLite/FTS projections | Rebuildable; never the sole source of truth. |
21
+ | Explicit memory records | decisions, lessons, preferences, gotchas, run summaries | Small, structured, and future-recallable. |
22
+ | Cache/working state | context bootstrap packet, backend health, run-local snapshots | Useful for current work; not durable memory by default. |
23
+ | Audit/event log | milestones, telemetry, JSONL ledgers, Rally facts | Explains what happened and supports replay/debugging. |
24
+
25
+ Design implication: do not promote raw transcripts, vector hits, cache entries,
26
+ or retrieved chunks directly into memory. Promote only the distilled rule,
27
+ decision, preference, or lesson, with source evidence and freshness/supersession
28
+ metadata.
29
+
7
30
  ## The four stores
8
31
 
9
32
  | Store | Path | Purpose | Scope |
@@ -191,9 +214,9 @@ Capture `RUN_ID` from stdout and cite it in the scorecard. Always pass `--securi
191
214
 
192
215
  ### Resolved debugger incidents
193
216
 
194
- Use the native `Skill("build-loop:debugging-store")`. Procedure also in `Skill("build-loop:debugging-memory")` §"Review-F outcome feedback":
217
+ Use the native `Skill("build-loop:debugging-memory")` with `{op:"store", ...}`. Procedure also in `Skill("build-loop:debugging-memory")` §"Review-F outcome feedback":
195
218
 
196
- - For each newly resolved Review-B/Iterate failure: invoke `build-loop:debugging-store` with `{symptom, root_cause, fix, tags: ["build-loop", project, layer], files}`.
219
+ - For each newly resolved Review-B/Iterate failure: invoke `build-loop:debugging-memory` `{op:"store", symptom, root_cause, fix, tags: ["build-loop", project, layer], files}`.
197
220
  - For each Review-B memory gate where a prior `KNOWN_FIX` or `LIKELY_MATCH` was applied: invoke `outcome` MCP tool with `{incident_id, result: "worked"|"failed"|"modified", notes}`. This trains the verdict classifier.
198
221
 
199
222
  Both steps are required to close the memory-first gate's feedback loop. Skipping `outcome` means the verdict classifier never improves from this build's signal.
@@ -136,6 +136,20 @@ python3 ${CLAUDE_PLUGIN_ROOT}/scripts/memory_writer.py \
136
136
 
137
137
  The writer auto-detects `source_repo` from the workdir's git remote, appends a row to the lane-local `INDEX.jsonl`, appends a row to the global update ledger at `indexes/updates.jsonl`, and (on update) preserves `created_at` + `applied_in_repos` so cross-repo validation history survives edits. Direct writes are repair/fallback work only: use them only when no canonical writer exists or the current task is explicitly a memory-system repair, then run the host index/check step and verify reachability.
138
138
 
139
+ ### Codex cross-repo write guard
140
+
141
+ When using Codex `apply_patch`, path resolution is relative to the active
142
+ workspace, not to a shell command `workdir`. If a memory artifact belongs in a
143
+ different repo such as `build-loop-memory`, use one of these safe paths:
144
+
145
+ - Prefer `scripts/memory_writer.py` for normal memory writes.
146
+ - If direct repair is unavoidable, pass absolute target paths to `apply_patch`.
147
+ - Before moving a generated file that already appeared in a Codex file card,
148
+ leave an openable pointer, mirror, or stub at the old path and update any
149
+ dependent indexes/links. Do not strand UI/Finder file cards at missing paths.
150
+ - After a move, verify both the canonical destination and every old path that a
151
+ user-facing file card, index, or markdown link still references.
152
+
139
153
  ### Reader side — surface peer writes via INDEX.jsonl
140
154
 
141
155
  Between phases (or at every M2 heartbeat), tail since your last check:
@@ -246,6 +260,8 @@ Run once after this version of build-loop is installed; the migration completes
246
260
 
247
261
  Every build-loop run appends a single milestone record at **Review-G** via `scripts/append_milestone.py`. Each record captures what shipped and the repo HEAD sha at write time.
248
262
 
263
+ The milestone (durable `milestones.jsonl` in build-loop-memory) is **distinct from** `state.json.runs[]`, which Phase 6 Learn scans for pain signals. The orchestrator's Review-G writes both; an **inline run or the closeout** writes neither unless it calls them explicitly. So at run-close, in addition to the milestone, record the run for Learn with `python3 scripts/append_run.py --workdir "$PWD" --run-id <id> --goal "..." --outcome <done|partial|blocked>` (append-only, idempotent on `run_id`; capture `--manual-intervention "<phase>:<note>"` for any step the user had to re-prompt). Without it, inline work never reaches the `runs[] >= 3` Learn threshold. See `references/phase-6-learn.md` §Detect.
264
+
249
265
  JSONL contract (frozen — sibling staleness-check reads this):
250
266
 
251
267
  ```
@@ -2,9 +2,26 @@
2
2
 
3
3
  # Model Tier Mapping — Multi-Provider Substitution Reference
4
4
 
5
- Build-loop is provider-agnostic at the tier level. Agent frontmatter uses Anthropic aliases (`fable`, `opus`, `sonnet`, `haiku`) by default because Claude Code is the primary host, but the **tier abstraction** (Frontier / Thinking / Code / Pattern) is what governs the role assignment. This reference documents how to swap providers cleanly.
5
+ Build-loop is provider-agnostic at the tier level. Agent frontmatter uses Anthropic aliases (`fable`, `opus`, `sonnet`, `haiku`) by default because Claude Code is the primary host, but the **tier abstraction** is what governs the role assignment. This reference documents how to swap providers cleanly.
6
6
 
7
- ## Canonical tier definitions
7
+ ## Two-axis taxonomy (the source of truth)
8
+
9
+ Model selection runs on **two orthogonal axes**, encoded as structured data in **`references/model-taxonomy.json`** (the single source of truth; `scripts/model_taxonomy.py` is the loader every selection script imports):
10
+
11
+ - **SEGMENT axis — work role / primary output.** Seven segments: Generative Reasoning, Agentic Execution, Representation/Retrieval, Realtime Interaction, Perception/Input Understanding, Generative Media, Governance/Evaluation. Each has subsegments. A model with image/audio INPUT but a reasoning primary job is Generative Reasoning with a `multimodal-input` TAG — only classify into Perception/Realtime/Media when that IS the primary product role. Tags (`long-context`, `agentic`, `multimodal-input`, `cost-sensitive`, `restricted`, …) are a third axis.
12
+ - **CAPABILITY-TIER axis — a 7-rung ladder:** `T0` experimental/restricted frontier · `T1` ultra-frontier · `T2` frontier · `T3` balanced workhorse · `T4` efficient near-frontier · `T5` utility/nano/edge · `T-S` specialist infrastructure (off the capability ladder).
13
+
14
+ **Legacy aliases (back-compat, never removed):** the four legacy tier tokens fold onto the ladder — `frontier→T1`, `thinking→T2`, `code→T3`, `pattern→T4`. Existing config `modelOverrides`, plan `tier:` frontmatter, `route_decision`, and every existing test reference the legacy tokens and keep resolving to the same models.
15
+
16
+ **Selection policy (Hybrid):** per `(segment, tier)` there is an ORDERED preferred-model list (order = capability rank, honoring Accuracy>Speed>Cost). The resolver (`scripts/model_resolver.py resolve_role`) picks the highest-ranked AVAILABLE + host-reachable id; ties / equal-or-unranked candidates are broken by release recency (newer wins). Users reorder via `.build-loop/config.json`. On a Claude host, a host-unreachable cross-vendor model is filtered out, so a generative_reasoning/thinking role resolves to `opus`, never the recency-newer but unreachable `gpt-5.5`.
17
+
18
+ **Classification (host-LLM, no vendor API):** an unseen model is classified into BOTH segment + tier by the host LLM (`scripts/classify_model_tier.py` — rubric + WebSearch packet → `record`). Specialist segments grade on their own metrics (MTEB / recall / NDCG / WER / latency), not SWE-bench.
19
+
20
+ **Agent binding:** every agent declares a `(segment, tier)` ROLE in frontmatter, resolved to a concrete model at dispatch (M2.5 contract). `model:` stays as the harness default + fresh-install fallback. A new model is adopted by classifying it once — no agent edits.
21
+
22
+ **Dormant vs active segments:** *active* (live resolver) — generative_reasoning, agentic_execution, governance_evaluation. *partial* — representation_retrieval (embeddings; no agent-dispatch resolver). *dormant (DATA + reference only, no resolver wiring)* — realtime_interaction, perception_input, generative_media. The dormant segments are encoded for future skills; nothing walks them yet.
23
+
24
+ ## Canonical tier definitions (legacy 4-tier view — the Generative Reasoning T1–T4 cells)
8
25
 
9
26
  ### Frontier tier
10
27
  - **Role:** Planning synthesis AND verification verdicts. **Phase 2 Plan synthesis reaches Fable via the stakes-gated Advisor dispatch ladder** (`advisor` agent / peer host / already-Fable session; honestly-labeled inline-Opus fallback otherwise — `skills/build-loop/references/advisor-dispatch-ladder.md`); the Advisor v1 ladder is Phase 2 only, so Phase 1 Assess synthesis runs inline as today until v2. Plan content: frame goal, draft spec/ADRs, F-criteria, MECE partition. Verification-shaped agents whose verdicts gate downstream work: plan-critic, scope-auditor, independent-auditor, fix-critique, fact-checker, security-reviewer, overfitting-reviewer, promotion-reviewer.
@@ -27,7 +44,7 @@ Build-loop is provider-agnostic at the tier level. Agent frontmatter uses Anthro
27
44
  - **Role:** Application. Apply a known rule, spec, or pattern to bounded input. Scoped implementation per a commit's owned-files. Adversarial critic vs rubric. Mechanical simplify. The "how" decisions when the "what" is already settled.
28
45
  - **Benchmark contract:** SWE-bench Verified ≥75% AND tool-use accuracy ≥85% AND multi-turn coding rollout ≥80%.
29
46
  - **Cost expectation:** ~3-5× cheaper than Thinking tier per token. The default for the bulk of build-loop work.
30
- - **Anthropic default:** Sonnet 4.6 (`claude-sonnet-4-6`)
47
+ - **Anthropic default:** Sonnet 5 (`claude-sonnet-5`; alias `sonnet` auto-tracks the latest Sonnet generation)
31
48
  - **Verified equivalents:** Sonnet 4.7+ (when available), GPT-5.4 Mini (`gpt-5.4-mini` — fast coding + subagents)
32
49
  - **Local equivalents:** qwen2.5-coder-32B-instruct (mid-quality), Codestral 22B (reasonable substitute for bounded refactor work)
33
50
 
@@ -43,7 +60,7 @@ Build-loop is provider-agnostic at the tier level. Agent frontmatter uses Anthro
43
60
 
44
61
  | Provider | Frontier | Thinking | Code | Pattern |
45
62
  |---|---|---|---|---|
46
- | Anthropic (default) | Fable 5 (`fable`) | Opus 4.8 (`opus`) | Sonnet 4.6 (`sonnet`) | Haiku 4.5 (`haiku`) |
63
+ | Anthropic (default) | Fable 5 (`fable`) | Opus 4.8 (`opus`) | Sonnet 5 (`sonnet`) | Haiku 4.5 (`haiku`) |
47
64
  | OpenAI | `gpt-5.5` (Codex) | `gpt-5.4` | `gpt-5.4-mini` | `gpt-5-nano` |
48
65
  | Google | next-gen Gemini Ultra (when it clears the contract) | `gemini-2.5-pro` | `gemini-2.5-flash` | `gemini-flash-lite` |
49
66
  | Local (Ollama / MLX) | n/a — none meets contract yet | n/a — none meets contract yet | `qwen2.5-coder-32b` | `llama3.2-3b` |
@@ -52,7 +69,7 @@ Build-loop is provider-agnostic at the tier level. Agent frontmatter uses Anthro
52
69
 
53
70
  ### Selectable model registry (the machine-readable source of truth)
54
71
 
55
- The cells above are mirrored by `MODEL_REGISTRY` in `scripts/model_overrides.py`. List the selectable models per tier (and feed any tool) with:
72
+ The single source of truth is **`references/model-taxonomy.json`** (segments, ladder, per-`(segment,tier)` preferred lists, seed model metadata with release dates, legacy aliases, classification rubric). `MODEL_REGISTRY` / `TIER_DEFAULTS` / `TIER_FALLBACK` in `scripts/model_overrides.py` are now DERIVED from it (the legacy 4-token view) — there is one vocabulary in the codebase. List the selectable models per legacy tier with:
56
73
 
57
74
  ```bash
58
75
  python3 scripts/model_overrides.py --list-models # all tiers
@@ -61,6 +78,28 @@ python3 scripts/model_overrides.py --list-models --tier frontier --json
61
78
 
62
79
  The registry is **advisory**: override resolution still accepts any model id, so a brand-new model works the moment you put it in `modelOverrides` — it is simply flagged `registered: false` on the resolve envelope until it is added here. `TIER_DEFAULTS` (the Anthropic mapping) stays the fallback; registering a model makes it *selectable*, not the default.
63
80
 
81
+ ### Standing tier-fallback policy (when a tier's model is unavailable)
82
+
83
+ When a tier's resolved model is **unavailable** at dispatch time (provider outage, quota, region gate) and the caller supplied no explicit per-call fallback, resolution walks DOWN a fixed **tier-to-tier** graph to the fallback tier's default. The policy is expressed in tier/role terms — `TIER_FALLBACK` in `scripts/model_overrides.py` holds the edges; the concrete model ids live only in `TIER_DEFAULTS`/`MODEL_REGISTRY`, so swapping a model never touches the rule.
84
+
85
+ | Tier (role) | Standing fallback tier |
86
+ |---|---|
87
+ | **Frontier** (judgment) | **Thinking** — and no further (invariant below) |
88
+ | **Thinking** (coordination) | **Code** |
89
+ | **Code** (execution) | **Pattern** |
90
+ | **Pattern** (recognition) | none — bottom of the graph |
91
+
92
+ **HARD INVARIANT — a frontier/judgment role never resolves below the Thinking tier.** Frontier's only permitted standing fallback is Thinking; it must NEVER silently degrade to the Code or Pattern tier. Resolution enforces this by walking at most one edge from Frontier: if the Thinking-tier default is itself unavailable, Frontier resolution STOPS at Thinking rather than walking on to Code/Pattern. Every other tier may keep walking down the graph until a usable default is found or the graph bottoms out. The rationale is durable: a verification/planning verdict produced by a Code- or Pattern-tier model is worse than a delayed verdict, so the judgment surface degrades only to the next reasoning-class tier (Thinking), never to an execution/recognition tier. See `feedback_model_org_fable5.md` (Frontier-unavailable → Thinking tier, never Code).
93
+
94
+ An **explicit per-call fallback wins** over the standing policy — passing `--fallback <model>` (or a `fallback=` argument) is treated as deliberate caller intent and skips the standing walk entirely.
95
+
96
+ ```bash
97
+ # Drive the standing policy explicitly (frontier default unavailable):
98
+ python3 scripts/model_overrides.py --workdir "$PWD" --tier frontier \
99
+ --unavailable fable --json
100
+ # -> { "model": "<thinking default>", "source": "tier-fallback", "fallback_tier": "thinking" }
101
+ ```
102
+
64
103
  ## Three ways to swap
65
104
 
66
105
  ### 1. Edit agent frontmatter (one-time, per-host)
@@ -173,7 +212,7 @@ This dual-mode design is **not deprecated** — it's the intentional architectur
173
212
  ## Multi-model implications for the dispatch test
174
213
 
175
214
  When swapping providers, the dispatch-pattern A/B test should be re-run because:
176
- - **Wall-clock per tier varies by provider.** GPT-5 Codex may be faster or slower than Sonnet 4.6 at scoped code application.
215
+ - **Wall-clock per tier varies by provider.** GPT-5 Codex may be faster or slower than Sonnet 5 at scoped code application.
177
216
  - **Cost ratios shift.** Some providers price the Thinking tier closer to the Code tier (smaller multiplier); others price wider.
178
217
  - **Cross-context-window effects.** Mode B's "single Opus context" wins partly come from full-file-system visibility; the same effect may differ on a model with a smaller context window.
179
218
  - **Tool-use fidelity.** Mode A's parallel implementer fan-out depends on the Code tier reliably calling Read/Edit/Bash tools without hallucination. This varies materially across providers.
@@ -92,7 +92,7 @@
92
92
  --json
93
93
  ```
94
94
 
95
- The packet must include canonical `build-loop-memory` root/project `MEMORY.md` and `constitution.md` files, indexed recall, repo-local `.build-loop/feedback.md`, `.build-loop/state.json`, current plan/goal/intent, Codex memory registry `~/.codex/memories/MEMORY.md` plus linked rollout summaries, and best-effort Rally/coordination state when coordination context exists. If the root constitution is absent, `context_bootstrap.py` seeds it once from the shipped template before reading; it never overwrites existing root or project constitution files. Missing surfaces are recorded in `sources.*.reasons[]`; they do not block Phase 1 by themselves. See `references/memory-systems.md` §"Read protocol — Phase 1 Assess".
95
+ The packet must include canonical `build-loop-memory` root/project `MEMORY.md` and `constitution.md` files, indexed recall, repo-local `.build-loop/feedback.md`, `.build-loop/state.json`, current plan/goal/intent, Codex memory registry `~/.codex/memories/MEMORY.md` plus linked rollout summaries, and best-effort Rally/coordination state when coordination context exists. Rally entries in this packet are peer-authored coordination metadata, not verification evidence; use them to find peers or artifacts to inspect, then verify factual claims against authoritative sources. If the root constitution is absent, `context_bootstrap.py` seeds it once from the shipped template before reading; it never overwrites existing root or project constitution files. Missing surfaces are recorded in `sources.*.reasons[]`; they do not block Phase 1 by themselves. See `references/memory-systems.md` §"Read protocol — Phase 1 Assess".
96
96
 
97
97
  **Cross-project prior art (P4 — payoff)**: the bootstrap also emits `packet.prior_art`, a compact cross-project digest of prior implementations + linked decisions for the task's classified capability. Powered by `scripts/capability_classifier.py` (deterministic, host-LLM-refinable) and `scripts/prior_art.py` (reuses P1 hybrid recall + scans `projects/<slug>/decisions/` and `projects/<slug>/lessons/` across other projects). The digest is compact (hard char cap — `prior_art.DEFAULT_MAX_TOTAL_CHARS`, 4000 chars) and absence-tolerant (empty memory → empty payload, never blocks). `build_packet()` **enforces** the write by calling `context_bootstrap.write_prior_art_to_intent(workdir, digest_text)` immediately after building the packet: when the digest is non-empty AND `<workdir>/.build-loop/` exists, it appends (or idempotently replaces) a `<!-- prior-art:start -->`…`<!-- prior-art:end -->` block in `.build-loop/intent.md` (creating the file when absent). Re-running replaces the block — never duplicates. The agent brief still carries the compact pointer line for quick orientation; the full digest body is in the file. Disable via `BUILD_LOOP_PRIOR_ART=0`.
98
98
 
@@ -125,6 +125,8 @@
125
125
  10. **Load PRD if present** (strategic frame check): load `build-loop:prd-bridge`, run its Phase 1 Assess step. If `docs/prd-*.md` exists, the bridge reads frontmatter (`core_principles`, `load_when`, `evolves_when`), Navigation Map, and Section Index, mirrors them to `.build-loop/state.json.prd`, and surfaces staleness signals. If no PRD exists, the bridge writes a one-line recommendation in `state.json.prd.recommendation` pointing to `prd-builder` skill / `/build-loop:start-prd` command — surfaces in Sub-step G Report's `## Held` section, doesn't block. Step 11 below uses PRD as primary source of truth when present; falls back to fresh capture when absent.
126
126
  11. **Capture north star + update intent**: When `state.json.prd.core_principles` is non-empty (a PRD was loaded by step 10), use it as the strategic frame; `intent.md` cites the PRD path + revision rather than re-deriving. Otherwise use `references/intent-capability-pack.md` to identify app/repo purpose, primary users, core jobs, update intent, user value, and non-goals fresh. Write `.build-loop/intent.md` and mirror compact fields to `.build-loop/state.json.intent`.
127
127
 
128
+ **Spec/Plan author router (intent-driven, ordered)**: select the single skill that AUTHORS this run's plan/spec/task graph per `references/capability-routing.md` §"Spec/Plan author router (intent-driven, ordered)". Walk the ordered rows (first match wins) keyed on `run_active`, `plan_status`, `intent_kind`, `code_exists`, `goal_scope`, and record the matched decision into `state.json.intent.spec_router` as `{route_type, action, skill, fallback, matched_row, signals}`. `prd-bridge` is NOT in this router — it is step 10's PRD-consumer, orthogonal to author selection. The record is a recommendation that DRIVES Phase 2 (Phase 2 step 0 consumes it), never a hard block. Selection is by intent, not by trigger-word match.
129
+
128
130
  **Intent restatement protocol (always-on, LLM-judged)**: as part of writing `intent.md`, follow `references/intent-capability-pack.md` § Intent restatement protocol — write a one-line concrete restatement always; when the orchestrator LLM judges the goal genuinely ambiguous (per Step A heuristics), extend with 1-3 approach options + tradeoffs + tagged assumptions. The judgment is the orchestrator LLM's, not a regex or detector script. For a concrete unambiguous goal this is one line and the flow moves on immediately — zero fast-path friction. Never `AskUserQuestion`, never `## Held`, never blocks Phase 1. Fail-safe: any error here is logged as one line; the build continues. Phase 2 Plan's "fork on uncertainty" rule consumes `state.json.intent.approach_options` when the LLM's confidence stays medium/low AND Phase 2 surfaces 2+ viable approaches.
129
131
 
130
132
  12. **Assess modular structure**: Use `references/modular-systems-pack.md`. Identify current module boundaries, stable interfaces, coupling risks, likely MECE work partitions, and any justified modularity exception. Mirror compact fields to `.build-loop/state.json.structure`.
@@ -208,6 +210,22 @@
208
210
 
209
211
  These exist because mockup-parity ≠ design-rule compliance, and component polish does not prove the UI handles the right data. Code that matches the mockup but omits an input, output, state, validation layer, or fallback is not production-ready. See `phases/ui-validation.md` and `references/ui-io-contract.md`.
210
212
 
213
+ 15a. **Acceptance-probe contract** (deterministic gate #1 — binds Assess criteria to the Phase-4 re-run so a criterion's own repro can't silently fall out of scope). Every **defect/behavioral** criterion MUST carry three fields:
214
+ - `acceptance_probe` — a **paste-ready command** that reproduces the failure (boundary-appropriate, not a cheaper proxy).
215
+ - `baseline` — the **captured failing value** the probe returns NOW, at Assess (the "before" signal Review re-checks). An empty string is valid when "empty output is the bug." Make it a **specific** failing signal (e.g. `"route":"keyword"` or the full `degradedReason`), NOT a generic token like `error`/`FAIL`/`0`: the Review re-run uses substring containment, which is biased toward a false `blocked` (safe — it never lets a still-failing criterion pass), so a generic baseline that a fixed output could incidentally still contain (a success output of `no error` contains `error`) spuriously blocks. Pick the narrowest string present only while the bug is present.
216
+ - `boundary` — the boundary the probe observes: `data | api | render | console | visual`. Observe the boundary that matters, not a cheaper one (a render/console bug can pass a `data`-layer curl + DB query while still failing — sample-app 2026-06-13).
217
+
218
+ Record the probes in a fenced ```` ```acceptance_probe ```` JSON block inside `.build-loop/goal.md` (single source of truth) OR a `.build-loop/acceptance-probes.json` sidecar. Schema and shape: `scripts/acceptance_probe.py` module docstring. Then validate:
219
+ ```bash
220
+ python3 ${CLAUDE_PLUGIN_ROOT}/scripts/acceptance_probe.py classify --goal .build-loop/goal.md --json
221
+ ```
222
+ - `verdict: ok` — all criteria verifiable; proceed.
223
+ - `verdict: flagged` — at least one criterion is missing a probe/baseline/boundary; that criterion is `unverifiable` and is **flagged in the Assess brief, not silently passed**. Surface each as `[UNVERIFIABLE] <id> — missing <fields>`. Additive/opt-in: a net-new behavioral criterion with no probe yet degrades to `unverifiable`, it does NOT hard-fail.
224
+ - `verdict: invalid` (exit 1) — a **defect-class** criterion (`defect_class: true`) has NO probe. This is a hard failure: a fix for an observed bug must carry its reproducible repro. Add the probe before proceeding.
225
+ - `verdict: no_probes` — goal.md has no probe block at all (legacy/opt-in run). Proceed; the gate is dormant.
226
+
227
+ The captured baselines bind forward into Phase 4 Review-B, where the same probes are re-run (`scripts/acceptance_probe.py rerun`). See `references/phase-4-review.md` §Sub-step B.
228
+
211
229
  16. **Design eval graders per criterion** using the grading hierarchy:
212
230
  - **Prefer code-based graders** (fast, deterministic, cheap): test suite pass/fail, lint/type check, build succeeds, schema validation, accessibility audit
213
231
  - **Use LLM-as-judge graders** when code can't check the criterion:
@@ -8,7 +8,15 @@
8
8
 
9
9
  **Goal**: Break work into executable steps, then optimize the plan before execution.
10
10
 
11
- 0. **If no plan exists yet**: check whether `.build-loop/plan.md` is absent or empty. If so, invoke `Skill("build-loop:spec-writing")` to draft a build-loop-compatible plan markdown before proceeding. The spec-writing skill walks the completeness checklist (auth guard, external API contracts, rate-limit criterion, discoverability surfaces, server/client boundary, concurrency mechanism, observability events, input validation, UI input/output contract when UI is in scope, and routing-risk fields) and runs `check_checklist.py` + `plan-critic` on the output. It writes the plan to `docs/plans/<feature-slug>.md` and commits it before any implementation branches are cut. Only continue to step 1 once the spec-writing skill returns a plan path. Skip this step when a valid plan already exists and passed `plan-verify` on the previous run.
11
+ 0. **Consume the Phase 1 spec-router record (author selection)**: READ `state.json.intent.spec_router` (written by Phase 1 Assess step 11 per `references/capability-routing.md` §"Spec/Plan author router (intent-driven, ordered)"). Do NOT independently re-decide which author skill to call **branch on `action` first**, then on `skill`:
12
+ - `action: "noop"` → **terminal**: author NOTHING from the router. Skip the author/writing-plans invocation entirely and proceed to step 1's optimization work without drafting a spec. Do not fall through to `writing-plans`.
13
+ - `action: "recommend"` → surface-only: name the recommended `skill` in the report so the lead knows what to run if it chooses, but do NOT auto-invoke it. Then proceed. (Distinct from `call`, which auto-invokes, and `noop`, which skips silently.)
14
+ - `action: "call"`, `skill: "build-loop:spec-writing"` → invoke `Skill("build-loop:spec-writing")` to draft the plan (the `no-plan` case: `.build-loop/plan.md` absent/empty).
15
+ - `action: "call"`, `skill: "build-loop:writing-plans"` → the plan exists and is valid; go straight to step 1 (`writing-plans` turns it into the task/dependency graph). Skip spec-writing. `writing-plans` is the external superpowers skill, not vendored here; if absent, write a structured plan inline (see `references/capability-routing.md` §"Core loop skills/assets" fallback).
16
+ - `action: "call"`, `skill: "prd-builder"` → greenfield PRD authoring (only when `run_active == false`); outside an active run this row rarely reaches Phase 2.
17
+ - **Fallback** (record absent — older state, or a Codex lead that skipped step 11): apply the router's own logic inline. Compute `plan_status` exactly as the signal is defined — `no-plan` when `.build-loop/plan.md` is absent/empty OR the last `plan-verify` result failed; `plan-valid` otherwise. If `plan_status == no-plan`, invoke `Skill("build-loop:spec-writing")`; otherwise skip to step 1.
18
+
19
+ When spec-writing is invoked it walks the completeness checklist (auth guard, external API contracts, rate-limit criterion, discoverability surfaces, server/client boundary, concurrency mechanism, observability events, input validation, UI input/output contract when UI is in scope, and routing-risk fields), runs `check_checklist.py` + `plan-critic`, writes the plan to `docs/plans/<feature-slug>.md`, and commits it before any implementation branches are cut. Only continue to step 1 once a plan path exists.
12
20
 
13
21
  1. **Invoke `writing-plans` skill** for detailed task breakdown
14
22
  2. **Identify parallel-safe tasks** vs sequential dependencies — build a dependency graph
@@ -23,6 +31,7 @@
23
31
 
24
32
  Skip only for narrow single-file fixes, pure config changes, or decisions where the two answers are identical; in that case write `Approach Lenses: n/a - <reason>`.
25
33
  3b. **Depends-on (reads-from) section**: For any plan that ships code, add a `## Depends-on (reads-from)` section listing every data path, contract, or invariant the new/changed code reads. Each entry takes the form `- \`<path-or-contract>\` — verified` or `- \`<path-or-contract>\` — unverified`. Mark `verified` when you can confirm something in the repo writes that path or holds that invariant (grep, schema inspection, or test fixture confirms it); mark `unverified` when no writer exists or you cannot confirm. Any `unverified` entry is a BLOCKING unknown that must be resolved — either add the missing writer to the plan, remove the read, or add `override: reads-from-dependency` with rationale. This section is exempt for doc-only and config-only plans that name no source-code paths. Enforced by `plan-verify` rule `reads-from-dependency`.
34
+ 3c. **Activation Map section**: For any plan that proposes a new *event-driven or call-site-dependent* component — a stop/SessionStart/PreToolUse/PostToolUse hook, a cron/launchd job, a watcher, a git hook (pre-commit/post-commit), a webhook, or a gate that fires on a host event — add an `## Activation Map` section. This converts build-loop's recurring failure class (machinery built, activation path never verified — a dormant WARN gated on a dict that int()'d to 0, state_finalize reading the wrong phase key, repo-level codex hooks that never fired, run-identity reuse silently skipping records) into a structural plan requirement. Each entry takes the form `- <component> — trigger: <event-or-call-site> — verified-live: yes|pending`. The `trigger:` must name the *concrete* host event or call site (e.g. `PostToolUse:Bash matcher in hooks/hooks.json`, `SessionStart hook`, `pre-commit hook in .pre-commit-config.yaml`), not an aspiration ("runs at review time"). Mark `verified-live: yes` only when you have confirmed the trigger actually fires (a live run exercised it, or a test asserts the host event reaches the handler); mark `verified-live: pending` otherwise. Any `pending` entry must map to a verification task before Report — the plan does not close while a component's activation is unconfirmed. This section is exempt for plans that propose no new event-driven machinery (doc-only, refactor-only, pure inline-logic changes); a `## Activation Map` is not required there. Add `override: activation-map-exempt` with rationale only when the section genuinely does not apply. Enforced by `plan-verify` rule `activation-map-required` (BLOCKER: missing section on a dormant-risk plan, or any entry that names a `trigger:` without a `verified-live:` key).
26
35
 
27
36
  4. **Partition tasks and files MECE**: Use one grouping dimension per level (domain, layer, workflow, bounded context, adapter, or test surface). Every changed file gets exactly one owner; every required behavior, state, migration, test, and user-facing surface gets an owner.
28
37
  5. **Define subagent integration points**: Where do agents need to coordinate? Where must outputs be tested together? Record interface contracts and checkpoints for every boundary.
@@ -9,13 +9,15 @@
9
9
  **Goal**: Implement the plan using parallel subagents where possible.
10
10
 
11
11
  1. **Use `subagent-driven-development`** — dispatch subagents per task
12
- 2. **Model assignment**: Default implementer `model: sonnet`, `effort: medium`. Consult `Skill("build-loop:model-tiering")` for task-specific defaults and escalation triggers
12
+ 2. **Model assignment**: Default implementer `model: sonnet`, `effort: high` (`xhigh` on hard/code-heavy chunks — Sonnet 5's coding gains land at high/xhigh, claude-api T1). Consult `Skill("build-loop:model-tiering")` for task-specific defaults and escalation triggers
13
13
  3. **Parallel agents** where dependency graph allows. build-loop dispatches up to `effective_max` parallel subagents, where `effective_max = scripts/parallelism.py effective_max_implementers(workdir)` — machine-aware (default 8; `min(config.parallelism.maxImplementers, cpu_count−2, hard ceiling 12)`). Decompose the plan into as many independent MECE chunks as the dependency graph allows so the wider fan-out is filled; dispatch in batches via `plan_batches()` when chunk count exceeds `effective_max`.
14
14
 
15
15
  **Maximize parallel-safe chunks**: the partition should expose the maximum number of independent chunks (one file-owner each) so fan-out scales to the available `effective_max`. Serializing parallelizable work requires a `parallel_skipped_reason:` entry in the plan record.
16
16
 
17
- **MECE pre-dispatch gate (NEW)**: before fanning out, validate the partition is *mutually exclusive* — `python3 scripts/parallelism.py --check-partition <{agent:[owned_paths]} JSON>` (exit 1 + overlap report if any file is claimed by >1 agent). A non-empty overlap means two writers would race the shared worktree — fix the partition (or give the overlapping agent `isolation:"worktree"`) before dispatch. Complements `brief_mece_validator.py` (which checks a single brief *has* the six ownership fields) by checking they are disjoint *across* the fan-out — closing the file-race / fan-out scope-blindness class.
18
- 4. **Each agent gets**: minimal context + clear integration contract + relevant doc context for external APIs + the intent packet from `.build-loop/intent.md` + the MECE ownership packet from the plan (`owns`, `does not own`, `interface contract`, `integration checkpoint`, `allowed tools`, `denied tools`)
17
+ **MECE pre-dispatch gate (NEW)**: before fanning out, validate the partition is *mutually exclusive* — `python3 scripts/parallelism.py --check-partition <{agent:[owned_paths]} JSON>` (exit 1 + overlap report if any file is claimed by >1 agent). A non-empty overlap means two writers would race the shared worktree — fix the partition (or give the overlapping agent `isolation:"worktree"`) before dispatch. Complements `brief_mece_validator.py` (which checks a single brief *has* the seven ownership fields) by checking they are disjoint *across* the fan-out — closing the file-race / fan-out scope-blindness class.
18
+ 4. **Each agent gets**: minimal context + clear integration contract + relevant doc context for external APIs + the intent packet from `.build-loop/intent.md` + the MECE ownership packet from the plan (`owns`, `does not own`, `interface contract`, `integration checkpoint`, `allowed tools`, `denied tools`, `acceptance criteria`)
19
+
20
+ **`acceptance criteria` (7th field)**: the testable conditions the returning envelope must satisfy — a per-chunk oracle the verifier reads instead of re-deriving one from the diff. Linted by `scripts/brief_mece_validator.py`; a write-handoff brief missing it now lints invalid. *Recovery-path was deliberately rejected as an 8th field — recovery stays centralized in the orchestrator's status-routing + stuck-cascade, not duplicated per handoff.*
19
21
  4a. **Implementers do NOT commit** (NEW 2026-05-07 — single-writer git contract). Implementers modify the working tree and return `files_changed` + `commit_subject` + `commit_body` in their envelope. The orchestrator commits sequentially after each parallel batch returns: `git add -- <files>` + `git commit -m <subject> -m <body>`, one implementer at a time through the pre-commit hook. This prevents the parallel-commit race that lost 3 of 4 commits in example-app round 3 (2026-05-07). See `agents/build-orchestrator.md` §"Phase 3 commit step" for the full procedure.
20
22
  4b. **Halt-and-ask backstop for architectural-class decisions** (NEW — C5). When an implementer encounters a synthesis-class decision NOT in the plan's `synthesis_dimensions` AND it's architectural-class (where a phase lives, defensive contract shape, error-propagation policy, persistence boundary, hard-fail counters), the implementer returns `status: "blocked"` with the decision in `novel_decisions[]` and does NOT commit. The orchestrator dispatches each blocked decision to the configured Thinking-tier resolver (per `references/model-tier-mapping.md` — never a hardcoded model name), persists resolutions to `state.json.novelDecisionResolutions[]`, and re-dispatches the implementer with resolutions appended to its brief. Hard-fail counter N=3 per chunk; exhausted chunks surface as ❓ Unfixed in Review-F. C3's attestation lint and C4's synthesis-critic still cover what they can grade — C5 catches what falls outside both. Full procedure: `agents/build-orchestrator.md` §"Phase 3 halt-and-ask branch".
21
23
  5. **Codex execution adapter**: If running in Codex, load `references/codex-subagents.md` before any spawn decision. Spawn `explorer` or `worker` subagents only when the Codex permission gate passed; otherwise execute locally. When spawning a worker, use `templates/codex-worker-prompt.md`, prefer explicit prompt packets over full context forks, and require the worker return changed files, validation, unresolved risks, and integration notes.