@gotgenes/pi-subagents 6.18.1 → 6.18.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [6.18.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.1...pi-subagents-v6.18.2) (2026-05-24)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * plan extract ParentSessionInfo from AgentSpawnConfig ([#166](https://github.com/gotgenes/pi-packages/issues/166)) ([aff7b35](https://github.com/gotgenes/pi-packages/commit/aff7b35c98503fbb3da6a287631a2aa5c4d498fd))
14
+ * **retro:** add planning stage notes for issue [#166](https://github.com/gotgenes/pi-packages/issues/166) ([6138473](https://github.com/gotgenes/pi-packages/commit/613847313d56a9df8b479726d831679391bd0c1a))
15
+ * **retro:** add retro notes for issue [#165](https://github.com/gotgenes/pi-packages/issues/165) ([2a3e70d](https://github.com/gotgenes/pi-packages/commit/2a3e70dc6b903b4c053e7f6ebc09169bc3e34bf6))
16
+ * **retro:** add TDD stage notes for issue [#166](https://github.com/gotgenes/pi-packages/issues/166) ([2696da5](https://github.com/gotgenes/pi-packages/commit/2696da599de72f1a881577a4de8fedc57472a695))
17
+ * update architecture doc — AgentSpawnConfig step 3 complete ([125450b](https://github.com/gotgenes/pi-packages/commit/125450ba9ea5753b4cad07ed4d1675dcdbc7e319))
18
+
8
19
  ## [6.18.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.0...pi-subagents-v6.18.1) (2026-05-24)
9
20
 
10
21
 
@@ -500,20 +500,20 @@ These are fire-and-forget broadcast events — no request IDs, no reply channels
500
500
  These interfaces carry hidden dependencies that obscure true coupling.
501
501
  Bags with 10+ fields are the highest priority for decomposition.
502
502
 
503
- | Interface | Fields | Consumers | Severity |
504
- | --------------------------- | --------- | ------------------------------------------------- | -------- |
505
- | `ResolvedSpawnConfig` | 15 | foreground-runner, background-spawner, agent-tool | Critical |
506
- | `AgentSpawnConfig` | 13 | agent-manager (internal) | Critical |
507
- | `RunOptions` | 12 | agent-runner | High |
508
- | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
509
- | `NotificationDetails` | 10 | notification | Medium |
510
- | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
511
- | `RunnerIO` | 9 methods | agent-runner | Medium |
512
- | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
513
- | `AgentToolDeps` | 8 | agent-tool | Low |
514
- | `AgentMenuDeps` | 8 | agent-menu | Low |
515
- | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
516
- | `AgentRecordInit` | 8 | agent-record | Low |
503
+ | Interface | Fields | Consumers | Severity |
504
+ | --------------------------- | ---------------------------------- | ------------------------------------------------- | -------- |
505
+ | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | done |
506
+ | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | done |
507
+ | `RunOptions` | 12 | agent-runner | High |
508
+ | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
509
+ | `NotificationDetails` | 10 | notification | Medium |
510
+ | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
511
+ | `RunnerIO` | 9 methods | agent-runner | Medium |
512
+ | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
513
+ | `AgentToolDeps` | 8 | agent-tool | Low |
514
+ | `AgentMenuDeps` | 8 | agent-menu | Low |
515
+ | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
516
+ | `AgentRecordInit` | 8 | agent-record | Low |
517
517
 
518
518
  ### Complexity hotspots
519
519
 
@@ -586,20 +586,20 @@ interface SpawnPresentation {
586
586
  `agent-tool` uses all three to build the `AgentSpawnConfig` and the result text.
587
587
  After decomposition, each consumer declares its real dependencies explicitly.
588
588
 
589
- #### AgentSpawnConfig (13 fields extract ParentSessionInfo)
589
+ #### AgentSpawnConfig ParentSessionInfo extracted (done, [#166][166])
590
590
 
591
- Several fields form a natural cluster around parent session identity:
591
+ The `parentSessionFile`, `parentSessionId`, and `toolCallId` fields were grouped into `ParentSessionInfo`:
592
592
 
593
593
  ```typescript
594
- /** Parent session identity — always travel together. */
595
- interface ParentSessionInfo {
594
+ /** Parent session identity — always travel together from the tool boundary. */
595
+ export interface ParentSessionInfo {
596
596
  parentSessionFile?: string;
597
597
  parentSessionId?: string;
598
598
  toolCallId?: string;
599
599
  }
600
600
  ```
601
601
 
602
- Extracting this from `AgentSpawnConfig` reduces it from 13 to 10 fields and introduces a named concept that currently exists only as scattered optional fields.
602
+ `AgentSpawnConfig` now carries `parentSession?: ParentSessionInfo` instead of three flat optional fields.
603
603
 
604
604
  #### RunOptions (12 fields → extract RunContext)
605
605
 
@@ -676,10 +676,10 @@ Split the 15-field bag into `SpawnIdentity`, `SpawnExecution`, and `SpawnPresent
676
676
  Each consumer declares its real dependencies.
677
677
  Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
678
678
 
679
- ### Step 3: Extract ParentSessionInfo from AgentSpawnConfig ([#166][166])
679
+ ### Step 3: Extract ParentSessionInfo from AgentSpawnConfig ([#166][166]) — Complete
680
680
 
681
- Extract `parentSessionFile`, `parentSessionId`, `toolCallId` into a `ParentSessionInfo` value object.
682
- Reduces AgentSpawnConfig from 13 to 10 fields.
681
+ Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessionInfo`.
682
+ `AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
683
683
 
684
684
  ### Step 4: Narrow RunnerIO ([#167][167])
685
685
 
@@ -0,0 +1,231 @@
1
+ ---
2
+ issue: 166
3
+ issue_title: "refactor(pi-subagents): extract ParentSessionInfo from AgentSpawnConfig (13 fields)"
4
+ ---
5
+
6
+ # Extract ParentSessionInfo from AgentSpawnConfig
7
+
8
+ ## Problem Statement
9
+
10
+ `AgentSpawnConfig` in `agent-manager.ts` has 13 fields.
11
+ Three of those fields — `parentSessionFile`, `parentSessionId`, and `toolCallId` — form a natural cluster around parent session identity.
12
+ They always travel together through `agent-tool.ts` → `foreground-runner.ts` / `background-spawner.ts` → `AgentManager.spawn()`.
13
+ Extracting them into a named value object reduces `AgentSpawnConfig` from 13 to 11 fields (10 + 1 nested) and introduces the `ParentSessionInfo` domain concept.
14
+
15
+ ## Goals
16
+
17
+ - Extract `ParentSessionInfo` interface with `parentSessionFile`, `parentSessionId`, and `toolCallId`.
18
+ - Replace the three flat optional fields on `AgentSpawnConfig` with a single optional `parentSession?: ParentSessionInfo` field.
19
+ - Replace the flat fields on `BackgroundParams`, `ForegroundParams`, and `RunOptions` with the same grouped type.
20
+ - Update all callers (agent-tool, foreground-runner, background-spawner, agent-manager, agent-runner) and their tests.
21
+ - Non-breaking refactor — no public API changes (the `SubagentsService` boundary does not expose these fields).
22
+
23
+ ## Non-Goals
24
+
25
+ - Changing the `NotificationState` or `notification` module — they remain as-is; `toolCallId` is just extracted from the group at the `AgentManager.spawn` boundary.
26
+ - Further decomposition of `AgentSpawnConfig` (e.g., extracting execution or callback clusters) — tracked separately.
27
+ - Modifying `session-dir.ts` or `deriveSubagentSessionDir` — the function signature stays the same; callers just unwrap `parentSession.parentSessionFile` before calling it.
28
+
29
+ ## Background
30
+
31
+ Issue #165 (closed) decomposed `ResolvedSpawnConfig` into `SpawnIdentity`, `SpawnExecution`, and `SpawnPresentation`.
32
+ This issue continues that structural improvement by grouping the parent-session fields that flow from `agent-tool.ts` through to `agent-runner.ts`.
33
+
34
+ The three fields are:
35
+
36
+ - `parentSessionFile` — path to the parent session's JSONL file, used by `deriveSubagentSessionDir` to place child sessions next to the parent.
37
+ - `parentSessionId` — session ID of the parent agent, stored in the child session's `parentSession` header via `sessionManager.newSession()`.
38
+ - `toolCallId` — tool call ID for background notification wiring; when set, `AgentManager.spawn` creates a `NotificationState`.
39
+
40
+ All three originate in `agent-tool.ts`'s `execute` function and are threaded unchanged through intermediate modules.
41
+
42
+ ### Current flow
43
+
44
+ ```text
45
+ agent-tool execute → getSessionInfo() + toolCallId param
46
+ → BackgroundParams { parentSessionFile, parentSessionId, toolCallId }
47
+ → spawnBackground → manager.spawn(opts: AgentSpawnConfig { ...flat fields })
48
+ → AgentManager.spawn → options.toolCallId → NotificationState
49
+ → startAgent → runner.run(RunOptions { parentSessionFile, parentSessionId })
50
+ → deriveSessionDir(parentSessionFile, ...)
51
+ → sessionManager.newSession({ parentSession: parentSessionId })
52
+
53
+ → ForegroundParams { parentSessionFile, parentSessionId }
54
+ → runForeground → manager.spawnAndWait(opts: AgentSpawnConfig { ...flat fields })
55
+ → (same AgentManager path)
56
+ ```
57
+
58
+ ### After extraction
59
+
60
+ ```text
61
+ agent-tool execute → getSessionInfo() + toolCallId param
62
+ → parentSession: ParentSessionInfo { parentSessionFile, parentSessionId, toolCallId }
63
+ → BackgroundParams { parentSession }
64
+ → spawnBackground → manager.spawn(opts: AgentSpawnConfig { parentSession })
65
+ → AgentManager.spawn → parentSession.toolCallId → NotificationState
66
+ → startAgent → runner.run(RunOptions { parentSession })
67
+ → deriveSessionDir(parentSession?.parentSessionFile, ...)
68
+ → sessionManager.newSession({ parentSession: parentSession?.parentSessionId })
69
+
70
+ → ForegroundParams { parentSession }
71
+ → runForeground → manager.spawnAndWait(opts: AgentSpawnConfig { parentSession })
72
+ → (same AgentManager path)
73
+ ```
74
+
75
+ ## Design Overview
76
+
77
+ ### `ParentSessionInfo` interface
78
+
79
+ ```typescript
80
+ export interface ParentSessionInfo {
81
+ /** Path to the parent session's JSONL file (for deriving the subagent session directory). */
82
+ parentSessionFile?: string;
83
+ /** Session ID of the parent agent (stored in the child session's parentSession header). */
84
+ parentSessionId?: string;
85
+ /** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
86
+ toolCallId?: string;
87
+ }
88
+ ```
89
+
90
+ All three fields remain optional — they are only available when spawning from an active session (the `SubagentsService` boundary omits them entirely).
91
+
92
+ The interface lives in `lifecycle/agent-manager.ts` alongside `AgentSpawnConfig` since that is the primary consumer.
93
+ If a future refactoring moves `AgentSpawnConfig` to its own file, `ParentSessionInfo` should move with it.
94
+
95
+ ### Consumer call-site sketch
96
+
97
+ ```typescript
98
+ // agent-tool.ts execute():
99
+ const parentSession: ParentSessionInfo = {
100
+ parentSessionFile: sessionInfo.parentSessionFile,
101
+ parentSessionId: sessionInfo.parentSessionId,
102
+ toolCallId,
103
+ };
104
+ // ...
105
+ spawnBackground(manager, widget, agentActivity, { config, snapshot, parentSession });
106
+ ```
107
+
108
+ The grouped object eliminates the three-field spread that was repeated in both `spawnBackground` and `runForeground` call sites.
109
+
110
+ ### `AgentSpawnConfig` change
111
+
112
+ ```typescript
113
+ export interface AgentSpawnConfig {
114
+ // ... existing fields (description, model, maxTurns, etc.)
115
+ /** Parent session identity — grouped fields that travel together from the tool boundary. */
116
+ parentSession?: ParentSessionInfo;
117
+ // Remove: parentSessionFile, parentSessionId, toolCallId
118
+ }
119
+ ```
120
+
121
+ ### `RunOptions` change
122
+
123
+ ```typescript
124
+ export interface RunOptions {
125
+ // ... existing fields
126
+ /** Parent session identity (file path + session ID). */
127
+ parentSession?: ParentSessionInfo;
128
+ // Remove: parentSessionFile, parentSessionId
129
+ }
130
+ ```
131
+
132
+ Note: `RunOptions` does not use `toolCallId` — it was never threaded to the runner.
133
+ The runner only reads `parentSessionFile` and `parentSessionId` from the group.
134
+
135
+ ### `getSessionInfo` return type update
136
+
137
+ The `getSessionInfo` callback in `AgentToolDeps` currently returns `{ parentSessionFile: string; parentSessionId: string }`.
138
+ It should remain unchanged — it does not include `toolCallId` (which comes from the `execute` callback's first argument).
139
+ The `agent-tool.ts` execute function constructs a `ParentSessionInfo` by merging `getSessionInfo()` output with `toolCallId`.
140
+
141
+ ## Module-Level Changes
142
+
143
+ ### New types
144
+
145
+ 1. `src/lifecycle/agent-manager.ts` — add `ParentSessionInfo` interface (exported).
146
+
147
+ ### Modified interfaces
148
+
149
+ 1. `src/lifecycle/agent-manager.ts` — `AgentSpawnConfig`: replace `parentSessionFile?`, `parentSessionId?`, `toolCallId?` with `parentSession?: ParentSessionInfo`.
150
+ 2. `src/lifecycle/agent-runner.ts` — `RunOptions`: replace `parentSessionFile?`, `parentSessionId?` with `parentSession?: ParentSessionInfo`.
151
+ 3. `src/tools/background-spawner.ts` — `BackgroundParams`: replace `parentSessionFile`, `parentSessionId`, `toolCallId` with `parentSession: ParentSessionInfo`.
152
+ 4. `src/tools/foreground-runner.ts` — `ForegroundParams`: replace `parentSessionFile`, `parentSessionId` with `parentSession: ParentSessionInfo`.
153
+
154
+ ### Modified implementations
155
+
156
+ 1. `src/lifecycle/agent-manager.ts` — `AgentManager.spawn()`: read `options.parentSession?.toolCallId` instead of `options.toolCallId`; pass `parentSession` to `RunOptions`.
157
+ 2. `src/lifecycle/agent-runner.ts` — `runAgent()`: read `options.parentSession?.parentSessionFile` and `options.parentSession?.parentSessionId`.
158
+ 3. `src/tools/agent-tool.ts` — `createAgentTool` execute: construct `ParentSessionInfo` from `getSessionInfo()` + `toolCallId`, pass as `parentSession` to both spawners.
159
+ 4. `src/tools/background-spawner.ts` — `spawnBackground()`: read `params.parentSession` and pass fields to `AgentSpawnConfig`.
160
+ 5. `src/tools/foreground-runner.ts` — `runForeground()`: read `params.parentSession` and pass fields to `AgentSpawnConfig`.
161
+
162
+ ### No changes needed
163
+
164
+ - `src/tools/agent-tool.ts` — `AgentToolDeps.getSessionInfo` return type stays the same.
165
+ - `src/session/session-dir.ts` — `deriveSubagentSessionDir` signature unchanged.
166
+ - `src/observation/notification-state.ts` — constructor signature unchanged.
167
+ - `src/service/service-adapter.ts` — does not pass parent session fields.
168
+ - `src/index.ts` — `getSessionInfo` callback unchanged.
169
+
170
+ ## Test Impact Analysis
171
+
172
+ ### New tests enabled
173
+
174
+ No new unit tests are enabled — this is a structural grouping, not new behavior.
175
+
176
+ ### Existing tests that need updates
177
+
178
+ 1. `test/lifecycle/agent-manager.test.ts` — update spawn calls from flat `parentSessionFile`/`parentSessionId`/`toolCallId` to nested `parentSession: { ... }` form; update assertions to read from `parentSession`.
179
+ 2. `test/lifecycle/agent-runner.test.ts` — update `RunOptions` construction from flat to nested `parentSession`.
180
+ 3. `test/tools/agent-tool.test.ts` — update assertion checking `toolCallId` on spawn opts to check `parentSession.toolCallId`.
181
+ 4. `test/tools/background-spawner.test.ts` — update `makeParams` factory from flat fields to `parentSession: { ... }`.
182
+ 5. `test/tools/foreground-runner.test.ts` — update params construction from flat fields to `parentSession: { ... }`.
183
+ 6. `test/helpers/make-deps.ts` — `getSessionInfo` mock stays unchanged (returns flat `{ parentSessionFile, parentSessionId }`).
184
+
185
+ ### Tests that stay as-is
186
+
187
+ - `test/session/session-dir.test.ts` — tests `deriveSubagentSessionDir` directly, no interface change.
188
+ - `test/observation/notification-state.test.ts` — tests `NotificationState` constructor directly.
189
+ - `test/observation/notification.test.ts` — tests notification formatting with `record.notification`, not spawn config.
190
+
191
+ ## TDD Order
192
+
193
+ 1. **Define `ParentSessionInfo` and update `AgentSpawnConfig`** — add interface, replace three flat fields with `parentSession?`.
194
+ Update `AgentManager.spawn` and `startAgent` to read from the nested group.
195
+ Update `agent-manager.test.ts` to use nested form.
196
+ Run `pnpm run check` to verify no downstream type errors remain.
197
+ Commit: `refactor: define ParentSessionInfo and nest in AgentSpawnConfig`
198
+
199
+ 2. **Update `RunOptions` in `agent-runner.ts`** — replace flat `parentSessionFile?`/`parentSessionId?` with `parentSession?`.
200
+ Update `runAgent` to read `options.parentSession?.parentSessionFile` and `options.parentSession?.parentSessionId`.
201
+ Update `agent-runner.test.ts`.
202
+ Commit: `refactor: nest ParentSessionInfo in RunOptions`
203
+
204
+ 3. **Update `BackgroundParams` and `spawnBackground`** — replace three flat fields with `parentSession: ParentSessionInfo`.
205
+ Update `spawnBackground` to pass `parentSession` to spawn opts.
206
+ Update `background-spawner.test.ts`.
207
+ Commit: `refactor: nest ParentSessionInfo in BackgroundParams`
208
+
209
+ 4. **Update `ForegroundParams` and `runForeground`** — replace two flat fields with `parentSession: ParentSessionInfo`.
210
+ Update `runForeground` to pass `parentSession` to spawn opts.
211
+ Update `foreground-runner.test.ts`.
212
+ Commit: `refactor: nest ParentSessionInfo in ForegroundParams`
213
+
214
+ 5. **Update `agent-tool.ts` execute** — construct `ParentSessionInfo` from `getSessionInfo()` + `toolCallId`, pass as `parentSession` to both spawner call sites.
215
+ Update `agent-tool.test.ts`.
216
+ Commit: `refactor: construct ParentSessionInfo in agent-tool execute`
217
+
218
+ 6. **Final verification** — run full test suite (`pnpm vitest run`) and type check (`pnpm run check`).
219
+ No separate commit unless adjustments are needed.
220
+
221
+ ## Risks and Mitigations
222
+
223
+ | Risk | Mitigation |
224
+ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
225
+ | Deep-merge trap: test factories using `Partial<BackgroundParams>` spread may silently ignore nested overrides | Audit `makeParams` in `background-spawner.test.ts` — convert from flat-field spread to nested `parentSession` construction |
226
+ | `toolCallId` conditionally absent for foreground calls | `ParentSessionInfo.toolCallId` is optional; `ForegroundParams.parentSession` includes it but won't set it — matches current behavior |
227
+ | Type check passes but runtime breaks due to nested access on undefined | `parentSession?` is optional on `AgentSpawnConfig`; all reads use optional chaining (`options.parentSession?.toolCallId`) |
228
+
229
+ ## Open Questions
230
+
231
+ None — the extraction is mechanical and the issue description is unambiguous.
@@ -38,3 +38,41 @@ The decomposition touched 7 files (4 source, 3 test) and kept the test count fla
38
38
  - Step 1 breaking all consumers simultaneously was handled smoothly by completing all steps before pushing, as planned.
39
39
  No transitional alias was needed.
40
40
  - The `background-spawner.test.ts` description-override test was the only unexpected friction point — the flat spread issue wasn't caught by the plan.
41
+
42
+ ## Stage: Final Retrospective (2026-05-24T15:00:14Z)
43
+
44
+ ### Session summary
45
+
46
+ Shipped issue #165 (CI green, released as `pi-subagents-v6.18.1`) and ran the final retrospective.
47
+ The most impactful outcome across all three sessions was the skill description improvements (commit `51f52ef`), which addressed a recurring `instruction-violation` pattern.
48
+
49
+ ### Observations
50
+
51
+ #### What went well
52
+
53
+ - The user's probing question ("This is consistent, though.
54
+ Why?") turned a simple skill-loading skip into a generalizable improvement to three skill descriptions and two prompt instructions.
55
+ This is a good example of the user investing a redirecting question instead of a correction.
56
+ - TDD execution was clean — 4 cycles, no rework, no type errors at the end.
57
+ The plan's risk mitigation ("land steps 1–4 on the same branch") worked as intended.
58
+ - Ship stage had zero friction: push, CI, close, release-please merge, tag — all first-try.
59
+
60
+ #### What caused friction (agent side)
61
+
62
+ - `instruction-violation` — Skipped loading the `colgrep` skill during planning despite explicit instructions.
63
+ Root cause: skill descriptions that read like tool reference manuals get deprioritized because the agent perceives them as redundant with the tool schema already in context.
64
+ Impact: no rework on the plan itself, but triggered a productive detour to improve skill descriptions.
65
+ User-caught.
66
+ - `missing-context` — The plan didn't anticipate that `Partial<ResolvedSpawnConfig>` spread in test factories would silently break after nesting.
67
+ The `testing` skill already warns about spread-related pitfalls, but not this specific variant (flat keys ignored by top-level spread on a nested structure).
68
+ Impact: one test failure during step 4 that required a verbose inline fix (writing out the full `execution` sub-object).
69
+ Self-identified during implementation.
70
+
71
+ #### What caused friction (user side)
72
+
73
+ - None observed.
74
+ The user's intervention on the `colgrep` skill was well-timed and produced a higher-value outcome than skipping it would have.
75
+
76
+ ### Changes made
77
+
78
+ 1. Added a TDD planning rule to `.pi/skills/testing/SKILL.md` warning about `Partial<T>` spread not deep-merging into nested interfaces after a flat-to-nested refactor.
@@ -0,0 +1,39 @@
1
+ ---
2
+ issue: 166
3
+ issue_title: "refactor(pi-subagents): extract ParentSessionInfo from AgentSpawnConfig (13 fields)"
4
+ ---
5
+
6
+ # Retro: #166 — Extract ParentSessionInfo from AgentSpawnConfig
7
+
8
+ ## Stage: Planning (2026-05-24T16:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a 6-step TDD plan to extract `ParentSessionInfo` from `AgentSpawnConfig`.
13
+ The refactoring groups three co-traveling fields (`parentSessionFile`, `parentSessionId`, `toolCallId`) into a named value object, reducing `AgentSpawnConfig` from 13 to 11 fields.
14
+
15
+ ### Observations
16
+
17
+ - The `SubagentsService` boundary (`service-adapter.ts`) does not pass any of the three fields, so this is a purely internal refactoring with no public API impact.
18
+ - `getSessionInfo` in `AgentToolDeps` returns only `parentSessionFile` and `parentSessionId`; `toolCallId` comes from the `execute` callback's first argument — the plan keeps this separation and merges them at the `agent-tool.ts` boundary.
19
+ - `RunOptions` in `agent-runner.ts` never carried `toolCallId` (it was consumed in `AgentManager.spawn` before reaching the runner), so the nested `parentSession` on `RunOptions` only holds the two session fields.
20
+ - The deep-merge trap from the testing skill is relevant: `background-spawner.test.ts` has a `makeParams` factory that spreads flat fields — must be converted to nested `parentSession` construction.
21
+ - Issue #165 (decompose `ResolvedSpawnConfig`) is closed, so this plan builds on stable ground.
22
+
23
+ ## Stage: Implementation — TDD (2026-05-24T17:00:00Z)
24
+
25
+ ### Session summary
26
+
27
+ All 5 TDD cycles completed across `agent-manager.ts`, `agent-runner.ts`, `background-spawner.ts`, `foreground-runner.ts`, and `agent-tool.ts`.
28
+ Test count held steady at 805 (no net new tests — refactor only).
29
+ Type check and lint both clean after all steps.
30
+
31
+ ### Observations
32
+
33
+ - The `AgentSpawnConfig` field count went from 15 to 13 (not 13 → 10 as originally estimated) — the architecture doc quoted the issue's stale count; the actual pre-refactor interface had 15 fields (`bypassQueue` and others were already present).
34
+ The architecture doc was updated to reflect "done" with a note about the nested group rather than a specific before/after number.
35
+ - The deep-merge trap (noted in planning) did materialise: `background-spawner.test.ts`'s `makeParams` spread `Partial<BackgroundParams>` with flat fields.
36
+ Fixed by replacing the three flat fields with a single `parentSession` object at the factory level — top-level spread still works correctly since `parentSession` is one field.
37
+ - `RunOptions` in `agent-runner.ts` needed a new import of `ParentSessionInfo` from `agent-manager.ts`; no circular dependency since `agent-runner.ts` already imports from `agent-manager.ts`.
38
+ - `agent-tool.ts` still imports `AgentSpawnConfig` (needed by `AgentToolManager` interface) — the new `ParentSessionInfo` import was added alongside it.
39
+ - All 5 commits are clean `refactor:` messages; architecture doc update is a separate `docs:` commit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "6.18.1",
3
+ "version": "6.18.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"
@@ -53,6 +53,15 @@ interface SpawnArgs {
53
53
  options: AgentSpawnConfig;
54
54
  }
55
55
 
56
+ export interface ParentSessionInfo {
57
+ /** Path to the parent session's JSONL file (for deriving the subagent session directory). */
58
+ parentSessionFile?: string;
59
+ /** Session ID of the parent agent (stored in the child session's parentSession header). */
60
+ parentSessionId?: string;
61
+ /** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
62
+ toolCallId?: string;
63
+ }
64
+
56
65
  export interface AgentSpawnConfig {
57
66
  description: string;
58
67
  model?: Model<any>;
@@ -75,12 +84,8 @@ export interface AgentSpawnConfig {
75
84
  signal?: AbortSignal;
76
85
  /** Called when the agent session is created — receives the session and the agent's record. */
77
86
  onSessionCreated?: (session: AgentSession, record: AgentRecord) => void;
78
- /** Path to the parent session's JSONL file (for deriving the subagent session directory). */
79
- parentSessionFile?: string;
80
- /** Session ID of the parent agent (stored in the child session's parentSession header). */
81
- parentSessionId?: string;
82
- /** Tool call ID for background notification wiring. When set, spawn attaches NotificationState. */
83
- toolCallId?: string;
87
+ /** Parent session identity grouped fields that travel together from the tool boundary. */
88
+ parentSession?: ParentSessionInfo;
84
89
  }
85
90
 
86
91
  export class AgentManager {
@@ -158,8 +163,8 @@ export class AgentManager {
158
163
  });
159
164
  this.agents.set(id, record);
160
165
 
161
- if (options.toolCallId) {
162
- record.notification = new NotificationState(options.toolCallId);
166
+ if (options.parentSession?.toolCallId) {
167
+ record.notification = new NotificationState(options.parentSession.toolCallId);
163
168
  }
164
169
 
165
170
  if (options.isBackground) {
@@ -228,8 +233,7 @@ export class AgentManager {
228
233
  isolated: options.isolated,
229
234
  thinkingLevel: options.thinkingLevel,
230
235
  cwd: worktreeCwd,
231
- parentSessionFile: options.parentSessionFile,
232
- parentSessionId: options.parentSessionId,
236
+ parentSession: options.parentSession,
233
237
  signal: record.abortController!.signal,
234
238
  registry: this.registry,
235
239
  onSessionCreated: (session) => {
@@ -9,6 +9,7 @@ import {
9
9
  type SettingsManager,
10
10
  } from "@earendil-works/pi-coding-agent";
11
11
  import type { AgentConfigLookup } from "#src/config/agent-types";
12
+ import type { ParentSessionInfo } from "#src/lifecycle/agent-manager";
12
13
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
13
14
  import { extractText } from "#src/session/context";
14
15
  import type { EnvInfo } from "#src/session/env";
@@ -146,10 +147,8 @@ export interface RunOptions {
146
147
  thinkingLevel?: ThinkingLevel;
147
148
  /** Override working directory (e.g. for worktree isolation). */
148
149
  cwd?: string;
149
- /** Path to the parent session's JSONL file (for deriving the subagent session directory). */
150
- parentSessionFile?: string;
151
- /** Session ID of the parent agent (stored in the child session's parentSession header). */
152
- parentSessionId?: string;
150
+ /** Parent session identity (file path + session ID). */
151
+ parentSession?: ParentSessionInfo;
153
152
  /** Called once after session creation — session delivery mechanism. */
154
153
  onSessionCreated?: (session: AgentSession) => void;
155
154
  /**
@@ -308,9 +307,9 @@ export async function runAgent(
308
307
  // Create a persisted SessionManager so transcripts are written in Pi's
309
308
  // official JSONL format. Falls back to a temp directory when the parent
310
309
  // session is not persisted (e.g. headless/API mode).
311
- const sessionDir = io.deriveSessionDir(options.parentSessionFile, cfg.effectiveCwd);
310
+ const sessionDir = io.deriveSessionDir(options.parentSession?.parentSessionFile, cfg.effectiveCwd);
312
311
  const sessionManager = io.createSessionManager(cfg.effectiveCwd, sessionDir);
313
- sessionManager.newSession({ parentSession: options.parentSessionId });
312
+ sessionManager.newSession({ parentSession: options.parentSession?.parentSessionId });
314
313
 
315
314
  const { session } = await io.createSession({
316
315
  cwd: cfg.effectiveCwd,
@@ -3,7 +3,7 @@ import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
3
3
  import { Text } from "@earendil-works/pi-tui";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { AgentTypeRegistry } from "#src/config/agent-types";
6
- import type { AgentSpawnConfig } from "#src/lifecycle/agent-manager";
6
+ import type { AgentSpawnConfig, ParentSessionInfo } from "#src/lifecycle/agent-manager";
7
7
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
8
8
  import { spawnBackground } from "#src/tools/background-spawner";
9
9
  import { runForeground } from "#src/tools/foreground-runner";
@@ -303,6 +303,7 @@ Guidelines:
303
303
  // ---- Boundary extraction (after config so inheritContext is resolved) ----
304
304
  const snapshot = buildSnapshot(config.execution.inheritContext);
305
305
  const { parentSessionFile, parentSessionId } = getSessionInfo();
306
+ const parentSession: ParentSessionInfo = { parentSessionFile, parentSessionId, toolCallId };
306
307
 
307
308
  // ---- Resume existing agent ----
308
309
  if (params.resume) {
@@ -337,7 +338,7 @@ Guidelines:
337
338
  manager,
338
339
  widget,
339
340
  agentActivity,
340
- { config, snapshot, parentSessionFile, parentSessionId, toolCallId },
341
+ { config, snapshot, parentSession },
341
342
  );
342
343
  }
343
344
 
@@ -346,7 +347,7 @@ Guidelines:
346
347
  manager,
347
348
  widget,
348
349
  agentActivity,
349
- { config, snapshot, parentSessionFile, parentSessionId },
350
+ { config, snapshot, parentSession },
350
351
  signal,
351
352
  onUpdate,
352
353
  );
@@ -1,4 +1,4 @@
1
- import type { AgentSpawnConfig } from "#src/lifecycle/agent-manager";
1
+ import type { AgentSpawnConfig, ParentSessionInfo } from "#src/lifecycle/agent-manager";
2
2
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
3
3
  import type { AgentActivityAccess } from "#src/tools/agent-tool";
4
4
  import { textResult } from "#src/tools/helpers";
@@ -24,9 +24,7 @@ export interface BackgroundWidgetDeps {
24
24
  export interface BackgroundParams {
25
25
  config: ResolvedSpawnConfig;
26
26
  snapshot: ParentSnapshot;
27
- parentSessionFile: string;
28
- parentSessionId: string;
29
- toolCallId: string;
27
+ parentSession: ParentSessionInfo;
30
28
  }
31
29
 
32
30
  /**
@@ -46,8 +44,7 @@ export function spawnBackground(
46
44
  let id: string;
47
45
  try {
48
46
  id = manager.spawn(params.snapshot, identity.subagentType, execution.prompt, {
49
- parentSessionFile: params.parentSessionFile,
50
- parentSessionId: params.parentSessionId,
47
+ parentSession: params.parentSession,
51
48
  description: execution.description,
52
49
  model: execution.model,
53
50
  maxTurns: execution.effectiveMaxTurns,
@@ -57,7 +54,6 @@ export function spawnBackground(
57
54
  isBackground: true,
58
55
  isolation: execution.isolation,
59
56
  invocation: execution.agentInvocation,
60
- toolCallId: params.toolCallId,
61
57
  onSessionCreated: (session) => {
62
58
  bgState.setSession(session);
63
59
  subscribeUIObserver(session, bgState);
@@ -1,5 +1,5 @@
1
1
  import type { AgentToolResult } from "@earendil-works/pi-coding-agent";
2
- import type { AgentSpawnConfig } from "#src/lifecycle/agent-manager";
2
+ import type { AgentSpawnConfig, ParentSessionInfo } from "#src/lifecycle/agent-manager";
3
3
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
4
4
  import type { AgentActivityAccess } from "#src/tools/agent-tool";
5
5
  import {
@@ -39,8 +39,7 @@ export interface ForegroundWidgetDeps {
39
39
  export interface ForegroundParams {
40
40
  config: ResolvedSpawnConfig;
41
41
  snapshot: ParentSnapshot;
42
- parentSessionFile: string;
43
- parentSessionId: string;
42
+ parentSession: ParentSessionInfo;
44
43
  }
45
44
 
46
45
  /**
@@ -109,8 +108,7 @@ export async function runForeground(
109
108
  isolation: execution.isolation,
110
109
  invocation: execution.agentInvocation,
111
110
  signal,
112
- parentSessionFile: params.parentSessionFile,
113
- parentSessionId: params.parentSessionId,
111
+ parentSession: params.parentSession,
114
112
  onSessionCreated: (session, record) => {
115
113
  fgState.setSession(session);
116
114
  recordRef = record;