@treeseed/dsh-chapters 0.1.22 → 0.1.24

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.
Files changed (46) hide show
  1. package/cordis.patch.yml +5 -0
  2. package/docs/architecture.md +30 -0
  3. package/docs/contract.md +10 -3
  4. package/docs/development.md +14 -0
  5. package/docs/plans-carriage.md +1 -1
  6. package/docs/superpowers/plans/2026-10-17-chapters-save.md +198 -0
  7. package/lib/arrival.js +2 -1
  8. package/lib/arrival.js.map +1 -1
  9. package/lib/auto-save.d.ts +71 -0
  10. package/lib/auto-save.js +182 -0
  11. package/lib/auto-save.js.map +1 -0
  12. package/lib/commands.d.ts +3 -0
  13. package/lib/commands.js +6 -2
  14. package/lib/commands.js.map +1 -1
  15. package/lib/continue-core.js +14 -0
  16. package/lib/continue-core.js.map +1 -1
  17. package/lib/engine-core.js +7 -0
  18. package/lib/engine-core.js.map +1 -1
  19. package/lib/engine.js +2 -2
  20. package/lib/engine.js.map +1 -1
  21. package/lib/index.d.ts +8 -0
  22. package/lib/index.js +36 -1
  23. package/lib/index.js.map +1 -1
  24. package/lib/injections.js +6 -0
  25. package/lib/injections.js.map +1 -1
  26. package/lib/notice.d.ts +41 -27
  27. package/lib/notice.js +24 -16
  28. package/lib/notice.js.map +1 -1
  29. package/lib/render.js +7 -0
  30. package/lib/render.js.map +1 -1
  31. package/lib/rules-commands.js +4 -3
  32. package/lib/rules-commands.js.map +1 -1
  33. package/lib/save-core.d.ts +42 -0
  34. package/lib/save-core.js +84 -0
  35. package/lib/save-core.js.map +1 -0
  36. package/lib/store.d.ts +5 -0
  37. package/lib/store.js +11 -2
  38. package/lib/store.js.map +1 -1
  39. package/lib/sync.js +5 -4
  40. package/lib/sync.js.map +1 -1
  41. package/lib/tools.js +36 -63
  42. package/lib/tools.js.map +1 -1
  43. package/lib/types.d.ts +19 -0
  44. package/lib/types.js +12 -0
  45. package/lib/types.js.map +1 -1
  46. package/package.json +2 -1
package/cordis.patch.yml CHANGED
@@ -59,6 +59,11 @@
59
59
  # mergeThreshold: 0.3 # τ for merging turns into one chapter at
60
60
  # # archive time. Composition is archive-time
61
61
  # # only: tune for future archives.
62
+ # subagentAutoSave: true # archive a subagent's completed turns into
63
+ # # searchable chapters when its run settles
64
+ # # (save-only: no session, no compaction;
65
+ # # enrichment queued, never per-child).
66
+ # # false = listener never registers.
62
67
  #
63
68
  # # ---- context economy -------------------------------------------------
64
69
  # toolResultDeferFloorTokens: 200 # archive-time inline floor
@@ -49,6 +49,11 @@ What we *do* take from the XML instinct, because it was a real concern:
49
49
 
50
50
  - **Frontmatter carries the parseable structure** — `chapter`, `session`, `root`, `seqRange`, `sha256`,
51
51
  `tokens`, `artifacts`. Queryable with a YAML parser, invisible to the reading experience.
52
+ Chapters authored by a **subagent** carry two more lines — `subagent: true` and
53
+ `parentSession: <id>` — copied from the child's durable session header, which the host itself
54
+ stamps (`childSessionMeta`: `parentSession`, `origin: subagent`, `delegationDepth`, `agentPreset`;
55
+ measured, `spikes/subagent-end/FINDINGS.md`). Emitted ONLY when the header has `parentSession`,
56
+ so existing chapter bytes never change — which is also why no tape needs re-recording.
52
57
  - **There is one index, not three.** The TOC lists the chapters for reading; the storage domain already
53
58
  holds the machine-readable enumeration. A separate `MANIFEST.md` was proposed and **rejected as
54
59
  redundant** — a regenerated duplicate that can drift, whose growth becomes a context cost when read
@@ -213,6 +218,31 @@ is **going on**, authored while the model still holds the thread:
213
218
  - Invariant check: the model authors *state*, never archive text (invariant 4 intact); the note rides
214
219
  the replacement event append-only; every byte of the conversation still lives in chapters.
215
220
 
