session-orchestrator 4.1.0 → 4.2.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/skills/session-plan/SKILL.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
- package/.cursor/skills/session-plan/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +8 -8
- package/agents/ux-evaluator.md +1 -1
- package/commands/close.md +3 -3
- package/commands/go.md +2 -0
- package/commands/memory-cleanup.md +4 -3
- package/commands/persona-panel.md +1 -1
- package/commands/session.md +3 -2
- package/docs/README.md +4 -4
- package/docs/USER-GUIDE.md +115 -48
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +55 -1
- package/docs/ci-setup.md +1 -1
- package/docs/codex-setup.md +1 -0
- package/docs/components.md +2 -2
- package/docs/cursor-setup.md +1 -0
- package/docs/events-schema.md +4 -1
- package/docs/instruction-delivery.md +1 -1
- package/docs/memory-proposal-flow.md +3 -3
- package/docs/migration-v4.md +2 -2
- package/docs/owner-config-schema.md +74 -90
- package/docs/persona-panel.md +4 -4
- package/docs/pi-setup.md +1 -0
- package/docs/rule-authoring.md +13 -6
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +55 -22
- package/docs/session-config-template.md +9 -5
- package/docs/vault-docs-architecture.md +4 -2
- package/hooks/_lib/hook-import-set.json +28 -3
- package/hooks/_lib/vcs-create-matcher.mjs +214 -16
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/pre-bash-issue-budget.mjs +123 -26
- package/hooks/subagent-telemetry.mjs +106 -20
- package/package.json +4 -4
- package/scripts/baseline-archetypes.mjs +28 -0
- package/scripts/lib/auto-dialectic.mjs +0 -68
- package/scripts/lib/baseline-archetypes.mjs +439 -0
- package/scripts/lib/build-live-signals.mjs +5 -6
- package/scripts/lib/config/issue-budget.mjs +68 -8
- package/scripts/lib/config/private-config-dir.mjs +3 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config-schema.mjs +79 -0
- package/scripts/lib/file-lock.mjs +47 -5
- package/scripts/lib/issue-budget-reconcile.mjs +392 -0
- package/scripts/lib/issue-budget.mjs +76 -3
- package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
- package/scripts/lib/maintenance-due-banner.mjs +440 -0
- package/scripts/lib/owner-config.example.yaml +29 -46
- package/scripts/lib/owner-yaml.mjs +14 -13
- package/scripts/lib/quality-gate.mjs +13 -6
- package/scripts/lib/resource-probe/evaluate.mjs +19 -21
- package/scripts/lib/rules-sync.mjs +34 -4
- package/scripts/lib/session-close-backfill.mjs +182 -40
- package/scripts/lib/session-end/phase-skip.mjs +85 -86
- package/scripts/lib/session-end/tail-runner.mjs +178 -0
- package/scripts/lib/session-schema/constants.mjs +6 -0
- package/scripts/lib/session-schema/validator.mjs +20 -0
- package/scripts/lib/session-shape.mjs +558 -0
- package/scripts/lib/session-start-probes.mjs +10 -3
- package/scripts/lib/session-token-rollup.mjs +95 -10
- package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
- package/scripts/lib/state-md.mjs +1 -0
- package/scripts/lib/subagents-schema.mjs +77 -9
- package/scripts/lib/telemetry/pricing.mjs +197 -0
- package/scripts/lib/telemetry/sync.mjs +50 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
- package/scripts/lib/validate/check-unwired-features.mjs +8 -7
- package/scripts/lib/vault-mirror/process.mjs +2 -1
- package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
- package/scripts/lib/wave-resource-gate.mjs +23 -27
- package/scripts/lib/wave-sizing.mjs +10 -3
- package/scripts/materialize-wave-scope.mjs +68 -14
- package/scripts/print-applicable-rules.mjs +7 -6
- package/scripts/print-learnings-index.mjs +3 -2
- package/scripts/session-shape.mjs +266 -0
- package/skills/_shared/config-reading.md +15 -9
- package/skills/_shared/private-capability-context.md +89 -0
- package/skills/bootstrap/SKILL.md +61 -13
- package/skills/bootstrap/_shared-template.md +99 -14
- package/skills/bootstrap/deep-template.md +36 -26
- package/skills/bootstrap/fast-template.md +44 -8
- package/skills/bootstrap/intensity-heuristic.md +10 -4
- package/skills/bootstrap/private-contract.md +119 -0
- package/skills/bootstrap/public-fallback.md +30 -18
- package/skills/bootstrap/standard-template.md +39 -24
- package/skills/discovery/probes-ui.md +1 -1
- package/skills/docs-orchestrator/audience-mapping.md +1 -1
- package/skills/evolve/SKILL.md +2 -2
- package/skills/gitlab-ops/SKILL.md +3 -3
- package/skills/grill/SKILL.md +1 -1
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/plan/mode-new.md +9 -0
- package/skills/reconcile/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +3 -2
- package/skills/session-end/phase-3-2-docs-verification.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +23 -65
- package/skills/session-end/phase-3-7a-recommendations.md +2 -2
- package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
- package/skills/session-end/references/phase-5-issue-cleanup.md +26 -0
- package/skills/session-end/session-metrics-write.md +31 -12
- package/skills/session-plan/SKILL.md +56 -48
- package/skills/session-plan/wave-template.md +8 -15
- package/skills/session-start/SKILL.md +18 -2
- package/skills/session-start/phase-2-5-docs-planning.md +1 -1
- package/skills/session-start/phase-8-5-express-path.md +12 -9
- package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +21 -5
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
- package/skills/test-runner/rubric-v1.md +2 -2
- package/skills/wave-executor/SKILL.md +42 -12
- package/skills/wave-executor/circuit-breaker.md +3 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +4 -2
- package/skills/wave-executor/references/wave-loop-review.md +1 -1
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
- package/templates/nextjs-minimal/package.json +1 -1
- package/templates/node-minimal/package.json +1 -1
- package/scripts/lib/multi-provider-build/providers.mjs +0 -64
- package/scripts/lib/multi-provider-build/templating.mjs +0 -130
- package/scripts/lib/owner-config/coerce.mjs +0 -29
- package/scripts/lib/owner-config/constants.mjs +0 -21
- package/scripts/lib/owner-config/defaults.mjs +0 -50
- package/scripts/lib/owner-config/error.mjs +0 -19
- package/scripts/lib/owner-config/index.mjs +0 -13
- package/scripts/lib/owner-config/merge.mjs +0 -52
- package/scripts/lib/owner-config/validate.mjs +0 -259
- package/scripts/lib/owner-config-loader.mjs +0 -170
- package/scripts/lib/owner-config.mjs +0 -28
- package/scripts/lib/soul-resolve.mjs +0 -130
- package/scripts/lib/vault-mirror/render.mjs +0 -8
|
@@ -55,7 +55,7 @@ Review `<state-dir>/rules/` files that are relevant to this session's work:
|
|
|
55
55
|
|
|
56
56
|
> **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
|
|
57
57
|
|
|
58
|
-
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.
|
|
58
|
+
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
59
59
|
|
|
60
60
|
> Gate: Only run if `persistence` is enabled in Session Config and `<state-dir>/STATE.md` exists.
|
|
61
61
|
1. Set frontmatter `status: completed`
|
|
@@ -117,7 +117,9 @@ try { await flush(); } catch { /* nie blockierend — der Close darf durch Telem
|
|
|
117
117
|
|
|
118
118
|
**Semantics.** `flush()` is fire-and-forget with an internal ~3s timeout. When the ingest endpoint is unreachable (offline), events stay in the bounded host-local queue (oldest-dropped on overflow) and are retried on a later close — nothing is lost or blocked. A one-line result MAY be surfaced in the Phase 6 close summary (`Telemetry: sent` / `queued` / `gated`), but a failure NEVER renders an error banner: under no circumstances may telemetry make `/close` fail or take materially longer than ~3s. The gate lives in the module (fail-closed via `resolveConsent`), so this phase carries no config-key check of its own beyond the `persistence: false` skip above.
|
|
119
119
|
|
|
120
|
-
Cross-reference: GitLab #844 (Epic #841); `docs/
|
|
120
|
+
Cross-reference: GitLab #844 (Epic #841); `docs/telemetry.md`; flush API in `scripts/lib/telemetry/sync.mjs` (`flush` — fire-and-forget, gated, never-throw).
|
|
121
|
+
|
|
122
|
+
Historical design: archived PRD `docs/prd/2026-07-20-anonymous-usage-telemetry.md` FA3. <!-- path-check: historical -->
|
|
121
123
|
|
|
122
124
|
### 3.5 Session Memory
|
|
123
125
|
|
|
@@ -138,7 +140,7 @@ Read `skills/session-end/learning-patterns.md` for extraction heuristics, confid
|
|
|
138
140
|
|
|
139
141
|
### Phase 3.6.x Tail — Mechanical Skip-Plan (#724)
|
|
140
142
|
|
|
141
|
-
> The Phase 3.6.x tail (3.6.3 Memory-Proposals, 3.6.4 Expired-Sweep, 3.6.
|
|
143
|
+
> The Phase 3.6.x tail (3.6.3 Memory-Proposals, 3.6.4 Expired-Sweep, 3.6.6 Skill-Judge, 3.6.8 Reconcile — 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09, their nudges replaced by the session-start `maintenance-due` probe `checkMaintenanceDue` in `scripts/lib/maintenance-due-banner.mjs`, and their deciders removed from `planTailPhases()`) is the historical close-out abort-attractor: six phases that in the overwhelming majority of sessions do nothing (no proposals queued, nothing expired, under cadence, judge off, reconcile off). Each already ships a mechanical fast-path in its own lib. This dispatcher computes — side-effect-free — WHICH of the six actually need to run, so you load ONLY the detail procedure for the `run: true` phases and emit a one-line skip report for the rest.
|
|
142
144
|
|
|
143
145
|
Run the aggregator ONCE. Config gates short-circuit FIRST (no disk touch); the input-detection helpers run only when the config gate passed. It NEVER throws — a per-phase probe error fail-opens to `run: true` (run the phase rather than silently lose it):
|
|
144
146
|
|
|
@@ -156,10 +158,10 @@ const { plan, skippedReport } = await planTailPhases({
|
|
|
156
158
|
|
|
157
159
|
Then:
|
|
158
160
|
|
|
159
|
-
1. **For every entry with `run: true`** — load its detail procedure from [`phase-3-6-tail.md`](../phase-3-6-tail.md) (the phase headings there match the `phase` id) and execute it exactly as written. The aggregator only DECIDES; the sub-file holds the full unabridged procedure.
|
|
161
|
+
1. **For every entry with `run: true`** — load its detail procedure from [`phase-3-6-tail.md`](../phase-3-6-tail.md) (the phase headings there match the `phase` id) and execute it exactly as written. The aggregator only DECIDES; the sub-file holds the full unabridged procedure. **3.6.4 is the one MECHANICAL phase:** its procedure is a single call to `runTailPhases({ repoRoot, plan })` from `scripts/lib/session-end/tail-runner.mjs` (which delegates to `runExpiredSweep`), reporting `result['3.6.4']` — `ran`, `scanned`, `archived` — with the event `orchestrator.learnings.sweep_applied` as the proof it ran. The planner fails OPEN, the runner fails CLOSED.
|
|
160
162
|
2. **For every entry with `run: false`** — do nothing for that phase; its `reason` is already captured for the report.
|
|
161
|
-
3. **Execute `run: true` phases in ascending phase order** (3.6.3 → 3.6.4 → 3.6.
|
|
162
|
-
4. **Emit `skippedReport`** as a single line in the Phase 6 Final Report (under the Learnings/metrics block), e.g. `Tail-Diät: 3.6.3 skipped (proposals empty) · 3.6.
|
|
163
|
+
3. **Execute `run: true` phases in ascending phase order** (3.6.3 → 3.6.4 → 3.6.6 → 3.6.8), matching the Phase 3.4 Runtime Ordering Note. The returned `plan` is already in that order.
|
|
164
|
+
4. **Emit `skippedReport`** as a single line in the Phase 6 Final Report (under the Learnings/metrics block), e.g. `Tail-Diät: 3.6.3 skipped (proposals empty) · 3.6.4 RUN (12 archived of 640 scanned) · 3.6.8 skipped (reconcile disabled) · …`.
|
|
163
165
|
|
|
164
166
|
**Full detail procedures:** [`phase-3-6-tail.md`](../phase-3-6-tail.md).
|
|
165
167
|
|
|
@@ -80,6 +80,32 @@
|
|
|
80
80
|
- After the artefact exists, reset `overflow` to `[]` in the counter file and record the collector issue ID / note path in the Phase 6 Final Report under `### Zurückgestellt (issue-budget)`.
|
|
81
81
|
- **Never exempt-by-accident:** the cap never applied to `priority::critical`, the carryover class (`[Carryover]`, SPIRAL/FAILED, `type::carryover`), or `broken-window` closure issues, so nothing on the Phase 1.65 carry-list can ever appear in `overflow[]`. The promises at Phase 1.8 ("SPIRAL / FAILED agent carryover … non-deselectable") and the Critical Rule "ALWAYS create issues for unfinished PLANNED work" stay intact by construction.
|
|
82
82
|
- Fail-open: a missing or malformed counter file means "no overflow" — log a WARN and continue the close.
|
|
83
|
+
- **3b.2 — Reconcile the record against the ledger (#1163 follow-up):** the drain answers "what did the cap park?"; this answers the prior question "did the cap ever run?". Call `reconcileIssueBudget` from `scripts/lib/issue-budget-reconcile.mjs` on the **in-memory session record** — the one Phase 3.7 is about to append to `.orchestrator/metrics/sessions.jsonl`, not a record read back from it. `issues_created` has NO code producer anywhere in this repo: it is the coordinator's own hand-assembled count, which is exactly why cross-checking it against a mechanically-written ledger is meaningful — the two halves have independent producers.
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
import {
|
|
87
|
+
reconcileIssueBudget,
|
|
88
|
+
emitIssueBudgetReconciled,
|
|
89
|
+
formatIssueBudgetReconcileWarn,
|
|
90
|
+
} from '${PLUGIN_ROOT}/scripts/lib/issue-budget-reconcile.mjs';
|
|
91
|
+
|
|
92
|
+
const reconcile = reconcileIssueBudget({
|
|
93
|
+
repoRoot,
|
|
94
|
+
record: sessionRecord, // in-memory, pre-write (Phase 3.7 appends it later)
|
|
95
|
+
sessionId: accountingSessionId, // semantic key
|
|
96
|
+
rawSessionId, // raw lock/registry key — BOTH are summed, never preferred
|
|
97
|
+
config: config['issue-budget'],
|
|
98
|
+
});
|
|
99
|
+
await emitIssueBudgetReconciled(repoRoot, reconcile);
|
|
100
|
+
console.log(formatIssueBudgetReconcileWarn(reconcile));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`reconcile.verdict` is one of `match` (everything the record claims is accounted for), `no-ledger` (`recorded > 0` and no counter file existed under EITHER key — the hook never charged a single create, so the cap was silently OFF; measured once at 26 recorded creations with no counter file), `escaped` (a ledger exists but `recorded > charged + exempt`), or `stale-record` (the ledger has spend and the record claims none — there the RECORD is the suspect half). `emitIssueBudgetReconciled` writes `orchestrator.issue_budget.reconciled` to `.orchestrator/metrics/events.jsonl`; `formatIssueBudgetReconcileWarn(result)` renders one info line on `match` and a path-quoting warning otherwise — print it in the Phase 6 Final Report under `### Zurückgestellt (issue-budget)`. Never throws, never blocks the close.
|
|
104
|
+
|
|
105
|
+
**Two ordering constraints, both load-bearing:**
|
|
106
|
+
1. **After the drain.** The drain resets `overflow[]` to `[]` and files the collector issue (itself exempt) — reconciling before it would read an overflow count that is about to change and miss the collector's own exempt charge.
|
|
107
|
+
2. **Before `reapStaleBudgetFiles`.** The reap deletes counter files; THIS session's file is exempt by age, but a session whose accounting key flipped mid-session has spend under a second key that is NOT exempt. Reaping first can therefore remove the very file this check reads, turning a real `escaped` into a false `no-ledger`.
|
|
108
|
+
|
|
83
109
|
- **Then reap stale counter files (#1151):** the per-session split (#1141) writes one file per accounting session and nothing ever deleted them, so `.orchestrator/runtime/issue-budget/` grew without bound in every working copy. After the drain, sweep files older than 14 days; THIS session's file is exempt regardless of age, and the call is best-effort (it never throws, so it can never abort the close).
|
|
84
110
|
|
|
85
111
|
```js
|
|
@@ -29,12 +29,16 @@
|
|
|
29
29
|
|
|
30
30
|
> **#701.2 DOC NOTE — `completed_at >= started_at` guard:** This invariant is enforced mechanically by `scripts/emit-session.mjs`. The writer applies `clampTimestampsMonotonic()` (from `scripts/lib/session-schema/timestamps.mjs`) before `validateSession()`, clamping any inversion of `completed_at < started_at` to `started_at` and recording forensics in `_clamped: true` / `_original_completed_at`. Previously-inverted entries (e.g. `main-2026-06-21-session-4`) are already corrected. **No per-session coordinator action is needed** — the writer enforces the invariant at write time. Do not add defensive clamping logic here; the canonical guard lives in `emit-session.mjs`.
|
|
31
31
|
|
|
32
|
-
1a. **Token Rollup (#644)** — before emitting the JSONL record, aggregate token usage from `subagents.jsonl` and merge the
|
|
32
|
+
1a. **Token Rollup (#644, extended #1244)** — before emitting the JSONL record, aggregate token usage from `subagents.jsonl` and merge the rollup fields onto the in-memory `$METRICS_ENTRY` JSON object. The join key is the session's UUID (`session_id` / `parent_session_id` on subagents.jsonl — the UUID form, not the semantic slug).
|
|
33
33
|
|
|
34
|
-
**
|
|
34
|
+
**This prose step is the ONLY path by which the hook's token fix reaches `sessions.jsonl`** — `rollupSessionTokens()` has no other production caller, so skipping or partially copying this step leaves the entire #1244 cache-token fix inert at the session level while the per-agent ledger looks correct.
|
|
35
|
+
|
|
36
|
+
**Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions. The same holds for `total_cost_usd`: `null` means "at least one agent ran on a model the price table does not know", never "$0".
|
|
35
37
|
|
|
36
38
|
**Provenance (#949):** the rollup sums ONLY records carrying `subagent_transcript_found: true` — the flag the producer sets when it read the subagent's own transcript. Pre-#949 records carry the PARENT transcript's running totals and are excluded, so a session made up entirely of them now reports `null` rather than a fabricated sum (73 historical sessions, 96,148,781 phantom tokens, measured 2026-08-11). Two consequences for readers: totals already written into `sessions.jsonl` before 2026-08-11 were produced by the unfiltered recipe and are a series break, not a trend; and `matched_records` counts start records and phantom stops alike, so it is NOT the denominator for a coverage ratio — use `subagents_with_tokens` against the session's real agent count.
|
|
37
39
|
|
|
40
|
+
**Schema boundary (#1244, 2026-09-09):** from `schema_version: 2` a subagent record's `token_input` is BILLABLE PROMPT VOLUME (uncached + cache_read + cache_creation); v1 records held raw uncached input only and are therefore EXCLUDED from every total and reported as `legacy_v1_records`. Sessions spanning the boundary are a second series break — do not trend across it.
|
|
41
|
+
|
|
38
42
|
Example (coordinator pseudo-code — adapt to your shell/JS context):
|
|
39
43
|
|
|
40
44
|
```js
|
|
@@ -42,14 +46,24 @@
|
|
|
42
46
|
import { rollupSessionTokens } from '../../scripts/lib/session-token-rollup.mjs';
|
|
43
47
|
|
|
44
48
|
const rollup = rollupSessionTokens({ parentSessionId: SESSION_UUID });
|
|
45
|
-
// rollup: { total_token_input, total_token_output, subagents_with_tokens, matched_records
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
// rollup: { total_token_input, total_token_output, subagents_with_tokens, matched_records,
|
|
50
|
+
// total_token_input_uncached, total_token_cache_read, total_token_cache_creation,
|
|
51
|
+
// total_cost_usd, cost_records_priced, cost_records_total, legacy_v1_records,
|
|
52
|
+
// _token_schema }
|
|
53
|
+
// Merge into the record — every field below is optional in the session schema (additive).
|
|
54
|
+
metricsEntry.total_token_input = rollup.total_token_input; // number | null
|
|
55
|
+
metricsEntry.total_token_output = rollup.total_token_output; // number | null
|
|
56
|
+
metricsEntry.subagents_with_tokens = rollup.subagents_with_tokens; // number (0 when no coverage)
|
|
57
|
+
metricsEntry.total_token_input_uncached = rollup.total_token_input_uncached; // number | null
|
|
58
|
+
metricsEntry.total_token_cache_read = rollup.total_token_cache_read; // number | null
|
|
59
|
+
metricsEntry.total_token_cache_creation = rollup.total_token_cache_creation; // number | null
|
|
60
|
+
metricsEntry.total_cost_usd = rollup.total_cost_usd; // number | null (null = unknown model)
|
|
61
|
+
metricsEntry._token_schema = rollup._token_schema; // 2
|
|
50
62
|
```
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
Report `cost_records_priced / cost_records_total` and `legacy_v1_records` in the session summary when either is non-zero — an unpriced or excluded remainder is what makes a cost figure honest.
|
|
65
|
+
|
|
66
|
+
Or, from a bash context, call the rollup via a helper node invocation and `jq`-merge the fields into `$METRICS_ENTRY` before step 2:
|
|
53
67
|
|
|
54
68
|
```bash
|
|
55
69
|
ROLLUP_JSON=$(node -e "
|
|
@@ -63,13 +77,18 @@
|
|
|
63
77
|
METRICS_ENTRY=$(printf '%s' "$METRICS_ENTRY" | jq \
|
|
64
78
|
--argjson r "${ROLLUP_JSON:-{}}" \
|
|
65
79
|
'. + {
|
|
66
|
-
total_token_input:
|
|
67
|
-
total_token_output:
|
|
68
|
-
subagents_with_tokens:
|
|
80
|
+
total_token_input: ($r.total_token_input // null),
|
|
81
|
+
total_token_output: ($r.total_token_output // null),
|
|
82
|
+
subagents_with_tokens: ($r.subagents_with_tokens // 0),
|
|
83
|
+
total_token_input_uncached: ($r.total_token_input_uncached // null),
|
|
84
|
+
total_token_cache_read: ($r.total_token_cache_read // null),
|
|
85
|
+
total_token_cache_creation: ($r.total_token_cache_creation // null),
|
|
86
|
+
total_cost_usd: ($r.total_cost_usd // null),
|
|
87
|
+
_token_schema: ($r._token_schema // 2)
|
|
69
88
|
}')
|
|
70
89
|
```
|
|
71
90
|
|
|
72
|
-
**If the rollup call fails** (e.g., `subagents.jsonl` absent, parse error), set
|
|
91
|
+
**If the rollup call fails** (e.g., `subagents.jsonl` absent, parse error), set the numeric totals to `null` / `0` and continue — the rollup is non-blocking. A session without token data still writes cleanly.
|
|
73
92
|
|
|
74
93
|
2. Append the prepared JSONL entry (from Phase 1.7, now including token fields from step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
|
|
75
94
|
```bash
|
|
@@ -8,7 +8,7 @@ model-preference-codex: gpt-5.4
|
|
|
8
8
|
model-preference-cursor: claude-opus-4-6
|
|
9
9
|
description: >
|
|
10
10
|
Creates a structured wave execution plan with role-based assignment after user alignment.
|
|
11
|
-
Decomposes agreed tasks into
|
|
11
|
+
Decomposes agreed tasks into waves resolved from the session mode by `scripts/session-shape.mjs`, with optimal agent assignment,
|
|
12
12
|
dependency ordering, and inter-wave checkpoints. Activated by session-start after Q&A phase completes.
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -48,6 +48,18 @@ This skill receives the agreed session scope from session-start. The scope inclu
|
|
|
48
48
|
|
|
49
49
|
These are passed via the conversation context (not a file). Parse the preceding session-start output to extract the agreed scope.
|
|
50
50
|
|
|
51
|
+
## Optional private capability context
|
|
52
|
+
|
|
53
|
+
Before either the express path or task decomposition, apply
|
|
54
|
+
[Private capability context](../_shared/private-capability-context.md) when the
|
|
55
|
+
owner explicitly supplies or authorizes a local catalog lookup for a known
|
|
56
|
+
private/internal planning audience. Reuse the bounded findings already supplied
|
|
57
|
+
by `/plan new` when applicable; do not repeat the same lookup. This step does not
|
|
58
|
+
require persistence. With no authorized context, or a public/unknown audience,
|
|
59
|
+
skip it without a prompt or lookup and continue the existing flow. Eligible
|
|
60
|
+
source references inform reuse alternatives and verification tasks; a catalog
|
|
61
|
+
match does not expand the agreed implementation scope or disable the express path.
|
|
62
|
+
|
|
51
63
|
## Express Path Short-Circuit (#214)
|
|
52
64
|
|
|
53
65
|
> Check this **before Step 0**. If the express path is active, this skill emits a minimal 1-wave plan and exits — no role decomposition, no wave splitting, no agent count computation.
|
|
@@ -81,6 +93,8 @@ Emit this 1-wave plan and exit the skill immediately (do not continue to Step 1
|
|
|
81
93
|
Express path — no inter-wave checks. Use /go to begin.
|
|
82
94
|
```
|
|
83
95
|
|
|
96
|
+
> The express path's 1-wave plan is the same shape housekeeping resolves to — one wave with `coordinatorDirect: true` and no dispatched agents (`scripts/session-shape.mjs --session-type housekeeping`). The express path stays as written above; it does not need to call the shape resolver to know that.
|
|
97
|
+
|
|
84
98
|
**When express-path banner is absent or `express-path.enabled: false`:** Proceed to Step 0 and the full planning flow as normal.
|
|
85
99
|
|
|
86
100
|
## Step 0: Read Session Config
|
|
@@ -88,11 +102,11 @@ Express path — no inter-wave checks. Use /go to begin.
|
|
|
88
102
|
Read and parse Session Config per `skills/_shared/config-reading.md`. Store result as `$CONFIG`.
|
|
89
103
|
|
|
90
104
|
Extract these fields for planning:
|
|
91
|
-
- `waves`
|
|
92
|
-
- `agents-per-wave` (
|
|
93
|
-
- `isolation`
|
|
94
|
-
- `enforcement` (default: warn) — `strict` / `warn` / `off`
|
|
95
|
-
- `max-turns`
|
|
105
|
+
- `waves` — number of execution waves; resolved by `scripts/session-shape.mjs` (`totalWaves`), do not compute by hand. The shape reports in `wavesConfigHonored` whether the configured value was used at all, and says why in `notes`.
|
|
106
|
+
- `agents-per-wave` (may have session-type overrides per `config-reading.md`) — the operator's ceiling; the per-wave cap that actually binds is resolved by `scripts/session-shape.mjs` (`waves[].agentCap`), do not compute by hand.
|
|
107
|
+
- `isolation` — Session Config input (`worktree` / `none` / `auto`) that feeds `configIsolation` into the graduated per-wave rule (`resolveIsolation`, issue #194, in `scripts/lib/wave-sizing.mjs`: an explicit config value always wins; otherwise ≤2 agents → `none`, ≥5 agents → `worktree`, 3-4 agents → `none` for housekeeping else `worktree`). The RESOLVED value for a given wave is `waves[].isolation` in the shape's JSON output (`scripts/session-shape.mjs`) — a wave with `coordinatorDirect: true`, or a read-only wave, resolves `none` without calling `resolveIsolation` at all. Do not compute by hand; the plan header's `Isolation:` line is copied straight from that wave entry.
|
|
108
|
+
- `enforcement` (default: warn) — Session Config input (`strict` / `warn` / `off`) that feeds `configEnforcement` into `resolveEnforcement` (same module); the resolved per-wave value is `waves[].enforcement`. Isolation `none` auto-promotes `warn` to `strict`, since the scope-enforcement hook is then the only barrier left.
|
|
109
|
+
- `max-turns` — agent turn budget; resolved by `scripts/session-shape.mjs` (`waves[].maxTurns`), do not compute by hand.
|
|
96
110
|
- `agent-mapping` (optional) — explicit role-to-agent bindings
|
|
97
111
|
- `persistence` (default: true) — whether to use STATE.md and learnings
|
|
98
112
|
|
|
@@ -211,11 +225,18 @@ When `docs-orchestrator.enabled: true`, session-start Phase 2.5 emits a delimite
|
|
|
211
225
|
|
|
212
226
|
**If the block is absent:** Do not fabricate Docs tasks. The Docs role remains empty; apply the empty-role rule from Step 2.
|
|
213
227
|
|
|
214
|
-
- Housekeeping sessions: skip Steps 1.8, 2, and 3 —
|
|
215
|
-
- No role classification —
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
|
|
228
|
+
- Housekeeping sessions: skip Steps 1.8, 2, and 3 — housekeeping is the **maintenance loop**, one coordinator-direct wave. `total-waves: 1` and the wave's `coordinatorDirect: true` come from the shape (`scripts/session-shape.mjs --session-type housekeeping`), not from this prose.
|
|
229
|
+
- No role classification — no wave-executor dispatch, no per-role agent sizing.
|
|
230
|
+
- **Default scope, in this order:**
|
|
231
|
+
1. drift-check — `node skills/claude-md-drift-check/checker.mjs --mode warn`
|
|
232
|
+
2. expired-learnings sweep — `node scripts/sweep-expired-learnings.mjs --json`, then `--apply --json` when the dry run reports `archived > 0`
|
|
233
|
+
3. `/evolve analyze`
|
|
234
|
+
4. `/reconcile`
|
|
235
|
+
5. `/evolve dialectic` — dry-run first, then `--apply`
|
|
236
|
+
6. `/memory-cleanup`
|
|
237
|
+
- Operator-selected housekeeping issues are appended AFTER the six maintenance items, in the order the operator picked them.
|
|
238
|
+
- **Why coordinator-direct:** four of the six are AUQ-gated, and `AskUserQuestion` does not exist inside a dispatched agent (`.claude/rules/ask-via-tool.md` AUQ-004) — a wave-executor dispatch would strand the decision. "Coordinator-direct" means no wave-executor, NOT zero subagents: item 5 dispatches the read-only `dialectic-deriver` subagent directly.
|
|
239
|
+
- Wave plan output uses: `### Wave 1: Housekeeping (coordinator-direct, 0 agents)`
|
|
219
240
|
|
|
220
241
|
Record the assigned role next to each task before proceeding to Step 2.
|
|
221
242
|
|
|
@@ -311,7 +332,7 @@ Every item in the wave plan output carries an implicit `status: brainstormed` at
|
|
|
311
332
|
|
|
312
333
|
## Step 2: Wave Assignment
|
|
313
334
|
|
|
314
|
-
Distribute tasks across waves
|
|
335
|
+
Distribute tasks across the waves the session shape returned; each wave carries its own `role`. Which roles exist, and how many waves there are, is resolved by `scripts/session-shape.mjs` — see § Role-to-Wave Mapping below.
|
|
315
336
|
|
|
316
337
|
### Wave Roles
|
|
317
338
|
|
|
@@ -325,33 +346,30 @@ Distribute tasks across waves using 5 named roles. Read `waves` from Session Con
|
|
|
325
346
|
|
|
326
347
|
### Role-to-Wave Mapping
|
|
327
348
|
|
|
328
|
-
|
|
349
|
+
The wave list is not derived here. Resolve it ONCE at plan time from the session mode:
|
|
329
350
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
| 5 | W1=Discovery, W2=Impl-Core, W3=Impl-Polish, W4=Quality, W5=Finalization |
|
|
335
|
-
| 6+ | W1=Discovery, W2-W3=Impl-Core (split), W4-W5=Impl-Polish (split), W6=Quality+Finalization |
|
|
336
|
-
| 7 + `session-profile: ultradeep` | W1=Research+Code-Discovery, W2=Synthesis-Gate (`coordinator-direct: true`, `agents: 0`), W3=Impl-Core, W4=Impl-Polish, W5=Review-Panel, W6=Quality, W7=Release/Finalization |
|
|
351
|
+
```bash
|
|
352
|
+
node scripts/session-shape.mjs --repo-root "$PWD" --session-type <housekeeping|feature|deep> \
|
|
353
|
+
[--profile ultradeep] [--known-scope true|false] --task-count <N>
|
|
354
|
+
```
|
|
337
355
|
|
|
338
|
-
|
|
356
|
+
Run it **with** event emission (no `--no-event`) — that record (`orchestrator.session.shape_resolved` in `.orchestrator/metrics/events.jsonl`) is the canonical record of this session's shape. Use `--no-event` only for a throwaway planning dry-run.
|
|
339
357
|
|
|
340
|
-
|
|
358
|
+
It prints one JSON line carrying:
|
|
341
359
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
360
|
+
- `totalWaves` — the wave count
|
|
361
|
+
- `waves[]` — one record per wave: `n`, `role`, `agentCap`, `agentCapRaw`, `coordinatorDirect`, `writes`, `maxTurns`, `verification`, `qualityEarned`, `allowedPaths`
|
|
362
|
+
- `discovery` — whether a Discovery wave is part of the shape
|
|
363
|
+
- `wavesConfigHonored` — whether the Session Config `waves` value was used
|
|
364
|
+
- `notes` — human-readable reasons for any of the above
|
|
365
|
+
|
|
366
|
+
**The plan's wave list IS that output.** The coordinator fills tasks into the returned waves and NEVER adds, removes, or renumbers a wave — the sole exception is the empty-role rule below (and its coordinator-direct carve-out). `--known-scope true` is what drops the Discovery wave on a deep session; `--profile ultradeep` is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries `session-profile: ultradeep` (written by the `/session ultradeep` argument alias — see `commands/session.md`). `session-type` stays `deep`; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config `waves` value (the shape says so in `wavesConfigHonored` / `notes`). Spec: `docs/prd/2026-09-06-ultradeep-session-profile.md` § 5.
|
|
367
|
+
|
|
368
|
+
**Ultradeep agent counts per wave:** take each wave's cap from that wave's `agentCap` in the shape — there is no second table here to disagree with it. The caps are ceilings, not targets, and the Quality wave's cap is still EARNED per the Step 3 rule (the shape marks it `qualityEarned: true`); Research and Code-Discovery share wave 1's cap across their two separately-scoped groups; the Synthesis-Gate wave carries `agentCap: 0` with `coordinatorDirect: true` and writes only the coordinator's own artifacts (audit report, STATE.md, plan).
|
|
351
369
|
|
|
352
370
|
Wave 1 splits into two disjointly-scoped groups: **Research** agents (web-enabled, see `skills/wave-executor/SKILL.md` § Ultradeep Profile) and **Code-Discovery** agents (repo-only). Both are read-only. Wave 2 dispatches NO agents — the coordinator consolidates wave 1, writes `docs/audits/<YYYY-MM-DD>-<slug>.md`, and asks ONE blocking `AskUserQuestion` before wave 3.
|
|
353
371
|
|
|
354
|
-
When roles are combined into a single wave, agents from both roles execute in that wave.
|
|
372
|
+
When roles are combined into a single wave, agents from both roles execute in that wave.
|
|
355
373
|
|
|
356
374
|
**Docs role dispatch rule (conditional — `docs-orchestrator.enabled: true` only):**
|
|
357
375
|
|
|
@@ -365,13 +383,11 @@ When `docs-orchestrator.enabled: true`, apply the following concrete dispatch ru
|
|
|
365
383
|
- **NEVER add a 6th wave** for Docs. Docs always occupies an existing wave slot.
|
|
366
384
|
- When `docs-orchestrator.enabled` is `false` (default), this rule has no effect — the Docs role does not exist.
|
|
367
385
|
|
|
368
|
-
**Cross-role constraint in combined waves:** Tasks from different roles within a combined wave CANNOT be merged into a single agent
|
|
369
|
-
|
|
370
|
-
> Example: When Discovery+Impl-Core are combined (3-wave config), the wave runs Incremental quality checks (Impl-Core's level) rather than no verification (Discovery's level).
|
|
386
|
+
**Cross-role constraint in combined waves:** Tasks from different roles within a combined wave (the feature shape's `Impl-Polish+Quality` is the one today) CANNOT be merged into a single agent — the roles carry different scope permissions. If the combined wave's tasks exceed its `agentCap`, defer the lower-priority role's tasks: in `Impl-Polish+Quality`, defer Quality tasks to a separate phase within the same wave.
|
|
371
387
|
|
|
372
|
-
|
|
388
|
+
> A combined wave's `verification` field in the shape already carries the more restrictive of its two roles' levels — read it, do not re-derive it.
|
|
373
389
|
|
|
374
|
-
**Empty roles:** If a role has 0 tasks, skip its wave entirely. Do NOT dispatch an empty wave. Remaining waves retain their original role names but are renumbered sequentially
|
|
390
|
+
**Empty roles:** If a role has 0 tasks, skip its wave entirely. Do NOT dispatch an empty wave. Remaining waves retain their original role names but are renumbered sequentially, and `total-waves` in the plan output is updated to reflect the actual wave count. **This rule never applies to Discovery.** Discovery is dropped exactly once, at shape-resolution time, by passing `--known-scope true` to `scripts/session-shape.mjs` (§ Role-to-Wave Mapping above) — the shape itself renumbers the remaining waves and reports the new count as `totalWaves` in its JSON output, before the coordinator ever sees a wave list to assign tasks into. Applying this rule to Discovery by hand, after the fact, would be a second, competing renumbering of a decision the shape already made. The empty-role rule below is for the roles that stay ON the wave list after the shape is fixed (e.g., Docs, Quality) and whose task count can still fall to 0 during Step 1/1.8 classification.
|
|
375
391
|
|
|
376
392
|
**Exception — a wave declared `coordinator-direct: true` is NEVER removed by the empty-role rule.** The rule's premise is "0 tasks means nothing to dispatch, so the wave is dead weight". For a coordinator-direct wave that premise is inverted: dispatching zero agents is the wave's PURPOSE, not evidence of its emptiness. Its plan item therefore carries BOTH markers and is emitted verbatim:
|
|
377
393
|
|
|
@@ -446,17 +462,9 @@ Score the session scope to determine optimal agent counts per wave. Skip for hou
|
|
|
446
462
|
|
|
447
463
|
### Agent Count by Tier
|
|
448
464
|
|
|
449
|
-
|
|
450
|
-
|-------------|------|-----------|-----------|-------------|---------|-------------|
|
|
451
|
-
| feature | simple | 2-3 | 3-4 | 2-3 | 2 | 1 |
|
|
452
|
-
| feature | moderate | 4-5 | 5-6 | 4-5 | 3-4 | 2 |
|
|
453
|
-
| feature | complex | 5-6 | 6 | 5-6 | 4 | 2 |
|
|
454
|
-
| deep | simple | 3-4 | 4-6 | 3-4 | 3 | 2 |
|
|
455
|
-
| deep | moderate | 5-6 | 6-8 | 5-6 | 4-5 | 2-3 |
|
|
456
|
-
| deep | complex | 6-8 | 8-10 | 6-8 | 6 | 3-4 |
|
|
457
|
-
| housekeeping | (fixed) | — | 2 | 1 | 1 | 1 |
|
|
465
|
+
The caps themselves are **not** derived from the tier: each wave's ceiling is `waves[].agentCap` from the shape (`scripts/session-shape.mjs`, § Role-to-Wave Mapping above), and `agentCapRaw` is that cap before the Session Config `agents-per-wave` ceiling was applied.
|
|
458
466
|
|
|
459
|
-
|
|
467
|
+
What the tier score decides is **relaxation DOWNWARD only**: a simple-tier session may plan fewer agents than the wave's `agentCap` where the briefed work does not fill it. It may never plan more — the cap is a hard ceiling, and a moderate or complex tier does not raise it.
|
|
460
468
|
|
|
461
469
|
> **The Quality column is a CAP, not a target.** Every other column sizes to briefed work; the Quality column historically sized to the tier alone, so capacity went looking for work (tests written because a slot existed, not because a gap was measured). Quality capacity must be EARNED by measured demand. Compute the effective count as `min(<tier cap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`.
|
|
462
470
|
> - **0 HIGH and 0 MED gaps → the Quality role has 0 test-writing tasks**, and its wave is skipped by the Step 2 empty-role rule. This does NOT touch the read-only review panel (security-reviewer / qa-strategist / architect-reviewer) — that panel reviews, it does not write tests, and it keeps running as configured.
|
|
@@ -491,7 +499,7 @@ For each role's wave, distribute its classified tasks across the allocated agent
|
|
|
491
499
|
|
|
492
500
|
> **Template Reference:** See `wave-template.md` in this skill directory for the agent specification format, isolation settings, and count tables.
|
|
493
501
|
|
|
494
|
-
For each wave, define agents using the template format in `wave-template.md`.
|
|
502
|
+
For each wave, define agents using the template format in `wave-template.md`. The per-wave ceiling is that wave's `agentCap` from the shape (§ Role-to-Wave Mapping) — it already carries the `agents-per-wave` Session Config ceiling.
|
|
495
503
|
|
|
496
504
|
If project intelligence (learnings) suggests different sizing based on historical data, prefer the historical recommendation over the formula.
|
|
497
505
|
|
|
@@ -11,30 +11,23 @@ For each wave, define agents with:
|
|
|
11
11
|
Acceptance: [what "done" looks like — measurable]
|
|
12
12
|
Tools needed: [Read, Write, Edit, Bash, Grep, Glob, etc.]
|
|
13
13
|
Dependencies: [output from which prior role/agent]
|
|
14
|
-
Isolation: [worktree|none —
|
|
15
|
-
MaxTurns: [
|
|
14
|
+
Isolation: [worktree|none — copied verbatim from this wave's `waves[].isolation` entry in the shape output (`scripts/session-shape.mjs`); do not compute by hand]
|
|
15
|
+
MaxTurns: [this wave's `maxTurns` from `scripts/session-shape.mjs`; do not compute by hand]
|
|
16
16
|
status: brainstormed
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
- `Isolation: worktree` means the wave-executor will pass `isolation: "worktree"` to the Agent tool, giving each agent its own git worktree copy
|
|
19
|
+
- `Isolation: worktree` means the wave-executor will pass `isolation: "worktree"` to the Agent tool, giving each agent its own git worktree copy. Each wave's `isolation` (`worktree`/`none`) AND its paired `enforcement` (`strict`/`warn`/`off`) are computed per-wave from that wave's `agentCap` and the session type — the graduated rule (`resolveIsolation`/`resolveEnforcement`, issue #194: ≤2 agents → `none`, ≥5 agents → `worktree`, 3-4 agents → `none` for housekeeping else `worktree`, an explicit Session Config `isolation` value always wins) lives in `scripts/lib/wave-sizing.mjs`, not in `session-shape.mjs` itself — `session-shape.mjs` only calls it once per wave row and copies the two results onto that wave's record.
|
|
20
20
|
- `MaxTurns` is enforced via the agent prompt — wave-executor includes a turn limit instruction in each agent's prompt
|
|
21
21
|
- `status` is the mission-status enum value for this wave-plan item (#340). Always `brainstormed` in the initial plan. Wave-executor updates it at gate transitions (validated → in-dev → testing → completed). Rollback to `brainstormed` is allowed from any state. The five values are listed in `SKILL.md` § Mission-Status Enum; nothing validates them mechanically — `setMissionStatus` writes the string it is given to both STATE.md surfaces on purpose, so keeping the value in-enum is the coordinator's job.
|
|
22
22
|
- The wave-plan item's `id` becomes the `taskId` every `setMissionStatus` call for this item uses, and `setMissionStatus` REFUSES ids outside `[a-z][a-z0-9]*(?:-[a-z0-9]+)*-\d+` (lowercase segments, single hyphens, trailing bare digits) with `refused: 'id-grammar'` and a stderr WARN — nothing is written. Mint ids matching it: `m-1`, `docs-2`, `w2-1`, `w2-a-10` all accepted; `w2-a10`, `w3-p2`, `W3-I1`, `Docs_2` all refused.
|
|
23
23
|
|
|
24
24
|
> **Deconfliction rule:** Before finalizing agent specs for a wave, verify that no two agents in the same wave list overlapping `Files:` paths. If overlap is found, either merge the agents into one or move one task to a later wave. Two agents editing the same file in parallel causes merge conflicts that require manual resolution.
|
|
25
25
|
|
|
26
|
-
## Agent Count
|
|
26
|
+
## Agent Count and Turn Budget per Wave
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|-------------|-----------|-----------|-------------|---------|-------------|
|
|
30
|
-
| housekeeping | — | 1-6* | — | — | — |
|
|
31
|
-
| feature | 4-6 | 6 | 4-6 | 4 | 2 |
|
|
32
|
-
| deep | 6-8 | 6-10 | 6-8 | 6 | 2-4 |
|
|
28
|
+
There is no count table here. Both numbers come from the session shape resolved once at plan time (`SKILL.md` § Role-to-Wave Mapping):
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
- **Agent ceiling** — that wave's `agentCap` (already capped by the Session Config `agents-per-wave` value; `agentCapRaw` is the value before that ceiling).
|
|
31
|
+
- **Turn budget** — that wave's `maxTurns` (`null` on a `coordinatorDirect: true` wave, which dispatches no agents).
|
|
35
32
|
|
|
36
|
-
> **The Quality
|
|
37
|
-
|
|
38
|
-
> **Note:** For feature and deep sessions, prefer the complexity-based agent counts from Step 3. This table provides defaults when complexity scoring is skipped (housekeeping) or as a fallback.
|
|
39
|
-
|
|
40
|
-
> \* Housekeeping sessions use single-wave serial execution (see wave-executor). Agent counts are for the single consolidated wave, not per-role.
|
|
33
|
+
> **The Quality wave's cap is a CAP, not a target.** Quality capacity is need-gated: the effective count is `min(<the wave's agentCap>, ceil((HIGH + MED gaps from the most recent qa-strategist run) / 3))`. 0 gaps → 0 test-writing tasks and the wave is skipped (the read-only review panel is unaffected); no qa-strategist signal at all → a conservative 1-2, never the blind cap. The shape marks this wave `qualityEarned: true`. Full rule: `SKILL.md` § Agent Count by Tier footnote.
|
|
@@ -205,7 +205,7 @@ Group issues by:
|
|
|
205
205
|
|
|
206
206
|
## Phase 4: SSOT & Environment Check
|
|
207
207
|
|
|
208
|
-
> Always runs; every finding is a NON-BLOCKING banner in the Session Overview (never a gate — the Full Gate is the Quality wave's job). Covers SSOT freshness, the Baseline quality commands (resolved `.orchestrator/policy/quality-gates.json` → Session Config → defaults, each availability-checked with `command -v`), Pencil design status, plugin + `bootstrap.lock` freshness, and the banner-probe family registered in `scripts/lib/session-start-probes.mjs` (vault-staleness, telemetry-flush-health, ci-status, qg-command-drift, peer-cards, loop-readiness, instruction-budget,
|
|
208
|
+
> Always runs; every finding is a NON-BLOCKING banner in the Session Overview (never a gate — the Full Gate is the Quality wave's job). Covers SSOT freshness, the Baseline quality commands (resolved `.orchestrator/policy/quality-gates.json` → Session Config → defaults, each availability-checked with `command -v`), Pencil design status, plugin + `bootstrap.lock` freshness, and the banner-probe family registered in `scripts/lib/session-start-probes.mjs` (vault-staleness, telemetry-flush-health, ci-status, qg-command-drift, peer-cards, loop-readiness, instruction-budget, maintenance-due, sessions-staleness, sessions-integrity, owner-config, moc-staleness, context-coverage, claude-md-budget-lint, tests:src-ratio, project-hygiene, mirror-issues, git-config-drift). Per-probe module path, return contract and exact banner wording: [`references/phase-4-ssot-environment-check.md`](references/phase-4-ssot-environment-check.md).
|
|
209
209
|
|
|
210
210
|
## Phase 4.5: Resource Health (v3.1.0)
|
|
211
211
|
|
|
@@ -279,6 +279,20 @@ Surface context from previous sessions:
|
|
|
279
279
|
- ALWAYS verify current state in actual code — never assume based on memory or SSOT alone
|
|
280
280
|
|
|
281
281
|
**For `housekeeping` sessions:**
|
|
282
|
+
|
|
283
|
+
A housekeeping session IS the **maintenance loop** — the one place the repo's own upkeep runs, which is why the session-start `maintenance-due` probe (`checkMaintenanceDue`, `scripts/lib/maintenance-due-banner.mjs`) recommends exactly this session type, and why the retired session-end nudges (Phase 3.6.5 auto-dream, 3.6.7 auto-dialectic) no longer exist: a nudge fired at close asks the operator to do here what he can only do here.
|
|
284
|
+
|
|
285
|
+
Propose the **ordered default scope** below in the Phase 8 Q&A. Every step is AUQ-gated — the operator selects which of them run, none of them runs unasked:
|
|
286
|
+
|
|
287
|
+
1. **Drift-check as a work-list** — `checker.mjs --mode warn` (procedure below); its `errors[]`/`warnings[]` become candidate scope.
|
|
288
|
+
2. **Expired-learnings sweep** — the same sweep session-end 3.6.4 applies mechanically (`runTailPhases` / `runExpiredSweep`), run here when the `sweep` signal is due.
|
|
289
|
+
3. **`/evolve analyze`** — extract this period's session patterns into learnings.
|
|
290
|
+
4. **`/reconcile`** — turn high-confidence learnings into operator-approved `.claude/rules/` proposals.
|
|
291
|
+
5. **`/evolve dialectic`** — dry-run first, review `.orchestrator/dialectic-pending.md`, then apply. This step dispatches the read-only `dialectic-deriver` agent, so **"coordinator-direct" means no wave-executor, not zero subagents**. <!-- path-check: example -->
|
|
292
|
+
6. **`/memory-cleanup`** — `--dry-run` writes the MEMORY.md proposal to `.orchestrator/pending-dream.md`; `--apply-pending` applies it. <!-- path-check: example -->
|
|
293
|
+
|
|
294
|
+
Operator-selected issues (from Phase 6) are appended AFTER this loop, not interleaved with it — the loop's outputs (new learnings, new rules) are inputs the issue work should already see.
|
|
295
|
+
|
|
282
296
|
- Focus on git cleanup, documentation currency, CI health
|
|
283
297
|
- Skip deep research — prioritize operational tasks
|
|
284
298
|
- Run token efficiency check: `bash "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/token-audit.sh"` and include findings in Session Overview. Flag any HIGH/WARN items as recommended housekeeping tasks.
|
|
@@ -335,12 +349,14 @@ When all conditions are met, the CLI emits the banner on stderr:
|
|
|
335
349
|
```
|
|
336
350
|
Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.
|
|
337
351
|
```
|
|
338
|
-
Carry that banner into Phase 9 and hand off to session-plan as usual — session-plan short-circuits to a 1-wave `coordinator-direct` plan, which is the artifact `/go` detects. Tasks are then executed coordinator-direct (bypassing wave-executor, subagent dispatch and inter-wave checkpoints) and a Deviations entry is logged in STATE.md. Silent no-op when any condition fails — proceeds normally to Phase 9.
|
|
352
|
+
Carry that banner into Phase 9 and hand off to session-plan as usual — session-plan short-circuits to a 1-wave `coordinator-direct` plan, which is the artifact `/go` detects. That 1-wave plan is not a special case: it EQUALS the housekeeping shape `scripts/session-shape.mjs` resolves (`totalWaves: 1`, `coordinatorDirect: true`, one `Housekeeping` wave with `agentCap: 0`), so the express path and the ordinary housekeeping path emit the same shape and differ only in scope size. Tasks are then executed coordinator-direct (bypassing wave-executor, subagent dispatch and inter-wave checkpoints) and a Deviations entry is logged in STATE.md. Silent no-op when any condition fails — proceeds normally to Phase 9.
|
|
339
353
|
|
|
340
354
|
**See `phase-8-5-express-path.md` for full details.**
|
|
341
355
|
|
|
342
356
|
## Phase 9: Handoff to Session Plan
|
|
343
357
|
|
|
358
|
+
> **Record the wave shape ONCE, here, at plan time.** Run `node scripts/session-shape.mjs --repo-root "$PWD" --session-type <housekeeping|feature|deep> [--profile ultradeep] [--known-scope true|false] --task-count <N>` — the CLI wraps `resolveAndRecordSessionShape` / `resolveSessionShape` from `scripts/lib/session-shape.mjs`, prints ONE JSON line (`totalWaves`, `discovery`, `coordinatorDirect`, `waves[]`, `notes[]`) and records the event `orchestrator.session.shape_resolved`. session-plan CONSUMES that JSON instead of re-deriving the wave count; never hand-write a wave count that the shape already answers. Add `--no-event` only for a planning dry-run.
|
|
359
|
+
|
|
344
360
|
After user alignment:
|
|
345
361
|
1. Invoke the **session-plan** skill with the agreed scope
|
|
346
362
|
2. The session-plan skill will decompose tasks into waves and present the execution plan
|
|
@@ -23,7 +23,7 @@ If `DOCS_ENABLED` is not `true`, skip all remaining steps in this phase and proc
|
|
|
23
23
|
Using signals already gathered in Phases 2–5 (git analysis, VCS issues, branch state, SSOT checks), apply the following heuristic to determine which audiences are likely affected. Record each match with its triggering signal for inclusion in the output block.
|
|
24
24
|
|
|
25
25
|
**User audience** — flag as likely when any of the following are true:
|
|
26
|
-
- Affected files include `README.md`, `docs/user/**/*.md`, `docs/getting-started.md`, or `examples/**/*.md`
|
|
26
|
+
- Affected files include `README.md`, `docs/user/**/*.md`, `docs/getting-started.md`, or `examples/**/*.md` <!-- path-check: example -->
|
|
27
27
|
- Open or recently closed issues reference CLI UX changes, new user-facing commands, or a breaking API change
|
|
28
28
|
- New public commands are introduced (e.g. changes to `commands/` directory)
|
|
29
29
|
- Install flow or setup instructions are modified
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
After the user confirms the session type and scope via the Q&A above, evaluate whether the **Express Path** applies before handing off to session-plan. The express path collapses the full 5-wave plan into a single coordinator-direct phase for lightweight sessions.
|
|
6
6
|
|
|
7
|
+
> **The 1-wave plan is not a special artifact — it EQUALS the housekeeping shape.** `resolveSessionShape` in `scripts/lib/session-shape.mjs` (CLI: `node scripts/session-shape.mjs --repo-root "$PWD" --session-type housekeeping --task-count <N>`) already resolves `housekeeping` to `totalWaves: 1`, `coordinatorDirect: true`, one `Housekeeping` wave with `agentCap: 0` and `verification: 'full'`. Express path and ordinary housekeeping therefore emit the SAME shape and differ only in scope size; the shape is recorded once at Phase 9 as `orchestrator.session.shape_resolved`.
|
|
8
|
+
|
|
7
9
|
**Do not evaluate these conditions by hand — run the CLI (#1119, #1146).**
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -87,19 +89,20 @@ Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.
|
|
|
87
89
|
|
|
88
90
|
Hand off to Phase 9 as usual. The coordinator then executes the 1-wave plan session-plan emits directly, without dispatching subagents:
|
|
89
91
|
|
|
90
|
-
1. Proceed to Phase 9 (session-plan handoff) carrying the banner. session-plan short-circuits to the 1-wave `coordinator-direct` plan
|
|
91
|
-
2.
|
|
92
|
-
3.
|
|
93
|
-
4.
|
|
92
|
+
1. Proceed to Phase 9 (session-plan handoff) carrying the banner. session-plan short-circuits to the 1-wave `coordinator-direct` plan — the same shape `scripts/session-shape.mjs` resolves for `housekeeping`; `/go` detects it and does NOT invoke wave-executor.
|
|
93
|
+
2. **Run the maintenance loop FIRST** (`skills/session-start/SKILL.md` Phase 7, "For housekeeping sessions"): drift-check work-list, expired sweep, `/evolve analyze`, `/reconcile`, `/evolve dialectic` (dry-run then apply), `/memory-cleanup` — each AUQ-gated. `coordinator-direct` means no wave-executor, NOT zero subagents: the dialectic step dispatches the read-only `dialectic-deriver`. The operator-selected tasks run after the loop, because the loop's outputs (new learnings, new rules) are inputs the task work should already see.
|
|
94
|
+
3. For each agreed task (in dependency order): execute as a direct coordinator action — read files, make changes, run quality checks inline. No subagents, no inter-wave checkpoints.
|
|
95
|
+
4. Log the express-path activation in STATE.md `## Deviations` section: `Express path: N tasks executed coord-direct (express-path.enabled: true, session-type: housekeeping, scope: N issues)` — written BEFORE session-end is invoked. Then invoke `skills/session-end/SKILL.md` directly.
|
|
96
|
+
5. After session-end completes successfully: verify STATE.md `status` is `completed` and `## Deviations` contains the express-path entry from step 4. If either is missing, warn the user with a one-line note and instructions to re-run `/close` manually. Then return the final session summary to the user.
|
|
94
97
|
|
|
95
98
|
**Persistence contract:**
|
|
96
99
|
|
|
97
|
-
Step 1 is the Phase 9 handoff and ends the session-start turn — the operator types `/go` next, exactly as on the normal path. Steps 2–
|
|
100
|
+
Step 1 is the Phase 9 handoff and ends the session-start turn — the operator types `/go` next, exactly as on the normal path. Steps 2–5 then MUST all happen within a SINGLE coordinator turn, the one `/go` opens. Specifically:
|
|
98
101
|
|
|
99
|
-
- Step 2 (execute tasks)
|
|
100
|
-
- Step
|
|
101
|
-
- Step
|
|
102
|
-
- Step
|
|
102
|
+
- Step 2 (maintenance loop) and step 3 (execute tasks) happen first in that turn's main flow, in that order.
|
|
103
|
+
- Step 4a (deviations log) is written BEFORE session-end is invoked. The coordinator calls `appendDeviation()` from `scripts/lib/state-md.mjs` to append the `Express path:` bullet to the `## Deviations` section while STATE.md is still `status: active`.
|
|
104
|
+
- Step 4b (invoke session-end) flips `status` to `completed`, writes the metrics record to `.orchestrator/metrics/sessions.jsonl`, and runs the standard close flow. Session-end has no Express Path-specific logic — it treats this run identically to any other completed session.
|
|
105
|
+
- Step 5 (verification) is the coordinator's final action before returning control. The verification check uses `parseStateMd()` from `scripts/lib/state-md.mjs` to read the file and check `frontmatter.status === 'completed'` and that the body contains the literal string `Express path:`.
|
|
103
106
|
|
|
104
107
|
When `/go` is invoked and session-plan emitted a 1-wave Express Path plan (per `skills/session-plan/SKILL.md` § "Express Path Short-Circuit"), the `/go` command MUST detect this and route to coord-direct execution + session-end auto-invocation, NOT to wave-executor. See `commands/go.md` for the detection branch — that plan is the artifact `/go` keys on, which is why Phase 8.5 hands off to session-plan rather than skipping it.
|
|
105
108
|
|
|
@@ -251,4 +251,6 @@ import { setSessionProfile } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
|
251
251
|
contents = setSessionProfile(contents, 'ultradeep');
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
+
**The profile owns its wave count; the STATE.md write does not compute one.** `total-waves` comes from the resolved shape — `node scripts/session-shape.mjs --repo-root "$PWD" --session-type deep --profile ultradeep …` (`resolveSessionShape` / `resolveAndRecordSessionShape` in `scripts/lib/session-shape.mjs`), whose `totalWaves` field is authoritative and is recorded once as `orchestrator.session.shape_resolved`. The former AC-9 rule (the profile must honour the Session Config `waves:` value) is DROPPED as of 2026-09-09: a profile that cannot set its own wave count is not a wave-shape variant. Write `total-waves` from `shape.totalWaves`, never from a hand-derived number, and never from `waves:` when a profile is present — the shape's `wavesConfigHonored` flag and `notes[]` already say which of the two won.
|
|
255
|
+
|
|
254
256
|
For every other argument, write **nothing** — absence is the contract, never `''`, `none` or `null` as a value. `setSessionProfile(contents, null)` deletes a stale key inherited from a previous session's record; it throws on an empty-string profile, so never pass one.
|