session-orchestrator 3.24.0 → 4.0.1
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/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.codex-plugin/skills/architecture/SKILL.md +20 -0
- package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
- package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
- package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
- package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
- package/.codex-plugin/skills/close/SKILL.md +21 -0
- package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
- package/.codex-plugin/skills/debug/SKILL.md +21 -0
- package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/discovery/SKILL.md +21 -0
- package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
- package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
- package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
- package/.codex-plugin/skills/eli5/SKILL.md +21 -0
- package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/eval/SKILL.md +21 -0
- package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/evolve/SKILL.md +21 -0
- package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
- package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
- package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
- package/.codex-plugin/skills/go/SKILL.md +22 -0
- package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/grill/SKILL.md +21 -0
- package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
- package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
- package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
- package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
- package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
- package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
- package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/plan/SKILL.md +22 -0
- package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
- package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
- package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
- package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
- package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/release/SKILL.md +22 -0
- package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
- package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
- package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session/SKILL.md +21 -0
- package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/session-end/SKILL.md +22 -0
- package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
- package/.codex-plugin/skills/session-start/SKILL.md +22 -0
- package/.codex-plugin/skills/spinout/SKILL.md +21 -0
- package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
- package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
- package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test/SKILL.md +21 -0
- package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
- package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
- package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
- package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
- package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
- package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
- package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
- package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
- package/.cursor/commands/autopilot.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.cursor-plugin/plugin.json +30 -0
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1314 -2
- package/NOTICE +11 -6
- package/README.md +135 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +107 -29
- package/docs/components.md +38 -16
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +69 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +365 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1488 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +60 -14
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-codex-skills.mjs +246 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +292 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +88 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/host-paths.mjs +20 -4
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +30 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +41 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +215 -15
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +414 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +51 -11
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +64 -3
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +30 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +202 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-codex-skills.mjs +191 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
- package/scripts/lib/validate/check-skill-links.mjs +193 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
- package/scripts/lib/validate/check-unwired-features.mjs +163 -15
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/confidential-names.mjs +95 -30
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/validate/repo-files.mjs +48 -14
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +430 -31
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/site-numbers.mjs +344 -8
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +164 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/SKILL.md +10 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -41,13 +41,13 @@ The coordinator's **own** planned direct edits belong in `coordinator.json` in t
|
|
|
41
41
|
|
|
42
42
|
`wave-scope.json` is written into the WORKING COPY, not into the session — so until #1123 one session's manifest governed every session sharing that checkout. Measured 2026-08-22 (#1082): a Discovery wave's `allowedPaths: []` — which `wave-loop.md` prescribes for *every* Discovery wave — denied every write of an unrelated parallel session, with a deny reason that could only tell it to fix a wave plan it does not own.
|
|
43
43
|
|
|
44
|
-
Two OPTIONAL manifest fields close that: `
|
|
44
|
+
Two OPTIONAL manifest fields close that: `session_id` (the raw harness session id) and its human-readable twin `semantic_session_id` — the same spelling `.orchestrator/session.lock` and `current-session.json` use (renamed from `session` / `semantic_session` in #1153 P2; readers accept the legacy pair until the next minor release, see `MANIFEST_SESSION_KEYS` in `scripts/lib/session-identity/own-session.mjs`). Both come from ONE `attributionForRecord(repoRoot)` call (`scripts/lib/events.mjs`) in the same coordinator step that writes the rest of the manifest — `skills/wave-executor/wave-loop.md` § Scope Manifest 1. Unlike a raw lock read, `attributionForRecord()` reads `.orchestrator/session.lock` and then confirms the lock's raw `session_id` against `readProcessLocalSessionIds()` before returning anything — a mismatch (or no process-local id at all) yields `{}`, never a peer's ids (#1207).
|
|
45
45
|
|
|
46
46
|
The reader is `hooks/enforce-scope.mjs` **Gate 3b**, between the manifest parse (G3) and the path-guard gate (G4). It resolves identity via `new Set(readProcessLocalSessionIds({ hookInput: input }))` and classifies via `classifyManifestSession(scope, ownIds)`, both from [`scripts/lib/session-identity/own-session.mjs`](../scripts/lib/session-identity/own-session.mjs):
|
|
47
47
|
|
|
48
48
|
| Manifest state | `classifyManifestSession` verdict | Gate 3b disposition |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| no `
|
|
50
|
+
| no `session_id` / `semantic_session_id` (and no legacy `session` / `semantic_session`; pre-#1123) | `unknown` | ENFORCE — falls through unchanged |
|
|
51
51
|
| an id present and matching one of our own | `own` | ENFORCE — falls through unchanged |
|
|
52
52
|
| ids present, none matching, own identity resolvable | `foreign` | **ALLOW** + one `orchestrator.scope.foreign_session_ignored` event |
|
|
53
53
|
| ids present, own identity unresolvable (empty id set) | `unknown` | ENFORCE |
|
|
@@ -56,7 +56,7 @@ Five properties are choices, not omissions — and every one of them points the
|
|
|
56
56
|
|
|
57
57
|
- **Only what is PROVABLY foreign is foreign.** `readProcessLocalSessionIds()` returns the ids that are PROCESS-LOCAL — hook input (`session_id`/`sessionId`/`parent_session_id`) and `CLAUDE_CODE_SESSION_ID` — and an EMPTY set when neither yields an id, which can only produce `unknown`. The repo-global `session.lock` is deliberately NOT a tier here (#1194): it is ONE file shared by every session in the checkout, so unioning it let a peer's manifest match a peer-written lock id, classify `own`, and have Gate 7 deny the second session's legitimate writes — the exact lockout G3b exists to end. A better signal REPLACES a worse one (`.claude/rules/host-resources.md` § HR-102). A gate that guessed would turn "cannot tell" into a silent enforcement-off on every harness exporting no session id. Every value is trimmed on the way in, so a whitespace-only env var cannot enter as a phantom id that matches nothing (`.claude/rules/development.md` § env-var whitespace trap).
|
|
58
58
|
- **Union across the process-local tiers, not first-tier-wins.** Both process-local tiers are read and merged; only an id in NEITHER is somebody else's. Gating them against each other made the READER's identity a strict subset of the WRITER's — the manifest's `session` comes from `sessionAttribution()` = the same repo-global lock — and two divergences inside these tiers produce the same silent failure, the OWN manifest read `foreign` and the write gate switched itself off for the whole wave, logging an event indistinguishable from correct behaviour: (a) a nested harness where payload `session_id` ≠ `CLAUDE_CODE_SESSION_ID` (measured in `hooks/pre-bash-issue-budget.mjs` `resolveSessionId`); (b) a sub-agent invocation, whose own id is the subagent's while the manifest names the coordinator. A third divergence — a session that lost the lock race and wrote a PEER's id into its own manifest — is NO longer covered here since #1194 dropped the lock tier; it is the accepted cost in limit 11, and its defense is the writer guard. The merge only ADDS ids the process actually carries, so the security direction is unchanged: a manifest whose id appears in neither tier still classifies `foreign`. Its cost is named in limit 11 below.
|
|
59
|
-
- **The writer
|
|
59
|
+
- **The writer's binding check is mechanical, not a prose comparison (#1207).** Because the lock is repo-global, `skills/wave-executor/wave-loop.md` § Scope Manifest 1 calls `attributionForRecord(repoRoot)`, which confirms the lock's raw `session_id` against `readProcessLocalSessionIds()` internally and returns `{}` on any mismatch or absent process-local id — the coordinator writes whatever comes back, with no further comparison to perform. STATE.md is deliberately not part of this check: it is a shared working-copy artefact written by whichever session holds the lock, so under a peer-owned lock STATE.md's `session` field would agree with the lock about the same peer and "confirm" exactly the wrong id. OMIT the `session_id`/`semantic_session_id` keys whenever `attributionForRecord()` returns `{}` — unbound = ENFORCE. The reader's union covers the case anyway; the writer guard keeps the manifest readable as an audit record instead of publishing a foreign name.
|
|
60
60
|
- **Gate 3b runs after the parse, never on the raw bytes.** A corrupt manifest yields `{}`, hence no ids, hence `unknown` — and keeps failing closed. A gate that peeked at the bytes first would let a truncated manifest disarm the guard.
|
|
61
61
|
- **The empty string is a validator ERROR, not a third flavour of absent.** `validateSession()` → `validateOptionalSessionId()` in `scripts/validate-wave-scope.mjs` rejects `"session": ""` with *"an empty id attributes to nothing; omit the key entirely to declare the manifest unbound"*. An empty id satisfies a truthiness check while matching nobody, so every reader would classify the manifest FOREIGN where the writer meant UNBOUND — opposite dispositions, not a cosmetic ambiguity. An ABSENT key only WARNS, because the § 3.3 pre-union skeleton is itself an unbound manifest and so is every manifest written before #1123.
|
|
62
62
|
|
|
@@ -89,7 +89,7 @@ false
|
|
|
89
89
|
true true
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Both globs match `scripts/lib/x.mjs`, yet the direct comparison says `false`. For `assertFileScopeSubset()` that inexactness is *safe*: its glob branch reduces to verbatim presence plus literal-prefix coverage and therefore **over-approximates coverage**, which at worst accepts a union it could not fully prove. For a **collision** check the sign flips — the same over-approximation becomes a **false negative**, i.e. a missed collision, i.e. the incident. That is why the two exact stages decide first and stage 3 is reached only for pairs neither can settle.
|
|
92
|
+
Both globs match `scripts/lib/x.mjs`, yet the direct comparison says `false`. For `assertFileScopeSubset()` that inexactness is *safe*: its glob branch reduces to verbatim presence plus literal-prefix coverage and therefore **over-approximates coverage**, which at worst accepts a union it could not fully prove. For a **collision** check the sign flips — the same over-approximation becomes a **false negative**, i.e. a missed collision, i.e. the incident. That is why the two exact stages decide first and stage 3 is reached only for pairs neither can settle. <!-- path-check: example -->
|
|
93
93
|
|
|
94
94
|
## 4. The hook
|
|
95
95
|
|
|
@@ -174,7 +174,7 @@ Complete list of what this guard does **not** see, or sees only approximately:
|
|
|
174
174
|
8. **Lock loss reopens the race.** On lock timeout the cycle runs unlocked (row 14) — two dispatches starting together can then read the same ledger state and one record is lost. That is the pre-lock behaviour, chosen over denying on a lock-file problem.
|
|
175
175
|
9. **The session binding is self-declared** (§ 2.3). `session` is a plain field in a file any process in this working copy can write, so writing a foreign id into it turns the write gate off for that manifest. Named rather than hidden: it is the SAME power `enforcement: "off"` already grants in the same file, so Gate 3b adds no new authority — the manifest is the coordinator's own artefact either way.
|
|
176
176
|
10. **Only the WRITE gate is session-bound.** The dispatch ledger of § 4 takes its session component from the harness's own `input.session_id` (`waveKeyOf(projectDir, sessionId, …)`), and reads only `wave` and `role` out of `wave-scope.json` — the `session` field is not consulted there at all. So a peer session's manifest cannot bind this session's writes since #1123, but the two hooks reach that property by different routes, and a change to one does not carry to the other.
|
|
177
|
-
11. **A session that published a peer's id reads its OWN manifest as `foreign` (#1194).** Dropping the lock tier (§ 2.3) moved the cost to the other side of the trade
|
|
177
|
+
11. **A session that published a peer's id reads its OWN manifest as `foreign` (#1194) — the writer's defense is now mechanical (#1207).** Dropping the lock tier (§ 2.3) moved the cost to the other side of the trade: with a raw `sessionAttribution()` read, a session that lost the `bootstrapLock()` race could get the peer's id and write it into its own manifest, and Gate 3b would then classify the manifest `foreign`, standing its own write guard down. Since #1207 the writer calls `attributionForRecord(repoRoot)` instead — `skills/wave-executor/wave-loop.md` § Scope Manifest 1 — which confirms the lock's raw `session_id` against `readProcessLocalSessionIds()` before returning anything and yields `{}` on any mismatch, so the coordinator performs no manual comparison and STATE.md plays no part in it (a shared working-copy artefact written by whichever session holds the lock, not a process-local witness). A filled binding is therefore provably this session's own; unbound (`{}`) = ENFORCE. CEILING (BV-004): on a harness that exports no session env var and puts no `session_id` in the hook payload (Codex CLI, Cursor today) both tiers are empty, so G3b is permanently `unknown` = enforce = pre-#1123 behaviour there. Revisit when Codex/Cursor hook payloads carry a session id.
|
|
178
178
|
|
|
179
179
|
## 7. Debugging
|
|
180
180
|
|
|
@@ -61,6 +61,8 @@ Bypass via `SO_SKIP_CONFIG_VALIDATION=1`. Missing fields can be patched into an
|
|
|
61
61
|
|
|
62
62
|
**Stale-citation note:** an older code comment on the `custom-phases:` key in this repo's own `CLAUDE.md` cites a per-key regex (`/^custom-phases:\s*$/`) as the mechanism. That citation predates the #830 generalisation — `custom-phases.mjs` (like all 37 consumers) now delegates to the shared `matchBlockHeader(line, 'custom-phases')`, which is strictly MORE tolerant than the old per-key regex (it additionally accepts the dash-bullet and bold-bullet renderings). The no-inline-comment failure mode is unchanged; only the underlying mechanism moved from a bespoke regex to the shared helper. Treat any remaining per-key regex citation in prose (including in this file, prior to this section's introduction) as documentation of the OLD mechanism — the general contract above is current.
|
|
63
63
|
|
|
64
|
+
**A second, orthogonal gotcha shares this section: a multi-line `<!-- … -->` comment (#1162).** Every block-shaped parser now strips commented-out lines before matching, via `scripts/lib/config/block-preprocess.mjs` — so a block commented out to disable it can no longer be read as live config, and a bold-bullet sub-key rendering (`- **enabled:** true`) is normalised before parsing instead of silently missing its regex. The one failure mode that still exists is an **unterminated** `<!--` — a stray opener with no matching `-->` anywhere in the rest of the document. `scripts/parse-config.mjs` detects this ONCE per session (not once per parser) and prints a single stderr WARN: `⚠ <file>: unterminated <!-- at line N — comment stripping disabled for the whole document`. The fail-closed direction differs by consumer: a block PARSER gets its lines back UNFILTERED (nothing may silently vanish), while the two destructive-bypass scanners (`allow-config-weakening`, `allow-destructive-ops`) treat an unterminated comment as the bypass being **NOT ARMED** — an ambiguous document must never grant an opt-in it cannot read cleanly.
|
|
65
|
+
|
|
64
66
|
## Policy Files
|
|
65
67
|
|
|
66
68
|
Some sub-configs live in dedicated policy files under `.orchestrator/policy/`:
|
|
@@ -74,12 +76,46 @@ Some sub-configs live in dedicated policy files under `.orchestrator/policy/`:
|
|
|
74
76
|
|
|
75
77
|
| Field | Type | Default | Description |
|
|
76
78
|
|-------|------|---------|-------------|
|
|
77
|
-
| `agents-per-wave` | integer or integer with overrides | `6` | Maximum parallel subagents per wave. Supports session-type overrides: `6 (deep: 18)` outputs `{"default": 6, "deep": 18}`. Plain integers remain plain. The override key names a session type but does **not** create one: there is no `session-type:` Session Config key — `parseSessionConfig()` emits none, so writing one into a repo's `## Session Config` block is inert prose. The session type comes from the `/session` argument (default `deep`, see `commands/session.md`) and is persisted to STATE.md frontmatter as `session-type:`, which is the only live read (`scripts/print-applicable-rules.mjs` rule mode-gating). |
|
|
79
|
+
| `agents-per-wave` | integer or integer with overrides | `6` | Maximum parallel subagents per wave. Supports session-type overrides: `6 (deep: 18)` outputs `{"default": 6, "deep": 18}`. The override key set is OPEN — `_coerceInteger` (`scripts/lib/config/coercers.mjs`) parses whatever keys the parentheses contain, so `6 (deep: 18, ultradeep: 18)` outputs `{"default": 6, "deep": 18, "ultradeep": 18}` with no code change (see § Session Profile below). Plain integers remain plain. The override key names a session type but does **not** create one: there is no `session-type:` Session Config key — `parseSessionConfig()` emits none, so writing one into a repo's `## Session Config` block is inert prose. The session type comes from the `/session` argument (default `deep`, see `commands/session.md`) and is persisted to STATE.md frontmatter as `session-type:`, which is the only live read (`scripts/print-applicable-rules.mjs` rule mode-gating). |
|
|
78
80
|
| `agent-mapping` | object | null | Optional mapping of role keys to agent names for explicit agent binding. Keys: `impl`, `test`, `db`, `ui`, `security`, `compliance`, `docs`, `perf`. Example: `{ impl: code-editor, test: test-specialist }`. Overrides auto-discovery when present. Values may carry a channel prefix — see § `agent-mapping` values below. |
|
|
79
81
|
| `waves` | integer | `5` | Number of execution waves for feature and deep sessions. |
|
|
80
82
|
| `recent-commits` | integer | `20` | Number of recent commits to display during session start git analysis. |
|
|
81
83
|
| `special` | string | none | Repo-specific instructions. Freeform text that the orchestrator reads and follows during sessions. |
|
|
82
84
|
|
|
85
|
+
### Session Profile — `session-profile` (NOT a Session Config key)
|
|
86
|
+
|
|
87
|
+
`session-profile` names a WAVE-SHAPE variant on top of an unchanged `session-type`. It is listed here because it is easy to look for in the wrong place: **it is not a Session Config key and `parseSessionConfig()` does not emit one.** Writing `session-profile:` into a repo's `## Session Config` block is inert prose, exactly like `session-type:` (see the `agents-per-wave` row above).
|
|
88
|
+
|
|
89
|
+
| Aspect | Value |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Where it lives | STATE.md frontmatter (`session-profile: ultradeep`), written per session |
|
|
92
|
+
| Who writes it | The `/session ultradeep` argument alias — `commands/session.md` |
|
|
93
|
+
| Read/write API | `readSessionProfile` / `setSessionProfile` / `SESSION_PROFILE_FIELD` in `scripts/lib/state-md.mjs` |
|
|
94
|
+
| Absent means | No profile. Never an empty string, never `none` — `readSessionProfile` returns `null` |
|
|
95
|
+
| Session record | Optional `session_profile` field (`scripts/lib/session-schema/constants.mjs` `OPTIONAL_FIELDS`); records without it validate unchanged |
|
|
96
|
+
| Defined values | `ultradeep` (7 waves, coordinator-direct Synthesis-Gate at wave 2) — spec: `docs/prd/2026-09-06-ultradeep-session-profile.md` |
|
|
97
|
+
|
|
98
|
+
`session-type` NEVER becomes `ultradeep`: that value is a closed set in `scripts/lib/session-schema/constants.mjs`, `scripts/lib/wave-sizing.mjs` and `scripts/lib/session-close-backfill.mjs`, and an unknown member degrades SILENTLY there (telemetry maps it to `"other"`, the close-backfill labels it `housekeeping`). The profile field exists so no closed set has to change.
|
|
99
|
+
|
|
100
|
+
**Sizing an ultradeep session** uses the open override key set:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
agents-per-wave: 6 (deep: 18, ultradeep: 18)
|
|
104
|
+
waves: 5 # must be >= 7 for the ultradeep wave shape
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Verified against the parser (2026-09-06, `scripts/lib/config/coercers.mjs`):
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
$ node -e "import('./scripts/lib/config/coercers.mjs').then(m => console.log(JSON.stringify(
|
|
111
|
+
m._coerceInteger(new Map([['agents-per-wave','6 (deep: 18, ultradeep: 18)']]), 'agents-per-wave', 6))))"
|
|
112
|
+
{"default":6,"deep":18,"ultradeep":18}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Two consumers resolve that object to `.default` rather than to a mode key — `scripts/lib/resource-probe/evaluate.mjs` and `scripts/lib/wave-resource-gate.mjs` (see `heavy-repo` in § Environment Awareness) — so an `ultradeep: 18` override does NOT raise the resource gate's cap.
|
|
116
|
+
|
|
117
|
+
**Budgets are deliberately absent.** The PRD's `ultradeep.max-agents-total` / `max-wall-clock-hours` / `max-output-tokens` / `on-breach` block (§ 7) is NOT implemented and no key of that name is read anywhere. It stays deferred until three ultradeep runs have been measured, per `.claude/rules/host-resources.md` HR-105 — a threshold whose firing rate nothing records is unfalsifiable. Do not add one ahead of the measurement.
|
|
118
|
+
|
|
83
119
|
### `agent-mapping` values — channel prefixes (#1150)
|
|
84
120
|
|
|
85
121
|
A mapping value has three forms, distinguished by the colon:
|
|
@@ -281,7 +317,7 @@ slopcheck:
|
|
|
281
317
|
| `grounding-injection-max-files` | integer | `3` | Max files with recent `edit-format-friction` stagnation history to inject as line-numbered GROUNDING blocks into each agent's prompt before dispatch (wave-executor pre-dispatch step). Per-agent scope; selects top N by recency. `0` disables the feature. Gated on `persistence: true`. (#85) |
|
|
282
318
|
| `isolation` | string | `auto` | Agent isolation mode: `worktree`, `none`, or `auto`. `auto` resolves per-wave via the graduated default (#194): ≤2 agents → `none`, 3–4 agents on feature/deep → `worktree`, ≥5 agents → `worktree`, housekeeping 3–4 → `none`. Explicit `worktree` or `none` overrides the graduation. See [isolation graduation](#isolation-graduation) below. |
|
|
283
319
|
| `max-turns` | integer or string | `auto` | Maximum agent turns before PARTIAL. Auto: housekeeping=8, feature=15, deep=25. |
|
|
284
|
-
| `auto-commit-per-wave` | boolean | `false` | Automatically commit each wave's work after the Quality-Lite gate passes. Checkpoint commits per wave reduce the risk of data loss from `git stash` collisions in parallel sessions (V3.3 RESCUE incident — see GitLab #214). When `false`, all work is committed at session-end via `/close`. Requires `persistence: true`; the flag is silently ignored when `persistence: false`. Trade-off: each wave produces an additional commit; git log shows N+1 commits instead of 1. Use `/simplify` or `git rebase -i --autosquash` before final close to squash if a clean history is desired. **Implementation note:** the procedural commit sequence (`scripts/lib/auto-commit.mjs`) is deferred to V3.6. Until then, setting this flag to `true` triggers a session-start warning that auto-commits are not yet active — the flag is a no-op but is validated so projects can opt in early. |
|
|
320
|
+
| `auto-commit-per-wave` | boolean | `false` | Automatically commit each wave's work after the Quality-Lite gate passes. Checkpoint commits per wave reduce the risk of data loss from `git stash` collisions in parallel sessions (V3.3 RESCUE incident — see GitLab #214). When `false`, all work is committed at session-end via `/close`. Requires `persistence: true`; the flag is silently ignored when `persistence: false`. Trade-off: each wave produces an additional commit; git log shows N+1 commits instead of 1. Use `/simplify` or `git rebase -i --autosquash` before final close to squash if a clean history is desired. **Implementation note:** the procedural commit sequence (`scripts/lib/auto-commit.mjs`) is deferred to V3.6. Until then, setting this flag to `true` triggers a session-start warning that auto-commits are not yet active — the flag is a no-op but is validated so projects can opt in early. <!-- path-check: historical --> |
|
|
285
321
|
|
|
286
322
|
### enforcement-gates: the five gate keys (#800/#915)
|
|
287
323
|
|
|
@@ -704,6 +740,8 @@ vault-integration:
|
|
|
704
740
|
|
|
705
741
|
> **Host-local override (#653; extended #819).** `vault-dir` resolves host-locally with precedence: env-var (`SO_VAULT_DIR`) > `owner.yaml` `paths.vault-dir` > the committed default. `plan-baseline-path` resolves with an extra per-context tier in between: `SO_BASELINE_PATH` env > `owner.yaml` `baselines:` directory-prefix match against cwd > `owner.yaml` `paths.baseline-path` (legacy scalar) > the committed default. This keeps maintainer-specific absolute paths out of version control. Resolvers: `scripts/lib/config/host-paths.mjs` (both keys) and `scripts/lib/named-baseline-resolver.mjs` (the `baselines:` match tier).
|
|
706
742
|
|
|
743
|
+
> **`SO_CONFIG_HOME` — the host-private config directory itself.** A sibling override, one layer below `owner.yaml`'s own contents rather than a key inside it: `scripts/lib/host-identity.mjs` `_privateDir()` resolves the directory holding `owner.yaml`, `host-private.json`, and the host-alias ledger (`SO_HOST_ALIASES_FILE`, see `host-identity.mjs`) with precedence env-var (`SO_CONFIG_HOME`, names the private dir ITSELF) > `XDG_CONFIG_HOME` (names its PARENT — `owner-config-loader.mjs` uses the same variable the same way) > the homedir default `~/.config/session-orchestrator`. Both env vars are read with `.trim() || fallback`, not a bare `||` (`.claude/rules/development.md` § Error Handling env-var-fallback-whitespace trap).
|
|
744
|
+
|
|
707
745
|
> **Parser accepts three key-line renderings (#823).** The `vault-integration:` key line is recognized in plain form (`vault-integration:`), dash-bullet form (`- vault-integration:`), and bold-bullet form (`- **vault-integration:**`) — each paired with either the inline-object shape (`{ enabled: true, ... }` on the same line) or the indented block shape shown above. Parser: `scripts/lib/config/vault-integration.mjs` (`_parseVaultIntegration`).
|
|
708
746
|
|
|
709
747
|
| Field | Type | Default | Description |
|
|
@@ -844,7 +882,7 @@ Memory proposals are one of five Epic #498 Phase 2 features that share the same
|
|
|
844
882
|
|
|
845
883
|
Together: F2.1 captures fresh insight mid-flight, F2.2 consolidates old insight at scale, F2.3 surfaces it at the start, F2.4/F2.5 distill it into the durable peer-card profiles.
|
|
846
884
|
|
|
847
|
-
**Used by:** `scripts/lib/memory-proposals/{schema,store,collector,sink}.mjs`, `scripts/memory-propose.mjs`, `
|
|
885
|
+
**Used by:** `scripts/lib/memory-proposals/{schema,store,collector,sink}.mjs`, `scripts/memory-propose.mjs`, `docs/memory-proposal-flow.md`, `hooks/pre-bash-memory-propose-audit.mjs`, `skills/session-end/SKILL.md` Phase 3.6.3.
|
|
848
886
|
|
|
849
887
|
**Cross-reference:** issue #501, PRD F2.1 in the Learning-Memory Modernization PRD; issue #741.3 (`--dry-run` flag + `dry-run-ok` status). Sibling features: `memory.banner` (above, F2.3 / #505), `dialectic.cadence` (F2.5 / #506), Auto-Dream (F2.2 / #502, surfaced via `memory-cleanup-soft-limit`).
|
|
850
888
|
|
|
@@ -1240,7 +1278,7 @@ remote-hosts:
|
|
|
1240
1278
|
|
|
1241
1279
|
**Two enums, never conflated.** `roles-allowed` holds `agent-mapping` roles (`test`, `ui`, `perf`) — NOT wave roles (`Impl-Core`, `Quality`, …). The wave→role translation is `OFFLOADABLE_WAVE_ROLES` in `scripts/lib/wave-resource-gate.mjs`; a wave role absent from that map is local-only by default.
|
|
1242
1280
|
|
|
1243
|
-
**Placement contract.** The gate applies its offload arm only after the HR-004 heavy-repo cap, and only when the resource verdict was `reduce` or `coordinator-direct`. It does NOT probe the network: the coordinator supplies a readiness witness (`remoteReady: { m5: true }`, or an async `probeFn`). With no witness, no host counts as ready and the decision stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/
|
|
1281
|
+
**Placement contract.** The gate applies its offload arm only after the HR-004 heavy-repo cap, and only when the resource verdict was `reduce` or `coordinator-direct`. It does NOT probe the network: the coordinator supplies a readiness witness (`remoteReady: { m5: true }`, or an async `probeFn`). With no witness, no host counts as ready and the decision stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/dispatch-common.mjs`) is never offloaded regardless.
|
|
1244
1282
|
|
|
1245
1283
|
**agent-mapping interaction.** A declared alias is what an `agent-mapping` value of the form `<role>: ssh:<alias>` validates against; naming an undeclared host throws at parse time, naming the `ssh` channel with no target throws as for any other channel.
|
|
1246
1284
|
|
|
@@ -1542,43 +1580,7 @@ SO_DISABLED_HOOKS=enforce-scope,enforce-commands claude ...
|
|
|
1542
1580
|
|
|
1543
1581
|
Each hook handler imports `shouldRunHook` from `hooks/_lib/profile-gate.mjs` at the top level and calls `process.exit(0)` immediately when gated off. The exit is silent (no stdout, no stderr), so Claude Code sees an allow as if the hook had never run.
|
|
1544
1582
|
|
|
1545
|
-
##
|
|
1546
|
-
|
|
1547
|
-
Opt-in webhook notifications delivered by `scripts/lib/webhook-url.mjs`. The helper centralizes URL resolution so no personal-domain default ever silently fires — callers must supply a URL explicitly.
|
|
1548
|
-
|
|
1549
|
-
### Resolution order
|
|
1550
|
-
|
|
1551
|
-
For every supported kind the resolver checks sources in this order; the first non-empty string wins:
|
|
1552
|
-
|
|
1553
|
-
1. **Environment variable** `SO_WEBHOOK_<KIND>_URL` — uppercase kind, hyphens → underscores
|
|
1554
|
-
e.g. `SO_WEBHOOK_SLACK_URL`, `SO_WEBHOOK_GITLAB_PIPELINE_STATUS_URL`
|
|
1555
|
-
2. **Session Config** `webhooks.<kind>.url`
|
|
1556
|
-
3. **Error** — `WebhookConfigError` is thrown. No silent personal-domain fallback.
|
|
1557
|
-
|
|
1558
|
-
### Supported kinds
|
|
1559
|
-
|
|
1560
|
-
| Kind | Env variable | Config key |
|
|
1561
|
-
|------|-------------|------------|
|
|
1562
|
-
| `slack` | `SO_WEBHOOK_SLACK_URL` | `webhooks.slack.url` |
|
|
1563
|
-
| `discord` | `SO_WEBHOOK_DISCORD_URL` | `webhooks.discord.url` |
|
|
1564
|
-
| `generic` | `SO_WEBHOOK_GENERIC_URL` | `webhooks.generic.url` |
|
|
1565
|
-
| `gitlab-pipeline-status` | `SO_WEBHOOK_GITLAB_PIPELINE_STATUS_URL` | `webhooks.gitlab-pipeline-status.url` |
|
|
1566
|
-
|
|
1567
|
-
### Session Config example
|
|
1568
|
-
|
|
1569
|
-
```yaml
|
|
1570
|
-
webhooks:
|
|
1571
|
-
slack:
|
|
1572
|
-
url: https://hooks.slack.com/services/REDACTED/REDACTED/REDACTED
|
|
1573
|
-
discord:
|
|
1574
|
-
url: https://discord.com/api/webhooks/REDACTED/REDACTED
|
|
1575
|
-
generic:
|
|
1576
|
-
url: https://example.com/hooks/session-events
|
|
1577
|
-
gitlab-pipeline-status:
|
|
1578
|
-
url: https://gitlab.example.com/hooks/pipeline
|
|
1579
|
-
```
|
|
1580
|
-
|
|
1581
|
-
### Clank Event Bus (events.mjs / on-stop.mjs)
|
|
1583
|
+
## Clank Event Bus (events.mjs / on-stop.mjs)
|
|
1582
1584
|
|
|
1583
1585
|
The internal Clank Event Bus webhook is controlled by two environment variables:
|
|
1584
1586
|
|
|
@@ -1647,24 +1649,23 @@ Set `express-path.enabled: false` when:
|
|
|
1647
1649
|
- `skills/session-plan/SKILL.md` — Express Path Short-Circuit section (1-wave plan emission)
|
|
1648
1650
|
- GitLab issue `#214` (foundation and codification)
|
|
1649
1651
|
|
|
1650
|
-
## Autopilot Multi-Story (#431)
|
|
1651
|
-
|
|
1652
|
-
Opt-in configuration for `autopilot --multi-story` (`scripts/autopilot-multi.mjs`). Controls how parallel story pipelines are isolated when N stories run concurrently. Projects that do not use `--multi-story` leave this block unset and are unaffected.
|
|
1653
|
-
|
|
1654
|
-
All fields live under a top-level `autopilot` object in your Session Config host file (`CLAUDE.md` or `AGENTS.md`), for example:
|
|
1655
|
-
|
|
1656
|
-
```yaml
|
|
1657
|
-
autopilot:
|
|
1658
|
-
bg-isolation: worktree # worktree | none (default: worktree)
|
|
1659
|
-
```
|
|
1652
|
+
## Autopilot Multi-Story (#431) — removed
|
|
1660
1653
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1654
|
+
The `autopilot` block and its single field `autopilot.bg-isolation` are **gone**, not
|
|
1655
|
+
deprecated. Their only reader was `scripts/autopilot-multi.mjs`, retired together with <!-- path-check: historical -->
|
|
1656
|
+
`commands/autopilot-multi.md` by the 2026-09-06 360°-Audit (§ 5A: 0 telemetry, 0 fleet
|
|
1657
|
+
invocations in 90 days, no runtime consumer).
|
|
1664
1658
|
|
|
1665
|
-
|
|
1659
|
+
Verified 2026-09-06 at `e4674109`:
|
|
1660
|
+
`rg -n "bg-isolation|bgIsolation|deconflict-paths" scripts hooks tests` returns nothing;
|
|
1661
|
+
`scripts/parse-config.mjs` never parsed an `autopilot` key at all; `scripts/autopilot.mjs`
|
|
1662
|
+
has no `--multi-story` mode. Documenting the field as functional would therefore have been
|
|
1663
|
+
the exact failure the audit found elsewhere — a key an operator can set and no code can
|
|
1664
|
+
read. <!-- path-check: historical -->
|
|
1666
1665
|
|
|
1667
|
-
**
|
|
1666
|
+
**If your Session Config still carries an `autopilot:` block, delete it.** It is inert: no
|
|
1667
|
+
parser reads it, so removing it changes no behaviour. Single-story `/autopilot` is
|
|
1668
|
+
unaffected and takes no Session Config block.
|
|
1668
1669
|
|
|
1669
1670
|
## Wave Reviewers
|
|
1670
1671
|
|
|
@@ -57,6 +57,10 @@ special: "any repo-specific instructions" # freeform — orchestrator reads +
|
|
|
57
57
|
|
|
58
58
|
Read by: `skills/session-start/SKILL.md` (Phase 4.5), `skills/session-plan/SKILL.md`, `skills/wave-executor/wave-loop.md`.
|
|
59
59
|
|
|
60
|
+
**The override key set is open.** `_coerceInteger` (`scripts/lib/config/coercers.mjs`) parses whatever keys stand inside the parentheses, so `agents-per-wave: 6 (deep: 18, ultradeep: 18)` is valid today with no code change — it yields `{"default": 6, "deep": 18, "ultradeep": 18}`.
|
|
61
|
+
|
|
62
|
+
**`session-profile` is NOT a Session Config key — do not add one here.** The wave-shape profile (`ultradeep`) lives in STATE.md frontmatter, written per session by the `/session ultradeep` argument alias, and is absent by default. `parseSessionConfig()` emits no such key, so writing one into a repo's `## Session Config` block is inert prose — the same trap as `session-type:`. Full contract: [`session-config-reference.md` § Session Profile](./session-config-reference.md). The PRD's `ultradeep.max-*` budget block is deliberately NOT implemented and no key of that name is read anywhere (deferred until measured, HR-105).
|
|
63
|
+
|
|
60
64
|
## VCS & Infrastructure
|
|
61
65
|
|
|
62
66
|
```yaml
|
|
@@ -274,7 +278,7 @@ memory:
|
|
|
274
278
|
|
|
275
279
|
Agents invoke via `SO_WAVE_AGENT=1 node scripts/memory-propose.mjs …`. The `SO_WAVE_AGENT=1` env-var is set automatically by the wave-executor boilerplate; direct CLI calls without it exit `3` (`rejected-wrong-context`).
|
|
276
280
|
|
|
277
|
-
Read by: `scripts/lib/memory-proposals/{schema,store,collector,sink}.mjs`, `scripts/memory-propose.mjs`, `
|
|
281
|
+
Read by: `scripts/lib/memory-proposals/{schema,store,collector,sink}.mjs`, `scripts/memory-propose.mjs`, `docs/memory-proposal-flow.md`, `hooks/pre-bash-memory-propose-audit.mjs`, `skills/session-end/SKILL.md` Phase 3.6.3.
|
|
278
282
|
|
|
279
283
|
## Auto-Dream Proposal Filter (#566)
|
|
280
284
|
|
|
@@ -612,26 +616,6 @@ express-path:
|
|
|
612
616
|
|
|
613
617
|
Read by: `skills/session-start/phase-8-5-express-path.md`, `skills/session-plan/SKILL.md` (express-path short-circuit).
|
|
614
618
|
|
|
615
|
-
## Webhooks
|
|
616
|
-
|
|
617
|
-
Opt-in webhook notifications. The `scripts/lib/webhook-url.mjs` resolver checks env first (`SO_WEBHOOK_<KIND>_URL`), then this Session Config block. **No personal-domain default** — callers must supply a URL or the resolver throws.
|
|
618
|
-
|
|
619
|
-
```yaml
|
|
620
|
-
webhooks:
|
|
621
|
-
slack:
|
|
622
|
-
url: https://hooks.slack.com/services/REDACTED/REDACTED/REDACTED
|
|
623
|
-
discord:
|
|
624
|
-
url: https://discord.com/api/webhooks/REDACTED/REDACTED
|
|
625
|
-
generic:
|
|
626
|
-
url: https://example.com/hooks/session-events
|
|
627
|
-
gitlab-pipeline-status:
|
|
628
|
-
url: https://gitlab.example.com/hooks/pipeline
|
|
629
|
-
```
|
|
630
|
-
|
|
631
|
-
Measured: `scripts/lib/webhook-url.mjs` (`resolveWebhookUrl`) is the only reader of this `webhooks:` block, and it currently has **zero callers repo-wide** (`grep -rn "webhook-url" scripts/ hooks/` outside itself and one exemption comment in `check-unwired-features.mjs`) — the block is unreachable at HEAD; follow-up issue pending.
|
|
632
|
-
|
|
633
|
-
What actually fires a webhook today is a **separate** mechanism: `scripts/lib/events.mjs`'s `emitEvent()` reads `CLANK_EVENT_SECRET` + `CLANK_EVENT_URL` directly from the environment (never from this Session Config block) and, when both are set, fire-and-forget POSTs every emitted event to the internal Clank Event Bus. Every hook that calls `emitEvent()` — which is most of `hooks/` — participates in that path; none of them reads `webhooks:` here.
|
|
634
|
-
|
|
635
619
|
## Hook Runtime Profile (env-only, not config)
|
|
636
620
|
|
|
637
621
|
`SO_HOOK_PROFILE` and `SO_DISABLED_HOOKS` are environment variables, **not Session Config fields**. They control hook execution at runtime without editing `hooks.json`.
|
|
@@ -682,7 +666,7 @@ That's enough for `/session feature` → `/go` → `/close` to work end-to-end.
|
|
|
682
666
|
|
|
683
667
|
## Full opt-in baseline (copy-paste)
|
|
684
668
|
|
|
685
|
-
Everything turned on for a project that wants the full feature surface (vault, docs, drift checks, env-aware sizing
|
|
669
|
+
Everything turned on for a project that wants the full feature surface (vault, docs, drift checks, env-aware sizing). Trim to taste:
|
|
686
670
|
|
|
687
671
|
```yaml
|
|
688
672
|
## Session Config
|
|
@@ -969,13 +953,6 @@ config-protection:
|
|
|
969
953
|
mode: warn # warn | strict (strict blocks loosening, exit 2)
|
|
970
954
|
allow-config-weakening: false # per-session bypass (mirrors allow-destructive-ops)
|
|
971
955
|
|
|
972
|
-
# Webhooks (URLs are required when used — no defaults)
|
|
973
|
-
# webhooks:
|
|
974
|
-
# slack:
|
|
975
|
-
# url: https://hooks.slack.com/services/...
|
|
976
|
-
# gitlab-pipeline-status:
|
|
977
|
-
# url: https://gitlab.example.com/hooks/pipeline
|
|
978
|
-
|
|
979
956
|
# Agent mapping
|
|
980
957
|
agent-mapping:
|
|
981
958
|
impl: code-implementer
|
package/docs/telemetry.md
CHANGED
|
@@ -39,8 +39,11 @@ projection unit test enforces the drop of any non-whitelisted input field.
|
|
|
39
39
|
| `arch` | CPU architecture (e.g. `arm64`, `x64`). |
|
|
40
40
|
| `node_major` | Major Node.js version in use. |
|
|
41
41
|
| `ci` | Boolean — whether the run was detected as a CI environment. |
|
|
42
|
-
| `fleet` | Boolean —
|
|
43
|
-
| `
|
|
42
|
+
| `fleet` | Boolean — **DEPRECATED since 2026-09-06, removal 2027-03-06.** Identical in value to `fleet_self_declared` for the whole deprecation generation; kept so the server's existing `fleet` column stays comparable across the rename. |
|
|
43
|
+
| `fleet_self_declared` | Boolean, optional — the client's own claim that this send came from an operator host. **Self-declared, and the name says so on purpose:** the authoritative classification is server-side (see below). Derived from the *resolved consent state* (`enabled-fleet` from an `owner.yaml` opt-in, or `enabled-env` from `SO_TELEMETRY=1`), no longer from a raw `owner.yaml` read. |
|
|
44
|
+
| `session_profile` | Optional — the STATE.md frontmatter `session-profile`, and **whitelisted profile names only** (today exactly `ultradeep`). Anything else — a value your repo invented, a client name, a typo — is **omitted from the ping entirely**: never sent verbatim, and never flattened to `other` either. A SECOND axis beside `session_type`, never a substitute for it: an ultradeep session is `session_type: "deep"` PLUS `session_profile: "ultradeep"`. **Absent when no profile is set or the profile is not on the whitelist** (the key is omitted, never `null`), including on derived pings, which never invent one. The whitelist is enforced twice — client-side before the send, and again server-side, which rejects a record carrying an unlisted profile rather than storing it. |
|
|
45
|
+
| `session_record` | Optional — WHICH source the session facts in this ping came from: `ledger` (a matching `sessions.jsonl` record), `derived` (reconstructed from `events.jsonl`), `absent` (neither). When `absent`, `session_type` is `unknown` and `duration_bucket` is **not a measurement**. |
|
|
46
|
+
| `session_type` | One of `housekeeping`, `feature`, `deep`, `other`, `unknown`. `other` means MEASURED but not one of the three modes; `unknown` means NOT MEASURED. Before 2026-09-06 both collapsed to `other`. |
|
|
44
47
|
| `duration_bucket` | One of `<15m`, `15-60m`, `1-3h`, `>3h` — a coarse bucket, never an exact duration. |
|
|
45
48
|
| `skills[]` | Names of invoked skills, filtered against the shipped plugin roster — any name not in that roster becomes `"other"`. |
|
|
46
49
|
| `commands[]` | Names of invoked slash-commands, same filtering rule as `skills[]`. |
|
|
@@ -79,7 +82,12 @@ towards anonymizing rather than towards attributing:
|
|
|
79
82
|
This list is a hard invariant, not a deferral:
|
|
80
83
|
|
|
81
84
|
- No repository names, no file paths, no git remotes.
|
|
82
|
-
- No prompts, no session transcripts, no free-form text of any kind.
|
|
85
|
+
- No prompts, no session transcripts, no free-form text of any kind. Every
|
|
86
|
+
field on the wire is either a number, a boolean, or a value from a closed
|
|
87
|
+
set this repository ships. The last free-text field, `session_profile`, was
|
|
88
|
+
closed on 2026-09-06: it now carries whitelisted profile names only, and an
|
|
89
|
+
unlisted value is dropped before the payload is built (and refused again by
|
|
90
|
+
the server, so it cannot be stored even if some other client sent it).
|
|
83
91
|
- No command arguments — only whitelisted command/skill *names*, and only
|
|
84
92
|
from the shipped roster (anything else is reduced to `"other"`).
|
|
85
93
|
- No hostnames.
|
|
@@ -180,6 +188,38 @@ the offline queue is non-empty or a session has completed since — the latter
|
|
|
180
188
|
clause is what lets the fallback originate a ping instead of only retrying a
|
|
181
189
|
failed one (#1138).
|
|
182
190
|
|
|
191
|
+
## The other thing that leaves the host: the update check
|
|
192
|
+
|
|
193
|
+
Telemetry is not the only outbound request this plugin can make, so the second
|
|
194
|
+
one is documented here rather than somewhere an egress audit would miss it.
|
|
195
|
+
|
|
196
|
+
On **SessionStart**, `hooks/on-session-start.mjs` calls the plugin-update banner
|
|
197
|
+
(`scripts/lib/plugin-update-banner.mjs`), which asks npm whether a newer release
|
|
198
|
+
exists:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
GET https://registry.npmjs.org/session-orchestrator/latest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
What it is, precisely:
|
|
205
|
+
|
|
206
|
+
- **No payload and no identifier.** It is a plain `GET` of a constant, public
|
|
207
|
+
URL — no body, no query string, no `anon_id`, no headers this plugin adds.
|
|
208
|
+
npm sees a request for a public package's metadata, as `npm view` would.
|
|
209
|
+
- **At most once per 24 h.** The answer is cached
|
|
210
|
+
(`plugin-latest.json`, `CACHE_TTL_MS = 24 h`); within the TTL no request is
|
|
211
|
+
made at all. The request has a short timeout and every failure is silent.
|
|
212
|
+
- **Independent of telemetry consent.** It is not a ping and sends nothing about
|
|
213
|
+
you — but it is still traffic, so it honours the offline flags below.
|
|
214
|
+
|
|
215
|
+
**Kill switches** (any one of them, set to anything other than empty / `0` /
|
|
216
|
+
`false`, turns the whole check off — not merely the request; the SessionStart
|
|
217
|
+
record then also omits `plugin_version_latest`):
|
|
218
|
+
|
|
219
|
+
- `SO_DISABLE_UPDATE_CHECK` — this probe's own switch.
|
|
220
|
+
- `DO_NOT_TRACK` — the standard flag, also honoured by the telemetry path.
|
|
221
|
+
- `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`.
|
|
222
|
+
|
|
183
223
|
## Retention
|
|
184
224
|
|
|
185
225
|
- **Raw records:** kept 24 months, then pruned. The retention window exists
|
|
@@ -190,6 +230,99 @@ failed one (#1138).
|
|
|
190
230
|
- **Anonymous ID rotation:** every 90 days, independent of retention — a
|
|
191
231
|
rotated ID cannot be linked back to the one it replaced.
|
|
192
232
|
|
|
233
|
+
## When a ping is sent
|
|
234
|
+
|
|
235
|
+
Two triggers, deliberately independent of each other:
|
|
236
|
+
|
|
237
|
+
1. **SessionEnd** (`hooks/on-session-end.mjs`) — the mechanical close-time
|
|
238
|
+
flush.
|
|
239
|
+
2. **SessionStart** (`backfillOnSessionStart` in
|
|
240
|
+
`scripts/backfill-abandoned-sessions.mjs`) — drains whatever the PREVIOUS
|
|
241
|
+
session left queued. Since 4.0.0 every queued record is re-projected and
|
|
242
|
+
`session_profile`-whitelisted at the transport boundary before it is sent,
|
|
243
|
+
and a batch the server rejects with HTTP 400/422 is EVICTED (breadcrumb
|
|
244
|
+
`reason: rejected-evicted`) instead of being re-queued forever — a poison
|
|
245
|
+
record can no longer block every later flush.
|
|
246
|
+
|
|
247
|
+
Trigger 2 exists because trigger 1 fires only on a REGULAR close, and most
|
|
248
|
+
sessions do not have one: measured 2026-09-06 over 90 fleet days, **429 clean
|
|
249
|
+
closes against 2.016 distinct `session.started` ids = 21,3 %**. Roughly four
|
|
250
|
+
sessions in five never reached the only code path that sends. SessionStart is
|
|
251
|
+
the trigger that survives whatever killed the previous session — the same
|
|
252
|
+
argument the abandoned-session backfill already makes for the ledger.
|
|
253
|
+
|
|
254
|
+
The start-time flush is bounded (1,5 s POST budget), lazily imported, gated by
|
|
255
|
+
the same consent check, and swallows every error: it can never delay or break a
|
|
256
|
+
session start. A timeout is lossless — the batch lands in the offline queue.
|
|
257
|
+
|
|
258
|
+
A ping no longer depends on `sessions.jsonl`. When the ledger has no matching
|
|
259
|
+
record, `session_type` and `duration_bucket` are reconstructed from
|
|
260
|
+
`events.jsonl` and the ping is stamped `session_record: "derived"`; when neither
|
|
261
|
+
source has a type, it is `session_type: "unknown"` with `session_record:
|
|
262
|
+
"absent"` — never a measured-looking `other`.
|
|
263
|
+
|
|
264
|
+
### Sandbox guard
|
|
265
|
+
|
|
266
|
+
The sender refuses to send when it is not running in a real operator session.
|
|
267
|
+
This is not a nicety: on 2026-09-06 six agent sandboxes ran the SessionEnd hook
|
|
268
|
+
from a repo checkout and sent **six real pings to the production ingest server**,
|
|
269
|
+
minted against the operator's real `anon_id`, because `telemetry/paths.mjs`
|
|
270
|
+
resolves `~/.config/session-orchestrator/` from `homedir()` and does not honour
|
|
271
|
+
`SO_CONFIG_HOME` — faking the source never faked the destination.
|
|
272
|
+
|
|
273
|
+
A send is refused (no network, no queue write, no anon-ID mint) when **any** of:
|
|
274
|
+
|
|
275
|
+
- `SO_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK` is set;
|
|
276
|
+
- `SO_CONFIG_HOME` / `XDG_CONFIG_HOME` points somewhere other than the directory
|
|
277
|
+
the telemetry state is actually read from (unless the caller redirected the
|
|
278
|
+
state path too — that redirect succeeded, which is the opposite of the leak);
|
|
279
|
+
- `CLAUDE_PROJECT_DIR`, or the cwd, sits under the OS temp directory or `/tmp`.
|
|
280
|
+
|
|
281
|
+
The guard also **fails closed**: if any of its own probes throws, the send is
|
|
282
|
+
refused with `sandbox:probe-failed` rather than permitted. An environment the
|
|
283
|
+
guard could not classify is treated as one it would have refused; nothing is
|
|
284
|
+
lost, because the next session re-probes from scratch.
|
|
285
|
+
|
|
286
|
+
If you invoke any telemetry writer by hand, export `SO_TELEMETRY_DISABLED=1`.
|
|
287
|
+
|
|
288
|
+
## Server-side fleet attribution
|
|
289
|
+
|
|
290
|
+
The `fleet` flag on the wire is **self-declared and was measurably wrong**.
|
|
291
|
+
Until 2026-09-06 the client derived it as `ownerConfig?.telemetry?.enabled
|
|
292
|
+
=== true` — a statement about a FILE, not about a person. The operator's
|
|
293
|
+
second Mac has consent granted but no `telemetry:` block in `owner.yaml`,
|
|
294
|
+
so it declared itself external: **394 of 490 server records (80,4 %)**
|
|
295
|
+
counted the operator as an external user, and every week's
|
|
296
|
+
`fleet_vs_external` was wrong by that margin.
|
|
297
|
+
|
|
298
|
+
Two independent repairs, because the client alone cannot close this:
|
|
299
|
+
|
|
300
|
+
1. **Client-side** — `fleet_self_declared` is derived from the resolved
|
|
301
|
+
consent state (`enabled-fleet` / `enabled-env`), so a host opted in via
|
|
302
|
+
`SO_TELEMETRY=1` is no longer mistaken for an external install. The name
|
|
303
|
+
states the limit: a sandbox, or a host whose `owner.yaml` is unreachable,
|
|
304
|
+
still declares `false` however honest it is.
|
|
305
|
+
2. **Server-side (authoritative)** — set `SO_INGEST_FLEET_ANON_IDS` on the
|
|
306
|
+
ingest server to a comma-separated list of the operator's own `anon_id`
|
|
307
|
+
values. A matching record is **stored** as fleet regardless of what it
|
|
308
|
+
claims. The allowlist can only PROMOTE, never demote: a host that honestly
|
|
309
|
+
declares itself fleet stays fleet even if the operator forgot to list it.
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
SO_INGEST_FLEET_ANON_IDS=a3bb4907-…,c29cac99-…
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The record survives verbatim in `raw_json`, including its own `fleet` /
|
|
316
|
+
`fleet_self_declared` claim, so the client's declaration and the server's
|
|
317
|
+
verdict remain separable forever and the disagreement rate stays measurable.
|
|
318
|
+
`fleet_vs_external` in the weekly digest reads the stored column, i.e. the
|
|
319
|
+
server verdict. **The allowlist applies at INSERT time**, so it cannot repair
|
|
320
|
+
rows already written — a `fleet_vs_external` computed over a range that
|
|
321
|
+
predates the change is known-wrong and re-running the digest will not fix it.
|
|
322
|
+
|
|
323
|
+
Unset by default: with no `SO_INGEST_FLEET_ANON_IDS`, storage takes the
|
|
324
|
+
client's word exactly as it did before.
|
|
325
|
+
|
|
193
326
|
## Schema evolution
|
|
194
327
|
|
|
195
328
|
The schema is **additive-only** within a given `schema_version`: new
|
|
@@ -198,6 +331,27 @@ without a version bump. The server accepts both the current and the
|
|
|
198
331
|
immediately previous `schema_version`, so a slightly-outdated client is
|
|
199
332
|
never hard-broken by a server-side schema update.
|
|
200
333
|
|
|
334
|
+
Unknown top-level fields are accepted by the server and preserved verbatim
|
|
335
|
+
inside `raw_json`, so an additive field round-trips through a server that
|
|
336
|
+
predates it — which is what makes a *rename* safe: emit both names for one
|
|
337
|
+
generation, then drop the old one.
|
|
338
|
+
|
|
339
|
+
**In flight now (added 2026-09-06, schema v1, additive):**
|
|
340
|
+
|
|
341
|
+
| Field | Status | Removal |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| `fleet_self_declared` | new name for `fleet` | — |
|
|
344
|
+
| `fleet` | deprecated alias, same value | **2027-03-06** |
|
|
345
|
+
| `session_record` | new (`ledger` \| `derived` \| `absent`) | — |
|
|
346
|
+
| `session_profile` | new (STATE.md `session-profile`, whitelisted names only; omitted when unset or unlisted) | — |
|
|
347
|
+
|
|
348
|
+
Client-side the frozen whitelist is split in two: `USAGE_PING_FIELDS` (the
|
|
349
|
+
REQUIRED v1 contract, which `tests/telemetry/parity.test.mjs` asserts the
|
|
350
|
+
server independently requires field by field) and
|
|
351
|
+
`USAGE_PING_OPTIONAL_FIELDS`. `projectUsagePing` projects the UNION, so the
|
|
352
|
+
data-minimization tripwire still holds — a field must be on a reviewed list
|
|
353
|
+
before it can reach the wire.
|
|
354
|
+
|
|
201
355
|
## Relationship to `telemetry-claims.md`
|
|
202
356
|
|
|
203
357
|
This page describes the **opt-in, client-side usage-telemetry pipeline**
|