session-orchestrator 4.0.1 → 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 +57 -0
- package/README.md +55 -51
- 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/release.md +4 -4
- 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/ci/assert-coverage-green.mjs +100 -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/events.mjs +3 -3
- 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/project-hygiene.mjs +182 -6
- 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-identity/own-session.mjs +24 -13
- 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-owner-leakage.mjs +17 -8
- 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-mirror/render-sessions.mjs +8 -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/release.mjs +32 -11
- 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
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
> - `memory.proposals.enabled` is `false` (default: `true`)
|
|
12
12
|
> - `.orchestrator/metrics/proposals.jsonl` does not exist OR contains zero entries
|
|
13
13
|
|
|
14
|
-
After learnings are written (Phase 3.6) and BEFORE
|
|
14
|
+
After learnings are written (Phase 3.6) and BEFORE the Skill-Applied Judge (Phase 3.6.6 — Phase 3.6.5 is retired), collect agent-proposed memory entries written during this session and present them to the operator via `AskUserQuestion` multiSelect. Approved entries flow to `learnings.jsonl` with `_provenance: agent-proposed@<wave-id>`. Rejected entries are archived to `.orchestrator/proposals.rejected.log`.
|
|
15
15
|
|
|
16
16
|
The proposals queue is populated mid-session by wave-executor agents calling `node scripts/memory-propose.mjs --type ... --subject ... --insight ... --evidence ... --confidence ...`. The CLI enforces:
|
|
17
17
|
- Quota per wave (default 5, configurable via `memory.proposals.quota-per-wave`)
|
|
@@ -138,40 +138,29 @@ The proposals queue is populated mid-session by wave-executor agents calling `no
|
|
|
138
138
|
|
|
139
139
|
> Best-effort, non-blocking. Skip silently if the sweep script errors or `.orchestrator/metrics/learnings.jsonl` is absent.
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
**MECHANICAL since 2026-09-09.** This phase is no longer a two-command prose recipe ("run `--json`, then `--apply --json` when `archived > 0`") — that recipe was the reason the apply path had ZERO session-end callers: measured across three consumer repos, 0 sweeps had ever been applied and 628 learnings were resident in the active stores. The dry-run decision already lives in `planTailPhases()`; the APPLY half now lives in `scripts/lib/session-end/tail-runner.mjs`.
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
After learnings are written (Phase 3.6) and `planTailPhases()` has produced its `plan` (see § "Phase 3.6.x Tail — Mechanical Skip-Plan" in `references/phase-3-documentation-updates.md`), call `runTailPhases` ONCE and read the `3.6.4` slot of its keyed result:
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
```javascript
|
|
146
|
+
import { runTailPhases } from '${PLUGIN_ROOT}/scripts/lib/session-end/tail-runner.mjs';
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
const tail = await runTailPhases({ repoRoot: process.cwd(), plan });
|
|
149
|
+
const sweep = tail['3.6.4'];
|
|
150
|
+
// { ran: true, scanned, archived, archivePath } | { ran: false, reason: 'plan-skip' | 'no-plan' | 'error', error? }
|
|
151
|
+
```
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
- `runTailPhases` delegates to `runExpiredSweep({ repoRoot, plan, now })` — the same module's single-phase entry point — and returns a KEYED shape so a caller keeps working when a second phase becomes mechanical. Today exactly one phase is: 3.6.3, 3.6.5–3.6.8 stay coordinator-executed because they are AUQ-gated or need a subagent dispatch a library function cannot make.
|
|
154
|
+
- **Never throws, fails CLOSED.** Any error yields `{ ran: false, reason: 'error' }` and the close proceeds. Stale-past-grace entries move into `.orchestrator/metrics/learnings-archive.jsonl` (append-only, never deleted).
|
|
155
|
+
- **Report** `sweep.ran`, `sweep.scanned` and `sweep.archived` in the Phase 6 Final Report, e.g. `expired-sweep: 12 archived of 640 scanned`. When `ran: false`, report the `reason` instead — a skipped sweep is a stated outcome, never silence.
|
|
156
|
+
- **The proof it ran is the event `orchestrator.learnings.sweep_applied`** in `.orchestrator/metrics/events.jsonl` (payload source `session-end-3.6.4`, which separates it from the standalone CLI). A close claiming a sweep with no such event did not sweep.
|
|
151
157
|
|
|
152
|
-
|
|
153
|
-
import { shouldDispatchAutoDream } from '${PLUGIN_ROOT}/scripts/lib/auto-dream.mjs';
|
|
154
|
-
import { resolveMemoryDir } from '${PLUGIN_ROOT}/scripts/lib/memory-paths.mjs';
|
|
155
|
-
const repoRoot = process.cwd();
|
|
156
|
-
const memoryDir = resolveMemoryDir(repoRoot);
|
|
157
|
-
const decision = await shouldDispatchAutoDream({
|
|
158
|
-
repoRoot,
|
|
159
|
-
memoryDir,
|
|
160
|
-
threshold: config['memory-cleanup-threshold'] ?? 5,
|
|
161
|
-
softLimit: config['memory-cleanup-soft-limit'] ?? 180,
|
|
162
|
-
});
|
|
163
|
-
```
|
|
164
|
-
3. If `decision.trigger === false`: log `auto-dream: not triggered (${decision.reason})` and continue. Emit no nudge.
|
|
165
|
-
4. If `decision.trigger === true`: **do not dispatch a subagent** — there is no `memory-cleanup` agent in `agents/`, so the historical `Agent({…})` dispatch pointed at the agent name `memory-cleanup` (a subagent type that was never built) and never fired (see #614). Instead, emit a manual-cadence nudge and continue:
|
|
158
|
+
The standalone `node scripts/sweep-expired-learnings.mjs --apply --json` CLI remains available for manual/out-of-session use; it is no longer the session-end path.
|
|
166
159
|
|
|
167
|
-
|
|
160
|
+
### 3.6.5 Auto-Dream Dispatch (#502, F2.2) — RETIRED
|
|
168
161
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
The pending-dream sidecar at `.orchestrator/pending-dream.md` is intentionally outside the vault tree — vault-mirror (Phase 3.7) must exclude it from its scope so the proposal survives the session close without being mirrored into 50-sessions/.
|
|
173
|
-
|
|
174
|
-
Cross-reference: PRD F2.2 acceptance criteria; `scripts/lib/auto-dream.mjs` API (`shouldDispatchAutoDream`, `readDreamSignals`, `writePendingDream`, `readPendingDream`, `applyPendingDream`).
|
|
162
|
+
> **RETIRED 2026-09-09.** The nudge is replaced by the session-start `maintenance-due` probe (`checkMaintenanceDue`, `scripts/lib/maintenance-due-banner.mjs`), whose `memory-cleanup` signal reuses the very same `shouldDispatchAutoDream` decision — a nudge emitted while the operator is closing down was read by nobody. Its decider is also gone from `planTailPhases()` in `scripts/lib/session-end/phase-skip.mjs`; the heading stays because other docs cite it.
|
|
163
|
+
> The housekeeping session runs `/memory-cleanup` itself (see `skills/session-start/SKILL.md` Phase 7 — the maintenance loop). `scripts/lib/auto-dream.mjs` (`shouldDispatchAutoDream`, `readDreamSignals`, `writePendingDream`, `readPendingDream`, `applyPendingDream`) stays in use: the probe reads it, and `/memory-cleanup --dry-run` / `--apply-pending` still write and consume `.orchestrator/pending-dream.md`. <!-- path-check: example -->
|
|
175
164
|
|
|
176
165
|
### 3.6.6 Skill-Applied Judge (#645, L3)
|
|
177
166
|
|
|
@@ -181,7 +170,7 @@ Cross-reference: PRD F2.2 acceptance criteria; `scripts/lib/auto-dream.mjs` API
|
|
|
181
170
|
>
|
|
182
171
|
> When skipped, log `skill-judge: disabled (skill-evolution.judge=false)` (or `persistence=false`) and return. **This is the disabled-path guarantee:** with the judge off, only L1 (`skill-invocations.jsonl`, written by the PreToolUse hook) and L2 (`scripts/lib/skill-health/join.mjs`) records exist — no judgment, no error, zero L3 code executes. Do NOT import `scripts/lib/skill-judge.mjs` on the disabled path.
|
|
183
172
|
|
|
184
|
-
After learnings are written (Phase 3.6)
|
|
173
|
+
After learnings are written (Phase 3.6), and when the judge is enabled, run a **bounded, read-only LLM-judge** over this session's selected skills to emit ADVISORY per-skill applied/completed judgments to `.orchestrator/metrics/skill-judgments.jsonl`.
|
|
185
174
|
|
|
186
175
|
**The #614 distinction (the whole point of L3's Design A):** unlike the 3.6.5 / 3.6.7 nudge-only paths — which cannot dispatch a live subagent because the target read-only agents (`memory-cleanup`, `dialectic-deriver`) cannot write their own sidecars — L3 performs a **LIVE read-only dispatch**. This is #614-safe because the read-only `skill-applied-judge` agent **RETURNS JSON** and the **COORDINATOR writes the sidecar**, not the agent. A read-only agent that returns judgments is allowed; a read-only agent that must write a file is the #614 trap.
|
|
187
176
|
|
|
@@ -238,41 +227,10 @@ After learnings are written (Phase 3.6) and the auto-dream decision is made (Pha
|
|
|
238
227
|
|
|
239
228
|
Cross-reference: PRD §A L3 acceptance criteria (#645, epic #643); `scripts/lib/skill-judge.mjs` API (`runSkillJudge`, `validateModel`, `estimateInputTokens`, `checkBudget`, `buildJudgePrompt`, `parseJudgeResponse`); `scripts/lib/skill-judgments-schema.mjs` (`appendSkillJudgment`, `readSkillJudgments`, `validateSkillJudgment`); agent `agents/skill-applied-judge.md`.
|
|
240
229
|
|
|
241
|
-
### 3.6.7 Auto-Dialectic Dispatch (#506, F2.5)
|
|
242
|
-
|
|
243
|
-
> Skip this phase if `dialectic.cadence: 0` (kill-switch per PRD F2.5 AC3). Also skip if `persistence` is `false` in Session Config.
|
|
244
|
-
|
|
245
|
-
After learnings are written (Phase 3.6) and the auto-dream decision is made (Phase 3.6.5), determine whether to emit a **manual-cadence nudge** to run `/evolve --dialectic` in the next session. The decision uses sessions-since-last-dialectic counted against `.orchestrator/dialectic-last-run`. There is no `evolve` agent in the registry, and the nearest one (`dialectic-deriver`) is `sandbox-tier: read-only` and cannot write the sidecar — so the historical auto-dialectic subagent dispatch never fired (see #614). On trigger, emit the nudge and advance `.orchestrator/dialectic-last-run`; the timestamp is updated only when the nudge is emitted (not on skip), so the reminder surfaces once per cadence window rather than every session. A manually-run `/evolve --dialectic --dry-run` writes the proposed diff to `.orchestrator/dialectic-pending.md`.
|
|
246
|
-
|
|
247
|
-
1. Read `dialectic.cadence` (default 5), `dialectic.model` (default haiku), `dialectic.budget-tokens` (default 8000) from `$CONFIG`.
|
|
248
|
-
|
|
249
|
-
2. Invoke `decideAndRecordAutoDialectic` from `scripts/lib/auto-dialectic.mjs`:
|
|
250
|
-
```javascript
|
|
251
|
-
import { decideAndRecordAutoDialectic } from '${PLUGIN_ROOT}/scripts/lib/auto-dialectic.mjs';
|
|
252
|
-
const decision = await decideAndRecordAutoDialectic({
|
|
253
|
-
repoRoot: process.cwd(),
|
|
254
|
-
cadence: config.dialectic?.cadence ?? 5,
|
|
255
|
-
});
|
|
256
|
-
```
|
|
257
|
-
Same return shape as `shouldDispatchAutoDialectic` (`{trigger, reason, signals}`) — `decideAndRecordAutoDialectic` calls it internally and additionally emits the mechanical `orchestrator.dialectic.nudge_decided` telemetry record on all four return paths (#1200 part c), so the nudge decision is observable without depending on this prose actually reaching step 5/7.
|
|
258
|
-
|
|
259
|
-
3. If `decision.trigger === false`: log `auto-dialectic: not triggered (${decision.reason})` and continue. Emit no nudge. Do NOT update `.orchestrator/dialectic-last-run`.
|
|
260
|
-
|
|
261
|
-
4. **AC4 precondition guard:** Even if cadence met, if `signals.sessionsSinceLast === 0 && signals.learningsSinceLast === 0`, skip with reason `no-new-input-since-last-run`. The Final Report (Phase 6) MUST include the literal string `dialectic: skipped (no new input since last run)`.
|
|
262
|
-
|
|
263
|
-
5. If `decision.trigger === true`: **do not dispatch a subagent** (see #614 — no `evolve` agent exists; `dialectic-deriver` is read-only and cannot write the sidecar). Instead, emit a manual-cadence nudge and continue:
|
|
264
|
-
|
|
265
|
-
`auto-dialectic: cadence reached (${decision.reason}) — run /evolve --dialectic --dry-run manually in the next session, review .orchestrator/dialectic-pending.md, then apply with /evolve --dialectic --apply.`
|
|
266
|
-
|
|
267
|
-
The `shouldDispatchAutoDialectic` decision helper and `scripts/lib/auto-dialectic.mjs` lib stay in use: they compute the cadence signal that drives this nudge.
|
|
268
|
-
|
|
269
|
-
6. When the nudge is emitted (cadence reached), update `.orchestrator/dialectic-last-run` via `writeDialecticLastRun({ repoRoot, isoTimestamp: new Date().toISOString() })` so the cadence counter advances and the nudge does not repeat every session. Atomic; failures non-fatal.
|
|
270
|
-
|
|
271
|
-
7. Record outcome (skipped / nudge-emitted) for Phase 6 Final Report: `auto-dialectic: manual /evolve --dialectic --dry-run recommended (cadence reached) — apply with /evolve --dialectic --apply next session`.
|
|
272
|
-
|
|
273
|
-
The `.orchestrator/dialectic-pending.md` sidecar is intentionally outside the vault tree — vault-mirror (Phase 3.7) MUST exclude it from its scope.
|
|
230
|
+
### 3.6.7 Auto-Dialectic Dispatch (#506, F2.5) — RETIRED
|
|
274
231
|
|
|
275
|
-
|
|
232
|
+
> **RETIRED 2026-09-09.** The nudge is replaced by the session-start `maintenance-due` probe (`checkMaintenanceDue`, `scripts/lib/maintenance-due-banner.mjs`), whose `dialectic` signal reads the side-effect-free `shouldDispatchAutoDialectic` — never a variant that advances the last-run stamp, which would consume the very signal it reports. Its decider is also gone from `planTailPhases()` in `scripts/lib/session-end/phase-skip.mjs`; the heading stays because other docs cite it.
|
|
233
|
+
> The housekeeping session runs `/evolve dialectic` itself (see `skills/session-start/SKILL.md` Phase 7 — the maintenance loop): dry-run first, review `.orchestrator/dialectic-pending.md`, then apply. `scripts/lib/auto-dialectic.mjs` (`shouldDispatchAutoDialectic`, `writeDialecticLastRun`) and the read-only `dialectic-deriver` agent stay in use on that manual path. The recording wrapper around that signal, and its `orchestrator.dialectic.nudge_decided` event, were removed in #1288 — nothing emits that event any more. <!-- path-check: example -->
|
|
276
234
|
|
|
277
235
|
> **Dialectic chain rationale** — design choices in the manual `/evolve --dialectic` chain (`/evolve → runDialecticDeriver → dispatchAgent → Agent`). Session-end no longer auto-dispatches this chain (see #614 — the `evolve` agent never existed); the rationale below applies when you run `/evolve --dialectic` manually:
|
|
278
236
|
> - **/evolve → subagent (not direct invoke):** the manual `/evolve --dialectic` skill spawns a subagent so the dialectic pass runs in a fresh context window — keeping the deriver's input-heavy payload (top-50 learnings + last-10 sessions + 2 peer cards + steering) out of the invoking coordinator's context, and letting the deriver run as Haiku while the coordinator stays Opus.
|
|
@@ -286,7 +244,7 @@ Cross-reference: PRD F2.5 acceptance criteria (#506); `scripts/lib/auto-dialecti
|
|
|
286
244
|
> - `reconcile.enabled` is `false` (default: `false` — opt-in; this is the silent no-op path for all repos that have not opted in)
|
|
287
245
|
> - `.orchestrator/metrics/learnings.jsonl` does not exist OR contains zero entries
|
|
288
246
|
|
|
289
|
-
After the
|
|
247
|
+
After the Skill-Applied Judge (Phase 3.6.6 — Phase 3.6.7 is retired), and when the reconcile engine is enabled, run the **reconciliation engine** to turn high-confidence learnings into conditional-rule proposals and present them to the operator via `AskUserQuestion` multiSelect. Approved proposals flow to `.claude/rules/` via `writeApprovedRules`. Rejected proposals are archived to `.orchestrator/reconcile.rejected.log`. The engine NEVER writes `.claude/rules/` itself — every write is operator-AUQ-gated (#693 FA2/FA3 brandmauer).
|
|
290
248
|
|
|
291
249
|
#### Coordinator-direct procedure
|
|
292
250
|
|
|
@@ -323,7 +281,7 @@ After the auto-dialectic nudge decision is made (Phase 3.6.7), and when the reco
|
|
|
323
281
|
|
|
324
282
|
3. If `surfaced.length === 0`: log `reconcile: 0 proposals above confidence floor (eligible=${summary.eligible}, rejected=${summary.rejected}, floor=${floor})` and continue. No AUQ, no sidecar write.
|
|
325
283
|
|
|
326
|
-
4. **Write the human-readable proposal sidecar** `.orchestrator/metrics/reconcile-pending.md` so the operator can review raw content outside the AUQ:
|
|
284
|
+
4. **Write the human-readable proposal sidecar** `.orchestrator/metrics/reconcile-pending.md` so the operator can review raw content outside the AUQ: <!-- path-check: example -->
|
|
327
285
|
|
|
328
286
|
```
|
|
329
287
|
# Reconciliation Rule Proposals — <ISO timestamp>
|
|
@@ -76,7 +76,7 @@ try {
|
|
|
76
76
|
|
|
77
77
|
## Phase 3.7b: Durable-Commit Session Telemetry (#490 AC2)
|
|
78
78
|
|
|
79
|
-
> **Ordering:** Runs AFTER Phase 3.7a (Recommendation fields just-written to STATE.md) and BEFORE Phase 3.4 (`status: completed`). The canonical runtime order is `… → 3.6.
|
|
79
|
+
> **Ordering:** Runs AFTER Phase 3.7a (Recommendation fields just-written to STATE.md) and BEFORE Phase 3.4 (`status: completed`). The canonical runtime order is `… → 3.6.6 → 3.6.8 → 3.7 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md`). Both session-end-owned files (`sessions.jsonl` from Phase 3.7, `STATE.md` from Phase 3.7a) have already been written to disk; this step only declares them as the durable-commit set.
|
|
80
80
|
|
|
81
81
|
> **Ownership:** session-end commits ONLY the two files it owns — `.orchestrator/metrics/sessions.jsonl` and `<state-dir>/STATE.md`. `.orchestrator/metrics/autopilot.jsonl` is NOT session-end's responsibility: `scripts/lib/autopilot/loop.mjs` commits that file in the autopilot loop (the core `loop.mjs` wiring shipped in #490 Wave-2). Do not add autopilot.jsonl to the files array here.
|
|
82
82
|
|
|
@@ -95,6 +95,6 @@ await withDurableCommit(
|
|
|
95
95
|
);
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
- Use the platform-resolved `<state-dir>/STATE.md` path (e.g. `~/.claude/STATE.md` on Claude Code) — NOT a hardcoded `.claude/STATE.md`.
|
|
98
|
+
- Use the platform-resolved `<state-dir>/STATE.md` path (e.g. `~/.claude/STATE.md` on Claude Code) — NOT a hardcoded `.claude/STATE.md`. <!-- path-check: example -->
|
|
99
99
|
- The `files` array is staged individually by `durableCommit` (PSA-004: never `git add .`/`-A`); the existing `SAFE_BRANCH_RE` branch-name allowlist + cwd-confinement guards in `durable-telemetry.mjs` apply unchanged.
|
|
100
100
|
- `enabled: false` short-circuits before any git command runs, so the local-execution path performs zero VCS mutation — Phase 4 (`git add` + commit) remains the single staging point for local closes.
|
|
@@ -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
|
|