@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 +15 -0
- package/docs/architecture/architecture.md +152 -211
- package/docs/architecture/history/phase-15-domain-model-evolution.md +73 -0
- package/docs/decisions/0002-extensions-on-a-minimal-core.md +98 -0
- package/docs/plans/0256-extract-worktree-isolation.md +256 -0
- package/docs/plans/0257-extract-child-session-factory.md +283 -0
- package/docs/retro/0232-agent-resume-internal-observer-lifecycle.md +64 -0
- package/docs/retro/0256-extract-worktree-isolation.md +89 -0
- package/docs/retro/0257-extract-child-session-factory.md +31 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/lifecycle/agent-manager.ts +5 -2
- package/src/lifecycle/agent-runner.ts +14 -9
- package/src/lifecycle/agent.ts +18 -45
- package/src/lifecycle/child-lifecycle.ts +89 -0
- package/src/lifecycle/worktree-isolation.ts +59 -0
- package/src/service/service-adapter.ts +1 -1
- package/src/lifecycle/permission-bridge.ts +0 -63
- package/src/lifecycle/worktree-state.ts +0 -45
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
|
-
+
|
|
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
|
-
+
|
|
130
|
-
+
|
|
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
|
-
│ ├──
|
|
277
|
+
│ ├── child-lifecycle.ts child-execution lifecycle event publisher
|
|
279
278
|
│ ├── worktree.ts git worktree isolation
|
|
280
|
-
│ ├── worktree-
|
|
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
|
-
- `
|
|
357
|
-
|
|
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
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
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
|
-
- **
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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**
|
|
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)
|
|
512
|
+
- **Any future extension** (OTel, auditing, cost tracking) subscribes to the same events without pi-subagents knowing.
|
|
483
513
|
|
|
484
|
-
|
|
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 |
|
|
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 |
|
|
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
|
|
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 16 — invert dependencies: extensions on a minimal core)
|
|
710
723
|
|
|
711
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
-
|
|
723
|
-
-
|
|
724
|
-
-
|
|
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
|
-
|
|
728
|
-
|
|
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
|
-
|
|
731
|
-
Agent handles session creation internally during `run()` and notifies external observers via the lifecycle observer pattern.
|
|
740
|
+
### Steps
|
|
732
741
|
|
|
733
|
-
|
|
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
|
-
|
|
739
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
774
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
|
|
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/>
|
|
833
|
-
S2["Step 2<br/>
|
|
834
|
-
S3["Step 3<br/>
|
|
835
|
-
S4["Step 4<br/>
|
|
836
|
-
S5["Step 5<br/>
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
S1 -->
|
|
840
|
-
S2 -->
|
|
841
|
-
S3 -->
|
|
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 —
|
|
849
|
-
|
|
850
|
-
2. **Track B —
|
|
851
|
-
Step
|
|
852
|
-
|
|
853
|
-
|
|
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
|
|
883
|
-
|
|
|
884
|
-
| 1
|
|
885
|
-
| 2
|
|
886
|
-
| 3
|
|
887
|
-
| 4
|
|
888
|
-
| 5
|
|
889
|
-
| 6
|
|
890
|
-
| 7
|
|
891
|
-
| 8
|
|
892
|
-
| 9
|
|
893
|
-
| 10
|
|
894
|
-
| 11
|
|
895
|
-
| 12
|
|
896
|
-
| 13
|
|
897
|
-
| 14
|
|
898
|
-
| 15
|
|
899
|
-
| 16
|
|
900
|
-
| 17
|
|
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
|
|
905
|
-
|
|
|
906
|
-
| Foundation
|
|
907
|
-
| Core decomposition
|
|
908
|
-
| Interface polish
|
|
909
|
-
| Features
|
|
910
|
-
| AgentManager
|
|
911
|
-
| Encapsulation
|
|
912
|
-
| Testability
|
|
913
|
-
| Observation/ctx
|
|
914
|
-
| Phase 10
|
|
915
|
-
| Phase 11
|
|
916
|
-
| Phase 12
|
|
917
|
-
| Phase 13
|
|
918
|
-
| Phase 14
|
|
919
|
-
| Phase 15
|
|
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
|