@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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +71 -18
- package/README.md +208 -299
- package/agents/advisor.md +2 -0
- package/agents/alignment-checker.md +2 -0
- package/agents/api-assessor.md +3 -1
- package/agents/architecture-scout.md +2 -0
- package/agents/assessment-orchestrator.md +2 -0
- package/agents/build-orchestrator.md +13 -8
- package/agents/database-assessor.md +107 -3
- package/agents/design-contract-specialist.md +2 -0
- package/agents/fact-checker.md +2 -0
- package/agents/fix-critique.md +26 -5
- package/agents/frontend-assessor.md +3 -1
- package/agents/implementer.md +2 -0
- package/agents/independent-auditor.md +2 -0
- package/agents/mock-scanner.md +2 -0
- package/agents/optimize-runner.md +2 -0
- package/agents/overfitting-reviewer.md +2 -0
- package/agents/performance-assessor.md +3 -1
- package/agents/plan-critic.md +5 -2
- package/agents/promotion-reviewer.md +2 -0
- package/agents/recurring-pattern-detector.md +4 -1
- package/agents/retrospective-synthesizer.md +14 -0
- package/agents/root-cause-investigator.md +39 -2
- package/agents/scope-auditor.md +2 -0
- package/agents/security-reviewer.md +3 -0
- package/agents/self-improvement-architect.md +2 -0
- package/agents/synthesis-critic.md +2 -0
- package/agents/transcript-pattern-miner.md +2 -0
- package/agents/ui-validator.md +2 -0
- package/assets/build-loop-plugin-icon.png +0 -0
- package/bin/build-loop-install.js +234 -0
- package/commands/run.md +1 -1
- package/hooks/git/pre-commit +40 -0
- package/hooks/git/pre-push +35 -10
- package/hooks/hooks.json +42 -5
- package/hooks/pre-edit-architecture.sh +2 -2
- package/hooks/route-guard.sh +15 -0
- package/hooks/route_guard.py +221 -0
- package/hooks/session-start-extensions.sh +10 -0
- package/hooks/session-start-git-hooks.sh +6 -1
- package/hooks/session-start-rally-point.sh +23 -1
- package/hooks/test_session_start_rally_version.sh +123 -0
- package/package.json +7 -2
- package/plugin-artifacts/codex/.codex-plugin/plugin.json +1 -1
- package/plugin-artifacts/codex/AGENTS.md +77 -16
- package/plugin-artifacts/codex/README.md +208 -299
- package/plugin-artifacts/codex/assets/build-loop-plugin-icon.png +0 -0
- package/plugin-artifacts/codex/references/agent-role-taxonomy.md +34 -0
- package/plugin-artifacts/codex/references/backlog-system.md +285 -0
- package/plugin-artifacts/codex/references/capability-routing.md +49 -0
- package/plugin-artifacts/codex/references/coordination-file-template.md +2 -2
- package/plugin-artifacts/codex/references/coordination-rules.md +275 -6
- package/plugin-artifacts/codex/references/memory-systems.md +25 -2
- package/plugin-artifacts/codex/references/memory.md +16 -0
- package/plugin-artifacts/codex/references/model-tier-mapping.md +45 -6
- package/plugin-artifacts/codex/references/phase-1-assess.md +19 -1
- package/plugin-artifacts/codex/references/phase-2-plan.md +10 -1
- package/plugin-artifacts/codex/references/phase-3-execute.md +5 -3
- package/plugin-artifacts/codex/references/phase-4-review.md +22 -1
- package/plugin-artifacts/codex/references/phase-5-iterate.md +4 -3
- package/plugin-artifacts/codex/references/phase-6-learn.md +3 -1
- package/plugin-artifacts/codex/references/verify-dispatch.md +85 -0
- package/plugin-artifacts/codex/skills/build-loop/SKILL.md +19 -10
- package/plugin-artifacts/codex/skills/build-loop/fallbacks.md +1 -1
- package/plugin-artifacts/codex/skills/build-loop/references/capability-routing.md +49 -0
- package/plugin-artifacts/codex/skills/build-loop/references/experiment-results-template.md +101 -0
- package/plugin-artifacts/codex/skills/build-loop/references/memory.md +16 -0
- package/plugin-artifacts/codex/skills/build-loop/references/output-style.md +98 -5
- package/plugin-artifacts/codex/skills/build-loop/references/phase-1-assess.md +19 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-2-plan.md +10 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-3-execute.md +5 -3
- package/plugin-artifacts/codex/skills/build-loop/references/phase-4-review.md +22 -1
- package/plugin-artifacts/codex/skills/build-loop/references/phase-5-iterate.md +4 -3
- package/plugin-artifacts/codex/skills/build-loop/references/phase-6-learn.md +3 -1
- package/plugin-artifacts/codex/skills/build-loop/references/verify-dispatch.md +85 -0
- package/plugin-artifacts/codex/skills/build-loop/templates/codex-worker-prompt.md +4 -0
- package/scripts/build_codex_plugin_artifact.py +7 -0
- package/scripts/embed_backend.py +17 -13
- package/scripts/lessons_index/query.py +1 -1
- package/scripts/memory_graph/__init__.py +2 -2
- package/scripts/sync_navgator_lessons.py +4 -4
- package/skills/api-registry-bridge/SKILL.md +1 -1
- package/skills/auto-decision-capture/SKILL.md +4 -3
- package/skills/auto-finding-capture/SKILL.md +119 -0
- package/skills/build-loop/SKILL.md +19 -10
- package/skills/build-loop/fallbacks.md +1 -1
- package/skills/build-loop/references/capability-routing.md +49 -0
- package/skills/build-loop/references/experiment-results-template.md +101 -0
- package/skills/build-loop/references/memory.md +14 -0
- package/skills/build-loop/references/output-style.md +98 -5
- package/skills/build-loop/references/phase-1-assess.md +19 -1
- package/skills/build-loop/references/phase-2-plan.md +10 -1
- package/skills/build-loop/references/phase-3-execute.md +5 -3
- package/skills/build-loop/references/phase-4-review.md +20 -1
- package/skills/build-loop/references/phase-5-iterate.md +4 -3
- package/skills/build-loop/references/phase-6-learn.md +2 -0
- package/skills/build-loop/references/verify-dispatch.md +85 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +4 -0
- package/skills/debug-loop/SKILL.md +4 -2
- package/skills/debugging-memory/SKILL.md +24 -12
- package/skills/{debugging/assess/SKILL.md → debugging-memory/references/assess.md} +6 -13
- package/skills/{debugging/memory/SKILL.md → debugging-memory/references/search.md} +10 -17
- package/skills/{debugging/store/SKILL.md → debugging-memory/references/store.md} +5 -13
- package/skills/debugging-memory/references/subagent-integration.md +3 -3
- package/skills/defenseclaw-bridge/SKILL.md +1 -1
- package/skills/{loop-builder → focused-loop-builder}/SKILL.md +6 -6
- package/skills/ibr-bridge/SKILL.md +1 -1
- package/skills/knowledge/SKILL.md +13 -2
- package/skills/{knowledge-review/SKILL.md → knowledge/references/review-mode.md} +8 -20
- package/skills/logging-tracer/SKILL.md +20 -4
- package/skills/model-bakeoff/SKILL.md +44 -0
- package/skills/model-tiering/SKILL.md +55 -20
- package/skills/optimize/SKILL.md +2 -2
- package/skills/prd-bridge/SKILL.md +1 -1
- package/skills/recursive-retrospective/SKILL.md +46 -0
- package/skills/recursive-retrospective/references/01-retrospective.md +136 -0
- package/skills/recursive-retrospective/references/02-learning-capture.md +72 -0
- package/skills/recursive-retrospective/references/03-judge.md +45 -0
- package/skills/research/SKILL.md +1 -1
- package/skills/root-cause-analysis/SKILL.md +42 -0
- package/skills/security-methodology/SKILL.md +10 -1
- package/skills/security-scan/SKILL.md +55 -0
- package/skills/spec-writing/SKILL.md +42 -1
- package/skills/sync-skills/SKILL.md +8 -7
- package/commands/assess.md +0 -41
- package/commands/compose-handoff.md +0 -113
- package/commands/debug.md +0 -20
- package/commands/debugger-detail.md +0 -34
- package/commands/debugger-scan.md +0 -15
- package/commands/debugger-status.md +0 -20
- package/commands/debugger.md +0 -32
- package/commands/optimize-run.md +0 -31
- package/commands/promote-experiment.md +0 -129
- package/commands/rally-point.md +0 -301
- package/commands/research-run.md +0 -22
- package/commands/review-knowledge.md +0 -16
- package/commands/self-review.md +0 -51
- package/commands/setup-memory.md +0 -32
- package/commands/start-prd.md +0 -57
- package/commands/test.md +0 -34
- package/commands/verify-plan.md +0 -39
- package/skills/debugging/debug-loop/SKILL.md +0 -231
- package/skills/logging-tracer-bridge/SKILL.md +0 -70
- /package/skills/{loop-builder → focused-loop-builder}/presets/active-project-evidence.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/generic-artifact-loop.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/presentation-audit.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/research-synthesis.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/presets/source-ingestion-raw-data-audit.yaml +0 -0
- /package/skills/{loop-builder → focused-loop-builder}/references/spec-format.md +0 -0
- /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
|
|
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;
|
|
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 (
|
|
456
|
+
## MECE Packets (briefs require all seven; rally packets six + optional 7th)
|
|
188
457
|
|
|
189
|
-
**Every implementation handoff to a peer MUST spell out
|
|
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
|
|
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
|
|
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"
|
|
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-
|
|
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**
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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. **
|
|
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:
|
|
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
|
|
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.
|