session-orchestrator 3.22.0 → 3.24.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +8 -0
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/050-plan.mdc +2 -2
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/remote-offload/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +446 -0
- package/README.md +22 -17
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -2
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +194 -25
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -7
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +120 -10
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +49 -2
- package/docs/session-config-reference.md +89 -9
- package/docs/session-config-template.md +38 -7
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +52 -1
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/cwd-change-restore.mjs +9 -29
- package/hooks/enforce-scope.mjs +93 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/on-session-end.mjs +486 -19
- package/hooks/on-session-start.mjs +263 -12
- package/hooks/on-stop.mjs +392 -24
- package/hooks/post-bash-write-verify.mjs +104 -4
- package/hooks/post-subagent-discovery-validator.mjs +182 -21
- package/hooks/post-tool-batch-wave-signal.mjs +165 -42
- package/hooks/post-tool-failure-corrective-context.mjs +9 -32
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/hooks/subagent-telemetry.mjs +11 -26
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/autopilot.mjs +31 -12
- package/scripts/backfill-abandoned-sessions.mjs +80 -11
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/emit-event.mjs +10 -2
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/auq/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +318 -18
- package/scripts/lib/build-live-signals.mjs +49 -27
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/block-header.mjs +55 -0
- package/scripts/lib/config/discovery-validator.mjs +7 -2
- package/scripts/lib/config/health-endpoints.mjs +383 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/remote-hosts.mjs +233 -0
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +87 -8
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/dispatcher/enumerate.mjs +2 -17
- package/scripts/lib/events-schema.mjs +48 -0
- package/scripts/lib/events.mjs +238 -5
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +25 -10
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/peer-discovery.mjs +20 -2
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +517 -18
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +126 -0
- package/scripts/lib/session-close-backfill.mjs +427 -37
- package/scripts/lib/session-discovery.mjs +69 -5
- package/scripts/lib/session-end/phase-skip.mjs +38 -5
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +220 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-canonical.mjs +446 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/schema.mjs +74 -8
- package/scripts/lib/telemetry/sync.mjs +91 -16
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +25 -65
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-skill-script-paths.mjs +436 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +333 -32
- package/scripts/lib/validate/check-validator-registration.mjs +248 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/markdown-fences.mjs +196 -0
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +381 -141
- package/scripts/lib/vault-status/narrative-mirror.mjs +190 -27
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-executor/remote-dispatch.mjs +504 -0
- package/scripts/lib/wave-resource-gate.mjs +127 -7
- package/scripts/lib/wave-transcript-tail.mjs +889 -0
- package/scripts/materialize-wave-scope.mjs +228 -15
- package/scripts/mcp-server.sh +11 -2
- package/scripts/memory-propose.mjs +132 -8
- package/scripts/parse-config.mjs +65 -0
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/site-numbers.mjs +36 -4
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +29 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +305 -51
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +19 -4
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/contract-version-bump/SKILL.md +1 -1
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +38 -1
- package/skills/journey-audit/SKILL.md +270 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +46 -3
- package/skills/remote-offload/SKILL.md +89 -0
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +20 -9
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +54 -17
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +5 -3
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +143 -22
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -44,7 +44,7 @@ Every harness creates friction. The goal is not minimum friction — it is usefu
|
|
|
44
44
|
- Silent partial completion (STATUS line requirement forces explicit reporting)
|
|
45
45
|
- Untracked carryover work (session-end plan verification catches unresolved tasks)
|
|
46
46
|
|
|
47
|
-
The harness does not hope agents self-correct. It detects stagnation patterns — pagination-spiral, turn-key-repetition, error-echo — classifies
|
|
47
|
+
The harness does not hope agents self-correct. It detects stagnation patterns — pagination-spiral, turn-key-repetition, error-echo (read by the coordinator during post-wave review), plus psa007-git-write and status-partial (detected live by the transcript tailer, recorded with `source: "tail"`) — classifies error-echo into the Error-Class Taxonomy defined in `circuit-breaker.md`, and re-scopes mechanically. Review logic lives in `wave-loop.md` § "Review Agent Outputs"; the tailer's start and its silence-is-not-success caveat live in the same file, step 2.0-bis.
|
|
48
48
|
|
|
49
49
|
## Platform Note
|
|
50
50
|
|
|
@@ -192,6 +192,8 @@ Read and follow `wave-loop.md` in this skill directory for the complete wave exe
|
|
|
192
192
|
|
|
193
193
|
The coordinator (you) is responsible for updating per-task mission status in STATE.md as tasks progress through the wave. Use `setMissionStatus(stateContent, taskId, status)` from `scripts/lib/state-md.mjs` and write the result back to STATE.md immediately.
|
|
194
194
|
|
|
195
|
+
**`taskId` grammar (enforced).** `setMissionStatus` refuses any `taskId` outside `[a-z][a-z0-9]*(?:-[a-z0-9]+)*-\d+` — lowercase segments joined by single hyphens, ending in a bare digit run. Accepted: `m-1`, `docs-2`, `w2-1`, `w2-a-10`. Refused (`refused: 'id-grammar'`): `w2-a10` (digits fused onto a letter segment), `w3-p2` (no trailing bare-digit segment), `W3-I1` (uppercase), `Docs_2` (underscore). A refused write returns `{ written: false, reason: 'id-grammar' }` from `setMissionStatusOnDisk` and logs a stderr WARN naming the rejected id — nothing is written to STATE.md on refusal, so mint ids matching this grammar from the start rather than relying on the refusal to catch a typo.
|
|
196
|
+
|
|
195
197
|
**Per-task transition rules (coordinator fires these, NOT wave-loop.md):**
|
|
196
198
|
|
|
197
199
|
| Transition | When to fire |
|
|
@@ -247,7 +249,7 @@ Each agent prompt MUST include:
|
|
|
247
249
|
3. **Acceptance criteria**: measurable definition of done
|
|
248
250
|
4. **Rule references**: the wave's applicable rules are injected automatically as the `<APPLICABLE-RULES>` block produced by `scripts/print-applicable-rules.mjs` (see `wave-loop.md` § "Pre-Dispatch: Glob-Scoped Rule Injection (#336/#694)"). The block is computed once per wave from the wave's `allowedPaths` and prepended to every agent prompt — do not hand-copy rule paths into the prompt. Past **learnings** arrive separately as the `<LEARNINGS-INDEX>` block from `scripts/print-learnings-index.mjs` (see `wave-loop.md` § "Pre-Dispatch: Learnings-Index Injection (#1014)"), computed **per agent** from its own file scope rather than once per wave.
|
|
249
251
|
5. **Testing expectation** (need-gated): "Before writing any test, name the concrete bug a NEW test would catch that the existing suite does not. No nameable bug → write NO test and report `no-tests-needed: <reason>` — that is a SUCCESS outcome, not a gap. With a nameable bug: exactly one test for it. Running existing tests is always mandatory."
|
|
250
|
-
6. **Commit instruction**: "Do NOT commit. The coordinator handles commits."
|
|
252
|
+
6. **Commit instruction**: "Do NOT commit. The coordinator handles commits. Never `git stash`, `git add`, `git checkout --` or `git reset` either (PSA-007) — to compare against the pre-change state, read `git show HEAD:<path>` (or `git show <sha>:<path>`); it never touches the shared index." Measured 2026-09-02: two agents in one wave reached for `git stash` to build a baseline; both recovered, both were the same shape.
|
|
251
253
|
7. **Turn limit**: Include the maxTurns instruction from `circuit-breaker.md`
|
|
252
254
|
8. **Verification before completion**: Before claiming any task done, run the verification command and quote the evidence inline. See `.claude/rules/verification-before-completion.md`.
|
|
253
255
|
|
|
@@ -479,7 +481,7 @@ An opt-in bounded-concurrency cursor-based pull loop that replaces the default P
|
|
|
479
481
|
|
|
480
482
|
## Anti-Patterns
|
|
481
483
|
|
|
482
|
-
- **NEVER**
|
|
484
|
+
- **NEVER** count launch acks as completions — verify the started set against `agent-<id>.meta.json` sidecars and completions against task-notifications (`wave-loop.md § Started-Set Verification`). `run_in_background: true` is ALLOWED and RECOMMENDED for wave dispatch: measured 2026-08-22 (v2.1.239), under blocking dispatch the coordinator was 143 s incapable of acting between an agent's mid-run escalation and its own next turn — escalation latency equals the batch's remaining runtime. Background dispatch returns turns to the coordinator between agent completions; a running agent received a queued message mid-run and answered ~9 min before its final report.
|
|
483
485
|
- **NEVER** skip inter-wave review — quality degrades exponentially
|
|
484
486
|
- **NEVER** let agents commit independently — coordinator commits at session end
|
|
485
487
|
- **NEVER** continue to next wave if previous wave has unresolved failures
|
|
@@ -103,12 +103,14 @@ The function never throws — it always returns a result object. Treat `skipped:
|
|
|
103
103
|
description: "...",
|
|
104
104
|
prompt: "...",
|
|
105
105
|
subagent_type: "general-purpose",
|
|
106
|
-
run_in_background:
|
|
106
|
+
run_in_background: true,
|
|
107
107
|
isolation: "worktree"
|
|
108
108
|
})
|
|
109
109
|
```
|
|
110
110
|
When resolved isolation is `none`, omit the `isolation` parameter (agents run in the coordinator's working tree).
|
|
111
111
|
|
|
112
|
+
`isolation` and `run_in_background` are orthogonal: worktree isolation partitions the filesystem, backgrounding decides when the coordinator's turn returns. A backgrounded worktree agent still merges back on completion — but do NOT read the immediate launch ack as "the worktree merged". Wait for the agent's task-notification before step 4 below (`wave-loop.md § Started-Set Verification`).
|
|
113
|
+
|
|
112
114
|
4. **Post-wave merge**: After wave completes, worktree changes are automatically available. If agents made changes in worktrees:
|
|
113
115
|
- Review each agent's changes for conflicts using `git diff` between worktree branches
|
|
114
116
|
- **Merge strategy**: Apply agent changes sequentially (by agent number). For each agent:
|
|
@@ -120,7 +122,11 @@ The function never throws — it always returns a result object. Treat `skipped:
|
|
|
120
122
|
|
|
121
123
|
## Stagnation Patterns
|
|
122
124
|
|
|
123
|
-
> Detection rules
|
|
125
|
+
> Detection rules applied at two different moments, by two different producers of the SAME `stagnation_detected` record (`wave-loop.md` § "Review Agent Outputs" carries the schema; `source` says which).
|
|
126
|
+
>
|
|
127
|
+
> - **Coordinator heuristics (3):** Pagination Spiral, Turn-Key Repetition, Error Echo. LLM heuristics, not executable code — the coordinator interprets them contextually from agent output and tool-call history during post-wave review (step 2 of `wave-loop.md`). Recorded with `source: "coordinator"`.
|
|
128
|
+
> - **Tail-mechanical (2):** PSA-007 Git-Write and Status-Partial (§ 4 / § 5 below). Executable regexes run live by `scripts/lib/wave-transcript-tail.mjs` against the session's subagent transcripts — no model call, no judgement. Recorded with `source: "tail"`.
|
|
129
|
+
> - **Error Echo is BOTH.** The coordinator's contextual reading of it is unchanged, and the tailer additionally matches its repeated-failure signature mechanically. One wave can therefore produce an error-echo record from either producer; the records are otherwise identical and are told apart only by `source`.
|
|
124
130
|
|
|
125
131
|
### 1. Pagination Spiral
|
|
126
132
|
|
|
@@ -153,19 +159,38 @@ Same error message returned 3 times, with the agent attempting the same fix (or
|
|
|
153
159
|
- `command-blocked` — denial from `enforce-commands.sh` (blocked command list).
|
|
154
160
|
- `other` — fallback when none of the above match.
|
|
155
161
|
|
|
156
|
-
The `error_class` value is used by the stagnation event-write rule in `wave-loop.md` § "Review Agent Outputs".
|
|
162
|
+
The `error_class` value is used by the stagnation event-write rule in `wave-loop.md` § "Review Agent Outputs". The taxonomy belongs to Error Echo **alone** — the four patterns below and above it omit the field entirely rather than falling back to `other`.
|
|
163
|
+
|
|
164
|
+
### 4. PSA-007 Git-Write (source: tail)
|
|
165
|
+
|
|
166
|
+
**Indicator:** a subagent's Bash call runs a git-write command — `git add`, `git commit`, `git stash`, `git push`, and equally `git mv` / `git rm` / `git reset` / `git checkout -- <file>`. `.claude/rules/parallel-sessions.md` § PSA-007 forbids all of them for dispatched agents: the git index and stash are shared resources of the working copy, not a per-agent workspace. Fleet evidence (2 repos, 2026-07, conf ≥ 0.9) records `index.lock` collisions and stash operations that silently discarded a sibling agent's work-in-progress.
|
|
167
|
+
|
|
168
|
+
**Detected by:** the tailer, mechanically, on the FIRST occurrence — not by post-wave review, and not on a repetition threshold. Today the coordinator learns of a subagent git-write only when the agent volunteers it in its own report.
|
|
169
|
+
|
|
170
|
+
**Action:** surface it in the wave progress update at once and inspect the shared index (`git status --porcelain`, `git stash list`) before the next dispatch. Do NOT re-dispatch into the same shared tree until the index state is understood — a stash the agent created is one the sibling cannot find. No `error_class`; `occurrences: 1` is the normal value.
|
|
171
|
+
|
|
172
|
+
### 5. Status-Partial (source: tail)
|
|
173
|
+
|
|
174
|
+
**Indicator:** the agent's own transcript contains `STATUS: partial` (or `STATUS: failed`) — a **self-reported** failure, not an inferred one, so there is nothing to interpret.
|
|
175
|
+
|
|
176
|
+
**Detected by:** the tailer, mechanically. The value here is **durability, not earliness**: when maxTurns kills an agent after it wrote the line but before its final report reaches the coordinator, the finding is lost today. The `events.jsonl` record survives that kill.
|
|
177
|
+
|
|
178
|
+
**Action:** treat exactly as the § Status Detection Protocol `partial` / `failed` branch above — carry forward the remaining work, or re-dispatch with narrower scope. A tail record is a **backstop for** the agent's report, never a replacement: when the report does arrive, the report wins and the tail record is corroboration (do not double-count one failure as two). No `error_class`; `occurrences: 1`.
|
|
157
179
|
|
|
158
180
|
### Decision Table
|
|
159
181
|
|
|
160
|
-
| Pattern | Indicator | Action | Error Class |
|
|
161
|
-
|
|
162
|
-
| Pagination Spiral | 3+ Read/Grep on same file with only pagination args, no Edit between | STAGNANT — re-dispatch with line-range scope | N/A |
|
|
163
|
-
| Turn-Key Repetition | 3 identical consecutive turn keys (pagination-stripped) | SPIRAL — revert, narrow, re-dispatch | N/A |
|
|
164
|
-
| Error Echo | Same error 3x, same fix attempted | FAILED — escalate with error context | see taxonomy above |
|
|
182
|
+
| Pattern | Indicator | Action | Error Class | Source |
|
|
183
|
+
|---------|-----------|--------|-------------|--------|
|
|
184
|
+
| Pagination Spiral | 3+ Read/Grep on same file with only pagination args, no Edit between | STAGNANT — re-dispatch with line-range scope | N/A | coordinator |
|
|
185
|
+
| Turn-Key Repetition | 3 identical consecutive turn keys (pagination-stripped) | SPIRAL — revert, narrow, re-dispatch | N/A | coordinator |
|
|
186
|
+
| Error Echo | Same error 3x, same fix attempted | FAILED — escalate with error context | see taxonomy above | coordinator **and** tail |
|
|
187
|
+
| PSA-007 Git-Write | Subagent Bash runs `git add`/`commit`/`stash`/`push` (or `mv`/`rm`/`reset`/`checkout --`) | Surface immediately; inspect shared index before next dispatch | N/A | tail |
|
|
188
|
+
| Status-Partial | Agent transcript carries `STATUS: partial` / `STATUS: failed` | Same as the Status Detection Protocol `partial`/`failed` branch; backstop for a lost report | N/A | tail |
|
|
165
189
|
|
|
166
190
|
### Detection Discipline
|
|
167
191
|
|
|
168
|
-
-
|
|
192
|
+
- The three **coordinator** checks run during step 2 of `wave-loop.md` ("Review Agent Outputs"), per agent, after the wave completes — not during the agent's execution. The two **tail** checks (§ 4 / § 5) run live and are the exception to that timing, which is exactly why they survive a maxTurns kill.
|
|
193
|
+
- **Tail silence is not a clean wave.** Transcripts flush per turn, so an agent inside one long tool call is unobservable for that call's duration (`.claude/rules/loop-and-monitor.md` § LM-002). Absence of tail records never substitutes for the post-wave review.
|
|
169
194
|
- Two different agents reading the same file is **not** a spiral. That is coordination across agents, not stagnation within an agent.
|
|
170
195
|
- A legitimate sequential read of a large file (e.g., reading lines 1-200, then 200-400 to gather full context for an upcoming edit) is **not** a pagination spiral if the agent eventually edits the file. The pattern triggers only when paging continues without ever producing an edit.
|
|
171
196
|
- These patterns are heuristics. When in doubt, prefer false negatives (let the agent finish) over false positives (kill productive work).
|
|
@@ -78,6 +78,14 @@ const gate = await evaluateWaveResourceGate({
|
|
|
78
78
|
| `proceed` | Dispatch at `gate.agents` (= `plannedAgents`). Include `gate.reasons` in the wave progress update (informational). |
|
|
79
79
|
| `reduce` | Dispatch at `gate.agents` (< `plannedAgents`). Log the reduction as a deviation in STATE.md. Include `gate.reasons` in the wave progress update. |
|
|
80
80
|
| `coordinator-direct` | Do NOT dispatch subagents. Coordinator executes the wave's tasks directly. Log as a deviation in STATE.md. Continue to `### 1. Dispatch Agents` only for stagnation-pattern detection wording — the section's execution is skipped. |
|
|
81
|
+
| `offload` (#1160) | The envelope carries `host` — `{ decision: 'offload', agents, host, reasons }`. Dispatch the offloadable roles to that alias via `dispatchRemote()` (`### Agent-Type Resolution`, `ssh:<alias>` branch) and the remaining roles locally at `gate.agents`. Placement, not reduction: log it in the wave progress update with the host named, since a wave that silently ran elsewhere is unreadable afterwards. |
|
|
82
|
+
|
|
83
|
+
**The offload witness is supplied, never probed (#1160).** The gate's own JSDoc states it: `@param {Record<string, boolean>} [opts.remoteReady] — #1160 readiness witness per declared host alias. The gate never probes the network itself; without a witness no host counts as ready and the decision stays local.` So an `offload` decision is impossible unless the coordinator passes one of two things alongside `config`/`plannedAgents`/`waveRole`:
|
|
84
|
+
|
|
85
|
+
- `remoteReady: { '<alias>': true }` — built from the SessionStart banner line `Offload <alias>: ready=yes`, which was measured at session start and needs no new network call here; or
|
|
86
|
+
- `probeFn: remoteReadyProbe` from `scripts/lib/wave-executor/remote-dispatch.mjs` — `@param {(alias: string) => Promise<boolean>} [opts.probeFn] — optional async witness, consulted only for aliases absent from `remoteReady`. Default null.` Use it when the banner is stale or absent; it costs a live probe per alias.
|
|
87
|
+
|
|
88
|
+
Passing neither is a valid choice, not a bug: the gate then behaves exactly as it did before #1160 and reduces locally.
|
|
81
89
|
|
|
82
90
|
Reasons MUST appear in the wave's progress update under a "Resource gate:" bullet. Measurements (RAM free GB, CPU %, concurrent sessions) appear verbatim so the user can trust the decision.
|
|
83
91
|
|
|
@@ -97,9 +105,13 @@ Skip the deviation entry on `proceed`, even when `concurrentSessions` warns —
|
|
|
97
105
|
|
|
98
106
|
When `worker-pool.enabled: true` in Session Config, dispatch via `runWavePool()` from `scripts/lib/wave-executor/pool.mjs` with `maxParallel = worker-pool.max-parallel || agents-per-wave` — the bounded cursor is the opt-in alternative that supersedes manual batching. Else fall back to the small-batch Agent() dispatch described below (3–4 calls per message, cumulative up to the wave's `agents-per-wave` cap).
|
|
99
107
|
|
|
108
|
+
**Worker-pool and background dispatch compose — neither replaces the other.** The pool is the opt-in for bounded-concurrency *pull* (how many agents may be in flight at once); background dispatch is the default *transport* (whether the coordinator's turn returns before an agent finishes). Pool workers may themselves background-dispatch. Adopting either removes no mechanism from the other, and neither adds a second mechanism to maintain.
|
|
109
|
+
|
|
100
110
|
**Worker-pool timing note:** when `worker-pool.enabled: true`, per-agent start and end times are recorded individually in subagents.jsonl as workers pull from the cursor at different moments. Wave-level timings (for progress updates and metrics) are computed as first-worker-start to last-worker-finish, not as a uniform fan-out timestamp.
|
|
101
111
|
|
|
102
|
-
Use the **Agent tool** to dispatch this wave's agents
|
|
112
|
+
Use the **Agent tool** to dispatch this wave's agents. **`run_in_background: true` is ALLOWED and RECOMMENDED for wave dispatch** — measured 2026-08-22 (v2.1.239): under blocking dispatch the coordinator was 143 s incapable of acting between an agent's mid-run escalation and its own next turn — escalation latency equals the batch's remaining runtime. Background dispatch returns turns to the coordinator between agent completions; a running agent received a queued message mid-run and answered ~9 min before its final report.
|
|
113
|
+
|
|
114
|
+
Dispatch in **SMALL BATCHES of 3–4 Agent() calls per message** (cumulative up to the wave's `agents-per-wave` cap). Large single-message fan-outs (>4 Agent() calls in one message) remain **FORBIDDEN** — fleet evidence (conf 1.0, 5 sessions) shows they drop Agent() calls SILENTLY (the coordinator receives fewer results than it dispatched, with no error), whereas serial / small-batch dispatch held 13/13 and 8/8. That evidence PREDATES backgrounding and is untouched by it; what backgrounding changes is the *cost* of batching, which is now near zero — a background batch returns its launch acks immediately, so batches no longer serialize the wave. Dispatch a batch, let its acks return, then dispatch the next, until every planned agent is started; then run **Started-Set Verification** below. See `docs/specs/2026-07-02-fleet-mining-followup-grill.md` (C4) for the batching-policy rationale. The `worker-pool.enabled: true` path (above) is the mechanised opt-in alternative to manual batching.
|
|
103
115
|
|
|
104
116
|
Read each wave's dispatch metadata from the session plan header (e.g., `(4 agents, parallel, isolation: worktree)`). When the plan specifies `isolation`, use it verbatim. When the plan does not specify, resolve the effective value via `resolveIsolation({ agentCount, sessionType, collisionRisk, configIsolation })` from `scripts/lib/wave-sizing.mjs` — the graduated default (#194) replaces the previous session-type-only switch. Pass the resolved value to each Agent() tool call per `circuit-breaker.md` (omit the parameter when resolved to `none`).
|
|
105
117
|
|
|
@@ -109,15 +121,26 @@ Before dispatching, verify the wave's agent count does not exceed `$CONFIG.agent
|
|
|
109
121
|
|
|
110
122
|
#### Contract-Lock Serialization (Pattern A, #730/H1)
|
|
111
123
|
|
|
112
|
-
When the session plan marks a wave task `contract-lock: true` (session-plan Step 3.5 step 6), dispatch that single agent ALONE as the first batch and WAIT for its
|
|
124
|
+
When the session plan marks a wave task `contract-lock: true` (session-plan Step 3.5 step 6), dispatch that single agent ALONE as the first batch and WAIT for its **task-notification** (`<status>completed</status>`) before dispatching the disjoint fan-out batches — NOT for its tool-result, which under background dispatch is the immediate launch ack and would release the fan-out against an unfrozen contract (§ Started-Set Verification). The lock agent freezes the shared contract (interfaces/schemas/shared types/constants) so the N follow-on agents build against a fixed surface instead of racing to invent it. Never place the contract-lock agent in the same batch as the impl agents — its output is an input to theirs. The contract file MUST NOT appear in any follow-on agent's allowedPaths (read-only reference). If the lock agent reports STATUS: partial/failed, PAUSE the fan-out and surface the choice via AskUserQuestion (proceed with partial contract / re-dispatch lock / abort wave).
|
|
125
|
+
|
|
126
|
+
#### Started-Set Verification (fail-loud — #724, #1115)
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
Once all batches for the wave have been dispatched, verify the **started set** against the planned agent list (the agents named in the session plan for this wave). This closes the silent-drop failure class that motivated the small-batch default above (a large fan-out drops calls with no error).
|
|
115
129
|
|
|
116
|
-
|
|
130
|
+
**Never count launch acks.** Under background dispatch every Agent() call returns an immediate `Async agent launched successfully` ack. Counting acks against the plan would report full success with zero work done — a fail-silent hole inside the fail-loud check. Two distinct signals, each with its own source:
|
|
117
131
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
|
|
132
|
+
- **STARTED** — the agent's `agent-<id>.meta.json` sidecar exists under `~/.claude/projects/<encoded-repo-path>/<session-uuid>/subagents/`. The harness writes it at spawn, carrying `toolUseId` + `description` (measured 2026-08-25). Same substrate the tailer reads — see the header of `scripts/lib/wave-transcript-tail.mjs`. The `subagents/` directory does not exist until the first spawn of the session.
|
|
133
|
+
- **COMPLETED** — the agent's task-notification carrying its `<tool-use-id>` and `<status>completed</status>`. NEVER the launch ack. `hooks/pre-task-scope-disjoint.mjs` (`ASYNC_LAUNCH_ACK` constant + `buildTranscriptIndex()`) already implements exactly this ack-vs-completion distinction across both the sync and async record shapes — **cite it as the reference implementation; do not restate its logic here and do not fork a second one.**
|
|
134
|
+
|
|
135
|
+
Three distinguishable states, each with its own action:
|
|
136
|
+
|
|
137
|
+
| State | Signal | Action |
|
|
138
|
+
|---|---|---|
|
|
139
|
+
| **never-started** | no `meta.json` sidecar after the batch's acks returned | Silent drop. **Re-dispatch ONLY the missing agents in a fresh batch** (3–4 per message) before proceeding to Review. Do NOT re-dispatch agents that already started — that would duplicate their file writes. **Before dispatching any re-dispatch (or fix-pass) batch, re-run the Pre-Dispatch Scope-Union Assertion (§ Scope Manifest #3, #796) for each re-dispatched agent** — `allowedPaths` MUST NOT shrink while sibling agents of this wave are still running, or the re-dispatched agent's legitimate writes will be denied by Gate 7. |
|
|
140
|
+
| **started-but-never-returned** | sidecar present, no task-notification | The tailer's territory (step 2.0-bis). Do NOT re-dispatch blindly — the agent may be inside one long tool call (transcripts flush per turn, so it is invisible meanwhile), and a second copy would race it on the same file scope. Inspect its `agent-<id>.jsonl` transcript, then decide. |
|
|
141
|
+
| **completed** | task-notification with `<status>completed</status>` | Proceed to `### 2. Review Agent Outputs`. |
|
|
142
|
+
|
|
143
|
+
- Record `agent_count_planned` (from the plan), `agent_count_started` (distinct agents with a `meta.json` sidecar, after any re-dispatch) and `agent_count_completed` (distinct agents whose task-notification arrived) in the wave metrics (see § Capture wave metrics). A persistent `planned > started` gap after re-dispatch is a silent drop; a `started > completed` gap at wave end is an agent that never returned. Both are deviations — log them to STATE.md `## Deviations`.
|
|
121
144
|
|
|
122
145
|
#### Pre-Dispatch New-Directory Detection (#243)
|
|
123
146
|
|
|
@@ -193,7 +216,7 @@ After running this detection block, call `resolveIsolation({ agentCount, session
|
|
|
193
216
|
|
|
194
217
|
#### Pre-Dispatch: Path-Cousin-Guard Injection (#730.3)
|
|
195
218
|
|
|
196
|
-
Before dispatching each agent whose fileScope contains a NEW (non-existent) file target, check for existing "cousin" files with a similar basename elsewhere in the repo — prevents the framing-wrong class where an agent creates `scripts/lib/foo/bar.mjs` while `scripts/lib/bar.mjs` already exists and serves the same purpose.
|
|
219
|
+
Before dispatching each agent whose fileScope contains a NEW (non-existent) file target, check for existing "cousin" files with a similar basename elsewhere in the repo — prevents the framing-wrong class where an agent creates `scripts/lib/foo/bar.mjs` while `scripts/lib/bar.mjs` already exists and serves the same purpose. <!-- path-check: example -->
|
|
197
220
|
|
|
198
221
|
**Detection (mechanical, reuses the new-file scan from #243 above):** for each not-yet-existing file target `<newPath>` in an agent's fileScope, take `basename(<newPath>)` minus extension; skip generic basenames (`index`, `utils`, `main`, `config`, or length ≤ 3 chars — false-positive control). Then:
|
|
199
222
|
|
|
@@ -269,12 +292,54 @@ For each agent in this wave:
|
|
|
269
292
|
- What NOT to touch (other agents' files)
|
|
270
293
|
>",
|
|
271
294
|
subagent_type: "<from session plan>", // resolved agent type
|
|
272
|
-
run_in_background:
|
|
295
|
+
run_in_background: true // RECOMMENDED — verify the started set via meta.json sidecars, never via the launch ack (§ Started-Set Verification)
|
|
273
296
|
})
|
|
274
297
|
- Turn budget and status reporting: "You have a maximum of [maxTurns] turns for this task. If you cannot complete within this budget, report STATUS: partial with what was accomplished and what remains. At the end of your work, report STATUS: done (all acceptance criteria met) or STATUS: partial (some criteria unmet — list which ones)."
|
|
275
298
|
- Optional open-questions reporting (Close Handover-Alignment-Gate, PRD 2026-07-07): "If you encountered a genuinely unresolved, user-facing question you could not answer within your task scope, report it as an additional line: OPEN-QUESTIONS: <question> | context: <one-line why this is unresolved> | candidates: <opt A / opt B>. This line is optional — omit it entirely when you have no such question. Do not use it for questions you could resolve yourself by reading more code."
|
|
276
299
|
```
|
|
277
300
|
|
|
301
|
+
##### Third branch: foreign-model dispatch (`cursor:<model>` — #1150)
|
|
302
|
+
|
|
303
|
+
The colon heuristic (§ "How to detect project agents") has **three** readings, not two: no colon = project agent, `session-orchestrator:<agent>` = plugin agent, and `cursor:<model>` = **foreign channel**. When the session plan or `agent-mapping` resolves an agent to `cursor:<model>` (e.g. `impl: cursor:composer-2.5`), do NOT call the Agent tool. Dispatch **coordinator-direct** via `dispatchForeign({ model, prompt, repoRoot, role, runId, timeoutSec })` from `scripts/lib/wave-executor/foreign-dispatch.mjs`. Contract, in the order it binds:
|
|
304
|
+
|
|
305
|
+
1. **`never_foreign` gate first.** `isNeverForeignRole(role)` returns `{ok:false, reason:'never-foreign-role'}` before any worktree or spawn. `NEVER_FOREIGN_ROLES` (impl-core, security-review, migration, release, secrets, incident, refactor-crosscut) is hand-copied from the operator's model-routing SSOT (ADR-002); `dispatch-cursor.sh` enforces none of it, so **this adapter is the only gate** — never route around it with a shell call.
|
|
306
|
+
2. **Detached worktree**, `<tmpdir>/so-foreign/<runId>` by default, `git worktree add --detach`. Never under `.claude/worktrees/` — eight readdir scanners in this repo read that path. The stream log is KEPT beside it (`<runId>.log.jsonl`), not trap-deleted.
|
|
307
|
+
3. **Verdict measured at the filesystem, never from the model's prose.** `ok` is true only on exit 0 + no timeout + a non-empty changed set (`git diff --name-only` ∪ `ls-files --others`, since a diff alone is blind to new files). **An empty diff is a failure regardless of what the report says.**
|
|
308
|
+
4. **MANDATORY Claude diff-review gate.** Read `result.diff` and judge it SEMANTICALLY before any merge-back — test-green is not the bar (measured counterexample: the foreign #1149 solution was test-green and semantically wrong). The **coordinator** applies (`git apply`) and commits; the foreign model never touches this repo's index or worktree (PSA-007). On review failure, discard the diff. **`result.hookTampering === true` invalidates the run regardless of `ok`**: the child repointed or rewrote the shared `.git` hooks path (a linked worktree isolates the tree, not `core.hooksPath`), and that write is invisible in `result.diff` — discard and do not merge. `null` means the fingerprint could not be read (not measured), never "clean".
|
|
309
|
+
5. **`removeForeignWorktree({ repoRoot, worktreePath })` after the review** — never before, because a failed run must stay inspectable. It **refuses a `worktreePath` outside `<tmpdir>/so-foreign*`** (returns `{ok:false, reason:'unsafe-...'}` and makes no git call), so it cannot `--force`-remove a sibling session's worktree.
|
|
310
|
+
|
|
311
|
+
**Trust boundary.** A foreign run bypasses the ENTIRE hook chain — no `PreToolUse:Agent`, no scope enforcement, no `SubagentStop` telemetry (verified against `hooks/hooks.json`, W1/D4). Everything inside the detached worktree is **untrusted** until a Claude coordinator has read the diff and applied it. The replacement ledger record is the `orchestrator.foreign_dispatch.completed` event the adapter emits (payload in `docs/events-schema.md`), which fires on refusals too — a blocked dispatch is a record, not a silence.
|
|
312
|
+
|
|
313
|
+
**Composition.** A foreign run is a background Bash task, so it composes with background `Agent()` dispatch in the same wave. It is **NOT** part of § Started-Set Verification: no `meta.json` sidecar exists for it and no task-notification arrives. Its lifecycle is the adapter's return value — do not count it as planned/started/completed, and do not read its absence from the started set as a drop.
|
|
314
|
+
|
|
315
|
+
**Model selection** is not decided here: the operator's model-routing SSOT (ADR-002) owns it. Working defaults: `composer-2.5` for foreign impl; `cursor-grok-4.6-high` for review / test-writing / judgment roles, at `timeoutSec ≥ 900`. Dogfood evidence (2026-08-25): `composer-2.5` on issue #1105 — 165 s, 2 files, +93 lines, mandatory review passed, merged only after independent test verification.
|
|
316
|
+
|
|
317
|
+
**Timeout.** `maxTurns` does not exist on this channel — see the § Platform-Specific Dispatch timeout note. The wall-clock SIGTERM (`DEFAULT_TIMEOUT_SEC = 900`, a floor) is the only circuit breaker.
|
|
318
|
+
|
|
319
|
+
##### Fourth branch: remote-host dispatch (`ssh:<alias>` — #1160)
|
|
320
|
+
|
|
321
|
+
A fourth colon reading, beside the `cursor:<model>` branch above: `ssh:<alias>` routes the role to a declared offload host. Reached either from an `offload` resource-gate decision (§ 0.5) or from an explicit `agent-mapping` entry. Do NOT call the Agent tool — dispatch coordinator-direct via `dispatchRemote()` from `scripts/lib/wave-executor/remote-dispatch.mjs`:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { dispatchRemote } from "scripts/lib/wave-executor/remote-dispatch.mjs";
|
|
325
|
+
// task fields, per the adapter's own JSDoc:
|
|
326
|
+
// host (offload alias, `-H`), repo, prompt (STDIN, never argv), role,
|
|
327
|
+
// runId (becomes `--job`), timeoutSec?, model?, patchPath?
|
|
328
|
+
const result = await dispatchRemote(
|
|
329
|
+
{ host, repo, prompt, role, runId, timeoutSec, model, patchPath },
|
|
330
|
+
{ repoRoot },
|
|
331
|
+
);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The contract mirrors the `cursor:` branch and differs in exactly one place — the artefact is a PATCH, not a worktree:
|
|
335
|
+
|
|
336
|
+
1. **`never_foreign` gate first**, same `NEVER_FOREIGN_ROLES` lock, refused before any spawn; a refusal is a ledger record, not a silence.
|
|
337
|
+
2. **The prompt goes on STDIN**, never into argv.
|
|
338
|
+
3. **Verdict = exit 0 AND a non-empty patch.** The adapter's own words: *"`ok` is false unless the child exited 0 AND left a non-empty patch."* A green-sounding remote report with an empty patch is a failure.
|
|
339
|
+
4. **The coordinator reads and reviews the patch, then applies it with `git apply`.** The remote never touches this repo — same PSA-007 boundary as the foreign branch, and the same semantic-review bar (test-green is not the bar).
|
|
340
|
+
5. **Telemetry:** `orchestrator.remote_dispatch.completed`, emitted on refusals too.
|
|
341
|
+
6. **Not part of § Started-Set Verification** — no `meta.json` sidecar, no task notification, exactly as for the `cursor:` branch. Its lifecycle is the adapter's return value.
|
|
342
|
+
|
|
278
343
|
#### Pre-Dispatch Grounding Injection (#85)
|
|
279
344
|
|
|
280
345
|
Before dispatching each agent, prepend a line-numbered GROUNDING block to its prompt for any file in the agent's scope that has recent edit-format-friction history. This helps the agent reference edits by line number instead of re-matching exact character spans, reducing Edit-tool retry loops.
|
|
@@ -502,9 +567,9 @@ Rules:
|
|
|
502
567
|
|
|
503
568
|
4. **Finalization** → direct execution (no subagent needed)
|
|
504
569
|
|
|
505
|
-
> **How to detect project agents:** The session plan's "Agent Registry" section lists all discovered agents. If an agent name does NOT contain a colon (`:`), it's a project-level agent. If it contains `session-orchestrator:`, it's a plugin agent.
|
|
570
|
+
> **How to detect project agents:** The session plan's "Agent Registry" section lists all discovered agents. If an agent name does NOT contain a colon (`:`), it's a project-level agent. If it contains `session-orchestrator:`, it's a plugin agent. If it contains `cursor:` (e.g. `cursor:composer-2.5`), it is a **foreign-channel dispatch**, not an Agent-tool dispatch at all — see § Third branch: foreign-model dispatch. Any other prefix is rejected at parse time by `scripts/lib/config.mjs` (known channels: `cursor`, `session-orchestrator`), so an unroutable value never reaches this chain.
|
|
506
571
|
|
|
507
|
-
|
|
572
|
+
**`run_in_background: true` is ALLOWED and RECOMMENDED for wave dispatch.** Measured 2026-08-22 (v2.1.239): under blocking dispatch the coordinator was 143 s incapable of acting between an agent's mid-run escalation and its own next turn — escalation latency equals the batch's remaining runtime. Background dispatch returns turns to the coordinator between agent completions; a running agent received a queued message mid-run and answered ~9 min before its final report. Still dispatch in small batches of 3–4 Agent() calls per message (never a large single-message fan-out — see § Dispatch Agents; large fan-outs drop calls silently, conf 1.0), then run **Started-Set Verification** — an agent counts as started on its `meta.json` sidecar and as completed on its task-notification, never on the launch ack.
|
|
508
573
|
|
|
509
574
|
#### Platform-Specific Dispatch
|
|
510
575
|
|
|
@@ -527,6 +592,8 @@ Dispatch via Codex's multi-agent system — describe the task and specify the ag
|
|
|
527
592
|
The `agents-per-wave` config is ignored on Cursor — all work is sequential. Session-reviewer dispatch is deferred to session-end (Phase 1.8).
|
|
528
593
|
|
|
529
594
|
> **Timeout note:** Agent timeout is controlled by `maxTurns` from `circuit-breaker.md`, not by a time-based timeout. Claude Code's built-in turn limit provides the safety net. There is no need to set explicit time-based timeouts on agent dispatch.
|
|
595
|
+
>
|
|
596
|
+
> **Foreign-dispatch exception (#1150):** this is FALSE for a `cursor:<model>` dispatch. `cursor-agent` has no `maxTurns` and no turn limit of any kind, so a wall-clock SIGTERM is the **only** circuit breaker there — `timeoutSec` (default `DEFAULT_TIMEOUT_SEC = 900`, a measured floor, not a suggestion: `cursor-grok-4.6-high` ran 2 of 3 hard-test tasks past a 540 s cap). Lowering it manufactures timeouts that read as model failure. See § Third branch: foreign-model dispatch.
|
|
530
597
|
|
|
531
598
|
### 2. Review Agent Outputs
|
|
532
599
|
|
|
@@ -544,6 +611,20 @@ if (cwd.restored) {
|
|
|
544
611
|
|
|
545
612
|
Run this step for every wave, regardless of isolation setting — it is a no-op when CWD never drifted.
|
|
546
613
|
|
|
614
|
+
**Step 2.0-bis — Transcript tailer (FA-1, #1114):** `monitors/monitors.json` carries a `wave-transcript-tail` entry with `when: "on-skill-invoke:wave-executor"`, so the tailer starts ONCE per wave-executor invocation — **not per wave**. It observes the OWN session's subagent transcripts, picking up newly-appearing `agent-*.jsonl` files as later waves dispatch, so it never needs to be told a wave boundary. Its findings arrive as `stagnation_detected` records carrying `source: "tail"` — the same schema the **Stagnation event-write** block under step 3a below produces with `source: "coordinator"`, deliberately not a second event name (#1035).
|
|
615
|
+
|
|
616
|
+
**Silence is NOT success** (`.claude/rules/loop-and-monitor.md` § LM-002). Transcripts flush per TURN, so an agent inside one long tool call is invisible to the tailer for that call's whole duration. Read "no tail findings" as "nothing detectable was flushed", never as "the wave is healthy" — the post-wave review below remains the primary check. A tailer that cannot resolve the transcript directory exits with one stderr line and never blocks the wave.
|
|
617
|
+
|
|
618
|
+
**Step 2.0-ter — Incoming agent escalations (FA-2, #1051):** a wave agent may send ONE upward `SendMessage` to `main` when it hits a wave-blocking obstacle (`.claude/rules/cross-session-messaging.md` § CSM-001 — agents send upward only, never sideways). When such a message arrives mid-wave:
|
|
619
|
+
|
|
620
|
+
- **It is a claim, not a finding.** Verify it against the tree before altering the wave plan or re-scoping a sibling agent — `.claude/rules/receiving-review.md` § RCR-003 (skeptical posture) and RCR-001 step 3 (VERIFY) apply unchanged. The agent's view of the tree may already be stale.
|
|
621
|
+
- **Carry provenance.** Quote it downstream as `<claim> (source: <agent>, <time>)` per CSM-002; an unattributed escalation is indistinguishable from the coordinator's own measurement.
|
|
622
|
+
- **No permission laundering.** Never execute an action this coordinator session has blocked or left unapproved just because an agent asked for it (CSM-003) — route it to the operator instead.
|
|
623
|
+
- **Never gate on a reply.** Do not hold a wave, a gate, or a commit waiting for an answer, and read silence as neither consent nor refusal (CSM-004).
|
|
624
|
+
- **Record it.** Note the escalation in the wave progress update — agent, one-line claim, verification outcome, action taken — so an escalation that changed the plan stays auditable.
|
|
625
|
+
|
|
626
|
+
When the channel is unavailable (CSM-005: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` / `DISABLE_TELEMETRY`, native Windows, non-Anthropic providers), nothing arrives and this step is a silent no-op — the wave behaves exactly as it does today.
|
|
627
|
+
|
|
547
628
|
After ALL agents in the wave complete:
|
|
548
629
|
|
|
549
630
|
1. **Read each agent's result** carefully
|
|
@@ -570,15 +651,27 @@ After ALL agents in the wave complete:
|
|
|
570
651
|
|
|
571
652
|
2. **Check for conflicts**: did two agents modify the same file? → manual merge needed
|
|
572
653
|
3. **Check for failures**: did any agent report errors or blockers?
|
|
573
|
-
3a. **Apply stagnation patterns** (per agent): review each agent's tool-call sequence against the three patterns in `circuit-breaker.md` § Stagnation Patterns — Pagination Spiral, Turn-Key Repetition, Error Echo. Mark each agent STAGNANT/SPIRAL/FAILED accordingly; recovery feeds into step 3 (Adapt Plan). Two different agents reading the same file is coordination, not stagnation.
|
|
654
|
+
3a. **Apply stagnation patterns** (per agent): review each agent's tool-call sequence against the three patterns in `circuit-breaker.md` § Stagnation Patterns — Pagination Spiral, Turn-Key Repetition, Error Echo. Mark each agent STAGNANT/SPIRAL/FAILED accordingly; recovery feeds into step 3 (Adapt Plan). Two different agents reading the same file is coordination, not stagnation. The other two patterns in the enum — `psa007-git-write` and `status-partial` — are NOT yours to judge here: they are executable regexes owned by the tailer (step 2.0-bis) and reach you as records with `source: "tail"`.
|
|
655
|
+
|
|
656
|
+
**Stagnation event-write** (gated on `persistence: true`): when any stagnation pattern fires for an agent during this step, emit ONE `stagnation_detected` record through the **canonical emitter** — never a hand-rolled `>>` append. Hand-written appenders drift from `emitEvent()` (that drift is what produced the `stop` vs `orchestrator.session.stopped` divergence, #609/#611), and since #1114 this event has a SECOND producer (the transcript tailer, step 2.0-bis above), whose records must be field-for-field comparable with the coordinator's. One write path for both:
|
|
657
|
+
|
|
658
|
+
node "$PLUGIN_ROOT/scripts/emit-event.mjs" --type stagnation_detected --payload '<the payload object below>'
|
|
574
659
|
|
|
575
|
-
|
|
660
|
+
From a Node context, call `emitEvent('stagnation_detected', { ...payload, ...sessionAttribution(repoRoot) }, { repoRoot })` from `scripts/lib/events.mjs` instead — pass `repoRoot` **explicitly** so the record lands in THIS working copy's ledger and its attribution is read from the same root the line is pinned to (#941/#1147).
|
|
576
661
|
|
|
577
662
|
```json
|
|
578
|
-
{"
|
|
663
|
+
{"session":"<semantic session id>","wave":N,"agent":"<subagent_type>","pattern":"pagination-spiral|turn-key-repetition|error-echo","source":"coordinator","error_class":"<taxonomy value — omit field entirely unless pattern is error-echo>","file":"<relative path from project root, or null if not applicable>","occurrences":N}
|
|
579
664
|
```
|
|
580
665
|
|
|
581
|
-
|
|
666
|
+
The template lists only the THREE patterns you may write. The other two enum values — `psa007-git-write` and `status-partial` — are tail-only: they are emitted by `scripts/lib/wave-transcript-tail.mjs` with `source: "tail"`, never by the coordinator (see step 3a above).
|
|
667
|
+
|
|
668
|
+
`timestamp` and `event` are written by `emitEvent()` itself — do NOT hand-compose either (a hand-typed ISO string is the #540 corruption class).
|
|
669
|
+
|
|
670
|
+
**Field-name reconciliation (three session keys, none redundant):** `session` is the **SEMANTIC** session id — the one that matches `sessions.jsonl.session_id` (measured 2026-08-25: `"main-2026-08-24-session-1"`), which is the join key every consumer reads (`skills/session-end/metrics-collection.md` filters `.session == $sid` with `$sid = $SESSION_ID`, the semantic id). Writing a raw UUID here would produce a record that joins to nothing. `sessionAttribution(repoRoot)` additionally contributes the `session_id` / `semantic_session_id` pair, which is OMITTED rather than fabricated when no `session.lock` is readable — so `session` is the field a consumer may rely on, and the pair is additive provenance. Keep all three; do not collapse them into one.
|
|
671
|
+
|
|
672
|
+
**`source` (additive, #1114)** names WHO detected the pattern: `"coordinator"` for a record written here from post-wave review, `"tail"` for one written by `scripts/lib/wave-transcript-tail.mjs`. A consumer that does not know the field behaves exactly as before.
|
|
673
|
+
|
|
674
|
+
Assign `error_class` using the taxonomy defined in `circuit-breaker.md` § "3. Error Echo" → Error-Class Taxonomy. That assignment applies to **error-echo only** and is unchanged. Omit the field entirely for every other pattern — `pagination-spiral`, `turn-key-repetition`, and the two tail-detected patterns `psa007-git-write` / `status-partial` carry NO `error_class`, and an absent field means "no class applies", never `"other"`. Paths are relative to the project root. `occurrences` is the count of pattern repetitions detected — minimum 3 for the three threshold-based patterns; `psa007-git-write` and `status-partial` fire on the FIRST occurrence, so `occurrences: 1` is valid for those two.
|
|
582
675
|
|
|
583
676
|
3b. **Worktree base-ref freshness check (#195)**: For each agent dispatched with `isolation: "worktree"` in this wave, verify that the coordinator has not advanced `main` past the worktree's base commit before the merge-back copies files. Call `checkWorktreeBaseRefFresh({ suffix, targetBranch: 'main', agentScope, cwd })` from `scripts/lib/worktree-freshness.mjs`:
|
|
584
677
|
|
|
@@ -609,7 +702,7 @@ Log every non-`pass` result as an event to `.orchestrator/metrics/events.jsonl`
|
|
|
609
702
|
Build the on-disk change set as the UNION of the two commands' outputs (untracked files appear as `??` lines in `git status --porcelain` — strip the two-column status prefix). **Every path in an agent's declared `files_changed` MUST appear in that union.** A declared file that is absent from both is an **edit-persistence violation**:
|
|
610
703
|
|
|
611
704
|
- Treat that agent's result as **NOT verified** — do not count its claimed work as done, and do not feed its (phantom) changes into the next wave.
|
|
612
|
-
- **Recover** by either (a) re-dispatching that agent's task package in a fresh batch (per `####
|
|
705
|
+
- **Recover** by either (a) re-dispatching that agent's task package in a fresh batch (per `#### Started-Set Verification`), or (b) applying the missing edit coordinator-direct when the fix is small and unambiguous.
|
|
613
706
|
- **Log the deviation** to `## Deviations` in `<state-dir>/STATE.md` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` from `scripts/lib/state-md.mjs`:
|
|
614
707
|
```
|
|
615
708
|
- [<ISO 8601 UTC>] Wave N edit-persistence violation: agent "<description>" reported <done|partial> but declared file(s) <paths> are absent from the on-disk change set. Result treated as unverified — <re-dispatched | coordinator-direct fix>.
|
|
@@ -769,7 +862,7 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
769
862
|
|
|
770
863
|
**Mission-status transition:** after a successful auto-commit, transition the mission status for all tasks in this wave from `in-dev` → `testing` using `setMissionStatus(stateContent, taskId, 'testing')` from `scripts/lib/state-md.mjs`. This matches the coordinator-level rule in `SKILL.md § Mission-Status Updates`: "in-dev → testing: Quality wave begins and this item's implementation wave completed without failure." The auto-commit checkpoint fires at the same logical moment — after implementation completes and Quality-Lite passes.
|
|
771
864
|
|
|
772
|
-
**Implementation deferred:** This subsection documents the contract. The procedural body (git add/commit sequence + error handling) will land in a future release as `scripts/lib/auto-commit.mjs` (tracked in GitLab #214; not yet implemented as of v3.10.0). Until then, this section is a no-op stub when `auto-commit-per-wave: true` is set; the coordinator MUST warn the user at session-start that auto-commits are not yet active (emit: "auto-commit-per-wave is set but the implementation (scripts/lib/auto-commit.mjs) is not yet available — commits will occur at session-end via /close as normal").
|
|
865
|
+
**Implementation deferred:** This subsection documents the contract. The procedural body (git add/commit sequence + error handling) will land in a future release as `scripts/lib/auto-commit.mjs` (tracked in GitLab #214; not yet implemented as of v3.10.0). Until then, this section is a no-op stub when `auto-commit-per-wave: true` is set; the coordinator MUST warn the user at session-start that auto-commits are not yet active (emit: "auto-commit-per-wave is set but the implementation (scripts/lib/auto-commit.mjs) is not yet available — commits will occur at session-end via /close as normal"). <!-- path-check: planned #214 -->
|
|
773
866
|
|
|
774
867
|
---
|
|
775
868
|
|
|
@@ -783,9 +876,10 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
783
876
|
description: "Persona review — <reviewer-name> — Wave N",
|
|
784
877
|
prompt: "<include: wave scope, changed files list, relevant plan section>",
|
|
785
878
|
subagent_type: "session-orchestrator:<reviewer-name>",
|
|
786
|
-
run_in_background: false
|
|
879
|
+
run_in_background: false // deliberately blocking — see below
|
|
787
880
|
})
|
|
788
881
|
```
|
|
882
|
+
- **`run_in_background: false` here is deliberate, not an oversight.** Reviewers are dispatched AFTER the quality gate and are NOT in the session plan's agent list, so § Started-Set Verification has no expected-set to check them against and the launch ack — the only thing a background dispatch returns — is explicitly not countable. There is also no consumer for early results: the next step needs ALL reviewer verdicts before it can act (`skills/persona-panel/SKILL.md` § "background dispatch would add turn-juggling with no consumer for early results"; `skills/session-start/SKILL.md` names persona-panel among the keep-false skills).
|
|
789
883
|
- Each reviewer writes its findings to `.orchestrator/audits/wave-reviewer-<wave>-<reviewer-name>.md`. The coordinator does NOT need to create this file — the reviewer agent writes it directly.
|
|
790
884
|
- **Findings are ADVISORY**: reviewer output never blocks the subsequent wave. After all dispatched reviewers complete:
|
|
791
885
|
- If any reviewer reports **WARN**: surface the findings to the user in the wave progress summary. Feed actionable items into the next wave's agent assignments (step 3 — Adapt Plan). If a WARN/FAIL finding is surfaced but NOT converted into a fix task for the next wave, append ONE line to `## Deviations` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` (#730/H5): `- [<ISO 8601 UTC>] Wave N reviewer finding overridden (not actioned): <one-line finding>.` — session-end Phase 2.6 (Broken-Window Budget) walks these entries at close.
|
|
@@ -802,9 +896,10 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
802
896
|
description: "Review wave N output",
|
|
803
897
|
prompt: "<include: session plan, wave results, changed files list, acceptance criteria>",
|
|
804
898
|
subagent_type: "session-orchestrator:session-reviewer",
|
|
805
|
-
run_in_background: false
|
|
899
|
+
run_in_background: false // deliberately blocking — same reason as step 5a
|
|
806
900
|
})
|
|
807
901
|
```
|
|
902
|
+
- **`run_in_background: false` here is deliberate**, for the same reason as step 5a: the session-reviewer runs after the quality gate, is not in the session plan's agent list (so Started-Set Verification has no signal for it), and its verdict is needed whole before step 3 (Adapt Plan) can consume it.
|
|
808
903
|
- The session-reviewer checks changed files against the plan and reports PASS/WARN/FAIL per category (implementation, tests, TypeScript, security, silent failures, test depth, type design, issues).
|
|
809
904
|
- If the session-reviewer reports **WARN or FAIL** findings: add fix tasks to the next wave's agent assignments (feed into step 3 — Adapt Plan). If a WARN/FAIL finding is surfaced but NOT converted into a fix task for the next wave, append ONE line to `## Deviations` via `appendDeviationOnDisk(repoRoot, isoTimestamp, message)` (#730/H5): `- [<ISO 8601 UTC>] Wave N reviewer finding overridden (not actioned): <one-line finding>.` — session-end Phase 2.6 (Broken-Window Budget) walks these entries at close.
|
|
810
905
|
- After the **Quality** wave: dispatch the session-reviewer with **full session scope** (all files changed since session start, not just the current wave). Use `git diff --name-only $SESSION_START_REF..HEAD` to provide the complete changed files list.
|
|
@@ -835,8 +930,9 @@ If the commit itself fails (e.g., nothing to commit, pre-commit hook rejects), d
|
|
|
835
930
|
7. **Capture wave metrics**: If `persistence` is enabled in Session Config, record for this wave after all agents complete and quality checks run. If `persistence` is `false`, skip metrics capture entirely — do not accumulate in-memory metrics. Record:
|
|
836
931
|
- `wave_number`, `role`, `started_at` (when agents were dispatched), `completed_at` (when all finished)
|
|
837
932
|
- `agent_count`: number of agents dispatched
|
|
838
|
-
- `agent_count_planned`: agents named in the session plan for this wave (
|
|
839
|
-
- `agent_count_started`: distinct agents
|
|
933
|
+
- `agent_count_planned`: agents named in the session plan for this wave (Started-Set Verification, #724)
|
|
934
|
+
- `agent_count_started`: distinct agents whose `agent-<id>.meta.json` sidecar is present, after any silent-drop re-dispatch (Started-Set Verification, #724/#1115). NOT "produced a tool-result" — under background dispatch the launch ack is a result and would count an agent that never ran. A gap `agent_count_planned > agent_count_started` after re-dispatch signals a persistent silent drop.
|
|
935
|
+
- `agent_count_completed`: distinct agents whose task-notification (`<status>completed</status>`) arrived (#1115). A gap `agent_count_started > agent_count_completed` at wave end is an agent that started and never returned — the started-but-never-returned state, not a silent drop.
|
|
840
936
|
- Per-agent results: `{description, status: done|partial|failed, files_changed_count}`
|
|
841
937
|
- `files_changed`: total unique files changed this wave (from `git diff --stat --name-only`)
|
|
842
938
|
- `planned_files_count`: size of this wave's Planned set (union of agent file scopes) as computed in step 3c File-level grounding above. Reuse that value — do not recompute.
|
|
@@ -910,7 +1006,7 @@ After reviewing wave results, adjust the next wave's agent count based on perfor
|
|
|
910
1006
|
|
|
911
1007
|
After each wave completes and before the progress update, update `<state-dir>/STATE.md`:
|
|
912
1008
|
|
|
913
|
-
1. **Frontmatter**: set `current-wave` to the just-completed wave number; set `status` to `active` (or `paused` if waiting on user input)
|
|
1009
|
+
1. **Frontmatter**: set `current-wave` to the just-completed wave number; set `status` to `active` (or `paused` if waiting on user input). Readers that need the RUNNING wave (e.g. `scripts/memory-propose.mjs`) read `<state-dir>/wave-scope.json` `wave` (only when the manifest is bound to this session via `semantic_session`; an unbound manifest is ignored — it may be a peer's, #1123) and fall back to `current-wave + 1` (#1166) — do not change this field's meaning.
|
|
914
1010
|
2. **`## Current Wave`**: replace contents with next wave info — wave number, role, agents to dispatch and count
|
|
915
1011
|
3. **`## Wave History`**: append an entry for the completed wave (the `(planned … → actual …, over-delivery …)` parenthetical is omitted when `grounding-check: false`, since the counts are unavailable):
|
|
916
1012
|
> **Record the SUITE COUNT, not just "gates green" — and name the platform (#944).** The wave line MUST carry the full-suite pass/fail count from the gate that just ran (`<passed>/<failed>`), not merely that typecheck and lint were clean. A deep session on 2026-07-30 logged typecheck/lint/validate-plugin for every wave and no suite count; a test that had been vacuous for its entire life sat red on HEAD through three waves and was found only by the review panel — in a session whose own premise was turning CI from red to green.
|
|
@@ -1102,17 +1198,38 @@ Before each wave dispatch:
|
|
|
1102
1198
|
# Warning: policy file .orchestrator/policy/blocked-commands.json not found — using legacy hardcoded blocklist
|
|
1103
1199
|
```
|
|
1104
1200
|
|
|
1201
|
+
**Deriving the session binding (#1123):** `wave-scope.json` lives in the WORKING COPY, and `hooks/enforce-scope.mjs` applies whatever it finds there to every session running in that checkout. Without a binding, a Discovery wave's `allowedPaths: []` denied every write of an unrelated parallel session. Name the writer — both fields come from ONE `sessionAttribution()` call, which reads `.orchestrator/session.lock` once:
|
|
1202
|
+
```bash
|
|
1203
|
+
SESSION_BINDING=$(node --input-type=module -e "
|
|
1204
|
+
import { sessionAttribution } from '$PLUGIN_ROOT/scripts/lib/events.mjs';
|
|
1205
|
+
const a = sessionAttribution(process.cwd());
|
|
1206
|
+
const out = {};
|
|
1207
|
+
if (a.session_id) out.session = a.session_id;
|
|
1208
|
+
if (a.semantic_session_id) out.semantic_session = a.semantic_session_id;
|
|
1209
|
+
console.log(JSON.stringify(out));
|
|
1210
|
+
")
|
|
1211
|
+
```
|
|
1212
|
+
**Verify the binding names YOU before you write it (#1123 follow-up).** `sessionAttribution()` reads the repo-global `.orchestrator/session.lock`, which in a shared working copy can hold a PEER's id — a session that lost the acquire race (`bootstrapLock()` reason `active`) leaves the lock naming the session that won it. Compare both ids against your own session (STATE.md `session`): **if they do not match, OMIT the `session`/`semantic_session` keys entirely** and write an unbound manifest. Unbound = ENFORCE, which is the fail-closed direction; writing a foreign id instead publishes a manifest that classifies as somebody else's for every reader. The reader half is defensive against exactly this (Gate 3b resolves identity as the UNION of payload, env and lock, so a peer-owned-lock manifest still reads `own` to its writer), but the reader cannot repair a binding the writer knowingly got wrong — and a manifest naming a peer is unreadable as an audit record either way.
|
|
1213
|
+
|
|
1214
|
+
Merge `$SESSION_BINDING`'s keys into the manifest. **If a value is empty, OMIT the key — never write `"session": ""`.** An empty id is present-but-equal-to-nobody: the legacy warning stays silent while every reader compares it against its own id, finds no match, and treats the manifest as FOREIGN — the one disposition that skips enforcement entirely. `sessionAttribution()` already omits rather than fills (CI runs hold no lock), and `validate-wave-scope.mjs` rejects the empty string outright, so the honest path is also the only one that validates.
|
|
1215
|
+
|
|
1105
1216
|
```json
|
|
1106
1217
|
{
|
|
1107
1218
|
"wave": N,
|
|
1108
1219
|
"role": "<role>",
|
|
1109
1220
|
"enforcement": "<from Session Config, default: warn>",
|
|
1221
|
+
"session": "<raw session_id from sessionAttribution(); OMIT the key if unavailable>",
|
|
1222
|
+
"semantic_session": "<semantic_session_id from the same call; OMIT if unavailable>",
|
|
1110
1223
|
"allowedPaths": ["<from agent specs in session plan>"],
|
|
1111
1224
|
"blockedCommands": "<derived dynamically from the effective floor∪overlay policy via loadEffectivePolicy (severity: block rules, #972); falls back to legacy 5-element array if no policy resolves>",
|
|
1112
1225
|
"gates": "<copy of enforcement-gates from Session Config, or omit if unset>"
|
|
1113
1226
|
}
|
|
1114
1227
|
```
|
|
1115
1228
|
The `gates` field (optional) mirrors `enforcement-gates` from Session Config (#77). When present, hooks check each gate individually via `gate_enabled()`. Missing gate entries default to enabled, preserving default behavior.
|
|
1229
|
+
|
|
1230
|
+
The `wave` field also doubles as the RUNNING-wave signal for readers outside this step's STATE.md `current-wave` (which records the just-completed wave, per step 1 of `3a. Post-Wave: Update STATE.md` above): `scripts/memory-propose.mjs` reads it directly for proposal attribution only when the manifest is bound to the calling session (`semantic_session` matches STATE.md `session`); an unbound or foreign manifest is ignored and the reader falls back to `current-wave + 1` (#1166, #1123).
|
|
1231
|
+
|
|
1232
|
+
**What the binding means to a reader.** Three states, and the disposition differs for each. **Absent** = legacy = ENFORCE: a manifest written before #1123 (or by a stale skill body) binds nobody, so it must keep constraining everyone exactly as it did before — this is the only state that preserves the pre-#1123 contract, and `validate-wave-scope.mjs` marks it with one advisory stderr line rather than an error, because § 3.3's pre-union skeleton is itself an unbound manifest. **Own session** = ENFORCE, unchanged. **Foreign session** — `session` present and not this session's id — = ALLOW: `hooks/enforce-scope.mjs` lets the write through and emits `orchestrator.scope.foreign_session_ignored` so the skip is counted rather than silent. A foreign manifest is somebody else's wave plan; it never had authority over this session's writes, and the event is what keeps that visible instead of leaving an allow nothing recorded. (The reader half lives in `hooks/enforce-scope.mjs` — the writer's only obligation is to name itself honestly here.)
|
|
1116
1233
|
2. Validate by piping through `node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs"` (where `$PLUGIN_ROOT` is `$CLAUDE_PLUGIN_ROOT`, `$CODEX_PLUGIN_ROOT`, or `$CURSOR_RULES_DIR` per platform — see `skills/_shared/config-reading.md`). If validation fails (exit 1), fix the JSON based on stderr errors and retry.
|
|
1117
1234
|
3. **`allowedPaths` is COMPUTED from one canonical declaration array — never hand-transcribed (#1020/#1083).** Transcribing either declaration shape or the union by hand produced scope divergences. Globs stay verbatim (`scripts/*.sh`) — the enforcement hook resolves them at check time.
|
|
1118
1235
|
|
|
@@ -1140,6 +1257,10 @@ Before each wave dispatch:
|
|
|
1140
1257
|
|
|
1141
1258
|
> **`<state-dir>/filescopes/` is control state, like `wave-scope.json` itself — never a wave territory.** Step 3.1 necessarily runs before the union exists, so writing these files reports `bash-write-verify: N file(s) changed by a Bash call OUTSIDE the wave's allowedPaths` naming `filescopes/wave-<N>/*.json`. Expected once per wave rollover at this step; it is information, not a scope violation. Never widen `allowedPaths` to silence it — that would grant agents write access to the deconfliction record itself.
|
|
1142
1259
|
|
|
1260
|
+
**3.1a — a peer session's declared paths are their OWN record (#1195).** When a reachable peer session runs in this same checkout and has sent a Peer-Scope-Union request (`skills/_shared/parallel-aware-auq.md` § Peer-Scope-Union), carry its complete path list into the record array as exactly ONE record `{"id":"peer-session-<id>","files":[…]}` — `<id>` being the peer's semantic session id. Never merge peer paths into an agent's record: an agent record is a territory ONE agent may write, and a peer's paths are a territory NO agent of this wave may write. Keeping them separate is also what makes the disjointness check meaningful — a peer path colliding with an agent's scope is a real collision that must surface at 3.2, not be laundered by living in that agent's own record. Mechanically, a peer record takes part in `--assert-disjoint` and is EXCLUDED from `--union` (`unionFileScopes`, `scripts/lib/scope-gate.mjs`), so `allowedPaths` never grants a peer's paths to this wave's agents — which is precisely what keeps them a territory no agent may write. It also gets NO per-agent shape-(a) file: `materialize-wave-scope.mjs` writes peer records into the aggregate only, because `$AGENT_FILESCOPE_JSON` is addressed by an agent id at dispatch and no agent is dispatched for a peer session. `hooks/post-bash-write-verify.mjs` reads the same aggregate sidecar and reports a change under a `peer-session-*` record as a peer write instead of a violation — reachable only BECAUSE the union excludes it (the hook sees a path only while it is outside `allowedPaths`).
|
|
1261
|
+
|
|
1262
|
+
The record's lifecycle is the coordinator's: every wave rollover re-materializes it (3.1 rebuilds the whole array, so an omitted peer record silently revokes the union mid-session), and session-end removes it with the rest of `filescopes/` — a peer record outliving its peer grants paths nothing is watching.
|
|
1263
|
+
|
|
1143
1264
|
**3.2 — assert disjointness BEFORE computing the union.** The materialized aggregate is an ARRAY of `{id, files}` records (never an object map: a duplicated agent id must stay visible), including `coordinator.json`. Run:
|
|
1144
1265
|
|
|
1145
1266
|
```bash
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!-- source: session-orchestrator plugin (canonical: templates/_shared/journey-manifest.md) -->
|
|
2
|
+
<!--
|
|
3
|
+
Copy to `.orchestrator/journey-manifest.md` in the target repo and fill in every
|
|
4
|
+
section. `/journey-audit` REFUSES to run without this file.
|
|
5
|
+
|
|
6
|
+
The `## SAFETY` block is what gates R5 (the real end-to-end run against
|
|
7
|
+
production with a real account). Absent or empty → R5 is not dispatched and the
|
|
8
|
+
audit runs as a dry-run (R1–R4 + R7). Nothing else in this file can substitute
|
|
9
|
+
for it: the skill reads THIS heading, not your intent.
|
|
10
|
+
|
|
11
|
+
Credentials: env-var NAMES only, never values. A value pasted here is a secret
|
|
12
|
+
in a tracked file.
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
# Journey Manifest — <REPO NAME>
|
|
16
|
+
|
|
17
|
+
> Per-repo input for `/journey-audit`. Product truth, not code structure.
|
|
18
|
+
> Last verified: <YYYY-MM-DD> by <operator>
|
|
19
|
+
|
|
20
|
+
## Personas & Einstiegspunkte
|
|
21
|
+
|
|
22
|
+
| Persona | Ziel | Einstiegspunkt (URL/Route) | Erfolg heißt |
|
|
23
|
+
|---------|------|---------------------------|--------------|
|
|
24
|
+
| <anonymer Besucher> | <was er will> | `<https://…/>` | <messbarer Endzustand> |
|
|
25
|
+
| <zahlender Kunde> | | | |
|
|
26
|
+
| <Owner/Admin> | | | |
|
|
27
|
+
|
|
28
|
+
List every entry point a real user can reach — including the ones marketing links
|
|
29
|
+
to but the app never surfaces. A feature with no entry point is exactly the defect
|
|
30
|
+
class this audit exists to find.
|
|
31
|
+
|
|
32
|
+
## Wahrheits-SSOTs
|
|
33
|
+
|
|
34
|
+
Where the truth about the product lives in code. R3 compares every claim against
|
|
35
|
+
THESE files, not against other prose.
|
|
36
|
+
|
|
37
|
+
| Gegenstand | SSOT-Datei/Konstante |
|
|
38
|
+
|-----------|----------------------|
|
|
39
|
+
| Preise / Pläne / Limits | `<src/config/plans.ts>` |
|
|
40
|
+
| Feature-Flags | `<…>` |
|
|
41
|
+
| Währung / Steuer / Region | `<…>` |
|
|
42
|
+
| Quota / Kontingente | `<…>` |
|
|
43
|
+
| i18n-Namespaces (Marketing, FAQ, Mail) | `<…>` |
|
|
44
|
+
| Chat-/Bot-Faktenquelle | `<…>` |
|
|
45
|
+
|
|
46
|
+
## Outbound-Touchpoints (Input für R1/R2)
|
|
47
|
+
|
|
48
|
+
- Template-Verzeichnis: `<…>`
|
|
49
|
+
- Send-Pfad / Provider-Adapter: `<…>`
|
|
50
|
+
- Cron-/Scheduler-Quellen: `<…>`
|
|
51
|
+
- Render-Idiom + Beispiel-Props für R2: `<z. B. react-email, props aus …>`
|
|
52
|
+
|
|
53
|
+
## Chat-Interview (Frage → Soll-Antwort)
|
|
54
|
+
|
|
55
|
+
R4 asks these verbatim and scores the answer against the expected one. The expected
|
|
56
|
+
answer must be derivable from a Wahrheits-SSOT above — otherwise it is a second
|
|
57
|
+
claim, not a truth key.
|
|
58
|
+
|
|
59
|
+
| # | Frage (wörtlich) | Soll-Antwort (Kern-Fakt) | SSOT |
|
|
60
|
+
|---|------------------|--------------------------|------|
|
|
61
|
+
| 1 | <"Was kostet …?"> | <"…"> | `<datei>` |
|
|
62
|
+
| 2 | | | |
|
|
63
|
+
| 3 | | | |
|
|
64
|
+
|
|
65
|
+
## SAFETY
|
|
66
|
+
|
|
67
|
+
**MANDATORY for R5. Without this block filled in, R5 is not dispatched.**
|
|
68
|
+
|
|
69
|
+
- **Erlaubte Konten:** `<test-account@…>` — and no other. Never a real customer account.
|
|
70
|
+
- **Erlaubte Events/Objekte:** `<z. B. nur Events mit Präfix AUDIT-…>`
|
|
71
|
+
- **No-Go-Aktionen:** <Löschen fremder Daten · Mailversand an echte Empfänger · Plan-Downgrade · Refunds · Webhook-Replays gegen Prod>
|
|
72
|
+
- **Checkout-Grenze:** `<max. EUR X, Testkarte …, Live-Zahlungen verboten>`
|
|
73
|
+
- **Cleanup-Regel:** <was nach dem Durchstich wieder entfernt wird, von wem, woran man erkennt dass es weg ist>
|
|
74
|
+
- **Abbruchbedingung:** <woran R5 sofort stoppt und meldet statt weiterzumachen>
|
|
75
|
+
|
|
76
|
+
## Credential-Quellen (env-Namen, NIE Werte)
|
|
77
|
+
|
|
78
|
+
| Zweck | Env-Variable | Bezugsquelle |
|
|
79
|
+
|-------|--------------|--------------|
|
|
80
|
+
| Prod-Login R5 | `<AUDIT_TEST_ACCOUNT_EMAIL>` | `<1Password-Item / .env.local>` |
|
|
81
|
+
| DB read-only R6 | `<AUDIT_DB_READONLY_URL>` | `<…>` |
|
|
82
|
+
| Mail-Provider-API R6 | `<…>` | `<…>` |
|
|
83
|
+
| Plattform-CLI R7 | `<…>` | `<…>` |
|
|
84
|
+
|
|
85
|
+
## Realdaten-Queries (R6, read-only)
|
|
86
|
+
|
|
87
|
+
Only `SELECT`. A query that writes does not belong in this file.
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
-- Funnel: Registrierung → Aktivierung → Zahlung
|
|
91
|
+
SELECT …;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Identitäts-Regeln: <wie ein Nutzer über Tabellen hinweg identifiziert wird>
|
|
95
|
+
- Nicht messbar (bewusst benennen): <was die Daten NICHT hergeben>
|
|
96
|
+
|
|
97
|
+
## Plattform-Erwartung (R7)
|
|
98
|
+
|
|
99
|
+
- CLI-Logins: `<vercel · supabase · cloudflare · …>`
|
|
100
|
+
- Erwarteter Plan/Tier je Dienst: `<…>`
|
|
101
|
+
- Offene Perf-/Kosten-Issues: `<#…>`
|
|
102
|
+
|
|
103
|
+
## Bekannte Ausnahmen
|
|
104
|
+
|
|
105
|
+
Findings that are known and deliberately accepted — with the reason and a revisit
|
|
106
|
+
trigger. R1–R7 report them as `known-exception`, never as new findings.
|
|
107
|
+
|
|
108
|
+
| Befund | Warum akzeptiert | Revisit-Trigger |
|
|
109
|
+
|--------|------------------|-----------------|
|
|
110
|
+
| <…> | <…> | <…> |
|