@gotgenes/pi-subagents 11.2.0 → 11.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ 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
+ ## [11.4.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.3.0...pi-subagents-v11.4.0) (2026-05-29)
9
+
10
+
11
+ ### Features
12
+
13
+ * add child-execution lifecycle event publisher ([4d27c13](https://github.com/gotgenes/pi-packages/commit/4d27c130b4782b7fffb9b61a37e151f8500c55ea))
14
+ * emit child-execution lifecycle events and retire permission-bridge ([c8daee4](https://github.com/gotgenes/pi-packages/commit/c8daee4bcf21f6720d9dbc164282fb6a04e552b1))
15
+
16
+ ## [11.3.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.2.0...pi-subagents-v11.3.0) (2026-05-29)
17
+
18
+
19
+ ### Features
20
+
21
+ * **pi-subagents:** add WorktreeIsolation collaborator ([ee7ab73](https://github.com/gotgenes/pi-packages/commit/ee7ab73a53f8643b5887856c33d53786a5a5a9cc))
22
+
8
23
  ## [11.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.1.0...pi-subagents-v11.2.0) (2026-05-28)
9
24
 
10
25
 
@@ -112,7 +112,7 @@ classDiagram
112
112
  +toolUses: number
113
113
  +lifetimeUsage: LifetimeUsage
114
114
  +execution?: ExecutionState
115
- +worktreeState?: WorktreeState
115
+ +worktree?: WorktreeIsolation
116
116
  +notification?: NotificationState
117
117
  +markRunning()
118
118
  +markCompleted()
@@ -126,9 +126,8 @@ classDiagram
126
126
  +abort(): boolean
127
127
  +queueSteer(message)
128
128
  +flushPendingSteers(session)
129
- +setupWorktree(worktrees, isolation)
130
- +completeRun(result, worktrees)
131
- +failRun(err, worktrees)
129
+ +completeRun(result)
130
+ +failRun(err)
132
131
  +wireSignal(signal, onAbort)
133
132
  +attachObserver(unsub)
134
133
  +releaseListeners()
@@ -275,9 +274,9 @@ src/
275
274
  │ ├── concurrency-queue.ts background agent scheduling with configurable concurrency limit
276
275
  │ ├── parent-snapshot.ts immutable spawn-time parent state
277
276
  │ ├── execution-state.ts session/output phase state
278
- │ ├── permission-bridge.ts optional bridge to pi-permission-system registry
277
+ │ ├── child-lifecycle.ts child-execution lifecycle event publisher
279
278
  │ ├── worktree.ts git worktree isolation
280
- │ ├── worktree-state.ts worktree phase state
279
+ │ ├── worktree-isolation.ts worktree lifecycle collaborator
281
280
  │ └── usage.ts token usage tracking
282
281
 
283
282
  ├── observation/ progress tracking and notification
@@ -353,15 +352,16 @@ They declare this package as an optional peer dependency and use dynamic import
353
352
  - `AgentManager` — spawn, abort, resume, collection management, observer wiring.
354
353
  - `ConcurrencyQueue` — background agent scheduling with configurable concurrency limit.
355
354
  - `agent-runner` — session creation, turn loop, extension binding.
356
- - `permission-bridge` — optional cross-extension bridge to `@gotgenes/pi-permission-system`; registers each child session with `SubagentSessionRegistry` before `bindExtensions()` so the permission system detects in-process children deterministically.
357
- Scheduled for removal in Phase 16 replaced by lifecycle events that consumers listen for.
355
+ - `child-lifecycle` — publishes the child-execution lifecycle (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) on `pi.events`.
356
+ Reactive consumers subscribe: `@gotgenes/pi-permission-system` registers each child session on `session-created` and unregisters it on `disposed`.
357
+ This replaced the former outbound `permission-bridge` (#261, ADR 0002) — the core no longer looks up a named consumer.
358
358
  - `session-config` — pure configuration assembler (extracted from `agent-runner`).
359
359
  - `SubagentRuntime` — session-scoped state bag with methods.
360
360
  - `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
361
361
  - `record-observer` — session-event observer that updates record statistics without callback threading.
362
362
  - Agent type registry — default agents, custom `.md` file loading.
363
363
  - Prompt assembly, context extraction, skills, environment.
364
- - Worktree isolation.
364
+ - Worktree isolation — moving to `@gotgenes/pi-subagents-worktrees` via the workspace provider seam in Phase 16 (ADR 0002).
365
365
  - Token usage tracking.
366
366
  - Session directory derivation and persisted `SessionManager` for subagent transcripts.
367
367
  - Settings persistence.
@@ -450,9 +450,34 @@ These are fire-and-forget broadcast events — no request IDs, no reply channels
450
450
 
451
451
  ## Target architecture
452
452
 
453
- The long-term architectural direction is to make pi-subagents a **minimal core** with inverted dependencies.
454
- Today, pi-subagents reaches outward to pi-permission-system via a bridge module and owns tool/extension filtering logic that duplicates permission-system responsibilities.
455
- The target state eliminates this overlap and flips the dependency direction.
453
+ The long-term architectural direction is to make pi-subagents a **minimal orchestrator** with inverted dependencies.
454
+ The core spawns a child session derived from the parent, runs the turn loop, tracks and streams and collects the result, gates concurrency, supports resume, and **publishes its lifecycle**.
455
+ Everything else permissions, worktree/workspace isolation, UI, telemetry — is an extension that attaches through one of two surfaces and never reaches into the core.
456
+
457
+ The rationale and the full reasoning chain that led here are recorded in [`docs/decisions/0002-extensions-on-a-minimal-core.md`](../decisions/0002-extensions-on-a-minimal-core.md).
458
+
459
+ ### Two extension surfaces
460
+
461
+ Extensions attach through exactly two surfaces, distinguished by the direction of information flow.
462
+
463
+ 1. **Lifecycle events (observational) — unlimited.**
464
+ The core emits awaited, ordered events for the child-execution lifecycle (`spawning`, `session-created` pre-`bindExtensions`, `completed`, `disposed`).
465
+ Any number of extensions subscribe; handlers return nothing.
466
+ Reactive concerns live here: permission detection, telemetry, UI, notifications.
467
+ Adding a reactive concern never modifies the core.
468
+ 2. **Provider seams (generative) — rationed.**
469
+ The rare concern that must *inject* a value the core consumes synchronously registers a provider the core consults.
470
+ Today there is exactly one: the **workspace provider** (returns the child's working directory plus bracketed setup/teardown).
471
+ A provider seam is the only place the core is "open," so the list is kept as small as possible.
472
+
473
+ The discriminator when deciding how a concern attaches:
474
+
475
+ - It only needs to **know** what happened → subscribe to a lifecycle event (observational, unlimited).
476
+ - It must **return a value the core consumes** → register a provider (generative, rationed).
477
+
478
+ The governing rule — **no vacant hooks**: the architecture must *admit* a seam without *shipping* it until a concrete consumer exists.
479
+ A provider seam with no consumer is a speculative abstraction that taxes every reader and that `fallow` flags as dead.
480
+ Latent extensibility is the deliverable; a vacant hook is not.
456
481
 
457
482
  ### Core responsibilities (keep)
458
483
 
@@ -461,27 +486,34 @@ The target state eliminates this overlap and flips the dependency direction.
461
486
  - **Session lifecycle** — create child sessions, bind extensions, run conversation loop, track results.
462
487
  - **Concurrency management** — queue, abort, resume, max concurrency.
463
488
  - **Recursion guard** — remove pi-subagents' own three tools from child sessions (prevent infinite nesting).
464
- - **Lifecycle events** emit events on `pi.events` when child sessions are created, completed, etc.
489
+ With `isolated` removed, children always load the parent's resources, so the guard becomes unconditional rather than gated on `cfg.extensions`.
490
+ This is the core defending its own invariant, keyed off its own tool names — not policy.
491
+ - **Lifecycle events** — emit awaited, ordered events when child sessions spawn, are created, complete, and are disposed.
492
+ - **Workspace provider seam** — accept a registered `WorkspaceProvider` and consult it for the child's cwd; default to the parent's cwd when none is registered.
465
493
  - **Service API** — publish `SubagentsService` via `Symbol.for()` for cross-extension access.
466
494
 
467
495
  ### Responsibilities to remove
468
496
 
469
497
  - **Tool policy** (`disallowed_tools`) — access control belongs in pi-permission-system's `permission:` frontmatter.
470
498
  - **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
471
- - **Permission bridge** (`permission-bridge.ts`) — outbound coupling to pi-permission-system.
472
- Replaced by lifecycle events that pi-permission-system listens for.
473
- - **Extension lifecycle control** (`extensions: false`, `isolated`) extensions provide behavioral layers (permissions, formatting, context management) that benefit all agents.
474
- Blanket-disabling them is a blunt instrument with no clear use case; tool restrictions belong in the permission system.
499
+ - **Worktree isolation** (`worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, the `isolation: "worktree"` spawn mode) — environment policy, not core.
500
+ Git worktrees are one *strategy* for choosing the child's working directory; containers, throwaway tmpdirs, and remote sandboxes are others.
501
+ These move to `@gotgenes/pi-subagents-worktrees`, the first consumer of the workspace provider seam.
502
+ - **Extension lifecycle control** (`extensions: false`, `isolated`, `noSkills`) deny-at-use (the in-child permission layer blocking disallowed tool calls) covers what `isolated` pretended to do for tools.
503
+ 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.
475
504
 
476
505
  ### Composition model
477
506
 
478
- In the target state, pi-subagents publishes events and other packages hook in:
507
+ In the target state, pi-subagents publishes events and a provider seam; other packages hook in:
479
508
 
480
- - **pi-permission-system** listens for child session lifecycle events, applies per-agent policy (allow/ask/deny), gates tool calls at runtime.
509
+ - **pi-permission-system** (observational) subscribes to child-session lifecycle events, detects subagent execution context in the child, and gates tool calls at runtime.
510
+ - **pi-subagents-worktrees** (generative) registers a `WorkspaceProvider` that prepares a git worktree at run-start and tears it down after, supplying the child's cwd.
481
511
  - **pi-subagents-ui** (future) subscribes to the service API, renders the widget, conversation viewer, and `/agents` menu.
482
- - **Any future extension** (OTel, auditing, cost tracking) hooks into the same events without pi-subagents knowing.
512
+ - **Any future extension** (OTel, auditing, cost tracking) subscribes to the same events without pi-subagents knowing.
483
513
 
484
- This is achieved across three phases: Phase 14 (strip policy), Phase 16 (invert dependencies), and Phase 17 (extract UI).
514
+ Composition test: install neither extension, only permissions, only workspaces, or both the core is byte-for-byte identical in all four cases, and the two extensions never reference each other.
515
+
516
+ This is achieved across phases: Phase 14 (strip policy), Phase 16 (invert dependencies — extensions on a minimal core), and Phase 17 (extract UI).
485
517
 
486
518
  ## Current structural analysis
487
519
 
@@ -490,13 +522,13 @@ This is achieved across three phases: Phase 14 (strip policy), Phase 16 (invert
490
522
  | Metric | Value |
491
523
  | -------------------------- | --------------------------------- |
492
524
  | Health score | 78/100 (B) |
493
- | Total LOC | 8,382 (56 files) |
525
+ | Total LOC | 7,778 (57 files) |
494
526
  | Dead code | 0 files, 0 exports |
495
527
  | Maintainability index | 90.8 (good) |
496
528
  | Avg cyclomatic complexity | 1.4 |
497
529
  | P90 cyclomatic complexity | 2 |
498
530
  | Production duplication | 11 lines (1 internal clone group) |
499
- | Test duplication | 38 clone groups, 634 lines |
531
+ | Test duplication | 42 clone groups, 661 lines |
500
532
  | Fallow refactoring targets | 0 |
501
533
 
502
534
  ### Dependency bag inventory
@@ -687,185 +719,97 @@ See [phase-14-strip-policy.md](history/phase-14-strip-policy.md) for details.
687
719
  [#239]: https://github.com/gotgenes/pi-packages/issues/239
688
720
  [#242]: https://github.com/gotgenes/pi-packages/issues/242
689
721
 
690
- ## Improvement roadmap (Phase 15domain model evolution)
691
-
692
- Phase 15 evolves `Agent` from a passive state machine into an object that **owns its entire execution lifecycle**.
693
-
694
- Steps 1–2 (complete) moved per-agent behavior from `AgentManager` onto `Agent`: abort, steer buffering, worktree setup, and run lifecycle methods (`completeRun`, `failRun`).
695
- However, Agent still cannot *run itself*.
696
- `AgentManager.startAgent()` orchestrates the entire execution: calling the runner, handling session creation, wiring observers, and cleaning up worktrees.
697
- The manager reaches into Agent 10 times across `spawn()` + `startAgent()` — writing to `notification`, `execution`, and `promise` after construction, passing its own `worktrees` and `runner` as method arguments, and threading `onSessionCreated` callbacks through three layers.
698
-
699
- The remaining steps address this by making **Agent born complete**: constructed with all dependencies and configuration, owning its entire execution lifecycle.
700
-
701
- ### Architecture target
702
-
703
- Agent receives three concerns at construction:
704
-
705
- | Concern | Fields | Lifetime |
706
- | ----------- | ----------------------------------------------------------------------------- | ------------------------- |
707
- | Identity | id, type, description, invocation | Immutable |
708
- | Run config | snapshot, prompt, model, isolation, maxTurns, thinking, signal, parentSession | Immutable per-run |
709
- | Shared deps | runner, worktrees | Shared service references |
722
+ ## Improvement roadmap (Phase 16invert dependencies: extensions on a minimal core)
710
723
 
711
- `Agent.run()` encapsulates the full execution lifecycle:
724
+ 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.
725
+ 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).
712
726
 
713
- 1. Set up worktree internally (knows its own isolation mode, has worktrees).
714
- 2. Call `this.runner.run()` (has the runner).
715
- 3. Handle session creation internally: set `execution`, flush pending steers, attach record-observer.
716
- 4. Notify lifecycle observer (started, session created, completed, compacted).
717
- 5. Clean up worktree on completion or error.
718
- 6. Transition status.
727
+ ### Abandoned exploration: agent collaborator architecture
719
728
 
720
- `AgentManager` becomes a collection manager + observer wiring:
729
+ 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.
730
+ That framing was abandoned.
731
+ Pulling on a single late-bound `create(cwd?)` parameter on the planned `ChildSessionFactory` exposed deeper problems:
721
732
 
722
- - Creates complete Agent objects, stores them in the map.
723
- - Decides when to run (immediate or queue) and calls `agent.run()`.
724
- - Provides high-level actions: abort, list, cleanup.
725
- - Does *not* own the runner, worktrees, or any run-orchestration logic.
733
+ - `WorktreeIsolation.setup()` is a two-phase `construct-then-setup()` that violates "Construct complete" (principle 8) — the worktree is only *ready* at dequeue.
734
+ - 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.
735
+ - Worktrees are not intrinsic to subagents — they are one *workspace strategy* and belong outside the core, exactly as Phase 14 evicted tool/extension policy.
726
736
 
727
- The queue stores agent IDs, not `SpawnArgs`.
728
- When capacity opens, the manager looks up the agent and calls `agent.run()` — the agent already has everything.
737
+ Issue #256 (`WorktreeIsolation` as a collaborator) shipped under the abandoned plan and is now superseded by #263; issue #257 (`ChildSessionFactory` extraction) was parked.
738
+ 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).
729
739
 
730
- The `onSessionCreated` callback that currently threads through `AgentSpawnConfig` → `startAgent` → `RunOptions` → runner disappears.
731
- Agent handles session creation internally during `run()` and notifies external observers via the lifecycle observer pattern.
740
+ ### Steps
732
741
 
733
- The synchronous-throw contract for worktree failure (introduced in Step 2's hoist) is replaced by a uniform async error surface.
734
- Worktree failures inside `agent.run()` propagate through the promise.
735
- For background agents, errors surface via `get_subagent_result` and appear in `/agents`.
736
- For foreground agents, `spawnAndWait` awaits the promise naturally.
742
+ #### Step 1: Child-execution lifecycle events; retire permission-bridge [#261] Delivered
737
743
 
738
- The scheduling concern (queue, concurrency counter, drain) is tangled into `AgentManager` alongside collection management and run orchestration.
739
- `notifyConcurrencyChanged()` is a scheduling method exposed as a public API so settings can poke the queue a cross-concern leak.
744
+ Emit ordered child-execution events (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) carrying child identity (session directory, agent name, parent session id).
745
+ 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`.
740
746
 
741
- ### Findings summary
747
+ - Cross-package: pi-subagents (emit + remove bridge) and pi-permission-system (subscribe).
748
+ - 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.
749
+ Emitting `session-created` immediately before `bindExtensions()` therefore guarantees the registry entry lands pre-bind, with no new SDK hook.
750
+ The synchronous-handler constraint is encoded as a real-bus test in pi-permission-system.
751
+ - Outcome: the core stops reaching out to a named consumer; permission detection rides events.
752
+ - Deferred: removing the now-caller-less `registerSubagentSession`/`unregisterSubagentSession` from `PermissionsService` → #267; registry-detected resume ("executing now" → "exists" semantics) → #265.
742
753
 
743
- | Finding | Category | Impact | Risk | Priority |
744
- | ---------------------------------------------------------------------- | ------------ | ------ | ---- | -------- |
745
- | ~~`AgentRecord` is anemic — no behavior, manager reaches in 37×~~ | B: Oversized | 5 | 3 | ✅ |
746
- | ~~Agent cannot run itself — manager orchestrates 10 external touches~~ | C: Coupling | 5 | 3 | ✅ |
747
- | ~~Scheduling tangled into `AgentManager` (3 fields, 3 methods)~~ | A: Coupling | 4 | 2 | ✅ |
748
- | ~~`startAgent` uses `.then()`/`.catch()` instead of async/await~~ | C: Callbacks | 3 | 2 | ✅ |
749
- | ~~`onSessionCreated` callback flows through 3 layers~~ | C: Callbacks | 3 | 2 | subsumed |
750
- | ~~`resume()` duplicates observer subscribe/unsubscribe pattern~~ | A: Redundant | 2 | 1 | ✅ |
751
- | ~~`exec`/`registry` relay-only deps on `AgentManager`~~ | C: Coupling | 2 | 1 | ✅ |
754
+ #### Step 2: Define the `WorkspaceProvider` seam [#262]
752
755
 
753
- ### Step 1: Evolve AgentRecord into Agent with behavior — [#227] ✅ Complete
756
+ Add the `WorkspaceProvider` / `Workspace` interfaces and `SubagentsService.registerWorkspaceProvider`.
757
+ At run-start the core consults the registered provider (if any) for the child's cwd and a disposal handle; with no provider, the child runs in the parent's cwd.
754
758
 
755
- Rename `AgentRecord` `Agent` (or wrap it).
756
- Move per-agent behavior from `AgentManager` into the agent:
759
+ - Land alongside its first consumer (Step 3) to avoid a vacant hook — the "no vacant hooks" rule.
760
+ - Outcome: a single generative seam; the core no longer knows what an "isolation strategy" is.
757
761
 
758
- 1. `Agent.abort()` absorbs status-check + controller.abort + markStopped.
759
- 2. `Agent.queueSteer(message)` / `Agent.flushPendingSteers(session)` — moves pending steers from manager map to per-agent array.
760
- 3. `Agent.setupWorktree(worktrees, isolation)` — moves worktree creation into the agent.
762
+ #### Step 3: Extract worktrees to `@gotgenes/pi-subagents-worktrees` [#263]
761
763
 
762
- - Target: `src/lifecycle/agent-record.ts` `src/lifecycle/agent.ts`, `src/lifecycle/agent-manager.ts`
763
- - Smell: B (anemic domain model) + C (manager reaching into records)
764
- - Outcome: `AgentManager` delegates via Tell-Don't-Ask; per-agent state lives on the agent
764
+ 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.
765
+ Remove `worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, and the `isolation: "worktree"` mode from the core; drop `isolation` from the spawn API and `SubagentsService`.
765
766
 
766
- ### Step 2: Convert startAgent to async/await — [#228] ✅ Complete
767
+ - Supersedes #256.
768
+ New package registered in `release-please-config.json`; peer-depends on `@gotgenes/pi-subagents`.
769
+ - Outcome: git leaves the core; worktree users install one package, everyone else pays nothing.
767
770
 
768
- Converted `startAgent` to `async` with `try/catch` and dissolved `RunHandle` into `Agent` methods.
769
- `spawn()` assigns `record.promise = this.startAgent(...)` instead of calling `startAgent()` synchronously.
770
- `Agent` gained run lifecycle methods: `completeRun`, `failRun`, `wireSignal`, `attachObserver`, `releaseListeners`, `setOnRunFinished`.
771
- Worktree setup was hoisted to callers (`spawn`, `drainQueue`) to preserve the synchronous-throw contract.
771
+ #### Step 4: Remove `isolated` / `extensions: false` / `noSkills` [#264]
772
772
 
773
- - Depends on: #227
774
- - Target: `src/lifecycle/agent-manager.ts`, `src/lifecycle/agent.ts`
775
- - Smell: C (raw promise callbacks)
776
- - Outcome: zero `.then()`/`.catch()` in `agent-manager.ts`; `RunHandle` deleted; Agent owns run lifecycle
773
+ Children always load the parent's extensions and skills; the recursion guard becomes unconditional.
774
+ Deny-at-use (the in-child permission layer) covers tool restriction; prevent-load is left as a latent provider seam (not shipped).
777
775
 
778
- ### Step 3: Push exec/registry relay deps to runner construction — [#231] ✅
776
+ - Depends on: Step 1 (deny-at-use over events).
777
+ - Outcome: the `isolated`/`extensions`/`noSkills` axis is gone; one fewer conditional in the guard.
779
778
 
780
- `exec` and `registry` moved from `AgentManager` to `ConcreteAgentRunner` via a new `RunnerDeps` interface.
781
- `RunContext` shrunk from 4 to 2 per-call fields (`cwd`, `parentSession`).
782
- `AgentManagerOptions` shrunk from 7 to 5 fields.
779
+ #### Step 5: Born-complete child execution; dissolve the runner [#265]
783
780
 
784
- - Target: `src/lifecycle/agent-manager.ts`, `src/lifecycle/agent-runner.ts`, `src/index.ts`
785
- - Smell: C (relay-only dependencies)
786
- - Outcome: `AgentManager` loses 2 fields; `AgentManagerOptions` shrinks from 7 to 5 fields; runner is self-contained
781
+ With the cwd resolved through the provider seam (not relayed by `Agent`), child-session creation produces a born-complete execution (`{ session, outputFile?, dispose() }`).
782
+ `Agent` owns session interaction directly (prompt, steer, abort, resume, turn limits, response collection); `runAgent` / `resumeAgent` / `ConcreteAgentRunner` / `RunOptions` / `RunResult` dissolve.
783
+ Retain `getAgentConversation()` and `normalizeMaxTurns()`.
787
784
 
788
- ### Step 4: Agent born complete — Agent.run() absorbs startAgent — [#229] ✅
789
-
790
- Agent receives `runner`, `worktrees`, and a lifecycle observer at construction.
791
- Agent creates its own `AbortController` and `NotificationState` from `parentSession.toolCallId` — no external writes.
792
- `Agent.run()` encapsulates the entire execution lifecycle: worktree setup, runner invocation, session-creation handling, observer wiring, worktree cleanup, and status transitions.
793
- `startAgent` is deleted from `AgentManager`.
794
- The `onSessionCreated` callback is removed from `AgentSpawnConfig` — replaced by `AgentLifecycleObserver` passed at construction.
795
- `SpawnArgs` is deleted — Agent has its config from construction.
796
- The queue is simplified from `{ id, args }[]` to `string[]` (agent IDs only).
797
-
798
- `AgentManager.spawn()` becomes: create complete Agent, put in map, call `agent.run()` or queue the agent ID.
799
-
800
- - Depends on: #228, #231
801
- - Target: `src/lifecycle/agent.ts`, `src/lifecycle/agent-manager.ts`, `src/tools/background-spawner.ts`, `src/tools/foreground-runner.ts`
802
- - Smell: C (manager orchestrates 10 external touches on Agent) + C (callback flowing through 3 layers)
803
- - Outcome: Agent owns its entire execution lifecycle; `startAgent`, `SpawnArgs`, `onSessionCreated` callback deleted; zero post-construction writes from `AgentManager`
804
-
805
- ### Step 5: Extract ConcurrencyQueue from AgentManager — [#230]
806
-
807
- Extract `queue[]`, `runningBackground`, `_getMaxConcurrent`, `drainQueue()`, `finalizeBackgroundRun()` into a `ConcurrencyQueue` class.
808
- The queue stores agent IDs — not `SpawnArgs`.
809
- Drain calls `agent.run()` directly — no worktree setup, no args threading.
810
- `SettingsManager` talks to the queue directly — `notifyConcurrencyChanged()` is eliminated from `AgentManager`.
811
-
812
- - Depends on: #229
813
- - Target: new `src/lifecycle/concurrency-queue.ts`, `src/lifecycle/agent-manager.ts`, `src/index.ts`
814
- - Smell: A (tangled concerns) + C (cross-concern leak via `notifyConcurrencyChanged`)
815
- - Outcome: `AgentManager` loses 3 fields, 3 methods (~40 lines); scheduling is independently testable; queue interface is trivial (agent has everything)
816
-
817
- ### Step 6: Agent.resume() with internal observer lifecycle — [#232] ✅
818
-
819
- Agent has the runner from construction.
820
- `Agent.resume(prompt, signal)` manages its own observer subscription lifecycle using the same internal wiring as `run()`.
821
- `AgentManager.resume()` becomes a one-liner delegation to `agent.resume(prompt, signal)` — no manual `subscribeRecordObserver` / try-finally.
822
-
823
- - Depends on: #229
824
- - Target: `src/lifecycle/agent.ts`, `src/lifecycle/agent-manager.ts`
825
- - Smell: A (duplicated observer subscribe/unsubscribe pattern)
826
- - Outcome: `AgentManager.resume()` is a 4-line delegation; observer lifecycle is Agent-internal
785
+ - Depends on: Steps 2–4.
786
+ - Outcome: the "runner" concept is gone; `Agent.run()` is coordination, not assembly — the structural goal of the abandoned collaborator plan, reached cleanly.
827
787
 
828
788
  ### Step dependency diagram
829
789
 
830
790
  ```mermaid
831
791
  flowchart LR
832
- S1["Step 1<br/>Agent with behavior"]
833
- S2["Step 2<br/>async startAgent"]
834
- S3["Step 3<br/>runner self-contained"]
835
- S4["Step 4<br/>Agent.run()"]
836
- S5["Step 5<br/>ConcurrencyQueue"]
837
- S6["Step 6<br/>Agent.resume()"]
838
-
839
- S1 --> S2
840
- S2 --> S4
841
- S3 --> S4
792
+ S1["Step 1<br/>Lifecycle events<br/>(retire bridge)"]
793
+ S2["Step 2<br/>WorkspaceProvider seam"]
794
+ S3["Step 3<br/>Extract worktrees pkg"]
795
+ S4["Step 4<br/>Remove isolated"]
796
+ S5["Step 5<br/>Born-complete execution"]
797
+
798
+ S2 --> S3
799
+ S1 --> S4
800
+ S2 --> S5
801
+ S3 --> S5
842
802
  S4 --> S5
843
- S4 --> S6
844
803
  ```
845
804
 
846
805
  ### Tracks
847
806
 
848
- 1. **Track A — Foundation** (Step 3): Runner becomes self-contained.
849
- No dependencies on other Phase 15 steps; can start immediately.
850
- 2. **Track B — Agent lifecycle** (Steps 4, 6): Agent born complete, owns run + resume.
851
- Step 4 depends on Track A + Step 2.
852
- Step 6 depends on Step 4.
853
- 3. **Track C Scheduling** (Step 5): ConcurrencyQueue extraction.
854
- Depends on Step 4 (queue drains via `agent.run()`).
855
-
856
- ## Improvement roadmap (Phase 16 — invert dependencies)
857
-
858
- Phase 16 completes the architectural inversion by removing the outbound permission bridge and the `extensions: false` / `isolated` concepts.
859
- It depends on Phase 15's lifecycle observer (#229) as the replacement mechanism.
860
-
861
- Phase 16 is scoped but not yet broken into steps.
862
- Key changes:
863
-
864
- 1. Remove `permission-bridge.ts` — the outbound coupling to pi-permission-system.
865
- 2. Emit child session lifecycle events via the observer — pi-permission-system and other consumers listen for these events instead of being called.
866
- 3. Remove `extensions: false` — all child sessions load all extensions.
867
- 4. Dissolve or redefine `isolated` — without extension control and tool filtering, the concept either disappears or becomes purely about prompt composition (no skill preloading, no parent context inheritance).
868
- 5. Update pi-permission-system to listen for child session events instead of being registered by the bridge.
807
+ 1. **Track A — Inversion seams** (Steps 1, 2): lifecycle events and the workspace seam.
808
+ Independent of each other can proceed in parallel.
809
+ 2. **Track B — Eviction** (Steps 3, 4): worktrees and `isolated` leave the core.
810
+ Step 3 depends on Step 2.
811
+ 3. **Track C Consolidation** (Step 5): dissolve the runner around the new seam.
812
+ Depends on Tracks A and B.
869
813
 
870
814
  ## Improvement roadmap (Phase 17 — extract UI)
871
815
 
@@ -879,44 +823,46 @@ Phases 1–5, 7–14 are complete.
879
823
  Phase 6 (UI extraction to a separate package) is deferred.
880
824
  Detailed records are preserved in per-phase history files:
881
825
 
882
- | Phase | Title | Status | History |
883
- | -------- | --------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
884
- | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
885
- | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
886
- | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
887
- | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
888
- | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
889
- | 6 | Extract UI to separate package | Deferred → Phase 17 | — |
890
- | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
891
- | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
892
- | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
893
- | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
894
- | 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
895
- | 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
896
- | 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
897
- | 14 | Strip policy from core | Complete | [phase-14-strip-policy.md](history/phase-14-strip-policy.md) |
898
- | 15 | Domain model evolution | Planned | |
899
- | 16 | Invert dependencies | Planned | |
900
- | 17 | Extract UI to separate package | Planned | — |
826
+ | Phase | Title | Status | History |
827
+ | ----- | --------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------ |
828
+ | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
829
+ | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
830
+ | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
831
+ | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
832
+ | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
833
+ | 6 | Extract UI to separate package | Deferred → Phase 17 | — |
834
+ | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
835
+ | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
836
+ | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
837
+ | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
838
+ | 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
839
+ | 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
840
+ | 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
841
+ | 14 | Strip policy from core | Complete | [phase-14-strip-policy.md](history/phase-14-strip-policy.md) |
842
+ | 15 | Domain model evolution | Complete | [phase-15-domain-model-evolution.md](history/phase-15-domain-model-evolution.md) |
843
+ | 16 | Invert dependencies (extensions on a minimal core) | Planned | [ADR 0002](../decisions/0002-extensions-on-a-minimal-core.md) |
844
+ | 17 | Extract UI to separate package | Planned | — |
901
845
 
902
846
  ### Structural refactoring issues
903
847
 
904
- | Phase | Issue | Summary |
905
- | ------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
906
- | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
907
- | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
908
- | Interface polish | #66, #77 | SDK types, projectAgentsDir |
909
- | Features | #61 | JSONL session transcripts |
910
- | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
911
- | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
912
- | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
913
- | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
914
- | Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
915
- | Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
916
- | Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
917
- | Phase 13 | #214, #215, #216, #217, #218, #219 | Closure-to-class, buildParentContext, startAgent decomp, overwrite guard, settings SDK, test duplication |
918
- | Phase 14 | #237, #238, #239, #242 | Remove disallowed_tools, remove extensions filtering, collapse filterActiveTools, rename Agent to subagent |
919
- | Phase 15 | #227, #228, #231, #229, #230, #232 | Agent domain model, async startAgent, runner self-contained, Agent.run(), ConcurrencyQueue, Agent.resume() |
848
+ | Phase | Issue | Summary |
849
+ | -------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
850
+ | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
851
+ | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
852
+ | Interface polish | #66, #77 | SDK types, projectAgentsDir |
853
+ | Features | #61 | JSONL session transcripts |
854
+ | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
855
+ | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
856
+ | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
857
+ | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
858
+ | Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
859
+ | Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
860
+ | Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
861
+ | Phase 13 | #214, #215, #216, #217, #218, #219 | Closure-to-class, buildParentContext, startAgent decomp, overwrite guard, settings SDK, test duplication |
862
+ | Phase 14 | #237, #238, #239, #242 | Remove disallowed_tools, remove extensions filtering, collapse filterActiveTools, rename Agent to subagent |
863
+ | Phase 15 | #227, #228, #231, #229, #230, #232 | Agent domain model, async startAgent, runner self-contained, Agent.run(), ConcurrencyQueue, Agent.resume() |
864
+ | Phase 16 | #261, #262, #263, #264, #265 | Lifecycle events (retire permission-bridge), WorkspaceProvider seam, extract worktrees package, remove isolated, born-complete execution / dissolve runner |
865
+ | Phase 16 (abandoned) | #256 (superseded), #257 (parked) | Agent collaborator architecture — replaced by the inversion approach above (ADR 0002) |
920
866
 
921
867
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
922
868
 
@@ -949,9 +895,4 @@ The upstream test suite is run periodically as a regression canary for the agent
949
895
  [#217]: https://github.com/gotgenes/pi-packages/issues/217
950
896
  [#218]: https://github.com/gotgenes/pi-packages/issues/218
951
897
  [#219]: https://github.com/gotgenes/pi-packages/issues/219
952
- [#227]: https://github.com/gotgenes/pi-packages/issues/227
953
- [#228]: https://github.com/gotgenes/pi-packages/issues/228
954
- [#229]: https://github.com/gotgenes/pi-packages/issues/229
955
- [#230]: https://github.com/gotgenes/pi-packages/issues/230
956
898
  [#231]: https://github.com/gotgenes/pi-packages/issues/231
957
- [#232]: https://github.com/gotgenes/pi-packages/issues/232