@gotgenes/pi-subagents 11.6.0 → 12.1.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,26 @@ 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
+ ## [12.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v12.0.0...pi-subagents-v12.1.0) (2026-05-29)
9
+
10
+
11
+ ### Features
12
+
13
+ * export WorkspaceProvider collaborator types by name ([#272](https://github.com/gotgenes/pi-packages/issues/272)) ([1ff4697](https://github.com/gotgenes/pi-packages/commit/1ff4697a3033be445340d18af005a5d34fb5934d))
14
+
15
+ ## [12.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.6.0...pi-subagents-v12.0.0) (2026-05-29)
16
+
17
+
18
+ ### ⚠ BREAKING CHANGES
19
+
20
+ * SubagentRecord no longer carries worktreeResult, and the core no longer creates git worktrees. Worktree isolation moves to @gotgenes/pi-subagents-worktrees.
21
+ * the Agent tool no longer accepts isolation: "worktree", and SubagentsService.SpawnOptions no longer has an isolation field. Install @gotgenes/pi-subagents-worktrees and list the agent in worktreeAgents instead.
22
+
23
+ ### Features
24
+
25
+ * drop the isolation spawn axis from the subagents API ([2ff8970](https://github.com/gotgenes/pi-packages/commit/2ff897059feec67a49af7e3f54e0e4828faa2521))
26
+ * remove git worktree isolation from the subagents core ([2e81044](https://github.com/gotgenes/pi-packages/commit/2e81044221562c528d1fb296f356f60d81af0661))
27
+
8
28
  ## [11.6.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v11.5.0...pi-subagents-v11.6.0) (2026-05-29)
9
29
 
10
30
 
package/dist/public.d.ts CHANGED
@@ -20,8 +20,6 @@ type LifetimeUsage = {
20
20
 
21
21
  /** Agent type: any string name (built-in defaults or user-defined). */
22
22
  type SubagentType = string;
23
- /** Isolation mode for agent execution. */
24
- type IsolationMode = "worktree";
25
23
  interface AgentInvocation {
26
24
  /** Short display name, e.g. "haiku" — only set when different from parent. */
27
25
  modelName?: string;
@@ -30,7 +28,6 @@ interface AgentInvocation {
30
28
  isolated?: boolean;
31
29
  inheritContext?: boolean;
32
30
  runInBackground?: boolean;
33
- isolation?: IsolationMode;
34
31
  }
35
32
 
36
33
  /**
@@ -43,11 +40,11 @@ interface AgentInvocation {
43
40
  * Stats (toolUses, lifetimeUsage, compactionCount) are owned by the class and
44
41
  * accumulated via mutation methods (incrementToolUses, addUsage, incrementCompactions).
45
42
  *
46
- * Behavior (abort, steer buffering, worktree setup) lives on the agent
47
- * rather than on AgentManager — each agent manages its own lifecycle concerns.
43
+ * Behavior (abort, steer buffering) lives on the agent rather than on
44
+ * AgentManager — each agent manages its own lifecycle concerns.
48
45
  *
49
- * Worktree isolation is delegated to an optional WorktreeIsolation collaborator
50
- * (set at construction when isolation is requested); its presence IS the mode.
46
+ * The child's working directory is supplied by a registered WorkspaceProvider
47
+ * (the workspace seam); with no provider the child runs in the parent cwd.
51
48
  *
52
49
  * Phase-specific collaborators (execution, notification) are attached
53
50
  * after construction as lifecycle information becomes available.
@@ -121,10 +118,6 @@ interface SubagentRecord {
121
118
  completedAt?: number;
122
119
  lifetimeUsage: LifetimeUsage;
123
120
  compactionCount: number;
124
- worktreeResult?: {
125
- hasChanges: boolean;
126
- branch?: string;
127
- };
128
121
  }
129
122
  /** Options for spawning an agent via the service. */
130
123
  interface SpawnOptions {
@@ -136,7 +129,6 @@ interface SpawnOptions {
136
129
  inheritContext?: boolean;
137
130
  foreground?: boolean;
138
131
  bypassQueue?: boolean;
139
- isolation?: "worktree";
140
132
  }
141
133
  /** The public service contract for cross-extension subagent access. */
142
134
  interface SubagentsService {
@@ -175,4 +167,4 @@ declare function getSubagentsService(): SubagentsService | undefined;
175
167
  declare function unpublishSubagentsService(): void;
176
168
 
177
169
  export { SUBAGENT_EVENTS, getSubagentsService, publishSubagentsService, unpublishSubagentsService };
178
- export type { LifetimeUsage, SpawnOptions, SubagentRecord, SubagentStatus, SubagentsService, WorkspaceProvider };
170
+ export type { LifetimeUsage, SpawnOptions, SubagentRecord, SubagentStatus, SubagentsService, Workspace, WorkspaceDisposeOutcome, WorkspaceDisposeResult, WorkspacePrepareContext, WorkspaceProvider };
@@ -56,9 +56,9 @@ flowchart TB
56
56
  AgentManager["AgentManager<br/>(spawn, abort, collection)"]
57
57
  ConcurrencyQueue["ConcurrencyQueue<br/>(scheduling, drain)"]
58
58
  AgentRunner["agent-runner<br/>(session, turns, results)"]
59
- Agent["Agent<br/>(status, behavior: abort/steer/worktree/run lifecycle)"]
59
+ Agent["Agent<br/>(status, behavior: abort/steer/run lifecycle)"]
60
60
  ParentSnapshot["ParentSnapshot<br/>(frozen parent state)"]
61
- Worktree["worktree<br/>(git isolation)"]
61
+ Workspace["workspace<br/>(provider seam: child cwd + teardown)"]
62
62
  end
63
63
 
64
64
  subgraph observation["Observation domain"]
@@ -112,7 +112,6 @@ classDiagram
112
112
  +toolUses: number
113
113
  +lifetimeUsage: LifetimeUsage
114
114
  +execution?: ExecutionState
115
- +worktree?: WorktreeIsolation
116
115
  +notification?: NotificationState
117
116
  +markRunning()
118
117
  +markCompleted()
@@ -270,14 +269,12 @@ src/
270
269
  ├── lifecycle/ agent execution and state tracking
271
270
  │ ├── agent-manager.ts collection manager + observer wiring
272
271
  │ ├── agent-runner.ts session creation, turn loop, tool filtering
273
- │ ├── agent.ts owns full execution lifecycle (run, abort, steer, worktree)
272
+ │ ├── agent.ts owns full execution lifecycle (run, abort, steer, workspace)
274
273
  │ ├── concurrency-queue.ts background agent scheduling with configurable concurrency limit
275
274
  │ ├── parent-snapshot.ts immutable spawn-time parent state
276
275
  │ ├── execution-state.ts session/output phase state
277
276
  │ ├── child-lifecycle.ts child-execution lifecycle event publisher
278
277
  │ ├── workspace.ts workspace provider seam (generative extension surface)
279
- │ ├── worktree.ts git worktree isolation
280
- │ ├── worktree-isolation.ts worktree lifecycle collaborator
281
278
  │ └── usage.ts token usage tracking
282
279
 
283
280
  ├── observation/ progress tracking and notification
@@ -357,14 +354,14 @@ They declare this package as an optional peer dependency and use dynamic import
357
354
  Reactive consumers subscribe: `@gotgenes/pi-permission-system` registers each child session on `session-created` and unregisters it on `disposed`.
358
355
  This replaced the former outbound `permission-bridge` (#261, ADR 0002) — the core no longer looks up a named consumer.
359
356
  - `workspace` — the single generative seam (#262, ADR 0002): a registered `WorkspaceProvider` supplies a child's cwd plus bracketed `dispose()` at run-start.
360
- With no provider, children run in the parent cwd (default unchanged); the git worktree strategy moves behind this seam in #263.
357
+ With no provider, children run in the parent cwd (default unchanged); the git worktree strategy lives behind this seam in `@gotgenes/pi-subagents-worktrees` (#263, the seam's first consumer).
361
358
  - `session-config` — pure configuration assembler (extracted from `agent-runner`).
362
359
  - `SubagentRuntime` — session-scoped state bag with methods.
363
360
  - `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
364
361
  - `record-observer` — session-event observer that updates record statistics without callback threading.
365
362
  - Agent type registry — default agents, custom `.md` file loading.
366
363
  - Prompt assembly, context extraction, skills, environment.
367
- - Worktree isolation — moving to `@gotgenes/pi-subagents-worktrees` via the workspace provider seam in Phase 16 (ADR 0002).
364
+ - Worktree isolation — evicted to `@gotgenes/pi-subagents-worktrees` via the workspace provider seam in Phase 16 (#263, ADR 0002); `git` no longer appears in the core.
368
365
  - Token usage tracking.
369
366
  - Session directory derivation and persisted `SessionManager` for subagent transcripts.
370
367
  - Settings persistence.
@@ -418,7 +415,7 @@ Key types:
418
415
 
419
416
  - `SubagentsService` — `spawn`, `getRecord`, `listAgents`, `abort`, `steer`, `waitForAll`, `hasRunning`.
420
417
  - `SubagentRecord` — serializable agent snapshot (no live session objects).
421
- - `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `isolated`, `inheritContext`, `foreground`, `bypassQueue`, `isolation`.
418
+ - `SpawnOptions` — `description`, `model`, `maxTurns`, `thinkingLevel`, `isolated`, `inheritContext`, `foreground`, `bypassQueue`.
422
419
  - `SUBAGENT_EVENTS` — channel constants for `pi.events` subscriptions.
423
420
 
424
421
  ### Accessor pattern
@@ -501,7 +498,7 @@ Latent extensibility is the deliverable; a vacant hook is not.
501
498
  - **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
502
499
  - **Worktree isolation** (`worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, the `isolation: "worktree"` spawn mode) — environment policy, not core.
503
500
  Git worktrees are one *strategy* for choosing the child's working directory; containers, throwaway tmpdirs, and remote sandboxes are others.
504
- These move to `@gotgenes/pi-subagents-worktrees`, the first consumer of the workspace provider seam.
501
+ Evicted to `@gotgenes/pi-subagents-worktrees` (#263), the first consumer of the workspace provider seam.
505
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.
506
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.
507
504
 
@@ -608,7 +605,6 @@ interface SpawnExecution {
608
605
  inheritContext: boolean;
609
606
  runInBackground: boolean;
610
607
  isolated: boolean;
611
- isolation: IsolationMode | undefined;
612
608
  agentInvocation: AgentInvocation;
613
609
  }
614
610
 
@@ -765,8 +761,8 @@ Migrate `@gotgenes/pi-permission-system` to subscribe to `session-created`/`disp
765
761
  #### Step 2: Define the `WorkspaceProvider` seam — [#262] ✅ Delivered
766
762
 
767
763
  Added the `WorkspaceProvider` / `Workspace` interfaces (`src/lifecycle/workspace.ts`) and `SubagentsService.registerWorkspaceProvider` (single provider, throws on duplicate, returns an unregister disposer).
768
- Only `WorkspaceProvider` is named-re-exported from `service.ts`; `Workspace` and the context types resolve via inference when a consumer assigns to `WorkspaceProvider` (the worktrees package adds named re-exports in #263 when it imports them by name).
769
- At run-start `Agent.run()` consults the registered provider (provider-first precedence) for the child's cwd and a disposal handle; with no provider it falls back to the legacy worktree collaborator, and with neither the child runs in the parent's cwd.
764
+ All five workspace types are named-re-exported from `service.ts`: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult` (added in #272).
765
+ 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).
770
766
  On completion the core calls `Workspace.dispose({ status, description })` and appends the returned `resultAddendum` verbatim — the provider owns the wording.
771
767
 
772
768
  - The seam is additive and non-breaking: the existing `isolation: "worktree"` path is untouched (its eviction is Step 3).
@@ -774,13 +770,15 @@ On completion the core calls `Workspace.dispose({ status, description })` and ap
774
770
  Within #262 the seam is exercised only by test fakes; do not cut a release containing the seam without `@gotgenes/pi-subagents-worktrees`.
775
771
  - Outcome: a single generative seam; the core no longer knows what an "isolation strategy" is.
776
772
 
777
- #### Step 3: Extract worktrees to `@gotgenes/pi-subagents-worktrees` — [#263]
773
+ #### Step 3: Extract worktrees to `@gotgenes/pi-subagents-worktrees` — [#263] ✅ Delivered
778
774
 
779
775
  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.
780
- Remove `worktree.ts`, `worktree-isolation.ts`, `GitWorktreeManager`, and the `isolation: "worktree"` mode from the core; drop `isolation` from the spawn API and `SubagentsService`.
776
+ 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).
777
+ 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`.
781
778
 
782
779
  - Supersedes #256.
783
- New package registered in `release-please-config.json`; peer-depends on `@gotgenes/pi-subagents`.
780
+ 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.
781
+ From the release carrying #272, all five workspace types are importable by name: `WorkspaceProvider`, `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult`.
784
782
  - Outcome: git leaves the core; worktree users install one package, everyone else pays nothing.
785
783
 
786
784
  #### Step 4: Remove `isolated` / `extensions: false` / `noSkills` — [#264]
@@ -0,0 +1,147 @@
1
+ ---
2
+ issue: 272
3
+ issue_title: "Export WorkspaceProvider collaborator types by name from the public surface"
4
+ ---
5
+
6
+ # Export `WorkspaceProvider` collaborator types by name from the public surface
7
+
8
+ ## Problem Statement
9
+
10
+ `@gotgenes/pi-subagents` re-exports the `WorkspaceProvider` seam by name from its public surface, but not the four collaborator types that seam references: `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult`.
11
+ Those types are inlined into the bundled declaration (rolled in because `WorkspaceProvider` references them) but never exported by name.
12
+ A consumer implementing the seam — `@gotgenes/pi-subagents-worktrees` (#263) — cannot `import type { Workspace, WorkspacePrepareContext } from "@gotgenes/pi-subagents"`.
13
+ Instead it recovers the names through indexed-access gymnastics (`Parameters<WorkspaceProvider["prepare"]>[0]`, `NonNullable<Awaited<ReturnType<...>>>`).
14
+ That compiles and is type-safe, but every seam consumer has to repeat the same incantation rather than importing the names directly.
15
+
16
+ ## Goals
17
+
18
+ - Re-export `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult` by name from `src/service/service.ts`, alongside `WorkspaceProvider`.
19
+ - Confirm the four names land in the bundled `dist/public.d.ts` and are importable by an external consumer.
20
+ - Extend the verification harness (`scripts/verify-public-types.sh`) so its symbol guard and probe consumer assert the four names are present and importable.
21
+ - `feat:` — adds names to the publishable public API surface (purely additive, non-breaking).
22
+
23
+ ## Non-Goals
24
+
25
+ - No change to `src/lifecycle/workspace.ts` — the four interfaces already exist there with the right shapes; this issue only re-exports them.
26
+ - No change to the runtime accessor functions, `SubagentsService`, `SpawnOptions`, `SubagentRecord`, or `SUBAGENT_EVENTS`.
27
+ - No edits to `@gotgenes/pi-subagents-worktrees`.
28
+ Replacing its indexed-access aliases with named imports and bumping its `@gotgenes/pi-subagents` dependency is deferred to that package — it can only consume these names after `pi-subagents` cuts a new release carrying them (the registry-consumption model settled in #270).
29
+ - No source restructuring of the entry's type closure (out of scope per #270's design; the rollup bundle already inlines the closure).
30
+ - No new vitest unit test — the re-exports are type-only and erase at runtime, so they are verified by the type-level `verify:public-types` harness, not by the runtime suite.
31
+
32
+ ## Background
33
+
34
+ Relevant modules:
35
+
36
+ - `packages/pi-subagents/src/service/service.ts` — the public entry.
37
+ It currently imports `type { WorkspaceProvider } from "#src/lifecycle/workspace"` and `type { LifetimeUsage } from "#src/lifecycle/usage"`, and ends with `export type { LifetimeUsage, ..., WorkspaceProvider }`.
38
+ A standing comment already anticipates this issue: "Named re-exports of those collaborator types are tracked in #272."
39
+ - `packages/pi-subagents/src/lifecycle/workspace.ts` — defines all four collaborator interfaces plus `WorkspaceProvider`.
40
+ `WorkspacePrepareContext` carries `agentId`, `agentType`, `baseCwd`, and optional `invocation`; `WorkspaceDisposeOutcome` carries `status` and `description`; `WorkspaceDisposeResult` carries optional `resultAddendum`; `Workspace` carries `readonly cwd` and `dispose(outcome)`.
41
+ - `packages/pi-subagents/rollup.dts.config.mjs` — rolls `src/service/service.ts` into a self-contained `dist/public.d.ts`, inlining `#src/*` types and keeping `@earendil-works/*` external.
42
+ No config change is needed: once the names are exported from the entry, `rollup-plugin-dts` carries the (already-inlined) declarations through as named exports.
43
+ - `packages/pi-subagents/scripts/verify-public-types.sh` — the CI-backed verification harness.
44
+ It packs the tarball, runs a self-containment guard (`grep '#src'`), loops a symbol-presence guard (`for sym in getSubagentsService WorkspaceProvider SubagentsService LifetimeUsage`), then type-checks a throwaway `probe.ts` consumer against the packaged tarball.
45
+
46
+ Constraints from `AGENTS.md` and the package skill:
47
+
48
+ - Ship-source model with one build step: `build:types` (rollup) regenerates `dist/public.d.ts` at `prepack`; `dist/` is gitignored and must never be committed.
49
+ - Run `pnpm run verify:public-types` after any change to the public surface — it is also a CI step.
50
+ - Open-for-extension/closed-for-modification: pi-subagents is a minimal core; re-exporting collaborator types of an existing seam is consistent with that boundary (no new behavior, no consumer knowledge).
51
+
52
+ ## Design Overview
53
+
54
+ The change is additive and type-only.
55
+ `service.ts` already imports the seam's entry type; extend that import to bring in the four collaborator types, then list them in the existing `export type { … }`.
56
+
57
+ Import and re-export shape after the change:
58
+
59
+ ```typescript
60
+ import type { LifetimeUsage } from "#src/lifecycle/usage";
61
+ import type {
62
+ Workspace,
63
+ WorkspaceDisposeOutcome,
64
+ WorkspaceDisposeResult,
65
+ WorkspacePrepareContext,
66
+ WorkspaceProvider,
67
+ } from "#src/lifecycle/workspace";
68
+
69
+ export type {
70
+ LifetimeUsage,
71
+ SpawnOptions,
72
+ SubagentRecord,
73
+ SubagentStatus,
74
+ SubagentsService,
75
+ Workspace,
76
+ WorkspaceDisposeOutcome,
77
+ WorkspaceDisposeResult,
78
+ WorkspacePrepareContext,
79
+ WorkspaceProvider,
80
+ };
81
+ ```
82
+
83
+ Consumer call site this enables (the pattern that motivates the issue):
84
+
85
+ ```typescript
86
+ import type {
87
+ Workspace,
88
+ WorkspaceDisposeOutcome,
89
+ WorkspaceDisposeResult,
90
+ WorkspacePrepareContext,
91
+ WorkspaceProvider,
92
+ } from "@gotgenes/pi-subagents";
93
+
94
+ const provider: WorkspaceProvider = {
95
+ async prepare(ctx: WorkspacePrepareContext): Promise<Workspace | undefined> {
96
+ return { cwd: ctx.baseCwd, dispose: (_outcome: WorkspaceDisposeOutcome) => undefined };
97
+ },
98
+ };
99
+ ```
100
+
101
+ Edge cases:
102
+
103
+ - The standing comment in `service.ts` ("Named re-exports of those collaborator types are tracked in #272") becomes stale once the re-exports land — update it to describe the current state rather than a tracked future.
104
+ - `WorkspaceProvider` already pulls the four collaborator declarations into the rollup bundle by reference, so adding the named exports does not change the bundle's self-containment (the `grep '#src'` guard stays green).
105
+
106
+ ## Module-Level Changes
107
+
108
+ - `packages/pi-subagents/src/service/service.ts` — widen the `#src/lifecycle/workspace` type import to include `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult`; add the same four names to the `export type { … }` block; replace the "tracked in #272" comment with a present-tense description of the seam's named re-exports.
109
+ - `packages/pi-subagents/scripts/verify-public-types.sh` — add the four names to the `for sym in …` symbol-presence guard, and extend the inline `probe.ts` to import and exercise the four types by name (e.g. annotate a `prepare` implementation with `WorkspacePrepareContext`/`Workspace` and reference `WorkspaceDisposeOutcome`/`WorkspaceDisposeResult`).
110
+
111
+ No architecture-doc updates are required: `docs/architecture/architecture.md` lists `workspace.ts` under the Lifecycle domain but does not enumerate the public surface's named exports, and no complexity/health table references this change.
112
+
113
+ ## Test Impact Analysis
114
+
115
+ This is a type-only re-export change, not an extraction or refactor.
116
+
117
+ 1. New tests enabled: none at the vitest level — type-only re-exports erase at runtime and cannot be observed by the runtime suite.
118
+ The meaningful new assertion is at the type level: the extended `verify:public-types` probe proves the four names are importable from the packaged tarball, and the symbol guard proves they appear in `dist/public.d.ts`.
119
+ 2. Redundant existing tests: none.
120
+ `test/service/service.test.ts` exercises the runtime accessors and `SUBAGENT_EVENTS`; it is unaffected and stays as-is.
121
+ 3. Tests that must stay as-is: the full vitest suite (the regression canary) and `test/lifecycle/agent.test.ts` (which imports `Workspace`/`WorkspaceProvider` from `#src/lifecycle/workspace` internally — unrelated to the public re-export).
122
+
123
+ ## TDD Order
124
+
125
+ The "test surface" here is the type-level verification harness, which is the red→green loop for a type-only public-surface change.
126
+
127
+ 1. Extend the verification harness (red → green in one step).
128
+ Add the four names to the `for sym in …` guard in `scripts/verify-public-types.sh` and extend the inline `probe.ts` to import them by name.
129
+ Run `pnpm run verify:public-types` to confirm it fails (red) because the names are absent from `dist/public.d.ts` and the probe import is unresolved.
130
+ Then add the import and `export type` entries in `src/service/service.ts` and update the standing comment, and re-run `pnpm run verify:public-types` to confirm it passes (green).
131
+ Harness change and source change land together because the type checker proves them as a unit — a packaged-tarball probe cannot import names the entry does not yet export.
132
+ Commit: `feat: export WorkspaceProvider collaborator types by name (#272)`.
133
+
134
+ The harness step and the source step are bundled into a single commit deliberately: splitting them would leave the repo in a state where `verify:public-types` (a CI step) fails between commits.
135
+
136
+ ## Risks and Mitigations
137
+
138
+ - Risk: the rollup bundle does not surface the new names as named exports.
139
+ Mitigation: `rollup-plugin-dts` carries through whatever the entry exports; the symbol guard + probe in `verify:public-types` prove the names land in `dist/public.d.ts` and are importable.
140
+ - Risk: committing the generated `dist/public.d.ts`.
141
+ Mitigation: `dist/` is gitignored and regenerated at `prepack`; the plan commits only `service.ts` and the harness script.
142
+ - Risk: stale comment misleads future readers.
143
+ Mitigation: the comment update is part of the same step.
144
+
145
+ ## Open Questions
146
+
147
+ - The downstream simplification in `@gotgenes/pi-subagents-worktrees` (swap indexed-access aliases for named imports, bump the `@gotgenes/pi-subagents` dependency) is intentionally deferred until a `pi-subagents` release carries these exports — it is tracked with #263's follow-up, not this plan.
@@ -54,3 +54,53 @@ Root `pnpm run check`, root `pnpm run lint`, and `verify:public-types` all pass.
54
54
  - No `src/`/`test/` `.ts` files were touched, so the vitest suite and `tsc` were unaffected (confirmed via root check).
55
55
  - Pre-completion reviewer: WARN — no findings attributable to this session.
56
56
  Reviewer warnings: (1) the `package-pi-subagents` skill lacked a build-step note — addressed in commit `2ff5a375`; (2) `pnpm fallow dead-code` exits non-zero on a pre-existing finding in `packages/pi-subagents-worktrees/package.json` from the #263 scaffold (commit `9a7dcfc5`), out of scope for #270 and left for #263.
57
+
58
+ ## Stage: Final Retrospective (2026-05-29T21:00:00Z)
59
+
60
+ ### Session summary
61
+
62
+ Shipped #270 end-to-end across planning, build, and ship stages: diagnosed the cross-package type-resolution failure empirically, built a `rollup-plugin-dts` declaration bundle plus a pack-based verification harness, and published `@gotgenes/pi-subagents@11.6.0` (tag `pi-subagents-v11.6.0`).
63
+ Two CI failures during the ship stage — a pre-existing `pnpm fallow dead-code` gate and lockfile drift — required two extra fix commits before CI went green.
64
+
65
+ ### Observations
66
+
67
+ #### What went well
68
+
69
+ - Empirical-first diagnosis: `tsc --traceResolution` in planning pinned the exact two-part failure (consumer `paths` collision + the publisher's `imports`-field extensionless-`.ts` miss) and directly justified the chosen `.d.ts`-emit approach over the alias-free restructure.
70
+ - The flagged primary risk evaporated: `rollup-plugin-dts` resolved `#src/*` out of the box via the package `tsconfig` paths, producing a clean 178-line `dist/public.d.ts` with no resolver plugin.
71
+ - Novel, reusable pattern: `scripts/verify-public-types.sh` proves a ship-source package is externally type-consumable via `pnpm pack` → throwaway-consumer → `tsc`, with no publish round-trip.
72
+ Worth promoting if other packages grow public surfaces.
73
+ - Disciplined `ask_user` use on the genuinely ambiguous decisions (approach, bundler, artifact handling, scope), with strong user steering — the `tsup`-is-unmaintained redirect to `rollup-plugin-dts`, the "no workspace trickery / use released versions" directive, and the #263 chicken-and-egg catch that correctly narrowed scope.
74
+
75
+ #### What caused friction (agent side)
76
+
77
+ - `missing-context` — Pushed to `main` with a pre-existing `pnpm fallow dead-code` failure (unused `@earendil-works/pi-coding-agent` devDependency in `packages/pi-subagents-worktrees/package.json`, from the #263 scaffold).
78
+ The pre-completion reviewer reported it as `FAIL` but labelled it out-of-scope, and I accepted that framing and pushed.
79
+ The CI `Fallow dead-code gate` runs `if: github.ref == 'refs/heads/main'` — a hard gate that fires on every `main` push regardless of who introduced the failure — so CI failed (run `26659647270`).
80
+ Impact: 2 fix commits (`7e7afadd`, `10e74f2f`) and 2 extra CI cycles (~10 min).
81
+ The ship pre-push step runs only `pnpm run lint`, never `pnpm fallow dead-code`.
82
+ - `missing-context` — Removed the devDependency and committed/pushed `package.json` (`7e7afadd`) without the updated `pnpm-lock.yaml`.
83
+ CI's `pnpm install --frozen-lockfile` failed with `ERR_PNPM_OUTDATED_LOCKFILE` (run `26659851716`).
84
+ Impact: 1 extra commit (`10e74f2f`) and 1 extra CI cycle.
85
+ Self-identified from the CI log.
86
+ - `rabbit-hole` (minor) — While debugging the harness's `ERR_PNPM_IGNORED_BUILDS`, the `pnpm ... | tail; echo $?` idiom reported `tail`'s exit code, masking pnpm's real failure; took ~4 tool calls before tracing with `bash -x`.
87
+ Impact: added friction, no rework.
88
+
89
+ #### What caused friction (user side)
90
+
91
+ - The "pi-subagents-* extensions should use the released, npm-installed version, no workspace trickery" directive arrived mid-planning, after initial exploration.
92
+ Surfacing the consumption-model constraint at kickoff would have framed the scope question earlier.
93
+ Opportunity, not criticism — the same exchange produced the high-value chicken-and-egg catch (the registry version with the fix cannot exist until #270 publishes) that correctly deferred the worktrees flip to #263.
94
+ - A brief "there is no ADR 0003" → "My mistake" exchange; no rework.
95
+
96
+ ### Diagnostic details
97
+
98
+ - Model-performance correlation — the lone subagent dispatch (`pre-completion-reviewer`) ran on `anthropic/claude-sonnet-4-6`, appropriate for judgment-heavy review.
99
+ The dead-code-gate framing miss was a protocol-scope issue (pre-existing vs blocking), not a model-capability mismatch.
100
+ - Escalation-delay — no error sequence exceeded 5 consecutive tool calls; the harness `ERR_PNPM_IGNORED_BUILDS` resolved in ~4.
101
+ - Feedback-loop gap — build-stage verification ran incrementally after each step (good); the gap was at ship: the pre-push check omits the `main`-only gates (`pnpm fallow dead-code`) and lockfile validation that CI enforces, so a locally-clean `pnpm run lint` still failed CI twice.
102
+
103
+ ### Changes made
104
+
105
+ 1. `.pi/prompts/ship-issue.md` — renamed Step 2 to "Pre-push checks" and added `pnpm fallow dead-code` alongside `pnpm run lint`, with a one-line note that the gate is `main`-only and blocks pushes regardless of who introduced the failure.
106
+ 2. `AGENTS.md` (§ Code Style pnpm rules) — added a rule to run `pnpm install` and commit the updated `pnpm-lock.yaml` in the same commit when a `package.json` dependency changes, since CI installs with `--frozen-lockfile`.
@@ -0,0 +1,38 @@
1
+ ---
2
+ issue: 272
3
+ issue_title: "Export WorkspaceProvider collaborator types by name from the public surface"
4
+ ---
5
+
6
+ # Retro: #272 — Export `WorkspaceProvider` collaborator types by name from the public surface
7
+
8
+ ## Stage: Planning (2026-05-29T23:23:37Z)
9
+
10
+ ### Session summary
11
+
12
+ Planned a purely additive, type-only change to `@gotgenes/pi-subagents`'s public surface: re-export `Workspace`, `WorkspacePrepareContext`, `WorkspaceDisposeOutcome`, and `WorkspaceDisposeResult` by name from `src/service/service.ts`, alongside the already-exported `WorkspaceProvider`.
13
+ The plan bundles the source change with extending the `verify:public-types` harness (symbol guard + probe consumer) into a single `feat:` commit, since a packaged-tarball probe cannot import names the entry does not yet export.
14
+
15
+ ### Observations
16
+
17
+ - The issue's "Proposed change" was unambiguous, so the `ask-user` design gate was skipped.
18
+
19
+ ## Stage: Implementation — Build (2026-05-29T23:30:13Z)
20
+
21
+ ### Session summary
22
+
23
+ Implemented the single plan step: widened the `#src/lifecycle/workspace` import in `src/service/service.ts` to include all four collaborator types and added them to the `export type { … }` block, updating the standing comment from a future-tracking reference to a present-tense description.
24
+ Extended `scripts/verify-public-types.sh` — the symbol guard loop and inline `probe.ts` — to assert all four new names, confirmed the harness fails (red) before the source change and passes (green) after.
25
+ Also updated two stale forward-references to #272 in `docs/architecture/architecture.md` (flagged by the pre-completion reviewer).
26
+
27
+ ### Observations
28
+
29
+ - Red→green cycle through the type-level harness (`verify:public-types`) rather than vitest, exactly as planned.
30
+ - No rollup config change was needed — `rollup-plugin-dts` carries through whatever the entry exports; the symbol guard and `#src` self-containment guard both stayed green.
31
+ - The symbol guard already found the names present in the bundle (declared but not exported); the probe's type-check was the real red signal.
32
+ - Pre-completion reviewer: WARN — two stale forward-references to #272 in `docs/architecture/architecture.md`; both fixed in a follow-up `docs:` commit before shipping.
33
+ - No new vitest cycle: the re-exports are type-only and erase at runtime.
34
+ The red→green loop lives in the type-level `verify:public-types` harness, not the runtime suite — this is closer to a `/build-plan` shape than a code TDD cycle, but it still has a clean red→green via the probe consumer.
35
+ - The four interfaces already exist in `src/lifecycle/workspace.ts` with correct shapes; only re-exporting is in scope.
36
+ - `rollup.dts.config.mjs` needs no change — `WorkspaceProvider` already pulls the four collaborator declarations into the bundle by reference, so adding named exports keeps the `grep '#src'` self-containment guard green.
37
+ - A standing comment in `service.ts` ("Named re-exports … tracked in #272") goes stale on landing — the plan updates it in the same step.
38
+ - The downstream simplification in `@gotgenes/pi-subagents-worktrees` (swap indexed-access aliases for named imports, bump the dependency) is deferred until a `pi-subagents` release carries these exports — consistent with the registry-consumption model settled in #270.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "11.6.0",
3
+ "version": "12.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -68,7 +68,6 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
68
68
  inheritContext: fm.inherit_context != null ? fm.inherit_context === true : undefined,
69
69
  runInBackground: fm.run_in_background != null ? fm.run_in_background === true : undefined,
70
70
  isolated: fm.isolated != null ? fm.isolated === true : undefined,
71
- isolation: fm.isolation === "worktree" ? "worktree" : undefined,
72
71
  enabled: fm.enabled !== false, // default true; explicitly false disables
73
72
  source,
74
73
  });
@@ -1,4 +1,4 @@
1
- import type { AgentConfig, IsolationMode, ThinkingLevel } from "#src/types";
1
+ import type { AgentConfig, ThinkingLevel } from "#src/types";
2
2
 
3
3
  interface AgentInvocationParams {
4
4
  model?: string;
@@ -7,7 +7,6 @@ interface AgentInvocationParams {
7
7
  run_in_background?: boolean;
8
8
  inherit_context?: boolean;
9
9
  isolated?: boolean;
10
- isolation?: IsolationMode;
11
10
  }
12
11
 
13
12
  export function resolveAgentInvocationConfig(
@@ -21,7 +20,6 @@ export function resolveAgentInvocationConfig(
21
20
  inheritContext: boolean;
22
21
  runInBackground: boolean;
23
22
  isolated: boolean;
24
- isolation?: IsolationMode;
25
23
  } {
26
24
  return {
27
25
  modelInput: agentConfig?.model ?? params.model,
@@ -31,6 +29,5 @@ export function resolveAgentInvocationConfig(
31
29
  inheritContext: agentConfig?.inheritContext ?? params.inherit_context ?? false,
32
30
  runInBackground: agentConfig?.runInBackground ?? params.run_in_background ?? false,
33
31
  isolated: agentConfig?.isolated ?? params.isolated ?? false,
34
- isolation: agentConfig?.isolation ?? params.isolation,
35
32
  };
36
33
  }
package/src/index.ts CHANGED
@@ -28,7 +28,6 @@ import { ConcreteAgentRunner, type RunnerDeps } from "#src/lifecycle/agent-runne
28
28
  import { createChildLifecyclePublisher } from "#src/lifecycle/child-lifecycle";
29
29
  import { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
30
30
  import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
31
- import { GitWorktreeManager } from "#src/lifecycle/worktree";
32
31
  import { buildEventData, type NotificationDetails, NotificationManager } from "#src/observation/notification";
33
32
  import { createNotificationRenderer } from "#src/observation/renderer";
34
33
  import { createSubagentRuntime } from "#src/runtime";
@@ -166,7 +165,6 @@ export default function (pi: ExtensionAPI) {
166
165
 
167
166
  const manager = new AgentManager({
168
167
  runner: new ConcreteAgentRunner(runnerDeps),
169
- worktrees: new GitWorktreeManager(process.cwd()),
170
168
  baseCwd: process.cwd(),
171
169
  observer,
172
170
  queue,
@@ -14,11 +14,9 @@ import type { AgentRunner } from "#src/lifecycle/agent-runner";
14
14
  import type { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
15
15
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
16
16
  import type { WorkspaceProvider } from "#src/lifecycle/workspace";
17
- import type { WorktreeManager } from "#src/lifecycle/worktree";
18
- import { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
19
17
 
20
18
  import type { RunConfig } from "#src/runtime";
21
- import type { AgentInvocation, CompactionInfo, IsolationMode, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
19
+ import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
22
20
 
23
21
  /** Observer interface for agent lifecycle notifications. */
24
22
  export interface AgentManagerObserver {
@@ -31,7 +29,6 @@ export interface AgentManagerObserver {
31
29
 
32
30
  export interface AgentManagerOptions {
33
31
  runner: AgentRunner;
34
- worktrees: WorktreeManager;
35
32
  /** Concurrency queue — owns scheduling, limit checks, and drain logic. */
36
33
  queue: ConcurrencyQueue;
37
34
  /** Base working directory handed to a workspace provider (the parent cwd). */
@@ -54,8 +51,6 @@ export interface AgentSpawnConfig {
54
51
  * callers (e.g. cross-extension RPC) that must not be deferred by the queue.
55
52
  */
56
53
  bypassQueue?: boolean;
57
- /** Isolation mode - "worktree" creates a temp git worktree for the agent. */
58
- isolation?: IsolationMode;
59
54
  /** Resolved invocation snapshot captured for UI display. */
60
55
  invocation?: AgentInvocation;
61
56
  /** Parent abort signal - when aborted, the subagent is also stopped. */
@@ -71,7 +66,6 @@ export class AgentManager {
71
66
  private cleanupInterval: ReturnType<typeof setInterval>;
72
67
  private readonly observer?: AgentManagerObserver;
73
68
  private readonly runner: AgentRunner;
74
- private readonly worktrees: WorktreeManager;
75
69
  private readonly queue: ConcurrencyQueue;
76
70
  private readonly baseCwd: string;
77
71
  private getRunConfig?: () => RunConfig;
@@ -84,7 +78,6 @@ export class AgentManager {
84
78
 
85
79
  constructor(options: AgentManagerOptions) {
86
80
  this.runner = options.runner;
87
- this.worktrees = options.worktrees;
88
81
  this.queue = options.queue;
89
82
  this.baseCwd = options.baseCwd;
90
83
  this.observer = options.observer;
@@ -162,10 +155,6 @@ export class AgentManager {
162
155
  signal: options.signal,
163
156
  // Shared deps
164
157
  runner: this.runner,
165
- worktree:
166
- options.isolation === "worktree"
167
- ? new WorktreeIsolation(this.worktrees, id)
168
- : undefined,
169
158
  observer: this.buildObserver(options),
170
159
  getRunConfig: this.getRunConfig,
171
160
  baseCwd: this.baseCwd,
@@ -322,7 +311,5 @@ export class AgentManager {
322
311
  record.session?.dispose();
323
312
  }
324
313
  this.agents.clear();
325
- // Prune any orphaned git worktrees (crash recovery)
326
- try { this.worktrees.prune(); } catch (err) { debugLog("pruneWorktrees on dispose", err); }
327
314
  }
328
315
  }
@@ -8,11 +8,11 @@
8
8
  * Stats (toolUses, lifetimeUsage, compactionCount) are owned by the class and
9
9
  * accumulated via mutation methods (incrementToolUses, addUsage, incrementCompactions).
10
10
  *
11
- * Behavior (abort, steer buffering, worktree setup) lives on the agent
12
- * rather than on AgentManager — each agent manages its own lifecycle concerns.
11
+ * Behavior (abort, steer buffering) lives on the agent rather than on
12
+ * AgentManager — each agent manages its own lifecycle concerns.
13
13
  *
14
- * Worktree isolation is delegated to an optional WorktreeIsolation collaborator
15
- * (set at construction when isolation is requested); its presence IS the mode.
14
+ * The child's working directory is supplied by a registered WorkspaceProvider
15
+ * (the workspace seam); with no provider the child runs in the parent cwd.
16
16
  *
17
17
  * Phase-specific collaborators (execution, notification) are attached
18
18
  * after construction as lifecycle information becomes available.
@@ -27,7 +27,6 @@ import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
27
27
  import type { LifetimeUsage } from "#src/lifecycle/usage";
28
28
  import { addUsage } from "#src/lifecycle/usage";
29
29
  import type { Workspace, WorkspaceProvider } from "#src/lifecycle/workspace";
30
- import type { WorktreeIsolation } from "#src/lifecycle/worktree-isolation";
31
30
  import { NotificationState } from "#src/observation/notification-state";
32
31
  import { subscribeAgentObserver } from "#src/observation/record-observer";
33
32
  import type { RunConfig } from "#src/runtime";
@@ -70,7 +69,6 @@ export interface AgentInit {
70
69
 
71
70
  // Shared deps (required for run(), optional for tests)
72
71
  runner?: AgentRunner;
73
- worktree?: WorktreeIsolation;
74
72
  observer?: AgentLifecycleObserver;
75
73
  getRunConfig?: () => RunConfig;
76
74
  /** Resolves the registered workspace provider (if any) at run-start. */
@@ -130,8 +128,6 @@ export class Agent {
130
128
 
131
129
  // Shared deps — optional (required for run())
132
130
  private readonly _runner?: AgentRunner;
133
- /** Worktree isolation collaborator — present only when isolation: "worktree". */
134
- readonly worktree?: WorktreeIsolation;
135
131
  readonly observer?: AgentLifecycleObserver;
136
132
  private readonly _getRunConfig?: () => RunConfig;
137
133
  private readonly _getWorkspaceProvider?: () => WorkspaceProvider | undefined;
@@ -192,7 +188,6 @@ export class Agent {
192
188
 
193
189
  // Shared deps
194
190
  this._runner = init.runner;
195
- this.worktree = init.worktree;
196
191
  this.observer = init.observer;
197
192
  this._getRunConfig = init.getRunConfig;
198
193
  this._getWorkspaceProvider = init.getWorkspaceProvider;
@@ -215,8 +210,8 @@ export class Agent {
215
210
  }
216
211
 
217
212
  /**
218
- * Execute the full agent lifecycle: worktree setup, runner invocation,
219
- * session-creation handling, observer wiring, worktree cleanup, and
213
+ * Execute the full agent lifecycle: workspace preparation, runner invocation,
214
+ * session-creation handling, observer wiring, workspace disposal, and
220
215
  * status transitions.
221
216
  *
222
217
  * Requires runner and snapshot to be set at construction.
@@ -236,8 +231,8 @@ export class Agent {
236
231
 
237
232
  let cwd: string | undefined;
238
233
  try {
239
- // Provider-first: a registered workspace provider supplies the cwd and
240
- // owns teardown; otherwise fall back to the legacy worktree collaborator.
234
+ // A registered workspace provider supplies the child's cwd and owns its
235
+ // teardown; with no provider the child runs in the parent cwd.
241
236
  const provider = this._getWorkspaceProvider?.();
242
237
  if (provider) {
243
238
  this._workspace = await provider.prepare({
@@ -247,9 +242,6 @@ export class Agent {
247
242
  invocation: this.invocation,
248
243
  });
249
244
  cwd = this._workspace?.cwd;
250
- } else {
251
- this.worktree?.setup();
252
- cwd = this.worktree?.path;
253
245
  }
254
246
  } catch (err) {
255
247
  this.markError(err);
@@ -463,7 +455,7 @@ export class Agent {
463
455
  this._detachFn = undefined;
464
456
  }
465
457
 
466
- /** Complete a run: release listeners, worktree cleanup, status transition, execution update, notify observer. */
458
+ /** Complete a run: release listeners, dispose the workspace, status transition, execution update, notify observer. */
467
459
  completeRun(result: RunResult): void {
468
460
  this.releaseListeners();
469
461
 
@@ -476,11 +468,6 @@ export class Agent {
476
468
  : "completed";
477
469
  const disposeResult = this._workspace.dispose({ status: finalStatus, description: this.description });
478
470
  if (disposeResult?.resultAddendum) finalResult += disposeResult.resultAddendum;
479
- } else {
480
- const wtResult = this.worktree?.cleanup(this.description);
481
- if (wtResult?.hasChanges && wtResult.branch) {
482
- finalResult += `\n\n---\nChanges saved to branch \`${wtResult.branch}\`. Merge with: \`git merge ${wtResult.branch}\``;
483
- }
484
471
  }
485
472
 
486
473
  if (result.aborted) this.markAborted(finalResult);
@@ -495,14 +482,13 @@ export class Agent {
495
482
  this.observer?.onRunFinished?.(this);
496
483
  }
497
484
 
498
- /** Fail a run: mark error, release listeners, best-effort worktree cleanup, notify observer. */
485
+ /** Fail a run: mark error, release listeners, best-effort workspace dispose, notify observer. */
499
486
  failRun(err: unknown): void {
500
487
  this.markError(err);
501
488
  this.releaseListeners();
502
489
 
503
490
  try {
504
491
  if (this._workspace) this._workspace.dispose({ status: "error", description: this.description });
505
- else this.worktree?.cleanup(this.description);
506
492
  } catch (cleanupErr) { debugLog("workspace dispose on agent error", cleanupErr); }
507
493
 
508
494
  this.observer?.onRunFinished?.(this);
@@ -69,7 +69,6 @@ export class SubagentsServiceAdapter implements SubagentsService {
69
69
  isolated: options?.isolated,
70
70
  inheritContext: options?.inheritContext,
71
71
  bypassQueue: options?.bypassQueue,
72
- isolation: options?.isolation,
73
72
  isBackground,
74
73
  });
75
74
  }
@@ -134,8 +133,6 @@ export function toSubagentRecord(record: Agent): SubagentRecord {
134
133
  if (record.result !== undefined) out.result = record.result;
135
134
  if (record.error !== undefined) out.error = record.error;
136
135
  if (record.completedAt !== undefined) out.completedAt = record.completedAt;
137
- const worktreeResult = record.worktree?.cleanupResult;
138
- if (worktreeResult !== undefined) out.worktreeResult = worktreeResult;
139
136
 
140
137
  return out;
141
138
  }
@@ -10,13 +10,26 @@
10
10
  */
11
11
 
12
12
  import type { LifetimeUsage } from "#src/lifecycle/usage";
13
- import type { WorkspaceProvider } from "#src/lifecycle/workspace";
14
-
15
- // Generative extension seam (ADR 0002, Phase 16 Step 2). Only the provider
16
- // entry-point type is re-exported here; a consumer assigning to
17
- // `WorkspaceProvider` gets `Workspace` and the context types via inference.
18
- // The worktrees package (#263) adds named re-exports when it imports them.
19
- export type { LifetimeUsage, WorkspaceProvider };
13
+ import type {
14
+ Workspace,
15
+ WorkspaceDisposeOutcome,
16
+ WorkspaceDisposeResult,
17
+ WorkspacePrepareContext,
18
+ WorkspaceProvider,
19
+ } from "#src/lifecycle/workspace";
20
+
21
+ // Generative extension seam (ADR 0002, Phase 16 Step 2). The provider type
22
+ // and all four collaborator types it references are re-exported by name so
23
+ // consumers can import them directly rather than recovering them via
24
+ // indexed-access inference (e.g. `Parameters<WorkspaceProvider["prepare"]>[0]`).
25
+ export type {
26
+ LifetimeUsage,
27
+ Workspace,
28
+ WorkspaceDisposeOutcome,
29
+ WorkspaceDisposeResult,
30
+ WorkspacePrepareContext,
31
+ WorkspaceProvider,
32
+ };
20
33
 
21
34
  export type SubagentStatus =
22
35
  | "queued"
@@ -40,7 +53,6 @@ export interface SubagentRecord {
40
53
  completedAt?: number;
41
54
  lifetimeUsage: LifetimeUsage;
42
55
  compactionCount: number;
43
- worktreeResult?: { hasChanges: boolean; branch?: string };
44
56
  }
45
57
 
46
58
  /** Options for spawning an agent via the service. */
@@ -53,7 +65,6 @@ export interface SpawnOptions {
53
65
  inheritContext?: boolean;
54
66
  foreground?: boolean;
55
67
  bypassQueue?: boolean;
56
- isolation?: "worktree";
57
68
  }
58
69
 
59
70
  /** The public service contract for cross-extension subagent access. */
@@ -179,7 +179,7 @@ Guidelines:
179
179
  - Use model to specify a different model (as "provider/modelId", or fuzzy e.g. "haiku", "sonnet").
180
180
  - Use thinking to control extended thinking level.
181
181
  - Use inherit_context if the agent needs the parent conversation history.
182
- - Use isolation: "worktree" to run the agent in an isolated git worktree (safe parallel file modifications).`,
182
+ `,
183
183
  parameters: Type.Object({
184
184
  prompt: Type.String({
185
185
  description: "The task for the agent to perform.",
@@ -231,12 +231,6 @@ Guidelines:
231
231
  "If true, fork parent conversation into the agent. Default: false (fresh context).",
232
232
  }),
233
233
  ),
234
- isolation: Type.Optional(
235
- Type.Literal("worktree", {
236
- description:
237
- 'Set to "worktree" to run the agent in a temporary git worktree (isolated copy of the repo). Changes are saved to a branch on completion.',
238
- }),
239
- ),
240
234
  }),
241
235
 
242
236
  // ---- Custom rendering: Claude Code style ----
@@ -52,7 +52,6 @@ export function spawnBackground(
52
52
  inheritContext: execution.inheritContext,
53
53
  thinkingLevel: execution.thinking,
54
54
  isBackground: true,
55
- isolation: execution.isolation,
56
55
  invocation: execution.agentInvocation,
57
56
  observer: {
58
57
  onSessionCreated: (_agent, session) => {
@@ -105,7 +105,6 @@ export async function runForeground(
105
105
  isolated: execution.isolated,
106
106
  inheritContext: execution.inheritContext,
107
107
  thinkingLevel: execution.thinking,
108
- isolation: execution.isolation,
109
108
  invocation: execution.agentInvocation,
110
109
  signal,
111
110
  parentSession: params.parentSession,
@@ -12,7 +12,7 @@ import type { AgentTypeRegistry } from "#src/config/agent-types";
12
12
  import { resolveAgentInvocationConfig } from "#src/config/invocation-config";
13
13
  import { normalizeMaxTurns } from "#src/lifecycle/agent-runner";
14
14
  import { resolveInvocationModel } from "#src/session/model-resolver";
15
- import type { AgentInvocation, IsolationMode, SubagentType, ThinkingLevel } from "#src/types";
15
+ import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
16
16
  import {
17
17
  type AgentDetails,
18
18
  buildInvocationTags,
@@ -44,7 +44,6 @@ export interface SpawnExecution {
44
44
  inheritContext: boolean;
45
45
  runInBackground: boolean;
46
46
  isolated: boolean;
47
- isolation: IsolationMode | undefined;
48
47
  agentInvocation: AgentInvocation;
49
48
  }
50
49
 
@@ -104,7 +103,6 @@ export function resolveSpawnConfig(
104
103
  const inheritContext = resolvedConfig.inheritContext;
105
104
  const runInBackground = resolvedConfig.runInBackground;
106
105
  const isolated = resolvedConfig.isolated;
107
- const isolation = resolvedConfig.isolation;
108
106
 
109
107
  // Compute display model name (only shown when different from parent)
110
108
  const parentModelId = modelInfo.parentModel?.id;
@@ -125,7 +123,6 @@ export function resolveSpawnConfig(
125
123
  isolated,
126
124
  inheritContext,
127
125
  runInBackground,
128
- isolation,
129
126
  };
130
127
 
131
128
  const modeLabel = getPromptModeLabel(subagentType, registry);
@@ -151,7 +148,6 @@ export function resolveSpawnConfig(
151
148
  inheritContext,
152
149
  runInBackground,
153
150
  isolated,
154
- isolation,
155
151
  agentInvocation,
156
152
  },
157
153
  presentation: { modelName, agentTags, detailBase },
package/src/types.ts CHANGED
@@ -21,9 +21,6 @@ export interface SubscribableSession {
21
21
  /** Agent type: any string name (built-in defaults or user-defined). */
22
22
  export type SubagentType = string;
23
23
 
24
- /** Isolation mode for agent execution. */
25
- export type IsolationMode = "worktree";
26
-
27
24
  /** UI display and agent listing — name, display name, description, prompt mode. */
28
25
  export interface AgentIdentity {
29
26
  name: string;
@@ -55,8 +52,6 @@ export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
55
52
  runInBackground?: boolean;
56
53
  /** Default for spawn: no extension tools. undefined = caller decides. */
57
54
  isolated?: boolean;
58
- /** Isolation mode — "worktree" runs the agent in a temporary git worktree */
59
- isolation?: IsolationMode;
60
55
  /** true = this is an embedded default agent (informational) */
61
56
  isDefault?: boolean;
62
57
  /** false = agent is hidden from the registry */
@@ -73,7 +68,6 @@ export interface AgentInvocation {
73
68
  isolated?: boolean;
74
69
  inheritContext?: boolean;
75
70
  runInBackground?: boolean;
76
- isolation?: IsolationMode;
77
71
  }
78
72
 
79
73
  /**
@@ -54,7 +54,6 @@ export function buildEjectContent(cfg: AgentConfig): string {
54
54
  if (cfg.inheritContext) fmFields.push("inherit_context: true");
55
55
  if (cfg.runInBackground) fmFields.push("run_in_background: true");
56
56
  if (cfg.isolated) fmFields.push("isolated: true");
57
- if (cfg.isolation) fmFields.push(`isolation: ${cfg.isolation}`);
58
57
  return `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`;
59
58
  }
60
59
 
@@ -109,7 +109,6 @@ skills: <true (inherit all), false (none), or comma-separated skill names to pre
109
109
  inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
110
110
  run_in_background: <true to run in background by default. Default: false>
111
111
  isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
112
- isolation: <"worktree" to run in isolated git worktree. Omit for normal>
113
112
  ---
114
113
 
115
114
  <system prompt body — instructions for the agent>
package/src/ui/display.ts CHANGED
@@ -136,7 +136,6 @@ export function buildInvocationTags(
136
136
  if (!invocation) return { tags };
137
137
  if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
138
138
  if (invocation.isolated) tags.push("isolated");
139
- if (invocation.isolation === "worktree") tags.push("worktree");
140
139
  if (invocation.inheritContext) tags.push("inherit context");
141
140
  if (invocation.runInBackground) tags.push("background");
142
141
  if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
@@ -1,59 +0,0 @@
1
- /**
2
- * worktree-isolation.ts — WorktreeIsolation: collaborator that owns the
3
- * git-worktree lifecycle for an isolated agent.
4
- *
5
- * Constructed by AgentManager only when isolation === "worktree", bound to a
6
- * WorktreeManager and the agent id. Agent tells it `setup()` and
7
- * `cleanup(description)` instead of managing worktree internals itself.
8
- *
9
- * The presence/absence of this collaborator IS the isolation mode: Agent calls
10
- * `this.worktree?.setup()` rather than checking an isolation string.
11
- */
12
-
13
- import type { WorktreeCleanupResult, WorktreeInfo, WorktreeManager } from "#src/lifecycle/worktree";
14
-
15
- export class WorktreeIsolation {
16
- private _info?: WorktreeInfo;
17
- private _cleanupResult?: WorktreeCleanupResult;
18
-
19
- constructor(
20
- private readonly worktrees: WorktreeManager,
21
- private readonly agentId: string,
22
- ) {}
23
-
24
- /** Absolute worktree path — undefined before setup(). */
25
- get path(): string | undefined {
26
- return this._info?.path;
27
- }
28
-
29
- /** Cleanup outcome — undefined until cleanup() runs. */
30
- get cleanupResult(): WorktreeCleanupResult | undefined {
31
- return this._cleanupResult;
32
- }
33
-
34
- /**
35
- * Create the git worktree and store its info.
36
- * Throws on failure (strict isolation — no silent fallback).
37
- */
38
- setup(): void {
39
- const wt = this.worktrees.create(this.agentId);
40
- if (!wt) {
41
- throw new Error(
42
- 'Cannot run with isolation: "worktree" — not a git repo, no commits yet, or `git worktree add` failed. ' +
43
- "Initialize git and commit at least once, or omit `isolation`.",
44
- );
45
- }
46
- this._info = wt;
47
- }
48
-
49
- /**
50
- * Perform worktree cleanup and record the result.
51
- * No-op returning { hasChanges: false } if setup never ran.
52
- */
53
- cleanup(description: string): WorktreeCleanupResult {
54
- if (!this._info) return { hasChanges: false };
55
- const result = this.worktrees.cleanup(this._info, description);
56
- this._cleanupResult = result;
57
- return result;
58
- }
59
- }
@@ -1,194 +0,0 @@
1
- /**
2
- * worktree.ts — Git worktree isolation for agents.
3
- *
4
- * Creates a temporary git worktree so the agent works on an isolated copy of the repo.
5
- * On completion, if no changes were made, the worktree is cleaned up.
6
- * If changes exist, a branch is created and returned in the result.
7
- */
8
-
9
- import { execFileSync } from "node:child_process";
10
- import { randomUUID } from "node:crypto";
11
- import { existsSync } from "node:fs";
12
- import { tmpdir } from "node:os";
13
- import { join } from "node:path";
14
- import { debugLog } from "#src/debug";
15
-
16
- export interface WorktreeInfo {
17
- /** Absolute path to the worktree directory. */
18
- path: string;
19
- /** Branch name created for this worktree (if changes exist). */
20
- branch: string;
21
- }
22
-
23
- export interface WorktreeCleanupResult {
24
- /** Whether changes were found in the worktree. */
25
- hasChanges: boolean;
26
- /** Branch name if changes were committed. */
27
- branch?: string;
28
- /** Worktree path if it was kept. */
29
- path?: string;
30
- }
31
-
32
- /**
33
- * Create a temporary git worktree for an agent.
34
- * Returns the worktree path, or undefined if not in a git repo.
35
- */
36
- export function createWorktree(cwd: string, agentId: string): WorktreeInfo | undefined {
37
- // Verify we're in a git repo with at least one commit (HEAD must exist)
38
- try {
39
- execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
40
- execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 });
41
- } catch (err) {
42
- debugLog("createWorktree git rev-parse", err);
43
- return undefined;
44
- }
45
-
46
- const branch = `pi-agent-${agentId}`;
47
- const suffix = randomUUID().slice(0, 8);
48
- const worktreePath = join(tmpdir(), `pi-agent-${agentId}-${suffix}`);
49
-
50
- try {
51
- // Create detached worktree at HEAD
52
- execFileSync("git", ["worktree", "add", "--detach", worktreePath, "HEAD"], {
53
- cwd,
54
- stdio: "pipe",
55
- timeout: 30000,
56
- });
57
- return { path: worktreePath, branch };
58
- } catch (err) {
59
- debugLog("git worktree add", err);
60
- return undefined;
61
- }
62
- }
63
-
64
- /**
65
- * Clean up a worktree after agent completion.
66
- * - If no changes: remove worktree entirely.
67
- * - If changes exist: create a branch, commit changes, return branch info.
68
- */
69
- export function cleanupWorktree(
70
- cwd: string,
71
- worktree: WorktreeInfo,
72
- agentDescription: string,
73
- ): WorktreeCleanupResult {
74
- if (!existsSync(worktree.path)) {
75
- return { hasChanges: false };
76
- }
77
-
78
- try {
79
- // Check for uncommitted changes in the worktree
80
- const status = execFileSync("git", ["status", "--porcelain"], {
81
- cwd: worktree.path,
82
- stdio: "pipe",
83
- timeout: 10000,
84
- }).toString().trim();
85
-
86
- if (!status) {
87
- // No changes — remove worktree
88
- removeWorktree(cwd, worktree.path);
89
- return { hasChanges: false };
90
- }
91
-
92
- // Changes exist — stage, commit, and create a branch
93
- execFileSync("git", ["add", "-A"], { cwd: worktree.path, stdio: "pipe", timeout: 10000 });
94
- // Truncate description for commit message (no shell sanitization needed — execFileSync uses argv)
95
- const safeDesc = agentDescription.slice(0, 200);
96
- const commitMsg = `pi-agent: ${safeDesc}`;
97
- execFileSync("git", ["commit", "-m", commitMsg], {
98
- cwd: worktree.path,
99
- stdio: "pipe",
100
- timeout: 10000,
101
- });
102
-
103
- // Create a branch pointing to the worktree's HEAD.
104
- // If the branch already exists, append a suffix to avoid overwriting previous work.
105
- let branchName = worktree.branch;
106
- try {
107
- execFileSync("git", ["branch", branchName], {
108
- cwd: worktree.path,
109
- stdio: "pipe",
110
- timeout: 5000,
111
- });
112
- } catch (err) {
113
- debugLog("git branch", err);
114
- branchName = `${worktree.branch}-${Date.now()}`;
115
- execFileSync("git", ["branch", branchName], {
116
- cwd: worktree.path,
117
- stdio: "pipe",
118
- timeout: 5000,
119
- });
120
- }
121
- // Update branch name in worktree info for the caller
122
- worktree.branch = branchName;
123
-
124
- // Remove the worktree (branch persists in main repo)
125
- removeWorktree(cwd, worktree.path);
126
-
127
- return {
128
- hasChanges: true,
129
- branch: worktree.branch,
130
- path: worktree.path,
131
- };
132
- } catch (err) {
133
- debugLog("cleanupWorktree", err);
134
- try { removeWorktree(cwd, worktree.path); } catch (removeErr) { debugLog("removeWorktree on cleanup error", removeErr); }
135
- return { hasChanges: false };
136
- }
137
- }
138
-
139
- /**
140
- * Force-remove a worktree.
141
- */
142
- function removeWorktree(cwd: string, worktreePath: string): void {
143
- try {
144
- execFileSync("git", ["worktree", "remove", "--force", worktreePath], {
145
- cwd,
146
- stdio: "pipe",
147
- timeout: 10000,
148
- });
149
- } catch (err) {
150
- debugLog("git worktree remove", err);
151
- try {
152
- execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
153
- } catch (pruneErr) { debugLog("git worktree prune", pruneErr); }
154
- }
155
- }
156
-
157
- /**
158
- * Prune any orphaned worktrees (crash recovery).
159
- */
160
- export function pruneWorktrees(cwd: string): void {
161
- try {
162
- execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
163
- } catch (err) { debugLog("pruneWorktrees", err); }
164
- }
165
-
166
- /**
167
- * Interface for managing git worktrees relative to a fixed repository root.
168
- * Callers do not thread `cwd` per call — it is captured at construction time.
169
- */
170
- export interface WorktreeManager {
171
- create(id: string): WorktreeInfo | undefined;
172
- cleanup(wt: WorktreeInfo, description: string): WorktreeCleanupResult;
173
- prune(): void;
174
- }
175
-
176
- /**
177
- * Concrete implementation of WorktreeManager backed by the free functions in this module.
178
- * Captures `cwd` (the repository root) at construction and delegates each method.
179
- */
180
- export class GitWorktreeManager implements WorktreeManager {
181
- constructor(private readonly cwd: string) {}
182
-
183
- create(id: string): WorktreeInfo | undefined {
184
- return createWorktree(this.cwd, id);
185
- }
186
-
187
- cleanup(wt: WorktreeInfo, description: string): WorktreeCleanupResult {
188
- return cleanupWorktree(this.cwd, wt, description);
189
- }
190
-
191
- prune(): void {
192
- pruneWorktrees(this.cwd);
193
- }
194
- }