@gotgenes/pi-subagents 18.0.1 → 18.0.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.
Files changed (260) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/docs/architecture/architecture.md +157 -204
  3. package/docs/architecture/history/phase-19-implement-ui-decisions.md +282 -0
  4. package/package.json +5 -6
  5. package/src/lifecycle/subagent.ts +5 -9
  6. package/src/observation/notification.ts +23 -10
  7. package/src/observation/subagent-events-observer.ts +1 -3
  8. package/src/tools/get-result-report.ts +60 -0
  9. package/src/tools/get-result-tool.ts +25 -40
  10. package/src/types.ts +1 -1
  11. package/src/ui/session-navigator.ts +0 -2
  12. package/.prettierignore +0 -5
  13. package/AGENTS.md +0 -7
  14. package/docs/plans/0048-implement-subagents-api.md +0 -304
  15. package/docs/plans/0049-remove-group-join-output-file-rpc.md +0 -180
  16. package/docs/plans/0051-update-adr-0001-hard-fork.md +0 -77
  17. package/docs/plans/0052-remove-scheduled-subagents.md +0 -133
  18. package/docs/plans/0053-extract-model-resolution-from-execute.md +0 -181
  19. package/docs/plans/0054-decompose-index-into-modules.md +0 -302
  20. package/docs/plans/0057-structured-debug-logging.md +0 -154
  21. package/docs/plans/0061-session-format-transcript.md +0 -284
  22. package/docs/plans/0066-replace-as-any-with-sdk-types.md +0 -215
  23. package/docs/plans/0069-create-subagent-runtime.md +0 -345
  24. package/docs/plans/0070-extract-event-handlers.md +0 -306
  25. package/docs/plans/0071-extract-session-config-assembler.md +0 -362
  26. package/docs/plans/0072-inject-agent-manager-collaborators.md +0 -329
  27. package/docs/plans/0076-inject-cwd-into-agent-manager.md +0 -102
  28. package/docs/plans/0077-inject-project-agents-dir.md +0 -115
  29. package/docs/plans/0080-consolidate-agent-config-lookup.md +0 -247
  30. package/docs/plans/0084-extract-git-worktree-manager.md +0 -142
  31. package/docs/plans/0087-evolve-subagent-runtime-methods.md +0 -240
  32. package/docs/plans/0098-extract-agent-record-state-machine.md +0 -435
  33. package/docs/plans/0099-replace-ctx-with-parent-snapshot.md +0 -488
  34. package/docs/plans/0100-replace-callback-threading-with-session-subscription.md +0 -454
  35. package/docs/plans/0102-consolidate-test-record-factory.md +0 -176
  36. package/docs/plans/0108-extract-agent-type-registry.md +0 -322
  37. package/docs/plans/0109-extract-settings-manager.md +0 -276
  38. package/docs/plans/0110-agent-activity-tracker.md +0 -297
  39. package/docs/plans/0111-split-agent-record-lifecycle.md +0 -582
  40. package/docs/plans/0112-replace-agent-manager-callbacks.md +0 -241
  41. package/docs/plans/0113-disambiguate-spawn-options.md +0 -155
  42. package/docs/plans/0114-narrow-agent-tool-menu-deps.md +0 -279
  43. package/docs/plans/0115-decompose-agent-tool.md +0 -337
  44. package/docs/plans/0116-type-housekeeping.md +0 -351
  45. package/docs/plans/0118-settings-manager-apply-methods.md +0 -271
  46. package/docs/plans/0123-remove-vi-fn-cast-smell.md +0 -179
  47. package/docs/plans/0131-consolidate-shared-test-fixtures.md +0 -207
  48. package/docs/plans/0132-inject-io-into-session-config.md +0 -219
  49. package/docs/plans/0133-inject-sdk-boundary-into-agent-runner.md +0 -373
  50. package/docs/plans/0134-reduce-as-any-casts.md +0 -366
  51. package/docs/plans/0135-extract-display-helpers.md +0 -182
  52. package/docs/plans/0136-decompose-agent-menu.md +0 -300
  53. package/docs/plans/0144-consolidate-observation-model.md +0 -263
  54. package/docs/plans/0145-decompose-execute-push-ctx-to-boundary.md +0 -290
  55. package/docs/plans/0146-narrow-ui-context.md +0 -319
  56. package/docs/plans/0147-inject-wrap-text-into-conversation-viewer.md +0 -166
  57. package/docs/plans/0148-split-agent-widget-rendering.md +0 -255
  58. package/docs/plans/0152-add-prompt-snippet.md +0 -91
  59. package/docs/plans/0164-reorganize-into-domain-directories.md +0 -409
  60. package/docs/plans/0165-decompose-resolved-spawn-config.md +0 -157
  61. package/docs/plans/0166-extract-parent-session-info.md +0 -231
  62. package/docs/plans/0167-narrow-runner-io.md +0 -150
  63. package/docs/plans/0168-extract-tool-filter-config.md +0 -173
  64. package/docs/plans/0169-extract-run-context-from-run-options.md +0 -194
  65. package/docs/plans/0170-reduce-build-content-lines-complexity.md +0 -225
  66. package/docs/plans/0171-reduce-render-result-complexity.md +0 -200
  67. package/docs/plans/0172-extract-turn-formatting.md +0 -206
  68. package/docs/plans/0180-reorder-append-prompt-for-kv-cache.md +0 -100
  69. package/docs/plans/0185-remove-persistent-agent-memory.md +0 -161
  70. package/docs/plans/0188-replace-any-casts-with-sdk-types.md +0 -162
  71. package/docs/plans/0192-define-session-context-interface.md +0 -107
  72. package/docs/plans/0193-runtime-owns-context-queries.md +0 -245
  73. package/docs/plans/0194-align-tool-interfaces-for-structural-typing.md +0 -139
  74. package/docs/plans/0195-convert-tool-factories-to-classes.md +0 -264
  75. package/docs/plans/0196-convert-runner-menu-to-classes.md +0 -268
  76. package/docs/plans/0205-decompose-render-widget-lines.md +0 -140
  77. package/docs/plans/0206-decompose-show-agent-detail.md +0 -211
  78. package/docs/plans/0207-decompose-agent-widget-update.md +0 -242
  79. package/docs/plans/0208-extract-shared-test-fixtures.md +0 -298
  80. package/docs/plans/0214-convert-remaining-closure-factories-to-classes.md +0 -261
  81. package/docs/plans/0215-decompose-build-parent-context.md +0 -166
  82. package/docs/plans/0216-decompose-start-agent.md +0 -255
  83. package/docs/plans/0217-extract-overwrite-guard.md +0 -176
  84. package/docs/plans/0218-push-sdk-boundary-in-settings.md +0 -172
  85. package/docs/plans/0219-reduce-test-duplication-top-3-clone-families.md +0 -162
  86. package/docs/plans/0227-evolve-agent-record-into-agent.md +0 -322
  87. package/docs/plans/0228-async-start-agent-dissolve-run-handle.md +0 -288
  88. package/docs/plans/0229-agent-born-complete.md +0 -564
  89. package/docs/plans/0230-extract-concurrency-queue.md +0 -265
  90. package/docs/plans/0231-push-exec-registry-to-runner.md +0 -245
  91. package/docs/plans/0232-agent-resume-internal-observer-lifecycle.md +0 -180
  92. package/docs/plans/0237-remove-disallowed-tools.md +0 -180
  93. package/docs/plans/0238-remove-extensions-filtering.md +0 -191
  94. package/docs/plans/0239-collapse-filter-active-tools.md +0 -217
  95. package/docs/plans/0242-rename-agent-tool-to-subagent.md +0 -165
  96. package/docs/plans/0256-extract-worktree-isolation.md +0 -256
  97. package/docs/plans/0257-extract-child-session-factory.md +0 -285
  98. package/docs/plans/0262-add-workspace-provider-seam.md +0 -264
  99. package/docs/plans/0264-remove-extension-lifecycle-control.md +0 -277
  100. package/docs/plans/0265-born-complete-subagent-session.md +0 -333
  101. package/docs/plans/0270-type-consumable-public-surface.md +0 -204
  102. package/docs/plans/0272-export-workspace-collaborator-types.md +0 -147
  103. package/docs/plans/0277-encapsulate-agent-session.md +0 -304
  104. package/docs/plans/0280-rename-agent-to-subagent.md +0 -197
  105. package/docs/plans/0360-custom-agents-default-append.md +0 -132
  106. package/docs/plans/0373-extract-subagent-state.md +0 -250
  107. package/docs/plans/0374-encapsulate-subagent-start-notification.md +0 -268
  108. package/docs/plans/0375-extract-run-listener-workspace-bracket.md +0 -300
  109. package/docs/plans/0376-extract-manager-observer.md +0 -232
  110. package/docs/plans/0377-split-widget-delegation-from-runtime.md +0 -254
  111. package/docs/plans/0378-consolidate-lifecycle-test-fixtures.md +0 -243
  112. package/docs/plans/0379-consolidate-ui-tools-test-fixtures.md +0 -335
  113. package/docs/plans/0380-extract-layered-settings-loader.md +0 -223
  114. package/docs/plans/0381-replace-concurrency-queue-with-limiter.md +0 -267
  115. package/docs/plans/0400-include-parent-prompt-in-replace-mode.md +0 -199
  116. package/docs/plans/0403-abort-subagents-on-interrupt.md +0 -180
  117. package/docs/plans/0412-unify-session-mock-builders.md +0 -146
  118. package/docs/plans/0420-fold-run-metrics-live-activity-onto-record.md +0 -228
  119. package/docs/plans/0421-migrate-activity-readers-to-record-getters.md +0 -309
  120. package/docs/plans/0422-delete-activity-tracker-ui-observer.md +0 -204
  121. package/docs/plans/0423-widget-self-drive-from-lifecycle.md +0 -264
  122. package/docs/plans/0424-drop-widget-dep-from-subagent-tool.md +0 -166
  123. package/docs/plans/0425-reconcile-subagent-events-contract.md +0 -144
  124. package/docs/plans/0426-consolidate-residual-test-clone-families.md +0 -235
  125. package/docs/plans/0427-reconsider-ui-direction.md +0 -212
  126. package/docs/plans/0441-remove-agent-definition-management-subtree.md +0 -180
  127. package/docs/plans/0442-dissolve-agents-conversation-viewer.md +0 -195
  128. package/docs/plans/0443-consolidate-remaining-test-clone-families.md +0 -268
  129. package/docs/plans/0444-shrink-widget-to-background-agents.md +0 -156
  130. package/docs/plans/0445-native-session-navigation.md +0 -365
  131. package/docs/plans/0446-spike-session-navigation-entry-criteria.md +0 -171
  132. package/docs/plans/0447-extract-subagents-settings-command.md +0 -220
  133. package/docs/plans/0448-block-disabled-agent-spawn.md +0 -207
  134. package/docs/plans/0462-navigation-renderer-tui-components.md +0 -338
  135. package/docs/plans/0463-file-snapshot-source-evicted-agents.md +0 -306
  136. package/docs/plans/0470-refresh-readme-command-surface.md +0 -159
  137. package/docs/retro/0048-implement-subagents-api.md +0 -44
  138. package/docs/retro/0049-remove-group-join-output-file-rpc.md +0 -49
  139. package/docs/retro/0051-update-adr-0001-hard-fork.md +0 -39
  140. package/docs/retro/0053-extract-model-resolution-from-execute.md +0 -40
  141. package/docs/retro/0054-decompose-index-into-modules.md +0 -53
  142. package/docs/retro/0057-structured-debug-logging.md +0 -77
  143. package/docs/retro/0061-session-format-transcript.md +0 -41
  144. package/docs/retro/0066-replace-as-any-with-sdk-types.md +0 -44
  145. package/docs/retro/0069-create-subagent-runtime.md +0 -43
  146. package/docs/retro/0070-extract-event-handlers.md +0 -49
  147. package/docs/retro/0071-extract-session-config-assembler.md +0 -60
  148. package/docs/retro/0072-inject-agent-manager-collaborators.md +0 -46
  149. package/docs/retro/0076-inject-cwd-into-agent-manager.md +0 -35
  150. package/docs/retro/0077-inject-project-agents-dir.md +0 -33
  151. package/docs/retro/0080-consolidate-agent-config-lookup.md +0 -35
  152. package/docs/retro/0084-extract-git-worktree-manager.md +0 -37
  153. package/docs/retro/0087-evolve-subagent-runtime-methods.md +0 -42
  154. package/docs/retro/0098-extract-agent-record-state-machine.md +0 -46
  155. package/docs/retro/0099-replace-ctx-with-parent-snapshot.md +0 -37
  156. package/docs/retro/0100-replace-callback-threading-with-session-subscription.md +0 -36
  157. package/docs/retro/0102-consolidate-test-record-factory.md +0 -30
  158. package/docs/retro/0108-extract-agent-type-registry.md +0 -41
  159. package/docs/retro/0109-extract-settings-manager.md +0 -55
  160. package/docs/retro/0110-agent-activity-tracker.md +0 -44
  161. package/docs/retro/0111-split-agent-record-lifecycle.md +0 -61
  162. package/docs/retro/0112-replace-agent-manager-callbacks.md +0 -35
  163. package/docs/retro/0113-disambiguate-spawn-options.md +0 -29
  164. package/docs/retro/0114-narrow-agent-tool-menu-deps.md +0 -38
  165. package/docs/retro/0115-decompose-agent-tool.md +0 -51
  166. package/docs/retro/0116-type-housekeeping.md +0 -42
  167. package/docs/retro/0118-settings-manager-apply-methods.md +0 -40
  168. package/docs/retro/0123-remove-vi-fn-cast-smell.md +0 -49
  169. package/docs/retro/0131-consolidate-shared-test-fixtures.md +0 -46
  170. package/docs/retro/0132-inject-io-into-session-config.md +0 -33
  171. package/docs/retro/0133-inject-sdk-boundary-into-agent-runner.md +0 -45
  172. package/docs/retro/0134-reduce-as-any-casts.md +0 -56
  173. package/docs/retro/0135-extract-display-helpers.md +0 -38
  174. package/docs/retro/0136-decompose-agent-menu.md +0 -43
  175. package/docs/retro/0144-consolidate-observation-model.md +0 -39
  176. package/docs/retro/0145-decompose-execute-push-ctx-to-boundary.md +0 -56
  177. package/docs/retro/0146-narrow-ui-context.md +0 -70
  178. package/docs/retro/0147-inject-wrap-text-into-conversation-viewer.md +0 -90
  179. package/docs/retro/0148-split-agent-widget-rendering.md +0 -39
  180. package/docs/retro/0152-add-prompt-snippet.md +0 -34
  181. package/docs/retro/0164-reorganize-into-domain-directories.md +0 -82
  182. package/docs/retro/0165-decompose-resolved-spawn-config.md +0 -78
  183. package/docs/retro/0166-extract-parent-session-info.md +0 -66
  184. package/docs/retro/0167-narrow-runner-io.md +0 -63
  185. package/docs/retro/0168-extract-tool-filter-config.md +0 -76
  186. package/docs/retro/0169-extract-run-context-from-run-options.md +0 -68
  187. package/docs/retro/0170-reduce-build-content-lines-complexity.md +0 -72
  188. package/docs/retro/0171-reduce-render-result-complexity.md +0 -77
  189. package/docs/retro/0172-extract-turn-formatting.md +0 -80
  190. package/docs/retro/0180-reorder-append-prompt-for-kv-cache.md +0 -62
  191. package/docs/retro/0185-remove-persistent-agent-memory.md +0 -73
  192. package/docs/retro/0188-replace-any-casts-with-sdk-types.md +0 -69
  193. package/docs/retro/0192-define-session-context-interface.md +0 -59
  194. package/docs/retro/0193-runtime-owns-context-queries.md +0 -75
  195. package/docs/retro/0194-align-tool-interfaces-for-structural-typing.md +0 -62
  196. package/docs/retro/0195-convert-tool-factories-to-classes.md +0 -84
  197. package/docs/retro/0196-convert-runner-menu-to-classes.md +0 -73
  198. package/docs/retro/0205-decompose-render-widget-lines.md +0 -64
  199. package/docs/retro/0206-decompose-show-agent-detail.md +0 -67
  200. package/docs/retro/0207-decompose-agent-widget-update.md +0 -92
  201. package/docs/retro/0208-extract-shared-test-fixtures.md +0 -102
  202. package/docs/retro/0214-convert-remaining-closure-factories-to-classes.md +0 -66
  203. package/docs/retro/0215-decompose-build-parent-context.md +0 -61
  204. package/docs/retro/0216-decompose-start-agent.md +0 -80
  205. package/docs/retro/0217-extract-overwrite-guard.md +0 -63
  206. package/docs/retro/0218-push-sdk-boundary-in-settings.md +0 -72
  207. package/docs/retro/0219-reduce-test-duplication-top-3-clone-families.md +0 -64
  208. package/docs/retro/0227-evolve-agent-record-into-agent.md +0 -80
  209. package/docs/retro/0228-async-start-agent-dissolve-run-handle.md +0 -80
  210. package/docs/retro/0229-agent-born-complete.md +0 -89
  211. package/docs/retro/0230-extract-concurrency-queue.md +0 -38
  212. package/docs/retro/0231-push-exec-registry-to-runner.md +0 -71
  213. package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +0 -109
  214. package/docs/retro/0237-remove-disallowed-tools.md +0 -78
  215. package/docs/retro/0238-remove-extensions-filtering.md +0 -72
  216. package/docs/retro/0239-collapse-filter-active-tools.md +0 -70
  217. package/docs/retro/0242-rename-agent-tool-to-subagent.md +0 -82
  218. package/docs/retro/0256-extract-worktree-isolation.md +0 -89
  219. package/docs/retro/0257-extract-child-session-factory.md +0 -33
  220. package/docs/retro/0262-add-workspace-provider-seam.md +0 -89
  221. package/docs/retro/0264-remove-extension-lifecycle-control.md +0 -91
  222. package/docs/retro/0265-born-complete-subagent-session.md +0 -97
  223. package/docs/retro/0270-type-consumable-public-surface.md +0 -108
  224. package/docs/retro/0272-export-workspace-collaborator-types.md +0 -38
  225. package/docs/retro/0277-encapsulate-agent-session.md +0 -80
  226. package/docs/retro/0280-rename-agent-to-subagent.md +0 -96
  227. package/docs/retro/0360-custom-agents-default-append.md +0 -81
  228. package/docs/retro/0373-extract-subagent-state.md +0 -94
  229. package/docs/retro/0374-encapsulate-subagent-start-notification.md +0 -171
  230. package/docs/retro/0375-extract-run-listener-workspace-bracket.md +0 -104
  231. package/docs/retro/0376-extract-manager-observer.md +0 -81
  232. package/docs/retro/0377-split-widget-delegation-from-runtime.md +0 -105
  233. package/docs/retro/0378-consolidate-lifecycle-test-fixtures.md +0 -100
  234. package/docs/retro/0379-consolidate-ui-tools-test-fixtures.md +0 -52
  235. package/docs/retro/0380-extract-layered-settings-loader.md +0 -97
  236. package/docs/retro/0381-replace-concurrency-queue-with-limiter.md +0 -95
  237. package/docs/retro/0400-include-parent-prompt-in-replace-mode.md +0 -84
  238. package/docs/retro/0403-abort-subagents-on-interrupt.md +0 -90
  239. package/docs/retro/0412-unify-session-mock-builders.md +0 -80
  240. package/docs/retro/0420-fold-run-metrics-live-activity-onto-record.md +0 -90
  241. package/docs/retro/0421-migrate-activity-readers-to-record-getters.md +0 -105
  242. package/docs/retro/0422-delete-activity-tracker-ui-observer.md +0 -97
  243. package/docs/retro/0423-widget-self-drive-from-lifecycle.md +0 -112
  244. package/docs/retro/0424-drop-widget-dep-from-subagent-tool.md +0 -89
  245. package/docs/retro/0425-reconcile-subagent-events-contract.md +0 -101
  246. package/docs/retro/0426-consolidate-residual-test-clone-families.md +0 -100
  247. package/docs/retro/0427-reconsider-ui-direction.md +0 -102
  248. package/docs/retro/0441-remove-agent-definition-management-subtree.md +0 -97
  249. package/docs/retro/0442-dissolve-agents-conversation-viewer.md +0 -108
  250. package/docs/retro/0443-consolidate-remaining-test-clone-families.md +0 -28
  251. package/docs/retro/0444-shrink-widget-to-background-agents.md +0 -81
  252. package/docs/retro/0445-native-session-navigation.md +0 -122
  253. package/docs/retro/0446-spike-session-navigation-entry-criteria.md +0 -99
  254. package/docs/retro/0447-extract-subagents-settings-command.md +0 -91
  255. package/docs/retro/0448-block-disabled-agent-spawn.md +0 -75
  256. package/docs/retro/0462-navigation-renderer-tui-components.md +0 -105
  257. package/docs/retro/0463-file-snapshot-source-evicted-agents.md +0 -103
  258. package/docs/retro/0470-refresh-readme-command-surface.md +0 -40
  259. package/src/observation/notification-state.ts +0 -27
  260. package/vitest.config.ts +0 -14