221
+ ## The archive moments
222
+
223
+ Chapters are written by exactly five doors, and knowledge that trips none of them is knowledge
224
+ that never enters the index. Four are session-facing, all idempotent over a per-session watermark
225
+ (`fromSeq = max(chapter.endSeq) + 1`) and anchored on the last **completed** turn (`turn/end`):
226
+
227
+ 1. `chapters_continue` (tool) and `/chapters-continue` (deterministic door) — archive + child session.
228
+ 2. `chapters_fork` (tool) and `/chapters-fork` (door) — archive + branch session (no new bytes).
229
+ 3. `/chapters-save` — archive only, parent untouched.
230
+ 4. Compaction finalize — the engine's `summarize()` commits a plan pre-durable, and the post-commit
231
+ finalizer writes the chapter (issue #47 note: the fanout field study shows this is the ONLY one
232
+ of these that subagents historically reached — 20 child compactions, or nothing).
233
+ 5. **Subagent settlement (issue #47, this round)** — a host-plane listener on the published
234
+ `subagent/end` lifecycle event runs the SAME save pipeline (one implementation, shared with
235
+ door 3 by extraction — never a second renderer) for children whose header carries
236
+ `origin: subagent` and `agentPreset === chapters`. The emitter fires `end` synchronously before
237
+ the holder's dispose continuation (measured, spike), so the fast path is a synchronous
238
+ `agents.get(info.id).session.snapshotEvents()`; the fallback arm is `sessionQuery.listEvents`
239
+ over the durable log (ambient-readable — measured); when neither reads, the child is skipped
240
+ with a loud diagnostic, never guessed. Save-only, so the "no continuation-creating actions
241
+ mid-step" rule is not engaged: no session is created, no surface is compacted. Enrichment is
242
+ **queued** per settlement — a 10-child fanout must not `runNow` ten annotator passes. A
243
+ continuable child settles repeatedly; the watermark makes every repeat after the first a
244
+ save of only-new-turns. Config: `subagentAutoSave` (default on).
245
+
216
246
  ## Budgets and the Reserve Margin
217
247
 
218
248
  Two distinct ceilings. Conflating them caused the original design's silent-failure mode.
package/docs/contract.md CHANGED
@@ -196,9 +196,16 @@ resumable route. *(Phase 0 confirmed: durable, listed, resumable, byte-identical
196
196
  Keep `anchoredBoundaryOf` (`vendor/fork.ts:213`) with its role changed: it yields the **archive ceiling**
197
197
  (`boundarySeq`, `cut`) for *what to render*, never for seeding.
198
198
 
199
- Notice event shape — built and vocabulary-pinned by `src/notice.ts` (+ `test/notice.test.ts`): one
200
- `user/message`, `surfaceOp: 'append'`, at `seq = 0`, `source { kind: 'plugin', plugin: 'dsh-chapters',
201
- form: 'snapshot', sections: [{ name: 'chapters:toc', text }] }`. Continuity validation in `Session.create`
199
+ Notice event shape — built and vocabulary-pinned by `src/notice.ts` (+ `test/notice.test.ts`, + the
200
+ v4 admission rows in `tests/unit/host-seed-contract.test.ts`): one `user/message`, `surfaceOp: 'append'`,
201
+ at `seq = 0`, `source { kind: 'plugin:dsh-chapters', form: 'snapshot', sections: [{ name: 'chapters:toc', text }] }`
202
+ — **producer-owned kind, no `plugin` field**. Session format v4 (host 0.2.x, measured 2026-10-18 on
203
+ `0.2.0-rc.2`) retired the bare `kind: 'plugin'` wrapper: persistence-row admission refuses it with
204
+ `format v4 message requires a producer-owned source kind`
205
+ (`@deepseek-ai/dsh-session-format-v3-to-v4` `lib/index.js:126`; also
206
+ `dsh-session-persistence-jsonl/lib/worker.cjs:10902`). The v3→v4 migration's own
207
+ `rewritePluginSource` does exactly what we now emit: "kind is the producer's own kind and whose
208
+ `plugin` field is dropped". Continuity validation in `Session.create`
202
209
  requires the seq to be the next contiguous seed position (rejection message measured in r5).
203
210
 
204
211
  ## Compaction Engine Integration (Phase 0b)
@@ -94,6 +94,20 @@ and `frontload: true` (structural context injected into prompts). After editing
94
94
  `codegraph_sync` so subsequent queries reflect the new code. The index is gitignored and persists
95
95
  across sessions.
96
96
 
97
+ ### The `docs/superpowers/` plan directory
98
+
99
+ The upstream `writing-plans` / `brainstorming` skills (`.dsh/skills/`, from obra/superpowers — see
100
+ dev/install README § Skills) default their artifacts to `docs/superpowers/plans/YYYY-MM-DD-<feature>.md`
101
+ and `docs/superpowers/specs/…`. Those files are **tracked**: they are per-feature design records, and a
102
+ later session may re-reference them for the *why* behind an implementation. Two rules keep them honest:
103
+
104
+ 1. A plan is stamped with a `**Status:**` header when its work lands (implemented → commit refs +
105
+ pointers to the shipped code/tests/site docs), so nobody executes a spent to-do list.
106
+ 2. The durable cross-session record remains the chapters store and `docs/` proper; a superpowers plan
107
+ is a working artifact of one pre-implementation session, not a second index. If it rots into
108
+ contradiction with the shipped code, update or supersede it in place via git history — never rewrite
109
+ silently.
110
+
97
111
  ### Three test layers (and why the third exists)
98
112
 
99
113
  | Layer | Command | Runs | Covers |
@@ -20,7 +20,7 @@ something nobody approved (worse).
20
20
  | Rejection = failed call carrying review feedback; dismissal = failed call naming the takeover | `index.js` execute arm + package README § Model Experience | Three distinct non-approval shapes to classify, plus a fourth we must not invent. |
21
21
  | State is one log-only whole-value event `plan/mode { active }`, appended at the next **accepted in-turn pre-step** | `index.js:374, 389` | An approved exit may not have its mode event yet at archive time → the *result* is the primary signal, the mode event secondary. |
22
22
  | Mode state is recovered by **folding the log** (projection `apply`, `stateVersion: 3`) | `index.js:83-94` | Last logged value wins; we mirror that rule, we do not cache a verdict. |
23
- | The mode-switch notice rides as `source.kind 'plugin'`, `plugin: 'plan-mode'`, `form: 'notice'` | `index.js:392-410` | Our injection screen already omits it (`src/injections.ts:38-41, 94-102`). Deliberate — see § Non-Goals. |
23
+ | The mode-switch notice rides with `form: 'notice'` — v3 `source { kind: 'plugin', plugin: 'plan-mode' }`, v4 `{ kind: 'plan-mode' }` (measured dsh-plan-mode/lib/index.js:395-410 on 0.2.0-rc.2; issue #43) | `index.js:392-410` | The screen treats injected context as NOT conversation **except** this one sentence, which is archived verbatim (the named exception, `src/injections.ts:55-68`; both arms narrow by producer AND form). Amended 2026-10-10 — this row originally said "omits". |
24
24
  | `/plan <message>` steers a real user message, `source.kind 'user'`; bare `/plan`, `/plan off` and their terminal results stay outside model history | `index.js:220` + README § Human Command | The instruction a human typed under plan guidance **is** conversation and is archived verbatim today. Nothing to change. |
25
25
  | Freshly created agents begin plan mode **inactive**; `plan/mode` is per-session and not inheritable | README § Known Limitations | A `chapters_continue` / `chapters_fork` child cannot know it was mid-plan unless we carry that text. |
26
26
  | `tool/call` data shape is `data.{callId, name, arguments}`, `arguments` an object **or** a JSON string | `src/types.ts:8`, precedent `src/render.ts:253` | The extractor must accept both, exactly as the renderer already does. |
@@ -0,0 +1,198 @@
1
+ # Implementation Plan: `/chapters-save` Command
2
+
3
+ **Issue:** #11
4
+ **Date:** 2026-10-17
5
+ **Author:** Agent (subagent)
6
+ **Status:** ✅ IMPLEMENTED — shipped as a6c840d (#11 → PR #12), hardened by 75ca0ff (#16 → PR #17,
7
+ fire-and-forget enrichment after the timeout bug). `runSave` lives in `src/tools.ts` (`saveCommand`,
8
+ registered beside fork/continue); tests in `tests/unit/save-command.test.ts`; behavior documented on
9
+ the site at `site/src/content/docs/reference/commands.md` § `/chapters-save`. Kept as the design
10
+ record for that command — read it with the shipped code, not as an open to-do.
11
+
12
+ ## Goal
13
+
14
+ Implement `/chapters-save` — a host command that archives the current session into
15
+ searchable chapters **without** creating a child session or triggering compaction.
16
+ The parent session remains fully functional; archived content becomes searchable
17
+ via `chapters_search` from any session in the workspace.
18
+
19
+ ## Success Criteria
20
+
21
+ - `/chapters-save` archives completed turns into verbatim chapter files on disk
22
+ - Session state is updated with new chapter records (search index picks them up)
23
+ - Enrichment is triggered after archiving (if configured)
24
+ - Idempotent: respects the watermark, re-running saves only new content
25
+ - No child session is created
26
+ - No compaction is triggered
27
+ - Returns clear success/error messages
28
+
29
+ ## Architecture Decision
30
+
31
+ `/chapters-save` is the **side-effect-only half** of the deterministic door. It reuses
32
+ the same archive pipeline as `/chapters-fork` and `/chapters-continue`:
33
+
34
+ ```
35
+ composeChapters → renderChapter → writeArchive → commitChapters → scheduler.schedule
36
+ ```
37
+
38
+ But stops before `agents.create`. The key insight: `runContinue` in `src/continue-core.ts`
39
+ bundles rendering + writing + child creation into one function. We need to extract the
40
+ render+write portion into a reusable function, then call it from both `runContinue` and
41
+ the new `runSave`.
42
+
43
+ ### Why NOT reuse `runContinue` directly
44
+
45
+ `runContinue` calls `finishChild` which does budget preflight, notice assembly, and
46
+ `ports.createChild` — all of which are irrelevant for save. Extracting the archive-only
47
+ portion avoids coupling save to child-creation logic and keeps the atomicity contract
48
+ clean.
49
+
50
+ ## File Structure
51
+
52
+ | File | Change | Responsibility |
53
+ |------|--------|---------------|
54
+ | `src/continue-core.ts` | Add `runSave()` function | Pure archive-without-child orchestration |
55
+ | `src/tools.ts` | Add `saveCommand` to `buildChaptersTools` return | Host command definition + handler |
56
+ | `src/tools.ts` | Register `saveCommand` in `registerChaptersTools` | Wire into host command service |
57
+ | `tests/unit/save-command.test.ts` | New test file | Unit tests for the save command handler |
58
+ | `site/src/content/docs/reference/commands.md` | Already documented | No change needed |
59
+
60
+ ## Detailed Tasks
61
+
62
+ ### Task 1: Extract `runSave` in `src/continue-core.ts`
63
+
64
+ Add a new exported function that performs steps 1-3 of `runContinue` (validate, render,
65
+ write, commit) but skips step 4-5 (notice, budget, create child).
66
+
67
+ ```typescript
68
+ // src/continue-core.ts — new export
69
+
70
+ export interface SaveArgs {
71
+ callerSessionId: string
72
+ chapters: ChapterRange[]
73
+ toolResultOverrides: ToolResultOverride[]
74
+ }
75
+
76
+ export interface SaveConfig extends RenderConfig {
77
+ artifactStoreRoot: string
78
+ }
79
+
80
+ export interface SaveResult {
81
+ ok: boolean
82
+ chapters?: Array<{ number: number; path: string; startSeq: number; endSeq: number }>
83
+ warnings?: string[]
84
+ reason?: string
85
+ }
86
+
87
+ export async function runSave(
88
+ ports: Pick<ContinuePorts, 'readCallerEvents' | 'getState' | 'putState' | 'fs'>,
89
+ args: SaveArgs,
90
+ config: SaveConfig,
91
+ ): Promise<SaveResult>
92
+ ```
93
+
94
+ **Implementation notes:**
95
+ - Reuse `validateRanges`, `renderChapter`, `writeArchive`, `appendChapters` from existing imports
96
+ - Use `attemptKey(args.callerSessionId, ceiling)` for idempotent number allocation
97
+ - Call `ports.putState` with `appendChapters(state, wrote.records)` to commit records
98
+ - Do NOT call `ports.createChild`, `assembleNotice`, `preflight`, or `linkChild`
99
+ - Return `{ ok: true, chapters, warnings }` on success
100
+
101
+ ### Task 2: Add `saveCommand` in `src/tools.ts`
102
+
103
+ Inside `buildChaptersTools`, after the `continueCommand` definition (~line 529), add:
104
+
105
+ ```typescript
106
+ const saveCommand: HostCommand = {
107
+ name: 'chapters-save',
108
+ description: 'Archive completed turns into searchable chapters without creating a child session. The parent stays fully functional.',
109
+ input: { hint: '[title]' },
110
+ async handler(invocation): Promise<CommandResult> {
111
+ // 1. Get ports (same as deterministicCommand)
112
+ // 2. Read events, find anchor (last turn/end)
113
+ // 3. Check watermark — if nothing new, return success with "nothing to save"
114
+ // 4. Compose/derive ranges (same logic as deterministicCommand lines 485-494)
115
+ // 5. Call runSave(ports, { callerSessionId, chapters, toolResultOverrides }, config)
116
+ // 6. On success: trigger scheduler.schedule(cwd, 'archive:save')
117
+ // 7. Return success text with chapter count and seq range
118
+ }
119
+ }
120
+ ```
121
+
122
+ **Key differences from `deterministicCommand`:**
123
+ - No `intent`, no `handoffNote`, no `callerPreset`, no `rulesSection`, no `projectLine`
124
+ - No `runContinue` / `runFork` call — uses `runSave` instead
125
+ - No child session ID in the return message
126
+ - Simpler error handling (no Refusal/budget concerns)
127
+
128
+ Update the return value of `buildChaptersTools` (line 734):
129
+
130
+ ```typescript
131
+ return {
132
+ segment: chaptersSegment,
133
+ chaptersContinue, chaptersFork, chaptersSearch, chaptersArtifact, chaptersRulePropose,
134
+ forkCommand, continueCommand, saveCommand // ← add saveCommand
135
+ }
136
+ ```
137
+
138
+ ### Task 3: Register `saveCommand` in `registerChaptersTools`
139
+
140
+ In `registerChaptersTools` (line 737), add `saveCommand` to the command registration loop:
141
+
142
+ ```typescript
143
+ for (const def of [built.forkCommand, built.continueCommand, built.saveCommand]) {
144
+ const disposer = commands.register(def)
145
+ if (typeof disposer === 'function') commandDisposers.push(disposer as () => void)
146
+ }
147
+ ```
148
+
149
+ This is a one-line change to the existing loop — the disposal mechanism already handles N commands.
150
+
151
+ ### Task 4: Write tests in `tests/unit/save-command.test.ts`
152
+
153
+ Follow the pattern from `tests/unit/fork-command.test.ts`:
154
+
155
+ ```typescript
156
+ // Test cases:
157
+ // 1. Saves completed turns into chapter files on disk
158
+ // 2. Updates session state with chapter records
159
+ // 3. Respects watermark — second call with no new turns returns "nothing to save"
160
+ // 4. Returns error when no completed turn exists
161
+ // 5. Triggers scheduler.schedule after successful save
162
+ // 6. Does NOT create a child session (verify agents.create is never called)
163
+ // 7. Handles title argument (optional prefix)
164
+ ```
165
+
166
+ **Test infrastructure:** Same `world()` helper pattern as fork-command tests. Stub `ctx`
167
+ with `agents.create` that throws if called (proving no child is created). Real temp-dir
168
+ fs so chapter files materialize.
169
+
170
+ ### Task 5: Verify documentation
171
+
172
+ `site/src/content/docs/reference/commands.md` lines 77-94 already document `/chapters-save`.
173
+ Verify the documented behavior matches the implementation. No doc changes expected.
174
+
175
+ ## Dependencies Between Tasks
176
+
177
+ ```
178
+ Task 1 (runSave) → Task 2 (saveCommand) → Task 3 (registration) → Task 4 (tests)
179
+ Task 5 (docs verification) can run in parallel with Task 4
180
+ ```
181
+
182
+ ## Risk Assessment
183
+
184
+ | Risk | Mitigation |
185
+ |------|-----------|
186
+ | `runSave` duplicates logic from `runContinue` | Extract shared render+write into a private helper if duplication exceeds ~20 lines |
187
+ | Scheduler not triggered | Mirror the exact `config.scheduler?.schedule(cwd, 'archive:save')` pattern from fork/continue |
188
+ | Search index doesn't pick up new chapters | `commitChapters` via `appendChapters` updates the registry; search reads from registry |
189
+ | Watermark check wrong | Use identical logic to `deterministicCommand` lines 473-480 |
190
+
191
+ ## Verification
192
+
193
+ After implementation:
194
+ 1. `npm test` — all existing tests still pass (no regressions)
195
+ 2. `npm run build` — compiles cleanly
196
+ 3. Manual: run `/chapters-save` in a live session, verify chapter files appear in `.dsh-chapters/`
197
+ 4. Manual: run `chapters_search` from another session, verify archived content is findable
198
+ 5. Manual: run `/chapters-save` again immediately, verify "nothing to save" response
package/lib/arrival.js CHANGED
@@ -14,6 +14,7 @@
14
14
  import { artifactPath, sha256 } from "./render.js";
15
15
  import fs from 'node:fs/promises';
16
16
  import path from 'node:path';
17
+ import { resolveStoreRoot } from "./store.js";
17
18
  export const ARRIVAL_MARKER = '[dsh:artifact';
18
19
  const kb = (n) => n < 1024 ? `${n} B` : `${(n / 1024).toFixed(1)} KB`;
19
20
  export async function applyArrivalStubs(session, opts) {
@@ -45,7 +46,7 @@ export async function applyArrivalStubs(session, opts) {
45
46
  continue;
46
47
  const hash = sha256(text);
47
48
  const rel = artifactPath(hash);
48
- const abs = path.join(opts.cwd, opts.storeRoot, rel);
49
+ const abs = path.join(resolveStoreRoot(opts.cwd, opts.storeRoot), rel);
49
50
  try {
50
51
  await fs.mkdir(path.dirname(abs), { recursive: true, mode: 0o700 });
51
52
  // content-addressed: rewriting identical bytes is a no-op by definition
@@ -1 +1 @@
1
- {"version":3,"file":"arrival.js","sourceRoot":"","sources":["../src/arrival.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"arrival.js","sourceRoot":"","sources":["../src/arrival.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { type SaveSessionConfig } from './save-core.ts';
2
+ import { type SessionEventLike } from './types.ts';
3
+ import type { RegistryStore } from './store.ts';
4
+ /** What `subagent/end` carries — the published `SubagentRunEndInfo` subset we read. */
5
+ export interface SubagentEndInfoLike {
6
+ /** The child agent's id — a durable session id for local (in-process) runs. */
7
+ id?: string;
8
+ runId?: string;
9
+ provider?: string;
10
+ stopReason?: string;
11
+ }
12
+ export interface AutoSaveChildHeader {
13
+ id?: string;
14
+ cwd?: string;
15
+ parentSession?: string;
16
+ origin?: string;
17
+ agentPreset?: string;
18
+ }
19
+ export interface AutoSaveChildView {
20
+ events: SessionEventLike[];
21
+ header: AutoSaveChildHeader;
22
+ }
23
+ export interface AutoSaveDeps {
24
+ /** The preset id a child must carry to be archived (CHAPTERS_PRESET_ID in production). */
25
+ presetId: string;
26
+ /** Synchronous live read at emit time (host `agents.get(id)`); undefined when gone. */
27
+ liveChild: (sessionId: string) => AutoSaveChildView | undefined;
28
+ /** Durable fallback when the live handle is gone; undefined when unreadable. */
29
+ durableChild?: ((sessionId: string) => Promise<AutoSaveChildView | undefined>) | undefined;
30
+ /** The archive itself — saveSession in production, injected for containment tests. */
31
+ save: (child: AutoSaveChildView) => Promise<{
32
+ archived: number;
33
+ skipped: string | null;
34
+ }>;
35
+ warn: (message: string) => void;
36
+ /** Observability seam: what each settlement decided. Outcome vocabulary pinned by tests. */
37
+ onSettled?: ((sessionId: string, outcome: string) => void) | undefined;
38
+ }
39
+ /**
40
+ * The listener factory. Pure against injected reads — the real pipeline is wired
41
+ * by `wireSubagentAutoSave` below, and both are covered by
42
+ * tests/{unit,integration} rows. The callback itself is SYNCHRONOUS on the
43
+ * snapshot (that is the race-avoidance), chained asynchronously per child.
44
+ */
45
+ export declare function makeSubagentEndListener(deps: AutoSaveDeps): (info: SubagentEndInfoLike) => void;
46
+ /** A minimal slice of the host ctx the wiring reads (structural, cordis-free). */
47
+ export interface AutoSaveCtx {
48
+ get?(name: string): unknown;
49
+ logger?: {
50
+ info?(m: string): void;
51
+ warn?(m: string): void;
52
+ };
53
+ }
54
+ export interface AutoSaveWiring extends SaveSessionConfig {
55
+ presetId: string;
56
+ /** §5 sync scheduler: every archive is a push point. */
57
+ scheduler?: {
58
+ schedule(cwd: string, reason: string): void;
59
+ } | undefined;
60
+ }
61
+ /**
62
+ * Bind the listener to the host-plane services: `agents` for the live read,
63
+ * `sessionQuery` for the durable fallback, `saveSession` for the archive.
64
+ * Returns the callback to register with `ctx.on('subagent/end', …)`.
65
+ *
66
+ * ENRICHMENT DELIBERATELY ABSENT: settlement never runs the annotator — a
67
+ * ten-child fanout must not fire ten LLM drains; new chapters are pending work
68
+ * for the idle/afterPush ladders exactly like a `/chapters-save` that skipped
69
+ * the manual nudge. A test pins that nothing here calls runNow.
70
+ */
71
+ export declare function wireSubagentAutoSave(ctx: AutoSaveCtx, store: RegistryStore, wiring: AutoSaveWiring): (info: SubagentEndInfoLike) => void;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Door 5 — subagent settlement auto-save (issue #47).
3
+ *
4
+ * The host publishes a `subagent/end` lifecycle event for every settled child
5
+ * run (one-shot AND each residency epoch of a continuable child). This module
6
+ * turns each such settlement of a chapters-preset child into an archive through
7
+ * the SHARED save core (src/save-core.ts) — the same pipeline `/chapters-save`
8
+ * runs, never a second implementation.
9
+ *
10
+ * The seam facts this leans on are MEASURED, not assumed
11
+ * (spikes/subagent-end/FINDINGS.md, host-subagent-seam drift rows):
12
+ * - `end` fires synchronously from a microtask attached at spawn, strictly
13
+ * BEFORE the run holder's dispose continuation → a SYNCHRONOUS snapshot
14
+ * inside the listener sees the live child's complete log and header.
15
+ * - the child header is durable kernel state (`childSessionMeta`):
16
+ * `origin: 'subagent'`, `parentSession`, `agentPreset`, `delegationDepth`.
17
+ * The gate reads the HEADER, never an event scan (a synthetic child can
18
+ * complete turns without ever carrying `agent-preset/selected` — measured).
19
+ * - fallback arm: `sessionQuery.listEvents` + `listSessions` headers are
20
+ * ambient-readable after disposal (not capability-gated) — used only when
21
+ * the live handle is gone at emit.
22
+ *
23
+ * Containment is the signature-listener discipline: the emitter catches listener
24
+ * throws by contract, but we ALSO never let one lose knowledge silently — every
25
+ * non-archive is either a quiet, expected skip (wrong preset, no completed turn)
26
+ * or a LOUD warn (unreadable, save failed).
27
+ */
28
+ import { appendFileSync } from 'node:fs';
29
+ import { saveSession } from "./save-core.js";
30
+ import { lineageOf } from "./types.js";
31
+ const hasCompletedTurn = (events) => events.some((e) => e.type === 'turn/end');
32
+ /**
33
+ * The listener factory. Pure against injected reads — the real pipeline is wired
34
+ * by `wireSubagentAutoSave` below, and both are covered by
35
+ * tests/{unit,integration} rows. The callback itself is SYNCHRONOUS on the
36
+ * snapshot (that is the race-avoidance), chained asynchronously per child.
37
+ */
38
+ export function makeSubagentEndListener(deps) {
39
+ // Per-child promise chain: repeated settlements of the SAME child serialize
40
+ // (the watermark makes the second save only see the first's records); distinct
41
+ // children never queue against each other. Same pattern as the signature listener.
42
+ const chains = new Map();
43
+ return (info) => {
44
+ try {
45
+ const id = info?.id;
46
+ if (typeof id !== 'string' || id === '') {
47
+ deps.warn('subagent auto-save: settlement event without a child session id — nothing archived');
48
+ return;
49
+ }
50
+ // --- synchronous snapshot NOW: `end` strictly precedes disposal (measured).
51
+ let view;
52
+ try {
53
+ view = deps.liveChild(id);
54
+ }
55
+ catch (error) {
56
+ deps.warn(`subagent auto-save: live read failed for ${id}: ${String(error?.message ?? error)}`);
57
+ view = undefined;
58
+ }
59
+ const hadLiveHandle = view !== undefined;
60
+ const job = async () => {
61
+ let arm = 'live';
62
+ if (view === undefined && deps.durableChild !== undefined) {
63
+ try {
64
+ view = await deps.durableChild(id);
65
+ arm = 'durable';
66
+ }
67
+ catch (error) {
68
+ deps.warn(`subagent auto-save: durable read failed for ${id}: ${String(error?.message ?? error)}`);
69
+ }
70
+ }
71
+ if (view === undefined) {
72
+ deps.warn(`subagent auto-save: child ${id} unreadable at settlement (live handle ${hadLiveHandle ? 'read failed' : 'gone'}${deps.durableChild === undefined ? ', no durable arm available' : ', durable read empty'}) — its knowledge was NOT archived`);
73
+ deps.onSettled?.(id, 'skipped:unreadable');
74
+ return;
75
+ }
76
+ const suffix = arm === 'durable' ? '@durable' : '';
77
+ // Gates, in the order the docs state. Expected skips are quiet: this fires
78
+ // for EVERY subagent in the profile; a warn per non-chapters child would
79
+ // bury the anomalies that need a human.
80
+ if (view.header.origin !== 'subagent') {
81
+ deps.onSettled?.(id, `skipped:origin${suffix}`);
82
+ return;
83
+ }
84
+ if (view.header.agentPreset !== deps.presetId) {
85
+ deps.onSettled?.(id, `skipped:preset${suffix}`);
86
+ return;
87
+ }
88
+ if (!hasCompletedTurn(view.events)) {
89
+ deps.onSettled?.(id, `skipped:no-completed-turn${suffix}`);
90
+ return;
91
+ }
92
+ try {
93
+ const out = await deps.save(view);
94
+ deps.onSettled?.(id, out.skipped === null ? `saved${suffix}` : `skipped:${out.skipped}${suffix}`);
95
+ }
96
+ catch (error) {
97
+ deps.warn(`subagent auto-save: archiving ${id} failed: ${String(error?.message ?? error)} — its knowledge was NOT archived`);
98
+ deps.onSettled?.(id, `failed${suffix}`);
99
+ }
100
+ };
101
+ const prev = chains.get(id);
102
+ const next = prev === undefined ? job() : prev.then(job);
103
+ chains.set(id, next);
104
+ void next.catch(() => undefined).finally(() => { if (chains.get(id) === next)
105
+ chains.delete(id); });
106
+ }
107
+ catch (error) {
108
+ // never-break containment, twice: the emitter catches us, we catch ourselves.
109
+ deps.warn(`subagent auto-save listener threw (contained): ${String(error?.message ?? error)}`);
110
+ }
111
+ };
112
+ }
113
+ /**
114
+ * Bind the listener to the host-plane services: `agents` for the live read,
115
+ * `sessionQuery` for the durable fallback, `saveSession` for the archive.
116
+ * Returns the callback to register with `ctx.on('subagent/end', …)`.
117
+ *
118
+ * ENRICHMENT DELIBERATELY ABSENT: settlement never runs the annotator — a
119
+ * ten-child fanout must not fire ten LLM drains; new chapters are pending work
120
+ * for the idle/afterPush ladders exactly like a `/chapters-save` that skipped
121
+ * the manual nudge. A test pins that nothing here calls runNow.
122
+ */
123
+ export function wireSubagentAutoSave(ctx, store, wiring) {
124
+ const warn = (message) => {
125
+ ctx.logger?.warn?.(message);
126
+ // the engine's precedent (r19): a headless profile routes ctx.logger where
127
+ // nobody can see it; the sink env makes the loss readable from outside.
128
+ try {
129
+ const sink = process.env.DSH_CHAPTERS_ENGINE_ERRORS;
130
+ if (sink !== undefined)
131
+ appendFileSync(sink, `[${new Date().toISOString()}] ${message}\n`);
132
+ }
133
+ catch { /* diagnostics must never mask the real failure */ }
134
+ };
135
+ const liveChild = (sessionId) => {
136
+ const agents = ctx.get?.('agents');
137
+ const handle = agents?.get?.(sessionId);
138
+ const session = handle?.agent?.session ?? handle?.session;
139
+ if (session === undefined || typeof session.snapshotEvents !== 'function')
140
+ return undefined;
141
+ return { events: [...session.snapshotEvents()], header: (session.header ?? {}) };
142
+ };
143
+ const durableChild = async (sessionId) => {
144
+ const sq = ctx.get?.('sessionQuery');
145
+ if (sq?.listEvents === undefined || sq.listSessions === undefined)
146
+ return undefined;
147
+ const events = await sq.listEvents(sessionId);
148
+ if (!Array.isArray(events) || events.length === 0)
149
+ return undefined;
150
+ const records = await sq.listSessions();
151
+ const header = (records ?? []).find((r) => r?.header?.id === sessionId)?.header;
152
+ if (header === undefined)
153
+ return undefined;
154
+ return { events, header };
155
+ };
156
+ const save = async (child) => {
157
+ const cwd = child.header.cwd;
158
+ if (cwd === undefined || cwd === '')
159
+ throw new Error('subagent child session has no workspace cwd — nowhere reachable to write');
160
+ const out = await saveSession({
161
+ events: child.events,
162
+ sessionId: child.header.id ?? '',
163
+ cwd,
164
+ store,
165
+ config: wiring,
166
+ subagentOf: lineageOf(child.header),
167
+ });
168
+ if (out.skipped === null && out.records.length > 0 && wiring.scheduler !== undefined && cwd !== '') {
169
+ wiring.scheduler.schedule(cwd, 'archive:autoSave');
170
+ }
171
+ return { archived: out.records.length, skipped: out.skipped };
172
+ };
173
+ return makeSubagentEndListener({
174
+ presetId: wiring.presetId,
175
+ liveChild,
176
+ durableChild,
177
+ save,
178
+ warn,
179
+ onSettled: (id, outcome) => { ctx.logger?.info?.(`dsh-chapters: subagent ${id} settled → ${outcome}`); },
180
+ });
181
+ }
182
+ //# sourceMappingURL=auto-save.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-save.js","sourceRoot":"","sources":["../src/auto-save.ts"],"names":[],"mappings":"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"}
package/lib/commands.d.ts CHANGED
@@ -24,5 +24,8 @@ export interface HostCommandsConfig {
24
24
  kind: 'success' | 'error';
25
25
  text: string;
26
26
  }>;
27
+ /** Door 5 (issue #47): whether subagent settlements auto-archive. Reported in
28
+ * /chapters-status because it runs WITHOUT a human seeing it happen. */
29
+ subagentAutoSave?: boolean;
27
30
  }
28
31
  export declare function registerHostCommands(ctx: ToolsCtx, domain: DomainLike, config: HostCommandsConfig): () => void;