@gotgenes/pi-subagents 13.2.0 → 13.2.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 (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +21 -10
  3. package/dist/public.d.ts +34 -35
  4. package/docs/architecture/architecture.md +58 -148
  5. package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
  6. package/docs/decisions/0003-publish-bundled-type-declarations.md +3 -1
  7. package/docs/plans/0051-update-adr-0001-hard-fork.md +8 -6
  8. package/docs/plans/0257-extract-child-session-factory.md +3 -1
  9. package/docs/plans/0262-add-workspace-provider-seam.md +41 -39
  10. package/docs/plans/0264-remove-extension-lifecycle-control.md +100 -98
  11. package/docs/plans/0265-born-complete-subagent-session.md +5 -2
  12. package/docs/plans/0270-type-consumable-public-surface.md +3 -1
  13. package/docs/plans/0280-rename-agent-to-subagent.md +197 -0
  14. package/docs/retro/0051-update-adr-0001-hard-fork.md +4 -2
  15. package/docs/retro/0257-extract-child-session-factory.md +3 -1
  16. package/docs/retro/0262-add-workspace-provider-seam.md +3 -1
  17. package/docs/retro/0264-remove-extension-lifecycle-control.md +3 -1
  18. package/docs/retro/0270-type-consumable-public-surface.md +4 -2
  19. package/docs/retro/0277-encapsulate-agent-session.md +41 -0
  20. package/docs/retro/0280-rename-agent-to-subagent.md +96 -0
  21. package/package.json +1 -1
  22. package/src/index.ts +9 -9
  23. package/src/lifecycle/create-subagent-session.ts +1 -1
  24. package/src/lifecycle/{agent-manager.ts → subagent-manager.ts} +27 -27
  25. package/src/lifecycle/subagent-session.ts +2 -2
  26. package/src/lifecycle/{agent.ts → subagent.ts} +28 -28
  27. package/src/lifecycle/turn-limits.ts +1 -1
  28. package/src/lifecycle/workspace.ts +2 -2
  29. package/src/observation/notification.ts +9 -9
  30. package/src/observation/record-observer.ts +9 -9
  31. package/src/runtime.ts +1 -1
  32. package/src/service/service-adapter.ts +10 -10
  33. package/src/service/service.ts +5 -9
  34. package/src/tools/agent-tool.ts +5 -5
  35. package/src/tools/background-spawner.ts +3 -3
  36. package/src/tools/foreground-runner.ts +5 -5
  37. package/src/tools/get-result-tool.ts +2 -2
  38. package/src/tools/steer-tool.ts +2 -2
  39. package/src/types.ts +1 -1
  40. package/src/ui/agent-creation-wizard.ts +2 -2
  41. package/src/ui/agent-menu.ts +5 -5
  42. package/src/ui/agent-widget.ts +2 -2
  43. package/src/ui/conversation-viewer.ts +3 -3
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ 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
+ ## [13.2.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v13.2.1...pi-subagents-v13.2.2) (2026-06-01)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * use ADR-NNNN with links docs-wide ([c6b6431](https://github.com/gotgenes/pi-packages/commit/c6b6431c004f324931f23be46cf2e47e8fdac919))
14
+
15
+ ## [13.2.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v13.2.0...pi-subagents-v13.2.1) (2026-05-30)
16
+
17
+
18
+ ### Documentation
19
+
20
+ * **pi-subagents:** refresh permission-integration and architecture sections ([#267](https://github.com/gotgenes/pi-packages/issues/267)) ([4096f83](https://github.com/gotgenes/pi-packages/commit/4096f83c343250b4d2cb5a522bbb140e1e023ed3))
21
+
8
22
  ## [13.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v13.1.0...pi-subagents-v13.2.0) (2026-05-30)
9
23
 
10
24
 
package/README.md CHANGED
@@ -427,10 +427,10 @@ When [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permissio
427
427
  - **Tool filtering** — the permission system's `before_agent_start` handler removes denied tools from the child session before the agent starts.
428
428
  - **`ask`-state forwarding** — when a child session triggers an `ask` permission, the prompt forwards to the parent session's UI.
429
429
  The parent approves or denies, and the child resumes.
430
- - **Deterministic child detection** — every child session registers with the permission system's `SubagentSessionRegistry` before `bindExtensions()` fires, so detection does not rely on env vars or filesystem heuristics.
430
+ - **Deterministic child detection** — this extension publishes `subagents:child:session-created` before `bindExtensions()` fires; the permission system subscribes and registers the child session synchronously, so detection does not rely on env vars or filesystem heuristics.
431
431
 
432
432
  No configuration is required.
433
- When `@gotgenes/pi-permission-system` is not installed, the registration calls are silent no-ops.
433
+ When `@gotgenes/pi-permission-system` is not installed, the lifecycle events have no subscriber — a harmless no-op.
434
434
 
435
435
  ## Architecture
436
436
 
@@ -451,19 +451,27 @@ src/
451
451
  session-config.ts # Session configuration assembler
452
452
  prompts.ts # Config-driven system prompt builder
453
453
  context.ts # Parent conversation context for inherit_context
454
- skill-loader.ts # Preload skills from Pi-standard + Agent Skills spec
454
+ conversation.ts # Render a session's messages as formatted text
455
+ content-items.ts # Shared message content parsing
455
456
  env.ts # Environment detection (git, platform)
456
457
  model-resolver.ts # Fuzzy model matching
458
+ session-dir.ts # Session directory derivation
457
459
  lifecycle/ # Agent execution and state tracking
458
- agent-manager.ts # Spawn, queue, abort, resume, concurrency
459
- agent-runner.ts # Session creation, turn loop, tool filtering
460
- agent-record.ts # Status state machine
460
+ agent-manager.ts # Collection manager + observer wiring
461
+ agent.ts # Full execution lifecycle (run, abort, steer, workspace)
462
+ create-subagent-session.ts # Assembly factory: session creation, binding, tool filtering
463
+ subagent-session.ts # Born-complete child session: turn loop, steer, dispose
464
+ child-lifecycle.ts # Child-execution lifecycle event publisher
465
+ concurrency-queue.ts # Background agent scheduling
461
466
  parent-snapshot.ts # Immutable spawn-time parent state
462
- permission-bridge.ts # Optional bridge to pi-permission-system registry
463
- worktree.ts # Git worktree isolation
467
+ turn-limits.ts # Turn-count policy (normalizeMaxTurns)
468
+ workspace.ts # Workspace provider seam
469
+ usage.ts # Token usage tracking
464
470
  observation/ # Progress tracking and notification
465
471
  record-observer.ts # Session-event stats observer
466
472
  notification.ts # Completion nudges
473
+ notification-state.ts # Notification state tracking
474
+ renderer.ts # Notification rendering
467
475
  service/ # Cross-extension API boundary
468
476
  service.ts # SubagentsService interface + Symbol.for() accessors
469
477
  service-adapter.ts # SubagentsService wrapper around AgentManager
@@ -484,8 +492,9 @@ Each has a corresponding upstream PR:
484
492
  3. **`<active_agent>` system-prompt tag** (`src/prompts.ts`) — `buildAgentPrompt` prepends `<active_agent name="${config.name}"/>` to every assembled child system prompt (both `replace` and `append` modes).
485
493
  Downstream extensions like [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system) parse this tag to resolve per-agent `permission:` frontmatter inside the child session.
486
494
  Upstream PR: [tintinweb/pi-subagents#73](https://github.com/tintinweb/pi-subagents/pull/73).
487
- 4. **Permission-system registration** (`src/lifecycle/permission-bridge.ts`) — `runAgent` registers every child session with `@gotgenes/pi-permission-system`'s `SubagentSessionRegistry` before `bindExtensions()` and unregisters in the `finally` block.
488
- This enables deterministic child detection and `ask`-state forwarding to the parent UI.
495
+ 4. **Child-execution lifecycle events** (`src/lifecycle/child-lifecycle.ts`) — the child-session execution lifecycle is published as ordered events on `pi.events` (`subagents:child:spawning`, `session-created`, `completed`, `disposed`).
496
+ `session-created` fires synchronously before `bindExtensions()` so consumers (e.g. `@gotgenes/pi-permission-system`) can register the child session before binding proceeds.
497
+ This inverts the former outbound `permission-bridge` pattern ([ADR-0002] / [#261]) — the core publishes, consumers subscribe.
489
498
  No upstream equivalent — this feature is specific to the `@gotgenes` fork.
490
499
 
491
500
  The upstream `vitest` suite plus tests added for each patch all pass on every commit.
@@ -493,3 +502,5 @@ The upstream `vitest` suite plus tests added for each patch all pass on every co
493
502
  ## License
494
503
 
495
504
  MIT — [tintinweb](https://github.com/tintinweb) (upstream) and [Chris Lasher](https://github.com/gotgenes) (fork)
505
+
506
+ [ADR-0002]: docs/decisions/0002-extensions-on-a-minimal-core.md
package/dist/public.d.ts CHANGED
@@ -1,19 +1,5 @@
1
1
  import { ThinkingLevel } from '@earendil-works/pi-ai';
2
2
 
3
- /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
4
- /**
5
- * Lifetime usage components, accumulated via `message_end` events. Survives
6
- * compaction (which replaces session.state.messages and would reset any
7
- * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
8
- * the cumulative cached prefix re-read on that one call — summing across
9
- * turns counts the prefix N times. See issue #38.
10
- */
11
- type LifetimeUsage = {
12
- input: number;
13
- output: number;
14
- cacheWrite: number;
15
- };
16
-
17
3
  /**
18
4
  * types.ts — Type definitions for the subagent system.
19
5
  */
@@ -29,27 +15,19 @@ interface AgentInvocation {
29
15
  runInBackground?: boolean;
30
16
  }
31
17
 
18
+ /** usage.ts — Token usage: shapes, accumulator operators, session-stats readers. */
32
19
  /**
33
- * agent.ts Agent class with encapsulated status-transition logic and per-agent behavior.
34
- *
35
- * Status transitions (status, result, error, startedAt, completedAt) are owned
36
- * by the class and exposed via transition methods. External code reads these
37
- * fields through public properties but cannot write them directly.
38
- *
39
- * Stats (toolUses, lifetimeUsage, compactionCount) are owned by the class and
40
- * accumulated via mutation methods (incrementToolUses, addUsage, incrementCompactions).
41
- *
42
- * Behavior (abort, steer buffering) lives on the agent rather than on
43
- * AgentManager — each agent manages its own lifecycle concerns.
44
- *
45
- * The child's working directory is supplied by a registered WorkspaceProvider
46
- * (the workspace seam); with no provider the child runs in the parent cwd.
47
- *
48
- * Phase-specific collaborators (subagentSession, notification) are attached
49
- * after construction as lifecycle information becomes available.
20
+ * Lifetime usage components, accumulated via `message_end` events. Survives
21
+ * compaction (which replaces session.state.messages and would reset any
22
+ * stats-derived sum). cacheRead is excluded because each turn's cacheRead is
23
+ * the cumulative cached prefix re-read on that one call summing across
24
+ * turns counts the prefix N times. See issue #38.
50
25
  */
51
-
52
- type AgentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
26
+ type LifetimeUsage = {
27
+ input: number;
28
+ output: number;
29
+ cacheWrite: number;
30
+ };
53
31
 
54
32
  /**
55
33
  * workspace.ts — The single generative extension seam (ADR 0002, Phase 16 Step 2).
@@ -73,7 +51,7 @@ interface WorkspacePrepareContext {
73
51
  }
74
52
  /** Outcome the core reports to a workspace when the run ends. */
75
53
  interface WorkspaceDisposeOutcome {
76
- status: AgentStatus;
54
+ status: SubagentStatus;
77
55
  description: string;
78
56
  }
79
57
  /** What dispose may hand back for the core to fold into the child result. */
@@ -92,6 +70,28 @@ interface WorkspaceProvider {
92
70
  prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined>;
93
71
  }
94
72
 
73
+ /**
74
+ * subagent.ts — Subagent class with encapsulated status-transition logic and per-subagent behavior.
75
+ *
76
+ * Status transitions (status, result, error, startedAt, completedAt) are owned
77
+ * by the class and exposed via transition methods. External code reads these
78
+ * fields through public properties but cannot write them directly.
79
+ *
80
+ * Stats (toolUses, lifetimeUsage, compactionCount) are owned by the class and
81
+ * accumulated via mutation methods (incrementToolUses, addUsage, incrementCompactions).
82
+ *
83
+ * Behavior (abort, steer buffering) lives on the subagent rather than on
84
+ * SubagentManager — each subagent manages its own lifecycle concerns.
85
+ *
86
+ * The child's working directory is supplied by a registered WorkspaceProvider
87
+ * (the workspace seam); with no provider the child runs in the parent cwd.
88
+ *
89
+ * Phase-specific collaborators (subagentSession, notification) are attached
90
+ * after construction as lifecycle information becomes available.
91
+ */
92
+
93
+ type SubagentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
94
+
95
95
  /**
96
96
  * service.ts — Public API surface for cross-extension access to subagents.
97
97
  *
@@ -103,7 +103,6 @@ interface WorkspaceProvider {
103
103
  * svc?.spawn("Explore", "Check for stale TODOs");
104
104
  */
105
105
 
106
- type SubagentStatus = "queued" | "running" | "completed" | "steered" | "aborted" | "stopped" | "error";
107
106
  /** Serializable snapshot of an agent's state — no live session objects. */
108
107
  interface SubagentRecord {
109
108
  id: string;
@@ -51,11 +51,11 @@ flowchart TB
51
51
 
52
52
  subgraph lifecycle["Lifecycle domain"]
53
53
  direction TB
54
- AgentManager["AgentManager<br/>(spawn, abort, collection)"]
54
+ SubagentManager["SubagentManager<br/>(spawn, abort, collection)"]
55
55
  ConcurrencyQueue["ConcurrencyQueue<br/>(scheduling, drain)"]
56
56
  CreateSubagentSession["createSubagentSession<br/>(assembly factory)"]
57
57
  SubagentSession["SubagentSession<br/>(turn loop, steer, dispose)"]
58
- Agent["Agent<br/>(status, behavior: abort/steer/run lifecycle)"]
58
+ Subagent["Subagent<br/>(status, behavior: abort/steer/run lifecycle)"]
59
59
  ParentSnapshot["ParentSnapshot<br/>(frozen parent state)"]
60
60
  Workspace["workspace<br/>(provider seam: child cwd + teardown)"]
61
61
  end
@@ -85,9 +85,9 @@ flowchart TB
85
85
  Menu["agent-menu<br/>(slash command)"]
86
86
  end
87
87
 
88
- AgentTool --> AgentManager
89
- AgentManager --> Agent
90
- Agent --> CreateSubagentSession & SubagentSession
88
+ AgentTool --> SubagentManager
89
+ SubagentManager --> Subagent
90
+ Subagent --> CreateSubagentSession & SubagentSession
91
91
  CreateSubagentSession --> SubagentSession
92
92
  CreateSubagentSession --> SessionConfig
93
93
  SessionConfig --> AgentTypeRegistry
@@ -95,18 +95,18 @@ flowchart TB
95
95
  AgentTypeRegistry --> DefaultAgents & CustomAgents
96
96
  RecordObserver -.->|subscribes| SubagentSession
97
97
  UIObserver -.->|subscribes| SubagentSession
98
- Widget -.->|polls| AgentManager
98
+ Widget -.->|polls| SubagentManager
99
99
  ```
100
100
 
101
101
  ### Key domain types
102
102
 
103
103
  ```mermaid
104
104
  classDiagram
105
- class Agent {
105
+ class Subagent {
106
106
  +id: string
107
107
  +type: SubagentType
108
108
  +description: string
109
- +status: AgentStatus
109
+ +status: SubagentStatus
110
110
  +result?: string
111
111
  +error?: string
112
112
  +toolUses: number
@@ -123,7 +123,7 @@ classDiagram
123
123
  +run()
124
124
  +resume(prompt, signal)
125
125
  +abort(): boolean
126
- +steer(message): Promise<boolean>
126
+ +steer(message): Promise~boolean~
127
127
  +isSessionReady(): boolean
128
128
  +getConversation(): string | undefined
129
129
  +getContextPercent(): number | null
@@ -137,12 +137,12 @@ classDiagram
137
137
  +releaseListeners()
138
138
  }
139
139
 
140
- class AgentManager {
140
+ class SubagentManager {
141
141
  +spawn(snapshot, type, prompt, config)
142
142
  +spawnAndWait(snapshot, type, prompt, config)
143
143
  +resume(id, prompt, signal)
144
- +getRecord(id): Agent
145
- +listAgents(): Agent[]
144
+ +getRecord(id): Subagent
145
+ +listAgents(): Subagent[]
146
146
  +abort(id)
147
147
  }
148
148
 
@@ -171,10 +171,10 @@ classDiagram
171
171
  +hasRunning(): boolean
172
172
  }
173
173
 
174
- AgentManager --> Agent : creates/manages
175
- AgentManager --> ParentSnapshot : receives at spawn
176
- SubagentsService --> AgentManager : wraps via adapter
177
- AgentManager --> AgentTypeRegistry : resolves types
174
+ SubagentManager --> Subagent : creates/manages
175
+ SubagentManager --> ParentSnapshot : receives at spawn
176
+ SubagentsService --> SubagentManager : wraps via adapter
177
+ SubagentManager --> AgentTypeRegistry : resolves types
178
178
  ```
179
179
 
180
180
  ## Agent lifecycle
@@ -216,8 +216,8 @@ sequenceDiagram
216
216
  participant LLM as Parent LLM
217
217
  participant Tool as subagent tool
218
218
  participant Spawn as spawn-config
219
- participant Mgr as AgentManager
220
- participant Ag as Agent
219
+ participant Mgr as SubagentManager
220
+ participant Ag as Subagent
221
221
  participant Factory as createSubagentSession
222
222
  participant Asm as assembleSessionConfig
223
223
  participant Sub as SubagentSession
@@ -238,8 +238,8 @@ sequenceDiagram
238
238
  Sub->>Child: prompt + drive turn loop
239
239
  Child-->>Sub: result text
240
240
  Sub-->>Ag: TurnLoopResult
241
- Ag-->>Mgr: update Agent
242
- Mgr-->>Tool: Agent
241
+ Ag-->>Mgr: update Subagent
242
+ Mgr-->>Tool: Subagent
243
243
  Tool-->>LLM: formatted result
244
244
  Note over Mgr: disposeSession() fires `disposed` at cleanup (resume-detectable)
245
245
  ```
@@ -277,11 +277,11 @@ src/
277
277
  │ └── session-dir.ts session directory derivation
278
278
 
279
279
  ├── lifecycle/ agent execution and state tracking
280
- │ ├── agent-manager.ts collection manager + observer wiring
280
+ │ ├── subagent-manager.ts collection manager + observer wiring
281
281
  │ ├── create-subagent-session.ts assembly factory: session creation, binding, tool filtering
282
282
  │ ├── subagent-session.ts born-complete child session: turn loop, steer, dispose
283
283
  │ ├── turn-limits.ts normalizeMaxTurns (turn-count policy)
284
- │ ├── agent.ts owns full execution lifecycle (run, abort, steer, workspace)
284
+ │ ├── subagent.ts owns full execution lifecycle (run, abort, steer, workspace)
285
285
  │ ├── concurrency-queue.ts background agent scheduling with configurable concurrency limit
286
286
  │ ├── parent-snapshot.ts immutable spawn-time parent state
287
287
  │ ├── child-lifecycle.ts child-execution lifecycle event publisher
@@ -296,7 +296,7 @@ src/
296
296
 
297
297
  ├── service/ cross-extension API boundary
298
298
  │ ├── service.ts SubagentsService interface + Symbol.for() accessors
299
- │ └── service-adapter.ts SubagentsServiceAdapter class wrapping AgentManager
299
+ │ └── service-adapter.ts SubagentsServiceAdapter class wrapping SubagentManager
300
300
 
301
301
  ├── tools/ LLM-facing tool implementations
302
302
  │ ├── agent-tool.ts subagent tool definition, validation, dispatch
@@ -334,7 +334,7 @@ Record statistics (tool uses, token usage, compaction counts) are updated by `re
334
334
  UI streaming (active tools, response text, turn counts) is handled by `ui/ui-observer.ts`, which subscribes to the same session events independently.
335
335
  Neither observer wraps or forwards the other — both subscribe directly to the session.
336
336
 
337
- The widget reads agent state by polling a shared `Map<string, AgentActivityTracker>` on `SubagentRuntime` every 80 ms. The conversation viewer subscribes to session events via `Agent.subscribeToUpdates()` and reads messages via `Agent.messages` — no direct `AgentSession` reference (#277).
337
+ The widget reads agent state by polling a shared `Map<string, AgentActivityTracker>` on `SubagentRuntime` every 80 ms. The conversation viewer subscribes to session events via `Subagent.subscribeToUpdates()` and reads messages via `Subagent.messages` — no direct `AgentSession` reference (#277).
338
338
 
339
339
  ## Cross-extension architecture
340
340
 
@@ -343,7 +343,7 @@ flowchart TD
343
343
  subgraph core["@gotgenes/pi-subagents"]
344
344
  direction TB
345
345
  exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
346
- engine["Tools: subagent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, createSubagentSession, SubagentSession"]
346
+ engine["Tools: subagent, get_subagent_result,<br/>steer_subagent<br/>SubagentManager, createSubagentSession, SubagentSession"]
347
347
  ui_int["Internal UI: widget, viewer,<br/>/agents menu"]
348
348
  end
349
349
 
@@ -358,14 +358,14 @@ They declare this package as an optional peer dependency and use dynamic import
358
358
  ### What the core owns
359
359
 
360
360
  - The three tools: `subagent` (née `Agent`), `get_subagent_result`, `steer_subagent`.
361
- - `AgentManager` — spawn, abort, resume, collection management, observer wiring.
361
+ - `SubagentManager` — spawn, abort, resume, collection management, observer wiring.
362
362
  - `ConcurrencyQueue` — background agent scheduling with configurable concurrency limit.
363
363
  - `createSubagentSession` — assembly factory: session creation and extension binding; returns a born-complete `SubagentSession`.
364
364
  - `SubagentSession` — the born-complete child session: drives the turn loop (`runTurnLoop`/`resumeTurnLoop`), steers, and disposes (firing `disposed` at true session disposal, so resume executions are registry-detected).
365
365
  - `child-lifecycle` — publishes the child-execution lifecycle (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) on `pi.events`.
366
366
  Reactive consumers subscribe: `@gotgenes/pi-permission-system` registers each child session on `session-created` and unregisters it on `disposed`.
367
- This replaced the former outbound `permission-bridge` (#261, ADR 0002) — the core no longer looks up a named consumer.
368
- - `workspace` — the single generative seam (#262, ADR 0002): a registered `WorkspaceProvider` supplies a child's cwd plus bracketed `dispose()` at run-start.
367
+ This replaced the former outbound `permission-bridge` (#261, [ADR-0002]) — the core no longer looks up a named consumer.
368
+ - `workspace` — the single generative seam (#262, [ADR-0002]): a registered `WorkspaceProvider` supplies a child's cwd plus bracketed `dispose()` at run-start.
369
369
  With no provider, children run in the parent cwd (default unchanged); the git worktree strategy lives behind this seam in `@gotgenes/pi-subagents-worktrees` (#263, the seam's first consumer).
370
370
  - `session-config` — pure configuration assembler (called by `createSubagentSession`).
371
371
  - `SubagentRuntime` — session-scoped state bag with methods.
@@ -373,7 +373,7 @@ They declare this package as an optional peer dependency and use dynamic import
373
373
  - `record-observer` — session-event observer that updates record statistics without callback threading.
374
374
  - Agent type registry — default agents, custom `.md` file loading.
375
375
  - Prompt assembly, context extraction, skills, environment.
376
- - Worktree isolation — evicted to `@gotgenes/pi-subagents-worktrees` via the workspace provider seam in Phase 16 (#263, ADR 0002); `git` no longer appears in the core.
376
+ - Worktree isolation — evicted to `@gotgenes/pi-subagents-worktrees` via the workspace provider seam in Phase 16 (#263, [ADR-0002]); `git` no longer appears in the core.
377
377
  - Token usage tracking.
378
378
  - Session directory derivation and persisted `SessionManager` for subagent transcripts.
379
379
  - Settings persistence.
@@ -478,7 +478,7 @@ Extensions attach through exactly two surfaces, distinguished by the direction o
478
478
  Reactive concerns live here: permission detection, telemetry, UI, notifications.
479
479
  Adding a reactive concern never modifies the core.
480
480
  2. **Provider seams (generative) — rationed.**
481
- The rare concern that must *inject* a value the core consumes synchronously registers a provider the core consults.
481
+ The rare concern that must _inject_ a value the core consumes synchronously registers a provider the core consults.
482
482
  Today there is exactly one: the **workspace provider** (returns the child's working directory plus bracketed setup/teardown).
483
483
  A provider seam is the only place the core is "open," so the list is kept as small as possible.
484
484
 
@@ -487,7 +487,7 @@ The discriminator when deciding how a concern attaches:
487
487
  - It only needs to **know** what happened → subscribe to a lifecycle event (observational, unlimited).
488
488
  - It must **return a value the core consumes** → register a provider (generative, rationed).
489
489
 
490
- The governing rule — **no vacant hooks**: the architecture must *admit* a seam without *shipping* it until a concrete consumer exists.
490
+ The governing rule — **no vacant hooks**: the architecture must _admit_ a seam without _shipping_ it until a concrete consumer exists.
491
491
  A provider seam with no consumer is a speculative abstraction that taxes every reader and that `fallow` flags as dead.
492
492
  Latent extensibility is the deliverable; a vacant hook is not.
493
493
 
@@ -509,11 +509,11 @@ Latent extensibility is the deliverable; a vacant hook is not.
509
509
  - **Tool policy** (`disallowed_tools`) — access control belongs in pi-permission-system's `permission:` frontmatter.
510
510
  - **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
511
511
  - **Worktree isolation** (`worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, the `isolation: "worktree"` spawn mode) — environment policy, not core.
512
- Git worktrees are one *strategy* for choosing the child's working directory; containers, throwaway tmpdirs, and remote sandboxes are others.
512
+ Git worktrees are one _strategy_ for choosing the child's working directory; containers, throwaway tmpdirs, and remote sandboxes are others.
513
513
  Evicted to `@gotgenes/pi-subagents-worktrees` (#263), the first consumer of the workspace provider seam.
514
514
  - **Extension lifecycle control** (`extensions: false`, `isolated`, `noSkills`) — removed in #264.
515
515
  Deny-at-use (the in-child permission layer blocking disallowed tool calls) covers what `isolated` pretended to do for tools.
516
- Prevent-load (refusing to bind an extension because of load-time side effects, cost, or true sandboxing) is genuinely generative and is left as a *latent* (un-built) provider seam, added only if a real consumer needs it.
516
+ Prevent-load (refusing to bind an extension because of load-time side effects, cost, or true sandboxing) is genuinely generative and is left as a _latent_ (un-built) provider seam, added only if a real consumer needs it.
517
517
 
518
518
  ### Composition model
519
519
 
@@ -563,7 +563,7 @@ Bags with 10+ fields are the highest priority for decomposition.
563
563
  | `AgentToolDeps` | 8 | agent-tool | ✓ done |
564
564
  | `AgentMenuDeps` | 8 | agent-menu | ✓ done |
565
565
  | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
566
- | `AgentInit` | 8 | agent | Low |
566
+ | `SubagentInit` | 8 | subagent | Low |
567
567
 
568
568
  ### Complexity hotspots
569
569
 
@@ -595,19 +595,19 @@ One 11-line internal clone group remains within `agent-config-editor.ts` (lines
595
595
 
596
596
  ### Session encapsulation debt (Law of Demeter) — resolved by [#277] ✔️
597
597
 
598
- All consumer reach-throughs to the raw SDK `AgentSession` via `Agent.session` have been eliminated.
599
- `Agent.session` is removed; `SubagentSession.session` is marked `@internal` (lifecycle use only).
598
+ All consumer reach-throughs to the raw SDK `AgentSession` via `Subagent.session` have been eliminated.
599
+ `Subagent.session` is removed; `SubagentSession.session` is marked `@internal` (lifecycle use only).
600
600
  The intent-revealing replacements added by [#277]:
601
601
 
602
- | Reach-through | Sites | Replacement |
603
- | ---------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------ |
604
- | Steer buffer-or-deliver (was duplicated) | `service-adapter.ts`, `steer-tool.ts` | `Agent.steer(message)` |
605
- | Conversation viewing | `get-result-tool.ts`, `agent-menu.ts`, `conversation-viewer.ts` | `Agent.getConversation()` / `Agent.messages` |
606
- | Session-readiness guard | `agent-tool.ts`, `agent-manager.ts` | `Agent.isSessionReady()` |
607
- | Context-window stats | `steer-tool.ts`, `get-result-tool.ts`, `notification.ts`, `conversation-viewer.ts` | `Agent.getContextPercent()` |
608
- | Live updates (subscription) | `conversation-viewer.ts` | `Agent.subscribeToUpdates(fn)` |
609
- | Observer callback session param | `background-spawner.ts`, `foreground-runner.ts` | `agent.subagentSession` (narrowed callback) |
610
- | Session disposal | `agent-manager.ts` | `SubagentSession.dispose()` — resolved by [#265] |
602
+ | Reach-through | Sites | Replacement |
603
+ | ---------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------- |
604
+ | Steer buffer-or-deliver (was duplicated) | `service-adapter.ts`, `steer-tool.ts` | `Subagent.steer(message)` |
605
+ | Conversation viewing | `get-result-tool.ts`, `agent-menu.ts`, `conversation-viewer.ts` | `Subagent.getConversation()` / `Subagent.messages` |
606
+ | Session-readiness guard | `agent-tool.ts`, `subagent-manager.ts` | `Subagent.isSessionReady()` |
607
+ | Context-window stats | `steer-tool.ts`, `get-result-tool.ts`, `notification.ts`, `conversation-viewer.ts` | `Subagent.getContextPercent()` |
608
+ | Live updates (subscription) | `conversation-viewer.ts` | `Subagent.subscribeToUpdates(fn)` |
609
+ | Observer callback session param | `background-spawner.ts`, `foreground-runner.ts` | `subagent.subagentSession` (narrowed callback) |
610
+ | Session disposal | `subagent-manager.ts` | `SubagentSession.dispose()` — resolved by [#265] |
611
611
 
612
612
  ### Proposed bag decompositions
613
613
 
@@ -755,106 +755,15 @@ After Phase 15, Agent is born complete with all dependencies and configuration,
755
755
  All six steps are closed: [#227], [#228], [#231], [#229], [#230], [#232].
756
756
  See [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) for details.
757
757
 
758
- ## Improvement roadmap (Phase 16 — invert dependencies: extensions on a minimal core)
758
+ ## Phase 16 (complete)
759
759
 
760
- Phase 16 reclaims its original intent — invert the core's outbound dependencies — and extends it: worktree isolation joins permissions as an *extension* on a minimal core, leaving pi-subagents a pure child-session orchestrator.
761
- The decision and the full reasoning chain are recorded in [ADR 0002](../decisions/0002-extensions-on-a-minimal-core.md); the two-surface extension model is described under [Target architecture](#target-architecture).
762
-
763
- ### Abandoned exploration: agent collaborator architecture
764
-
765
- An earlier Phase 16 plan ("agent collaborator architecture") proposed giving `Agent` three collaborators a session factory, a `WorktreeIsolation`, and a lifecycle observer and dissolving the runner.
766
- That framing was abandoned.
767
- Pulling on a single late-bound `create(cwd?)` parameter on the planned `ChildSessionFactory` exposed deeper problems:
768
-
769
- - `WorktreeIsolation.setup()` is a two-phase `construct-then-setup()` that violates "Construct complete" (principle 8) — the worktree is only *ready* at dequeue.
770
- - The worktree and the child session share one lifespan, so they are one run-scoped resource, not sibling collaborators that `Agent` must sequence; the `cwd` parameter only existed because the worktree was split out and `Agent` relayed its output back in.
771
- - Worktrees are not intrinsic to subagents — they are one *workspace strategy* and belong outside the core, exactly as Phase 14 evicted tool/extension policy.
772
-
773
- Issue #256 (`WorktreeIsolation` as a collaborator) shipped under the abandoned plan and is now superseded by #263; issue #257 (`ChildSessionFactory` extraction) was parked.
774
- The structural win the collaborator plan chased — a born-complete child execution and the dissolution of the runner — is recovered by a cleaner route once the workspace seam exists (Step 5).
775
-
776
- ### Steps
777
-
778
- #### Step 1: Child-execution lifecycle events; retire permission-bridge — [#261] ✅ Delivered
779
-
780
- Emit ordered child-execution events (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) carrying child identity (session directory, agent name, parent session id).
781
- Migrate `@gotgenes/pi-permission-system` to subscribe to `session-created`/`disposed` for registration instead of being looked up by the core; delete `permission-bridge.ts`.
782
-
783
- - Cross-package: pi-subagents (emit + remove bridge) and pi-permission-system (subscribe).
784
- - Investigation (resolved): `pi.events` is a Node `EventEmitter`, so `emit()` dispatches listeners synchronously on the same call stack — a synchronous subscriber completes before `emit()` returns.
785
- Emitting `session-created` immediately before `bindExtensions()` therefore guarantees the registry entry lands pre-bind, with no new SDK hook.
786
- The synchronous-handler constraint is encoded as a real-bus test in pi-permission-system.
787
- - Outcome: the core stops reaching out to a named consumer; permission detection rides events.
788
- - Deferred: removing the now-caller-less `registerSubagentSession`/`unregisterSubagentSession` from `PermissionsService` → #267; registry-detected resume ("executing now" → "exists" semantics) → #265.
789
-
790
- #### Step 2: Define the `WorkspaceProvider` seam — [#262] ✅ Delivered
791
-
792
- Added the `WorkspaceProvider` / `Workspace` interfaces (`src/lifecycle/workspace.ts`) and `SubagentsService.registerWorkspaceProvider` (single provider, throws on duplicate, returns an unregister disposer).
793
- All five workspace types are named-re-exported from `service.ts`: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult` (added in #272).
794
- At run-start `Agent.run()` consults the registered provider for the child's cwd and a disposal handle; with no provider the child runs in the parent's cwd (the legacy worktree-collaborator fallback was removed when worktrees left the core in #263).
795
- On completion the core calls `Workspace.dispose({ status, description })` and appends the returned `resultAddendum` verbatim — the provider owns the wording.
796
-
797
- - The seam is additive and non-breaking: the existing `isolation: "worktree"` path is untouched (its eviction is Step 3).
798
- - Land alongside its first consumer (Step 3) to avoid a vacant hook — the "no vacant hooks" rule.
799
- Within #262 the seam is exercised only by test fakes; do not cut a release containing the seam without `@gotgenes/pi-subagents-worktrees`.
800
- - Outcome: a single generative seam; the core no longer knows what an "isolation strategy" is.
801
-
802
- #### Step 3: Extract worktrees to `@gotgenes/pi-subagents-worktrees` — [#263] ✅ Delivered
803
-
804
- New package implementing `WorkspaceProvider`: prepares a git worktree at run-start (born complete), tears it down after (saving the branch), and owns the "changes saved to branch" result.
805
- Worktree isolation is opt-in per agent type via the package's own `worktreeAgents` config; creation failure for an opted-in agent throws (strict, no silent fallback).
806
- Removed `worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, and the `isolation: "worktree"` mode from the core; dropped `isolation` from the spawn API and `SubagentsService`, and `worktreeResult` from `SubagentRecord`.
807
-
808
- - Supersedes #256.
809
- New package registered in `release-please-config.json` and `.pi/settings.json` (after pi-subagents); consumes the published `@gotgenes/pi-subagents` from the registry (`linkWorkspacePackages: false`), since `exports.types` resolves to the shipped declaration bundle.
810
- From the release carrying #272, all five workspace types are importable by name: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult`.
811
- - Outcome: git leaves the core; worktree users install one package, everyone else pays nothing.
812
-
813
- #### Step 4: Remove `isolated` / `extensions: false` / `noSkills` — [#264] ✅ Delivered
814
-
815
- Children always load the parent's extensions and skills; the recursion guard is now unconditional.
816
- Deny-at-use (the in-child permission layer) covers tool restriction; prevent-load is left as a latent provider seam (not shipped).
817
- The `skills` curation axis collapsed symmetrically with `extensions`: `AgentConfig.skills`, the skill-preload path (`skill-loader.ts`, `safe-fs.ts`, `preloadSkills`, `PromptExtras`), `SessionConfig.{extensions,noSkills,extras}`, and the `isolated:` / `extensions:` / `skills:` custom-agent frontmatter keys are all gone.
818
-
819
- - Depended on: Step 1 (deny-at-use over events).
820
- - Outcome: the `isolated`/`extensions`/`noSkills`/`skills` axis is gone; the guard is unconditional.
821
-
822
- #### Step 5: Born-complete child execution; dissolve the runner — [#265] ✅ Delivered
823
-
824
- `createSubagentSession()` is an assembly factory that returns a born-complete `SubagentSession` (session created, extensions bound, recursion guard applied).
825
- `SubagentSession` owns turn driving (`runTurnLoop`/`resumeTurnLoop`), steering, and disposal.
826
- `Agent.run()` is coordination, not assembly; `runAgent` / `resumeAgent` / `ConcreteAgentRunner` / `AgentRunner` / `RunOptions` / `RunResult` / `ExecutionState` dissolved.
827
- `getAgentConversation()` relocated to `session/conversation.ts`; `normalizeMaxTurns()` to `lifecycle/turn-limits.ts`.
828
- `disposed` now fires at true session disposal (cleanup), so resume executions are registry-detected (closing the gap deferred from #261).
829
-
830
- - Depends on: Steps 2–4.
831
- - Outcome: the "runner" concept is gone; `Agent.run()` is coordination, not assembly — the structural goal of the abandoned collaborator plan, reached cleanly.
832
-
833
- ### Step dependency diagram
834
-
835
- ```mermaid
836
- flowchart LR
837
- S1["Step 1<br/>Lifecycle events<br/>(retire bridge)"]
838
- S2["Step 2<br/>WorkspaceProvider seam"]
839
- S3["Step 3<br/>Extract worktrees pkg"]
840
- S4["Step 4<br/>Remove isolated"]
841
- S5["Step 5<br/>Born-complete execution"]
842
-
843
- S2 --> S3
844
- S1 --> S4
845
- S2 --> S5
846
- S3 --> S5
847
- S4 --> S5
848
- ```
849
-
850
- ### Tracks
851
-
852
- 1. **Track A — Inversion seams** (Steps 1, 2): lifecycle events and the workspace seam.
853
- Independent of each other — can proceed in parallel.
854
- 2. **Track B — Eviction** (Steps 3, 4): worktrees and `isolated` leave the core.
855
- Step 3 depends on Step 2.
856
- 3. **Track C — Consolidation** (Step 5): dissolve the runner around the new seam.
857
- Depends on Tracks A and B.
760
+ Phase 16 inverted the core's outbound dependencies: worktree isolation joined permissions as an _extension_ on a minimal core, leaving pi-subagents a pure child-session orchestrator.
761
+ The core now attaches extensions through exactly two surfaces observational lifecycle events (unlimited) and rationed generative provider seams (today only the workspace provider) and has zero knowledge of its consumers.
762
+ The "runner" concept is gone: `createSubagentSession()` returns a born-complete `SubagentSession` that owns turn driving, steering, and disposal, and `Subagent.run()` is coordination, not assembly.
763
+ The decision and the full reasoning chain are recorded in [ADR-0002]; the two-surface extension model is described under [Target architecture](#target-architecture).
764
+ All five steps are closed: [#261], [#262], [#263], [#264], [#265].
765
+ The earlier "agent collaborator architecture" framing (#256 superseded, #257 parked, #258 and #259 closed not-planned) was abandoned; its structural win was reached cleanly via the workspace seam.
766
+ See [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) for details.
858
767
 
859
768
  ## Improvement roadmap (Phase 17 — extract UI)
860
769
 
@@ -864,8 +773,8 @@ By this point the core is minimal and stable — the API boundary has been prove
864
773
 
865
774
  ## Refactoring history
866
775
 
867
- Phases 1–5, 7–14 are complete.
868
- Phase 6 (UI extraction to a separate package) is deferred.
776
+ Phases 1–5, 7–16 are complete.
777
+ Phase 6 (UI extraction to a separate package) is deferred → Phase 17.
869
778
  Detailed records are preserved in per-phase history files:
870
779
 
871
780
  | Phase | Title | Status | History |
@@ -885,7 +794,7 @@ Detailed records are preserved in per-phase history files:
885
794
  | 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
886
795
  | 14 | Strip policy from core | Complete | [phase-14-strip-policy.md](history/phase-14-strip-policy.md) |
887
796
  | 15 | Domain model evolution | Complete | [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) |
888
- | 16 | Invert dependencies (extensions on a minimal core) | Planned | [ADR 0002](../decisions/0002-extensions-on-a-minimal-core.md) |
797
+ | 16 | Invert dependencies (extensions on a minimal core) | Complete | [phase-16-invert-dependencies.md](history/phase-16-invert-dependencies.md) |
889
798
  | 17 | Extract UI to separate package | Planned | — |
890
799
 
891
800
  ### Structural refactoring issues
@@ -907,7 +816,7 @@ Detailed records are preserved in per-phase history files:
907
816
  | Phase 14 | #237, #238, #239, #242 | Remove disallowed_tools, remove extensions filtering, collapse filterActiveTools, rename Agent to subagent |
908
817
  | Phase 15 | #227, #228, #231, #229, #230, #232 | Agent domain model, async startAgent, runner self-contained, Agent.run(), ConcurrencyQueue, Agent.resume() |
909
818
  | Phase 16 | #261, #262, #263, #264, #265 | Lifecycle events (retire permission-bridge), WorkspaceProvider seam, extract worktrees package, remove isolated, born-complete execution / dissolve runner |
910
- | Phase 16 (abandoned) | #256 (superseded), #257 (parked) | Agent collaborator architecture — replaced by the inversion approach above (ADR 0002) |
819
+ | Phase 16 (abandoned) | #256 (superseded), #257 (parked), #258, #259 (not planned) | Agent collaborator architecture — replaced by the inversion approach above ([ADR-0002]) |
911
820
 
912
821
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
913
822
 
@@ -952,3 +861,4 @@ The upstream test suite is run periodically as a regression canary for the sessi
952
861
  [#264]: https://github.com/gotgenes/pi-packages/issues/264
953
862
  [#265]: https://github.com/gotgenes/pi-packages/issues/265
954
863
  [#277]: https://github.com/gotgenes/pi-packages/issues/277
864
+ [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md