@@ -1,285 +0,0 @@
1
- ---
2
- issue: 257
3
- issue_title: "Extract ChildSessionFactory from runner"
4
- ---
5
-
6
- # Extract ChildSessionFactory from runner
7
-
8
- > Superseded — issue #257 was closed `not_planned`.
9
- > Planning this extraction exposed that worktree isolation does not belong in the core; see [ADR-0002] and the reclaimed Phase 16 roadmap in [`docs/architecture/architecture.md`](../architecture/architecture.md).
10
- > The structural goal is recovered by #265.
11
- > This plan is retained for historical context only.
12
-
13
- ## Problem Statement
14
-
15
- `runAgent()` in `src/lifecycle/agent-runner.ts` conflates two concerns.
16
- The first is session *creation* — platform plumbing: env detection, config assembly, resource-loader construction, session-manager creation, `createSession()`, permission-bridge registration, `bindExtensions()`, and the post-bind recursion-guard tool filter.
17
- The second is session *interaction* — prompting, turn tracking, soft/hard turn-limit enforcement, response collection, and abort forwarding.
18
-
19
- This is Phase 16, Step 2 of the agent-collaborator architecture (`docs/architecture/architecture.md`).
20
- The step extracts the creation concern into a narrow `ChildSessionFactory` collaborator so session creation becomes independently testable and so `permission-bridge.ts` is imported by the factory rather than the runner.
21
- This is a lift-and-shift: `runAgent()` keeps its signature and delegates creation to the factory internally.
22
- `Agent` is not touched — that is Step 3 (#258).
23
-
24
- ## Goals
25
-
26
- - Define `ChildSessionFactory` (one method, `create(cwd?)`) and `ChildSessionResult` in a new module `src/lifecycle/child-session-factory.ts`.
27
- - Move the session-creation block out of `runAgent()` into a `ConcreteChildSessionFactory` class bound per-agent with creation config.
28
- - Move the `permission-bridge.ts` imports (`registerChildSession` / `unregisterChildSession`) and the recursion-guard helpers (`EXCLUDED_TOOL_NAMES`, `filterActiveTools`) from `agent-runner.ts` into the factory.
29
- - Expose teardown as a `cleanup()` function on the result so the runner (and, in Step 3, `Agent`) never imports the permission bridge.
30
- - Keep `runAgent()`'s signature `(snapshot, type, prompt, options, deps)` stable so the existing runner test suite continues to pass through delegation.
31
- - Add factory-level unit tests for session creation.
32
-
33
- This change is **not** breaking to any published API — `runAgent`, `RunnerDeps`, the IO interfaces, and the new factory types are all internal to the package.
34
-
35
- ## Non-Goals
36
-
37
- - No changes to `Agent` (`src/lifecycle/agent.ts`), `AgentManager`, or the tools — Step 3 (#258) makes `Agent` own the session and call `factory.create()`.
38
- - No `ConcreteAgentRunner.createFactory()` method yet — see the Design Overview decision below; it is added in Step 3 when `AgentManager` becomes its consumer.
39
- - No removal of `runAgent`, `resumeAgent`, `RunOptions`, `RunResult`, or the runner concept — that is Step 4 (#259).
40
- - No relocation of the session-creation IO interfaces (`RunnerIO`, `RunnerDeps`, `EnvironmentIO`, `SessionFactoryIO`, `CreateSessionOptions`, `ResourceLoaderOptions`, `ResourceLoaderLike`, `SessionManagerLike`) out of `agent-runner.ts` — they stay put to minimize churn; their home is revisited when the runner dissolves in Step 4.
41
- - No change to `assembleSessionConfig`, `session-config.ts`, `worktree-isolation.ts`, or the permission-bridge module itself.
42
-
43
- ## Background
44
-
45
- Relevant modules:
46
-
47
- - `src/lifecycle/agent-runner.ts` — `runAgent()` performs creation (effectiveCwd resolution, `detectEnv`, `assembleSessionConfig`, `createResourceLoader`+`reload`, `deriveSessionDir`, `createSessionManager`+`newSession`, `createSession`, `registerChildSession`, `bindExtensions`, post-bind `filterActiveTools`) then interaction (turn-tracking subscription, `collectResponseText`, `forwardAbortSignal`, `prompt`, finally `unregisterChildSession`, build `RunResult`).
48
- Holds the IO interfaces and `RunnerDeps`; `ConcreteAgentRunner.run()` delegates to `runAgent(..., this.deps)`.
49
- - `src/lifecycle/permission-bridge.ts` — `registerChildSession` / `unregisterChildSession`; no-ops when pi-permission-system is absent.
50
- Currently imported only by `agent-runner.ts`.
51
- - `src/session/session-config.ts` — `assembleSessionConfig()` returns `SessionConfig` with `effectiveCwd`, `systemPrompt`, `toolNames`, `extensions`, `thinkingLevel`, `noSkills`, and `agentMaxTurns` (= `agentConfig.maxTurns`).
52
- - `src/lifecycle/agent.ts` — `Agent.run()` calls `this._runner.run(...)`; `Agent` imports `RunResult` from the runner.
53
- Unchanged in this step.
54
- - `src/index.ts:136-166` — constructs `runnerDeps: RunnerDeps` and `new ConcreteAgentRunner(runnerDeps)`.
55
- Unchanged.
56
-
57
- Existing tests touching the runner:
58
-
59
- - `test/lifecycle/agent-runner.test.ts` (313 lines) — final-output capture, `bindExtensions` ordering, cwd/agentDir wiring, AGENTS.md suppression, `sessionFile` in `RunResult`, `newSession` with `parentSession`, `defaultMaxTurns`/`graceTurns` enforcement, resume fallback, and a permission-bridge describe block (register-before-bind, unregister-on-success, unregister-on-throw, sessionDir-as-key, agentName/parentSessionId).
60
- All exercise `runAgent()` directly via the `createRunnerIO()` helper and a `vi.mock("#src/lifecycle/permission-bridge")`.
61
- - `test/lifecycle/agent-runner-extension-tools.test.ts` — the post-bind recursion guard (`setActiveToolsByName` ordering, EXCLUDED filtering, `extensions: false` skip).
62
- - `test/lifecycle/agent-runner-settings.test.ts` — `normalizeMaxTurns` only.
63
- - `test/lifecycle/concrete-agent-runner.test.ts` — `ConcreteAgentRunner.run()`/`resume()` delegation.
64
- - `test/helpers/runner-io.ts` — `createRunnerIO()`, `createAgentLookup()`, `createRunnerDeps()` shared stubs.
65
-
66
- AGENTS.md / skill constraints that apply:
67
-
68
- - ES2024 target; Biome (not Prettier) formats; tabs (match `permission-bridge.ts`/`worktree-isolation.ts` style — new file uses tabs).
69
- - Tests use `vi.hoisted(...)` for module-level mocks (the permission-bridge mock pattern already exists).
70
- - fallow flags exports/members with no production consumer — drives the `createFactory` deferral decision below and the requirement that the factory have a production consumer (`runAgent`) by the end of the work.
71
- - The full vitest suite must pass before publishing.
72
-
73
- ## Design Overview
74
-
75
- ### Decision model
76
-
77
- `runAgent()` keeps its signature.
78
- Internally it constructs a `ConcreteChildSessionFactory` from the creation-relevant inputs plus `deps`, calls `factory.create(options.context.cwd)` to obtain `{ session, outputFile, cleanup, agentMaxTurns }`, then runs the unchanged interaction logic.
79
- The `finally` block calls `cleanup()` instead of `unregisterChildSession(sessionDir)`.
80
- `RunResult.sessionFile` comes from the factory's `outputFile` instead of a second `sessionManager.getSessionFile()` call at the end (same value — `getSessionFile()` is stable after `newSession()`; the existing test asserts the constant `/sessions/child.jsonl`).
81
-
82
- ### Data shapes
83
-
84
- ```typescript
85
- // src/lifecycle/child-session-factory.ts
86
- import type { Model } from "@earendil-works/pi-ai";
87
- import type { AgentSession } from "@earendil-works/pi-coding-agent";
88
- import type { RunnerDeps } from "#src/lifecycle/agent-runner";
89
- import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
90
- import type { ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
91
-
92
- /** Per-agent session-creation config, bound at factory construction. */
93
- export interface ChildSessionConfig {
94
- snapshot: ParentSnapshot;
95
- type: SubagentType;
96
- model?: Model<any>;
97
- isolated?: boolean;
98
- thinkingLevel?: ThinkingLevel;
99
- parentSession?: ParentSessionInfo;
100
- }
101
-
102
- /** Result of creating a configured child session. */
103
- export interface ChildSessionResult {
104
- session: AgentSession;
105
- /** Path to the persisted session JSONL file, if persisted. */
106
- outputFile?: string;
107
- /** Tear down creation side effects (permission-bridge unregister). */
108
- cleanup: () => void;
109
- /**
110
- * Per-agent configured max turns (from agentConfig.maxTurns) for the
111
- * caller's turn-limit enforcement. Crosses the creation/interaction seam
112
- * because it is computed during config assembly but consumed by the run loop.
113
- */
114
- agentMaxTurns?: number;
115
- }
116
-
117
- /** Creates a configured child AgentSession. Narrow: one method. */
118
- export interface ChildSessionFactory {
119
- create(cwd?: string): Promise<ChildSessionResult>;
120
- }
121
-
122
- export class ConcreteChildSessionFactory implements ChildSessionFactory {
123
- constructor(
124
- private readonly config: ChildSessionConfig,
125
- private readonly deps: RunnerDeps,
126
- ) {}
127
-
128
- async create(cwd?: string): Promise<ChildSessionResult> { /* lifted creation block */ }
129
- }
130
- ```
131
-
132
- Two deliberate refinements of the issue's sketch, both forced by the lift-and-shift and documented here:
133
-
134
- 1. `ChildSessionResult` adds `agentMaxTurns?: number`.
135
- The turn-limit resolution `normalizeMaxTurns(options.maxTurns ?? cfg.agentMaxTurns ?? options.defaultMaxTurns)` lives in the interaction half (`runAgent`), but `cfg.agentMaxTurns` is only known after `assembleSessionConfig`, which moves into the factory.
136
- The narrowest way to carry it across the seam is a single field on the result (ISP — not the whole `SessionConfig`).
137
- It remains useful in Step 3 when `Agent` owns turn enforcement.
138
- 2. `ChildSessionConfig` is narrow — only the six creation inputs.
139
- The issue's target lists `prompt`, `maxTurns`, and `getRunConfig` as bound config, but those are interaction concerns; binding them now would violate ISP for a factory whose only job is creation.
140
- They stay in `runAgent`'s `options` and migrate to the factory's config only if/when Step 3 needs them there.
141
-
142
- ### Why `ConcreteAgentRunner.createFactory()` is deferred to Step 3
143
-
144
- The issue describes the runner gaining `createFactory(config)`.
145
- Adding it in this step produces an unused class member: `runAgent()` builds the factory directly (it is a free function with `deps`, not a runner instance), and `AgentManager` — the eventual caller of `createFactory` — is not wired to it until Step 3. fallow flags unused class members.
146
- Adding it now would require either a `// fallow-ignore` suppression or rewiring `ConcreteAgentRunner.run()` to take a factory, which would change `runAgent`'s signature and force a premature rewrite of the 313-line runner test file.
147
- Deferring `createFactory` to Step 3 keeps this step a clean, fallow-green lift-and-shift and aligns with the architecture's "Agent is not changed yet" framing.
148
- The factory still has a production consumer in this step — `runAgent` — so the new class is not dead.
149
-
150
- ### Consumer call-site sketch (Tell-Don't-Ask)
151
-
152
- `runAgent()` after extraction (interaction only):
153
-
154
- ```typescript
155
- const factory = new ConcreteChildSessionFactory(
156
- {
157
- snapshot,
158
- type,
159
- model: options.model,
160
- isolated: options.isolated,
161
- thinkingLevel: options.thinkingLevel,
162
- parentSession: options.context.parentSession,
163
- },
164
- deps,
165
- );
166
- const { session, outputFile, cleanup, agentMaxTurns } = await factory.create(options.context.cwd);
167
-
168
- options.onSessionCreated?.(session);
169
- const maxTurns = normalizeMaxTurns(options.maxTurns ?? agentMaxTurns ?? options.defaultMaxTurns);
170
- // ... turn-tracking subscription, collector, abort forwarding ...
171
- try {
172
- await session.prompt(effectivePrompt);
173
- } finally {
174
- unsubTurns();
175
- collector.unsubscribe();
176
- cleanupAbort();
177
- cleanup(); // was: unregisterChildSession(sessionDir)
178
- }
179
- return { responseText, session, aborted, steered: softLimitReached, sessionFile: outputFile };
180
- ```
181
-
182
- `runAgent` tells the factory "create me a session" and tells the result "clean up" — no reach-through, no bridge import.
183
-
184
- ### Extracted-module interaction with upstream dependencies
185
-
186
- `ConcreteChildSessionFactory.create()` is the verbatim creation block, re-rooted onto `this.config` / `this.deps`.
187
- It carries no output-argument mutation or reverse-search patterns from the original (the block already only reads from `deps.io` and returns a session).
188
- The one in-place dependency it touches — `sessionManager` from `deps.io.createSessionManager` — is local to `create()`, captured in the returned `outputFile` and `cleanup` closure (which closes over `sessionDir`).
189
- The upstream API (`deps.io`, `assembleSessionConfig`, the permission-bridge functions) needs no changes; nothing about the seam requires fixing an upstream gap first.
190
-
191
- The factory reads four of `ParentSnapshot`'s fields (`cwd`, `systemPrompt`, `model`, `modelRegistry`); `parentContext` stays with `runAgent` for the prompt prefix.
192
- Passing the cohesive `ParentSnapshot` value object whole is appropriate.
193
-
194
- ### Edge cases
195
-
196
- - `cwd` omitted → `create()` falls back to `snapshot.cwd`, identical to today's `options.context.cwd ?? snapshot.cwd`.
197
- - `extensions: false` → factory skips the recursion-guard filter (`setActiveToolsByName` not called), identical to today.
198
- - `prompt()` throws → `runAgent`'s `finally` still calls `cleanup()`, so `unregisterChildSession` runs (existing "unregisters even when prompt throws" test preserved).
199
- - pi-permission-system absent → register/unregister remain no-ops (bridge behavior unchanged).
200
-
201
- ## Module-Level Changes
202
-
203
- - New: `src/lifecycle/child-session-factory.ts`
204
- - `ChildSessionConfig`, `ChildSessionResult`, `ChildSessionFactory` interfaces.
205
- - `ConcreteChildSessionFactory` class with the lifted `create(cwd?)` body.
206
- - Moved here from `agent-runner.ts`: the `registerChildSession` / `unregisterChildSession` imports, the `EXCLUDED_TOOL_NAMES` constant, and the `filterActiveTools` helper.
207
- - Imports (type-only) `RunnerDeps` from `agent-runner.ts` — type-only, so no runtime import cycle; the runtime arrow is one-way (`agent-runner` imports the factory class as a value).
208
- - Changed: `src/lifecycle/agent-runner.ts`
209
- - Remove the permission-bridge import, `EXCLUDED_TOOL_NAMES`, and `filterActiveTools`.
210
- - Add `import { ConcreteChildSessionFactory } from "#src/lifecycle/child-session-factory"`.
211
- - `runAgent()`: replace the creation block (effectiveCwd → post-bind filter) with `new ConcreteChildSessionFactory(...).create(options.context.cwd)`; resolve `maxTurns` from the returned `agentMaxTurns`; call `cleanup()` in the `finally`; set `RunResult.sessionFile = outputFile`.
212
- - Keep `RunnerDeps`, all IO interfaces, `RunResult`, `RunOptions`, `normalizeMaxTurns`, `collectResponseText`, `getLastAssistantText`, `forwardAbortSignal`, `resumeAgent`, `getAgentConversation`, and `ConcreteAgentRunner` unchanged.
213
- - Check the unused-import set after the move: `AgentSession` and `assembleSessionConfig`/`AssemblerIO` may no longer be referenced in `agent-runner.ts` once creation leaves; remove any now-dead imports (the factory imports them instead).
214
- - Doc updates (`docs/architecture/architecture.md`):
215
- - Lifecycle subgraph (≈ lines 54-60): add a `ChildSessionFactory` node; rewire the `AgentRunner --> SessionConfig` edge to `AgentRunner --> ChildSessionFactory --> SessionConfig` (the subscribe edges from observers stay on `AgentRunner`).
216
- - Layout listing (≈ lines 270-280): add `child-session-factory.ts child session creation (env, config assembly, bind, tool filter)`; update the `agent-runner.ts` line to "turn loop, results (creation delegated to ChildSessionFactory)".
217
- - Component dependency bullets (≈ lines 354-357): update the `agent-runner` bullet and add a `child-session-factory` bullet.
218
- - The fallow health snapshot (dated table, ≈ line 925) is left unchanged — it is a point-in-time fallow dump regenerated at phase boundaries, not per-step.
219
- - Doc update (`.pi/skills/package-pi-subagents/SKILL.md`): Lifecycle domain row — add `child-session-factory.ts`; bump the Lifecycle module count (9 → 10) and the total file count (56 → 57).
220
-
221
- Removed/moved symbols and their consumers (grepped across `src/` and `test/`):
222
-
223
- - `EXCLUDED_TOOL_NAMES`, `filterActiveTools` — private to `agent-runner.ts`, no other consumer; moved (not deleted) into the factory.
224
- - `registerChildSession` / `unregisterChildSession` imports — only `agent-runner.ts` imported them in `src/`; the import moves to the factory.
225
- The test mock `vi.mock("#src/lifecycle/permission-bridge")` is path-based and continues to intercept the factory's import unchanged.
226
- - No exported symbol is removed, so no excess-property or dangling-import breakage in `src/`.
227
-
228
- ## Test Impact Analysis
229
-
230
- 1. New unit tests enabled by the extraction (`test/lifecycle/child-session-factory.test.ts`, using `createRunnerDeps()` + a session stub):
231
- - register-before-`bindExtensions` ordering; register key = `sessionDir`; `agentName`/`parentSessionId` forwarded.
232
- - `cleanup()` calls `unregisterChildSession(sessionDir)`.
233
- - effective cwd/agentDir wiring into the loader and settings manager; AGENTS.md/CLAUDE.md/APPEND_SYSTEM suppression.
234
- - `newSession` called with `parentSession`.
235
- - `outputFile` = persisted session file; `agentMaxTurns` surfaced from the assembled config.
236
- - post-bind recursion guard: `setActiveToolsByName` once after bind; includes extension tool when `extensions: true`; excludes `EXCLUDED_TOOL_NAMES`; `extensions: false` skips the filter (migrated from `agent-runner-extension-tools.test.ts`).
237
- 2. Existing tests that become redundant / can be trimmed: the pure-creation assertions in `agent-runner.test.ts` (cwd/agentDir wiring, AGENTS.md suppression, `newSession` with `parentSession`, the permission "registers before bind"/"registers with sessionDir key"/"agentName+parentSessionId" cases) duplicate the new factory tests once migrated; the `agent-runner-extension-tools.test.ts` recursion-guard block moves to the factory test.
238
- These all currently pass through `runAgent → factory` delegation, so trimming is cleanup, not a correctness fix.
239
- 3. Existing tests that must stay (genuinely exercise the interaction layer or the delegation seam):
240
- `agent-runner.test.ts` keeps final-output capture + fallback, `defaultMaxTurns`/`graceTurns`/`maxTurns`-precedence enforcement, resume fallback, "binds extensions before prompting" (the create-then-prompt ordering is `runAgent`'s orchestration), "returns `sessionFile` in `RunResult`" (verifies `runAgent` surfaces `outputFile`), and "unregisters after success"/"unregisters even when prompt throws" (verify `runAgent` calls `cleanup()`).
241
- `agent-runner-settings.test.ts` (`normalizeMaxTurns`) and `concrete-agent-runner.test.ts` (`run`/`resume` delegation) are untouched.
242
-
243
- ## TDD Order
244
-
245
- 1. Add `ChildSessionFactory` with factory-level unit tests.
246
- Surface: `test/lifecycle/child-session-factory.test.ts`.
247
- Covers the creation behaviors and the recursion-guard cases listed in Test Impact #1.
248
- Implement `src/lifecycle/child-session-factory.ts` (interfaces + `ConcreteChildSessionFactory`, with the permission-bridge import and tool-filter helpers).
249
- The factory is standalone here — `runAgent` still has its own creation copy — so `pnpm fallow dead-code` will transiently flag `ConcreteChildSessionFactory` (consumed in step 2); that is expected and resolved by the next commit.
250
- Commit: `test(pi-subagents): add ChildSessionFactory creation tests` then `feat(pi-subagents): add ChildSessionFactory for child session creation`.
251
- 2. Delegate session creation from `runAgent()` to the factory.
252
- Rewire `runAgent()` to construct the factory and call `create()`; remove the creation block, the permission-bridge import, `EXCLUDED_TOOL_NAMES`, and `filterActiveTools` from `agent-runner.ts`; resolve `maxTurns` from `agentMaxTurns`; call `cleanup()` in `finally`; set `sessionFile = outputFile`.
253
- Trim the now-redundant creation tests from `agent-runner.test.ts` and migrate the recursion-guard block out of `agent-runner-extension-tools.test.ts` into the factory test (Test Impact #2).
254
- The factory now has a production consumer; `pnpm fallow dead-code` is clean.
255
- Run `pnpm run check` immediately (the creation extraction touches the runner's import surface).
256
- Commit: `refactor(pi-subagents): runAgent delegates session creation to ChildSessionFactory`.
257
- 3. Update the architecture doc and package skill.
258
- `docs/architecture/architecture.md` (lifecycle subgraph, layout listing, component bullets) and `.pi/skills/package-pi-subagents/SKILL.md` (Lifecycle row + counts).
259
- Commit: `docs(pi-subagents): reflect ChildSessionFactory extraction in architecture`.
260
-
261
- After all steps: `pnpm run check`, `pnpm run lint`, `pnpm -r run test`, `pnpm fallow dead-code`.
262
-
263
- ## Risks and Mitigations
264
-
265
- - Risk: a type-only import of `RunnerDeps` from `agent-runner.ts` into the factory while `agent-runner.ts` value-imports the factory looks circular.
266
- Mitigation: `import type` is fully erased, so the only runtime arrow is `agent-runner → child-session-factory`; verified by `pnpm run check` after step 2.
267
- - Risk: `RunResult.sessionFile` changes from a late `sessionManager.getSessionFile()` to the factory's `outputFile`.
268
- Mitigation: `getSessionFile()` is stable after `newSession()`; the existing assertion (`/sessions/child.jsonl`) and the persisted-file test both pass — confirmed by the runner suite in step 2.
269
- - Risk: the permission-bridge module mock stops intercepting after the import moves.
270
- Mitigation: `vi.mock()` is path-based; the factory imports the same `#src/lifecycle/permission-bridge` path, so the existing mock applies to the factory's calls.
271
- - Risk: trimming/migrating tests across `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` accidentally drops coverage.
272
- Mitigation: every trimmed assertion has an equivalent in the new factory test; the suite is the safety net (`pnpm -r run test`).
273
- - Risk: leftover dead imports in `agent-runner.ts` after the creation block leaves.
274
- Mitigation: step 2 ends with `pnpm run check` + `pnpm run lint`, which flag unused imports.
275
-
276
- ## Open Questions
277
-
278
- - Whether `ChildSessionResult.agentMaxTurns` should become a fully-resolved `maxTurns` (combining `options.maxTurns` / `defaultMaxTurns`) once Step 3 binds `getRunConfig` into the factory config.
279
- Deferred: keep the raw per-agent value for now; revisit when `Agent` owns turn enforcement.
280
- - Whether the session-creation IO interfaces (`RunnerIO`, `RunnerDeps`, `EnvironmentIO`, `SessionFactoryIO`, `CreateSessionOptions`, etc.) should move from `agent-runner.ts` into `child-session-factory.ts`.
281
- Deferred to Step 4, when the runner dissolves and the natural home for these creation contracts is the factory module.
282
- - Whether `ConcreteAgentRunner.createFactory()` lands in Step 3 (when `AgentManager` consumes it) exactly as the issue describes.
283
- Deferred to Step 3 per the Design Overview rationale.
284
-
285
- [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md
@@ -1,264 +0,0 @@
1
- ---
2
- issue: 262
3
- issue_title: "Add WorkspaceProvider extension seam"
4
- ---
5
-
6
- # Add the WorkspaceProvider extension seam
7
-
8
- ## Problem Statement
9
-
10
- Phase 16, Step 2 of [ADR-0002].
11
- The core needs only a working directory and a disposal hook for a child run; the default - the parent's cwd, with no setup or teardown - is always correct.
12
- "Where does a child run, and what brackets the run?"
13
- is a *strategy* (git worktree, container, tmpdir, remote sandbox), not core behavior.
14
- [ADR-0002] classifies this as the single *generative* extension surface: a concern that must return a value the core consumes synchronously attaches through a rationed provider seam, not an observational event.
15
- This issue adds that seam -`WorkspaceProvider` / `Workspace` plus `SubagentsService.registerWorkspaceProvider` - without the core gaining any knowledge of what an "isolation strategy" is.
16
-
17
- ## Goals
18
-
19
- - Define the `WorkspaceProvider` and `Workspace` interfaces in the core, with zero git or worktree knowledge.
20
- - Add `SubagentsService.registerWorkspaceProvider(provider): () => void` - a single-provider seam (chaining is out of scope) that throws if a provider is already registered and returns an unregister disposer.
21
- - At run-start, consult the registered provider for the child's cwd and a disposal handle; with no provider, the child runs in `baseCwd` (parent cwd - default behavior unchanged).
22
- - Call `dispose()` after the run and append the returned `resultAddendum` to the child's result.
23
- - This change is **additive and non-breaking** - the existing `isolation: "worktree"` path is left intact (its eviction is #263).
24
-
25
- ## Non-Goals
26
-
27
- - Removing `worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, or the `isolation: "worktree"` spawn mode - deferred to #263.
28
- - Removing `isolated` / `extensions: false` / `noSkills` - deferred to #264.
29
- - Born-complete child execution / dissolving the runner - deferred to #265.
30
- - Multiple/chained providers - out of scope per the issue; one provider only.
31
- - Shipping a concrete provider implementation - the worktrees package (#263) is the seam's first real consumer.
32
- Within this issue the seam is exercised only by test fakes; see Risks for the "no vacant hooks" release-coordination constraint.
33
-
34
- ## Background
35
-
36
- Relevant existing modules:
37
-
38
- - `src/lifecycle/agent.ts` - `Agent.run()` calls `this.worktree?.setup()` at run-start to obtain a cwd, threads it into `runner.run({ context: { cwd } })`, and on completion calls `this.worktree?.cleanup(description)`, appending a "Changes saved to branch ..." addendum.
39
- This is exactly the prepare/dispose shape the seam generalizes.
40
- - `src/lifecycle/worktree-isolation.ts` - `WorktreeIsolation` is the current run-scoped collaborator: `setup()` returns a path, `cleanup(description)` returns a `WorktreeCleanupResult`.
41
- The seam is its abstraction; #263 will reimplement it as a `WorkspaceProvider` in a separate package.
42
- - `src/lifecycle/agent-manager.ts` - constructs each `Agent`, owns the injected `WorktreeManager`, and threads `getRunConfig` as a getter.
43
- The same getter pattern is reused for the workspace provider.
44
- - `src/service/service.ts` - the package's public API surface (`package.json` `exports` points at `./src/service.ts`).
45
- `SubagentsService`, `SpawnOptions`, and `SubagentRecord` all live here; the seam types are re-exported here so the worktrees package can implement them.
46
- - `src/service/service-adapter.ts` - `SubagentsServiceAdapter implements SubagentsService`, wrapping the `AgentManagerLike` narrow interface.
47
- - `src/lifecycle/child-lifecycle.ts` - the *observational* lifecycle events from #261 (`spawning`, `session-created`, `completed`, `disposed`).
48
- The provider seam is orthogonal: events tell consumers what happened; the provider returns a value the core consumes.
49
-
50
- AGENTS.md constraints that apply:
51
-
52
- - Pi SDK imports stay out of library modules - the seam interfaces and `AgentManager` accept the provider as a parameter; `index.ts` (the SDK edge) supplies `baseCwd: process.cwd()`.
53
- - Do not read `process.cwd()` inside library functions - `baseCwd` is injected into `AgentManager` from `index.ts`.
54
- - When adding a public API pattern, follow the established convention: the repo's registration/subscription convention is an unsubscribe **function** (`() => void`, as in `SubscribableSession.subscribe` and `pi.events.on`), not a `Symbol.dispose` `Disposable`.
55
- The seam therefore returns `() => void`; this is a deliberate divergence from the issue's literal `Disposable` to match the codebase convention.
56
-
57
- ## Design Overview
58
-
59
- ### Seam interfaces
60
-
61
- Defined in a new core module `src/lifecycle/workspace.ts` (sibling to `child-lifecycle.ts`), re-exported from `service.ts` for public consumers.
62
- The `status` field reuses the core `AgentStatus` union (from `agent.ts`), re-exported publicly so the worktrees package can name it.
63
-
64
- ```typescript
65
- import type { AgentStatus } from "#src/lifecycle/agent";
66
- import type { AgentInvocation, SubagentType } from "#src/types";
67
-
68
- /** Context the core hands a provider when a child run starts. */
69
- export interface WorkspacePrepareContext {
70
- agentId: string;
71
- agentType: SubagentType;
72
- baseCwd: string;
73
- invocation?: AgentInvocation;
74
- }
75
-
76
- /** Outcome the core reports to a workspace when the run ends. */
77
- export interface WorkspaceDisposeOutcome {
78
- status: AgentStatus;
79
- description: string;
80
- }
81
-
82
- /** What dispose may hand back for the core to fold into the child result. */
83
- export interface WorkspaceDisposeResult {
84
- resultAddendum?: string;
85
- }
86
-
87
- /** A prepared working directory plus its bracketed teardown. Born complete. */
88
- export interface Workspace {
89
- readonly cwd: string; // the directory already exists
90
- dispose(outcome: WorkspaceDisposeOutcome): WorkspaceDisposeResult | void;
91
- }
92
-
93
- /** The single generative seam: supplies a child's workspace. */
94
- export interface WorkspaceProvider {
95
- prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
96
- }
97
- ```
98
-
99
- Note the addendum-formatting boundary: the core appends `resultAddendum` *verbatim*.
100
- The provider owns its own separator and wording (the worktrees package owns the "Changes saved to branch ..." string in #263).
101
- The core never formats branch text.
102
-
103
- ### Registration - single provider, throw on duplicate
104
-
105
- `AgentManager` holds an optional provider and exposes registration:
106
-
107
- ```typescript
108
- private workspaceProvider?: WorkspaceProvider;
109
-
110
- registerWorkspaceProvider(provider: WorkspaceProvider): () => void {
111
- if (this.workspaceProvider) {
112
- throw new Error(
113
- "A WorkspaceProvider is already registered; only one is supported.",
114
- );
115
- }
116
- this.workspaceProvider = provider;
117
- return () => {
118
- if (this.workspaceProvider === provider) this.workspaceProvider = undefined;
119
- };
120
- }
121
- ```
122
-
123
- The throw surfaces a misconfiguration loudly (two workspace extensions installed at once).
124
- The disposer clears the slot only if the same provider is still active, so a stale disposer cannot evict a later registration.
125
- `SubagentsServiceAdapter.registerWorkspaceProvider` delegates straight through; `AgentManagerLike` gains the method.
126
-
127
- ### Run-start consultation (Tell-Don't-Ask call site)
128
-
129
- `Agent.run()` consults the provider at the point where it currently calls `worktree?.setup()`.
130
- Provider-first precedence: when a provider supplies a workspace, the core routes cwd and dispose through it and skips the legacy worktree collaborator; with no provider it falls back to the existing worktree path; with neither it runs in `baseCwd` (cwd undefined → SDK uses the parent cwd).
131
-
132
- ```typescript
133
- // run() - replacing the worktree?.setup() block
134
- let cwd: string | undefined;
135
- try {
136
- const provider = this._getWorkspaceProvider?.();
137
- if (provider) {
138
- this._workspace = await provider.prepare({
139
- agentId: this.id,
140
- agentType: this.type,
141
- baseCwd: this._baseCwd,
142
- invocation: this.invocation,
143
- });
144
- cwd = this._workspace?.cwd;
145
- } else {
146
- this.worktree?.setup();
147
- cwd = this.worktree?.path;
148
- }
149
- } catch (err) {
150
- this.markError(err);
151
- this.releaseListeners();
152
- this.observer?.onRunFinished?.(this);
153
- return;
154
- }
155
- // ... runner.run({ context: { cwd, parentSession }, ... })
156
- ```
157
-
158
- On completion (`completeRun`) the core computes the final status, then disposes:
159
-
160
- ```typescript
161
- const finalStatus: AgentStatus =
162
- result.aborted ? "aborted" : result.steered ? "steered" : "completed";
163
- if (this._workspace) {
164
- const out = this._workspace.dispose({ status: finalStatus, description: this.description });
165
- if (out?.resultAddendum) finalResult += out.resultAddendum;
166
- } else {
167
- const wt = this.worktree?.cleanup(this.description);
168
- if (wt?.hasChanges && wt.branch) finalResult += `\n\n---\nChanges saved to branch \`${wt.branch}\`...`;
169
- }
170
- ```
171
-
172
- `failRun` mirrors this in a `try/catch`, disposing with `status: "error"` and discarding any addendum (matching the existing error-path behavior, which does not append branch text).
173
-
174
- The provider getter is injected into each `Agent` by `AgentManager.spawn` (`getWorkspaceProvider: () => this.workspaceProvider`), exactly like `getRunConfig`.
175
- `baseCwd` is injected into `AgentManager` from `index.ts` and threaded to each `Agent`.
176
-
177
- ### Why the worktree path stays (scope decision A)
178
-
179
- Per the clarification, #262 is the additive seam only; the legacy `isolation: "worktree"` orchestration is untouched and removed in #263.
180
- A genuinely separate strategy could register a provider today and get correct cwd + dispose behavior; worktree spawns keep working unchanged.
181
- Provider-first precedence means the two never silently conflict, and #263 collapses the branch by deleting the worktree arm.
182
-
183
- ### Edge cases
184
-
185
- - `prepare()` resolves `undefined` → `cwd` is undefined → runner uses `baseCwd` (parent cwd).
186
- No dispose call (no workspace).
187
- - `prepare()` rejects → `markError`, release listeners, notify observer, return (same shape as a worktree `setup()` failure today).
188
- - `dispose()` returns `void` or no `resultAddendum` → result unchanged.
189
- - Duplicate `registerWorkspaceProvider` → throws synchronously.
190
-
191
- ## Module-Level Changes
192
-
193
- | File | Change |
194
- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
- | `src/lifecycle/workspace.ts` | **New.** Defines `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, `WorkspaceDisposeResult`. No behavior. |
196
- | `src/lifecycle/agent.ts` | `AgentInit` gains optional `baseCwd?: string` and `getWorkspaceProvider?: () => WorkspaceProvider \| undefined`. New private fields `_baseCwd`, `_getWorkspaceProvider`, `_workspace?: Workspace`. `run()` provider-first prepare; `completeRun`/`failRun` dispose + verbatim `resultAddendum`. Export `AgentStatus` is already public. |
197
- | `src/lifecycle/agent-manager.ts` | `AgentManagerOptions` gains required `baseCwd: string`. New `workspaceProvider` field, `registerWorkspaceProvider()` (throw on dup, unregister disposer). `spawn()` passes `baseCwd` and the `getWorkspaceProvider` getter into each `Agent`. |
198
- | `src/service/service.ts` | Re-export the five seam types and `AgentStatus`. Add `registerWorkspaceProvider(provider: WorkspaceProvider): () => void` to the `SubagentsService` interface. |
199
- | `src/service/service-adapter.ts` | `AgentManagerLike` gains `registerWorkspaceProvider(provider): () => void`. `SubagentsServiceAdapter` implements the method, delegating to the manager. |
200
- | `src/index.ts` | Pass `baseCwd: process.cwd()` to the `new AgentManager({...})` construction (alongside the existing `GitWorktreeManager(process.cwd())`). |
201
- | `docs/architecture/architecture.md` | Mark Phase 16 Step 2 (#262) as landed in the roadmap; note the seam exists and `workspace.ts` is added to the lifecycle domain listing. |
202
-
203
- No exports are removed or renamed, so no `src/`/`test/` removed-symbol grep is required.
204
- No file in Module-Level Changes is also claimed as unchanged in Non-Goals (the worktree *modules* are non-goals; `agent.ts` touches the worktree *call path* additively, which is consistent).
205
-
206
- ### Grep checklist before finalizing
207
-
208
- - Objects typed as `SubagentsService` in tests: `test/service/service.test.ts` casts `{ spawn: () => "id" } as unknown as SubagentsService`, so adding an interface method does **not** break it (verified).
209
- - `new AgentManager(` call sites: `src/index.ts` (one) and `test/lifecycle/agent-manager.test.ts` `createManager` (one) - both updated for required `baseCwd` in the same step.
210
- - `AgentManagerLike` mocks in `test/service/service-adapter.test.ts` (`defaultManager`, inline `spawn:` stubs) - add `registerWorkspaceProvider` stub in the same step.
211
-
212
- ## Test Impact Analysis
213
-
214
- This is an additive seam, so the work is dominated by *new* tests; little existing coverage is affected.
215
-
216
- 1. New unit tests the seam enables: provider registration (throw-on-duplicate, disposer-unregisters), run-start consultation (cwd from `prepare`, `resultAddendum` appended on dispose), `prepare` returns undefined → `baseCwd`, `prepare` rejects → `markError`, and adapter delegation.
217
- These were impossible before because there was no provider abstraction to substitute.
218
- 2. Redundant existing tests: none.
219
- The seam does not subsume worktree tests - they exercise the legacy path, which is preserved.
220
- 3. Existing tests that must stay as-is: all `worktree.test.ts`, `worktree-isolation.test.ts`, and the AgentManager worktree-isolation tests (`calls worktrees.create` / `cleanup`) - they genuinely exercise the fallback path that remains in #262.
221
- The Agent no-provider tests assert unchanged worktree behavior.
222
-
223
- ## TDD Order
224
-
225
- 1. **Seam types + registration surface** - `feat`.
226
- New `src/lifecycle/workspace.ts`; re-export seam types + `AgentStatus` from `service.ts`; add `registerWorkspaceProvider` to `SubagentsService`, `AgentManagerLike`, and `SubagentsServiceAdapter` (delegating); add required `baseCwd` + provider field + `registerWorkspaceProvider` (throw on dup, disposer) to `AgentManager`; update `index.ts` and the `createManager` test factory for `baseCwd`.
227
- Tests: `agent-manager.test.ts` registration (throws on second register; disposer clears only the active provider; getter returns the registered provider) and `service-adapter.test.ts` delegation.
228
- This whole surface lands in one commit because the `SubagentsService` interface method forces the adapter to implement it and the required `baseCwd` forces both construction sites - splitting would not type-check.
229
- Suggested message: `feat: add WorkspaceProvider registration seam to subagents service`.
230
- Run `pnpm run check` immediately after (shared-interface change).
231
-
232
- 2. **Run-start consumption + dispose** - `feat`.
233
- `Agent`: `AgentInit` gains `baseCwd`/`getWorkspaceProvider`; new private fields; `run()` provider-first prepare; `completeRun`/`failRun` dispose + verbatim `resultAddendum`.
234
- `AgentManager.spawn` passes `baseCwd` and the `getWorkspaceProvider` getter (sole extra construction site, folded in).
235
- Tests: `agent.test.ts` - provider `prepare` supplies cwd to the runner; `dispose` `resultAddendum` appended to the result; `prepare` undefined → cwd falls back to `baseCwd`; `prepare` rejects → `markError` + `onRunFinished`; no-provider path still uses the worktree collaborator (regression guard).
236
- Suggested message: `feat: consult workspace provider for child cwd and disposal`.
237
- Run `pnpm run check` after (AgentInit change).
238
-
239
- 3. **Architecture doc update** - `docs`.
240
- Mark Phase 16 Step 2 (#262) landed in the roadmap; add `workspace.ts` to the lifecycle domain listing; cross-link the seam.
241
- Suggested message: `docs: record WorkspaceProvider seam in phase 16 roadmap`.
242
-
243
- ## Risks and Mitigations
244
-
245
- - **Vacant hook (the headline risk).**
246
- [ADR-0002]’s “no vacant hooks” rule says a provider seam with no consumer is a speculative abstraction that `fallow` flags as dead.
247
- Within #262 the seam is exercised only by test fakes.
248
- Mitigation: land #262 **alongside** #263 (its first real consumer, `@gotgenes/pi-subagents-worktrees`) - do not cut a release that contains the seam without the worktrees package.
249
- Track this as a release-coordination constraint; the architecture roadmap already pairs Steps 2 and 3.
250
- - **Dual cwd path confusion.**
251
- Provider-first precedence keeps worktree and provider from silently conflicting; the branch is documented and removed in #263.
252
- - **`baseCwd` source.**
253
- Injecting `process.cwd()` from `index.ts` matches the existing `GitWorktreeManager(process.cwd())` construction; no new global-state read enters a library module.
254
- - **Status timing in dispose.**
255
- The final status is computed before the status-transition methods mutate, so `dispose`'s outcome reflects the true terminal status.
256
-
257
- ## Open Questions
258
-
259
- - Should `baseCwd` eventually come from the parent `SessionContext.cwd` rather than `process.cwd()`?
260
- Deferred -`process.cwd()` preserves current worktree behavior; revisit during the born-complete work (#265).
261
- - Should the `disposed` lifecycle event (#261) and `Workspace.dispose` be reconciled into one teardown notion?
262
- Deferred - they serve different surfaces (observational vs generative); revisit if #265 dissolves the runner.
263
-
264
- [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md