@gotgenes/pi-subagents 13.2.0 → 13.2.1
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,13 @@ 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.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v13.2.0...pi-subagents-v13.2.1) (2026-05-30)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
* **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))
|
|
14
|
+
|
|
8
15
|
## [13.2.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v13.1.0...pi-subagents-v13.2.0) (2026-05-30)
|
|
9
16
|
|
|
10
17
|
|
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** —
|
|
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
|
|
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
|
-
|
|
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 #
|
|
459
|
-
agent
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
|
|
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. **
|
|
488
|
-
|
|
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.
|
|
@@ -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
|
-
##
|
|
758
|
+
## Phase 16 (complete)
|
|
759
759
|
|
|
760
|
-
Phase 16
|
|
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 `Agent.run()` is coordination, not assembly.
|
|
761
763
|
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
|
-
|
|
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.
|
|
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–
|
|
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) |
|
|
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)
|
|
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
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Phase 16: Invert dependencies — extensions on a minimal core
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Phase 16 reclaimed its original intent — invert the core's outbound dependencies — and extended it: worktree isolation joined permissions as an *extension* on a minimal core, leaving pi-subagents a pure child-session orchestrator.
|
|
6
|
+
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](../architecture.md#target-architecture).
|
|
7
|
+
|
|
8
|
+
All five steps are closed: [#261], [#262], [#263], [#264], [#265].
|
|
9
|
+
|
|
10
|
+
## Two extension surfaces
|
|
11
|
+
|
|
12
|
+
Extensions attach through exactly two surfaces, distinguished by the direction of information flow.
|
|
13
|
+
|
|
14
|
+
1. **Lifecycle events (observational) — unlimited.**
|
|
15
|
+
The core emits awaited, ordered events for the child-execution lifecycle (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`).
|
|
16
|
+
Any number of extensions subscribe; handlers return nothing.
|
|
17
|
+
Reactive concerns live here: permission detection, telemetry, UI, notifications.
|
|
18
|
+
Adding a reactive concern never modifies the core.
|
|
19
|
+
2. **Provider seams (generative) — rationed.**
|
|
20
|
+
The rare concern that must *inject* a value the core consumes synchronously registers a provider the core consults.
|
|
21
|
+
Today there is exactly one: the **workspace provider** (returns the child's working directory plus bracketed setup/teardown).
|
|
22
|
+
A provider seam is the only place the core is "open," so the list is kept as small as possible.
|
|
23
|
+
|
|
24
|
+
The discriminator when deciding how a concern attaches:
|
|
25
|
+
|
|
26
|
+
- It only needs to **know** what happened → subscribe to a lifecycle event (observational, unlimited).
|
|
27
|
+
- It must **return a value the core consumes** → register a provider (generative, rationed).
|
|
28
|
+
|
|
29
|
+
The governing rule — **no vacant hooks**: the architecture must *admit* a seam without *shipping* it until a concrete consumer exists.
|
|
30
|
+
A provider seam with no consumer is a speculative abstraction that taxes every reader and that `fallow` flags as dead.
|
|
31
|
+
Latent extensibility is the deliverable; a vacant hook is not.
|
|
32
|
+
|
|
33
|
+
## Abandoned exploration: agent collaborator architecture
|
|
34
|
+
|
|
35
|
+
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.
|
|
36
|
+
That framing was abandoned.
|
|
37
|
+
Pulling on a single late-bound `create(cwd?)` parameter on the planned `ChildSessionFactory` exposed deeper problems:
|
|
38
|
+
|
|
39
|
+
- `WorktreeIsolation.setup()` is a two-phase `construct-then-setup()` that violates "Construct complete" (principle 8) — the worktree is only *ready* at dequeue.
|
|
40
|
+
- 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.
|
|
41
|
+
- Worktrees are not intrinsic to subagents — they are one *workspace strategy* and belong outside the core, exactly as Phase 14 evicted tool/extension policy.
|
|
42
|
+
|
|
43
|
+
Issue #256 (`WorktreeIsolation` as a collaborator) shipped under the abandoned plan and was superseded by #263; issue #257 (`ChildSessionFactory` extraction) was parked.
|
|
44
|
+
Issues #258 (Agent owns session lifecycle via factory) and #259 (dissolve runner concept) belonged to the same abandoned plan — both depended on #256/#257 and were closed as not planned; their structural goals were recovered by Step 5 (#265) via a cleaner route.
|
|
45
|
+
The structural win the collaborator plan chased — a born-complete child execution and the dissolution of the runner — is recovered once the workspace seam exists (Step 5).
|
|
46
|
+
|
|
47
|
+
## Steps
|
|
48
|
+
|
|
49
|
+
### Step 1: Child-execution lifecycle events; retire permission-bridge — [#261]
|
|
50
|
+
|
|
51
|
+
Emit ordered child-execution events (`spawning`, `session-created` before `bindExtensions()`, `completed`, `disposed`) carrying child identity (session directory, agent name, parent session id).
|
|
52
|
+
Migrated `@gotgenes/pi-permission-system` to subscribe to `session-created`/`disposed` for registration instead of being looked up by the core; deleted `permission-bridge.ts`.
|
|
53
|
+
|
|
54
|
+
- Cross-package: pi-subagents (emit + remove bridge) and pi-permission-system (subscribe).
|
|
55
|
+
- 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.
|
|
56
|
+
Emitting `session-created` immediately before `bindExtensions()` therefore guarantees the registry entry lands pre-bind, with no new SDK hook.
|
|
57
|
+
The synchronous-handler constraint is encoded as a real-bus test in pi-permission-system.
|
|
58
|
+
- Outcome: the core stops reaching out to a named consumer; permission detection rides events.
|
|
59
|
+
- Deferred: removing the now-caller-less `registerSubagentSession`/`unregisterSubagentSession` from `PermissionsService` → #267; registry-detected resume ("executing now" → "exists" semantics) → #265.
|
|
60
|
+
|
|
61
|
+
### Step 2: Define the `WorkspaceProvider` seam — [#262]
|
|
62
|
+
|
|
63
|
+
Added the `WorkspaceProvider` / `Workspace` interfaces (`src/lifecycle/workspace.ts`) and `SubagentsService.registerWorkspaceProvider` (single provider, throws on duplicate, returns an unregister disposer).
|
|
64
|
+
All five workspace types are named-re-exported from `service.ts`: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult` (added in #272).
|
|
65
|
+
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.
|
|
66
|
+
On completion the core calls `Workspace.dispose({ status, description })` and appends the returned `resultAddendum` verbatim — the provider owns the wording.
|
|
67
|
+
|
|
68
|
+
- The seam is additive and non-breaking.
|
|
69
|
+
- Landed alongside its first consumer (Step 3) to avoid a vacant hook — the "no vacant hooks" rule.
|
|
70
|
+
- Outcome: a single generative seam; the core no longer knows what an "isolation strategy" is.
|
|
71
|
+
|
|
72
|
+
### Step 3: Extract worktrees to `@gotgenes/pi-subagents-worktrees` — [#263]
|
|
73
|
+
|
|
74
|
+
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.
|
|
75
|
+
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).
|
|
76
|
+
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`.
|
|
77
|
+
|
|
78
|
+
- Supersedes #256.
|
|
79
|
+
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`).
|
|
80
|
+
- Outcome: git leaves the core; worktree users install one package, everyone else pays nothing.
|
|
81
|
+
|
|
82
|
+
### Step 4: Remove `isolated` / `extensions: false` / `noSkills` — [#264]
|
|
83
|
+
|
|
84
|
+
Children always load the parent's extensions and skills; the recursion guard is now unconditional.
|
|
85
|
+
Deny-at-use (the in-child permission layer) covers tool restriction; prevent-load is left as a latent provider seam (not shipped).
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
- Depended on: Step 1 (deny-at-use over events).
|
|
89
|
+
- Outcome: the `isolated`/`extensions`/`noSkills`/`skills` axis is gone; the guard is unconditional.
|
|
90
|
+
|
|
91
|
+
### Step 5: Born-complete child execution; dissolve the runner — [#265]
|
|
92
|
+
|
|
93
|
+
`createSubagentSession()` is an assembly factory that returns a born-complete `SubagentSession` (session created, extensions bound, recursion guard applied).
|
|
94
|
+
`SubagentSession` owns turn driving (`runTurnLoop`/`resumeTurnLoop`), steering, and disposal.
|
|
95
|
+
`Agent.run()` is coordination, not assembly; `runAgent` / `resumeAgent` / `ConcreteAgentRunner` / `AgentRunner` / `RunOptions` / `RunResult` / `ExecutionState` dissolved.
|
|
96
|
+
`getAgentConversation()` relocated to `session/conversation.ts`; `normalizeMaxTurns()` to `lifecycle/turn-limits.ts`.
|
|
97
|
+
`disposed` now fires at true session disposal (cleanup), so resume executions are registry-detected (closing the gap deferred from #261).
|
|
98
|
+
|
|
99
|
+
- Depends on: Steps 2–4.
|
|
100
|
+
- Outcome: the "runner" concept is gone; `Agent.run()` is coordination, not assembly — the structural goal of the abandoned collaborator plan, reached cleanly.
|
|
101
|
+
|
|
102
|
+
## Step dependency diagram
|
|
103
|
+
|
|
104
|
+
```mermaid
|
|
105
|
+
flowchart LR
|
|
106
|
+
S1["Step 1<br/>Lifecycle events<br/>(retire bridge)"]
|
|
107
|
+
S2["Step 2<br/>WorkspaceProvider seam"]
|
|
108
|
+
S3["Step 3<br/>Extract worktrees pkg"]
|
|
109
|
+
S4["Step 4<br/>Remove isolated"]
|
|
110
|
+
S5["Step 5<br/>Born-complete execution"]
|
|
111
|
+
|
|
112
|
+
S2 --> S3
|
|
113
|
+
S1 --> S4
|
|
114
|
+
S2 --> S5
|
|
115
|
+
S3 --> S5
|
|
116
|
+
S4 --> S5
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Tracks
|
|
120
|
+
|
|
121
|
+
1. **Track A — Inversion seams** (Steps 1, 2): lifecycle events and the workspace seam.
|
|
122
|
+
Independent of each other — proceeded in parallel.
|
|
123
|
+
2. **Track B — Eviction** (Steps 3, 4): worktrees and `isolated` leave the core.
|
|
124
|
+
Step 3 depends on Step 2.
|
|
125
|
+
3. **Track C — Consolidation** (Step 5): dissolve the runner around the new seam.
|
|
126
|
+
Depends on Tracks A and B.
|
|
127
|
+
|
|
128
|
+
## Composition model
|
|
129
|
+
|
|
130
|
+
In the post-Phase-16 state, pi-subagents publishes events and a provider seam; other packages hook in:
|
|
131
|
+
|
|
132
|
+
- **pi-permission-system** (observational) subscribes to child-session lifecycle events, detects subagent execution context in the child, and gates tool calls at runtime.
|
|
133
|
+
- **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.
|
|
134
|
+
- **pi-subagents-ui** (future, Phase 17) subscribes to the service API, renders the widget, conversation viewer, and `/agents` menu.
|
|
135
|
+
- **Any future extension** (OTel, auditing, cost tracking) subscribes to the same events without pi-subagents knowing.
|
|
136
|
+
|
|
137
|
+
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.
|
|
138
|
+
|
|
139
|
+
[#261]: https://github.com/gotgenes/pi-packages/issues/261
|
|
140
|
+
[#262]: https://github.com/gotgenes/pi-packages/issues/262
|
|
141
|
+
[#263]: https://github.com/gotgenes/pi-packages/issues/263
|
|
142
|
+
[#264]: https://github.com/gotgenes/pi-packages/issues/264
|
|
143
|
+
[#265]: https://github.com/gotgenes/pi-packages/issues/265
|
|
@@ -37,3 +37,44 @@ Test count went from 960 → 973 (+13 net: +18 new tests, −5 removed tests for
|
|
|
37
37
|
- The `get-result-tool.test.ts` verbose conversation test needed updating: previously built a mock session with messages and passed it directly; now the stub's `getConversation` must return the expected text via `mockReturnValue`.
|
|
38
38
|
- Pre-completion reviewer returned **WARN** for two stale `architecture.md` passages: (1) conversation-viewer description still said "subscribes directly to `AgentSession`"; (2) `Agent` classDiagram still listed `queueSteer`/`flushPendingSteers` as public and omitted the 6 new public members.
|
|
39
39
|
Both fixed in a `docs:` commit before closing.
|
|
40
|
+
|
|
41
|
+
## Stage: Final Retrospective (2026-05-30T16:00:00Z)
|
|
42
|
+
|
|
43
|
+
### Session summary
|
|
44
|
+
|
|
45
|
+
Issue #277 shipped across three sessions (planning, TDD, ship) in a single day.
|
|
46
|
+
All 8 TDD steps completed cleanly, 13 implementation commits landed, and `pi-subagents-v13.2.0` released.
|
|
47
|
+
|
|
48
|
+
### Observations
|
|
49
|
+
|
|
50
|
+
#### What went well
|
|
51
|
+
|
|
52
|
+
- Planning correctly identified scope beyond the issue's three proposed methods: `getContextPercent()`, `subscribeToUpdates()`, and `messages` were needed to satisfy the "no production module outside `lifecycle/` references `AgentSession`" acceptance criterion.
|
|
53
|
+
This prevented mid-TDD design revisions.
|
|
54
|
+
- The discovery that `subscribeAgentObserver` already accepted `SubscribableSession` meant adding `subscribe()` to `SubagentSession` was sufficient for observer wiring — no type cast or adapter needed.
|
|
55
|
+
- Lift-and-shift execution was precise: new methods added first (steps 1-2), callers migrated by concern (steps 3-6), old getter removed last (step 7).
|
|
56
|
+
No step broke any other step's tests.
|
|
57
|
+
- Pre-completion reviewer caught two stale `architecture.md` passages (classDiagram with removed public methods, conversation-viewer description) that the implementation steps missed.
|
|
58
|
+
Both fixed in a `docs:` commit before closing.
|
|
59
|
+
|
|
60
|
+
#### What caused friction (agent side)
|
|
61
|
+
|
|
62
|
+
1. `missing-context` — `MockSession` interface lacked `messages`.
|
|
63
|
+
The field existed at runtime (via `createMockSession`'s spread + `Record<string, unknown>` return type) but the `MockSession` interface didn't declare it.
|
|
64
|
+
Adding `messages` to `createSubagentSessionStub` triggered a type error.
|
|
65
|
+
Impact: 2 extra edits to `mock-session.ts` (add field to interface, add to `base` object), no rework.
|
|
66
|
+
2. `missing-context` — `get-result-tool.test.ts` conversation test relied on raw mock session messages.
|
|
67
|
+
After migrating to `record.getConversation()`, the stub's `getConversation` returned `""` by default.
|
|
68
|
+
The test needed `stub.getConversation.mockReturnValue("...")` instead.
|
|
69
|
+
Impact: 1 test update, caught immediately by `pnpm vitest run`.
|
|
70
|
+
3. `missing-context` — `foreground-runner.test.ts` called `onSessionCreated(record, mockSess)` with 2 args.
|
|
71
|
+
After narrowing the callback to `(agent: Agent)`, the first test lacked `record.subagentSession` setup.
|
|
72
|
+
Impact: 2 test blocks updated, caught by `pnpm vitest run`.
|
|
73
|
+
4. `missing-context` — First `get-result-tool.ts` edit left a double-nested `if (conversation)` block.
|
|
74
|
+
The multi-edit replaced the outer `if (params.verbose && record.session)` but preserved the inner guard, creating `if (conversation) { if (conversation) { ... } }`.
|
|
75
|
+
Impact: Self-caught on immediate read-back; fixed in the same commit, no rework.
|
|
76
|
+
|
|
77
|
+
#### What caused friction (user side)
|
|
78
|
+
|
|
79
|
+
- None observed.
|
|
80
|
+
The user's involvement was limited to the `/plan-issue`, `/tdd-plan`, `/ship-issue`, and `/retro` prompts — no mid-session corrections or redirections were needed.
|