@gotgenes/pi-subagents 13.2.0 → 13.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +21 -10
  3. package/dist/public.d.ts +34 -35
  4. package/docs/architecture/architecture.md +58 -148
  5. package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
  6. package/docs/decisions/0003-publish-bundled-type-declarations.md +3 -1
  7. package/docs/plans/0051-update-adr-0001-hard-fork.md +8 -6
  8. package/docs/plans/0257-extract-child-session-factory.md +3 -1
  9. package/docs/plans/0262-add-workspace-provider-seam.md +41 -39
  10. package/docs/plans/0264-remove-extension-lifecycle-control.md +100 -98
  11. package/docs/plans/0265-born-complete-subagent-session.md +5 -2
  12. package/docs/plans/0270-type-consumable-public-surface.md +3 -1
  13. package/docs/plans/0280-rename-agent-to-subagent.md +197 -0
  14. package/docs/retro/0051-update-adr-0001-hard-fork.md +4 -2
  15. package/docs/retro/0257-extract-child-session-factory.md +3 -1
  16. package/docs/retro/0262-add-workspace-provider-seam.md +3 -1
  17. package/docs/retro/0264-remove-extension-lifecycle-control.md +3 -1
  18. package/docs/retro/0270-type-consumable-public-surface.md +4 -2
  19. package/docs/retro/0277-encapsulate-agent-session.md +41 -0
  20. package/docs/retro/0280-rename-agent-to-subagent.md +96 -0
  21. package/package.json +1 -1
  22. package/src/index.ts +9 -9
  23. package/src/lifecycle/create-subagent-session.ts +1 -1
  24. package/src/lifecycle/{agent-manager.ts → subagent-manager.ts} +27 -27
  25. package/src/lifecycle/subagent-session.ts +2 -2
  26. package/src/lifecycle/{agent.ts → subagent.ts} +28 -28
  27. package/src/lifecycle/turn-limits.ts +1 -1
  28. package/src/lifecycle/workspace.ts +2 -2
  29. package/src/observation/notification.ts +9 -9
  30. package/src/observation/record-observer.ts +9 -9
  31. package/src/runtime.ts +1 -1
  32. package/src/service/service-adapter.ts +10 -10
  33. package/src/service/service.ts +5 -9
  34. package/src/tools/agent-tool.ts +5 -5
  35. package/src/tools/background-spawner.ts +3 -3
  36. package/src/tools/foreground-runner.ts +5 -5
  37. package/src/tools/get-result-tool.ts +2 -2
  38. package/src/tools/steer-tool.ts +2 -2
  39. package/src/types.ts +1 -1
  40. package/src/ui/agent-creation-wizard.ts +2 -2
  41. package/src/ui/agent-menu.ts +5 -5
  42. package/src/ui/agent-widget.ts +2 -2
  43. package/src/ui/conversation-viewer.ts +3 -3
@@ -9,7 +9,7 @@ issue_title: "Remove isolated / extensions:false / noSkills from core"
9
9
 
10
10
  ### Session summary
11
11
 
12
- Planned Phase 16, Step 4: removing the extension-lifecycle-control axis (`isolated`, `extensions: false`, `noSkills`) from the pi-subagents core per ADR 0002.
12
+ Planned Phase 16, Step 4: removing the extension-lifecycle-control axis (`isolated`, `extensions: false`, `noSkills`) from the pi-subagents core per [ADR-0002].
13
13
  Confirmed all three prerequisite Phase 16 steps (#261, #262, #263) are closed, so the explicit "deny-at-use" dependency is satisfied.
14
14
  Produced a four-cycle TDD plan (`isolated` → `extensions` → `skills`/`noSkills`/preload → docs) and committed it.
15
15
 
@@ -87,3 +87,5 @@ The run had zero rework and a PASS pre-completion review; test count moved 1016
87
87
  1. Appended this Final Retrospective stage entry to `packages/pi-subagents/docs/retro/0264-remove-extension-lifecycle-control.md`.
88
88
  2. Considered but **declined** (user: "too narrow") a removal-coupling detection rule for `.pi/prompts/plan-issue.md` — the heuristic that a field named for removal may be the mechanism behind a separate surviving feature.
89
89
  No prompt or `AGENTS.md` changes were made this retro.
90
+
91
+ [ADR-0002]: ../decisions/0002-extensions-on-a-minimal-core.md
@@ -34,7 +34,7 @@ The plan adds a `rollup-plugin-dts` build that bundles `src/service/service.ts`
34
34
 
35
35
  ### Session summary
36
36
 
37
- Executed all four build-order steps: added `rollup` + `rollup-plugin-dts` and a `build:types` script that bundles `src/service/service.ts` into a self-contained `dist/public.d.ts`; wired conditional `exports` (`types` + `default`, fixing the stale path) with a `prepack` hook and a `files` allowlist; added a `pnpm pack` → throwaway-consumer → `tsc` verification harness (`scripts/verify-public-types.sh`) plus a CI step; and recorded ADR 0003.
37
+ Executed all four build-order steps: added `rollup` + `rollup-plugin-dts` and a `build:types` script that bundles `src/service/service.ts` into a self-contained `dist/public.d.ts`; wired conditional `exports` (`types` + `default`, fixing the stale path) with a `prepack` hook and a `files` allowlist; added a `pnpm pack` → throwaway-consumer → `tsc` verification harness (`scripts/verify-public-types.sh`) plus a CI step; and recorded [ADR-0003].
38
38
  A fifth commit documented the new build step in the `package-pi-subagents` skill (reviewer WARN).
39
39
  Root `pnpm run check`, root `pnpm run lint`, and `verify:public-types` all pass.
40
40
 
@@ -91,7 +91,7 @@ Two CI failures during the ship stage — a pre-existing `pnpm fallow dead-code`
91
91
  - The "pi-subagents-* extensions should use the released, npm-installed version, no workspace trickery" directive arrived mid-planning, after initial exploration.
92
92
  Surfacing the consumption-model constraint at kickoff would have framed the scope question earlier.
93
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.
94
+ - A brief "there is no [ADR-0003]" → "My mistake" exchange; no rework.
95
95
 
96
96
  ### Diagnostic details
97
97
 
@@ -104,3 +104,5 @@ Two CI failures during the ship stage — a pre-existing `pnpm fallow dead-code`
104
104
 
105
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
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`.
107
+
108
+ [ADR-0003]: ../decisions/0003-publish-bundled-type-declarations.md
@@ -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.
@@ -0,0 +1,96 @@
1
+ ---
2
+ issue: 280
3
+ issue_title: "Rename the internal Agent class to Subagent"
4
+ ---
5
+
6
+ # Retro: #280 — Rename the internal `Agent` class to `Subagent`
7
+
8
+ ## Stage: Planning (2026-05-31T00:09:51Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a numbered implementation plan to rename the subagent-instance cluster in `src/lifecycle/` from the bare `Agent*` family to `Subagent*`, consolidate the duplicate `AgentStatus` union into the public `SubagentStatus`, and update the architecture doc.
13
+ The plan is a 7-step refactor (no behavior change), each step an atomic language-service rename that leaves the tree green.
14
+
15
+ ### Observations
16
+
17
+ - Scope decisions confirmed with the user via `ask_user`: (1) rename the module files too (`agent.ts` → `subagent.ts`, `agent-manager.ts` → `subagent-manager.ts`, plus test/helper files), and (2) full-consistency rename of adjacent identifiers — `subscribeAgentObserver`, the `SubagentManagerObserver` `onAgent*` methods, and the `createTestAgent` helper.
18
+ - Layering catch: pointing `WorkspaceDisposeOutcome.status` directly at `service.ts`'s `SubagentStatus` would create a `lifecycle → service` cycle (`service.ts` already imports the workspace collaborator types).
19
+ Resolution: keep the union's single home in the lifecycle layer (`subagent.ts`) and have `service.ts` re-export it, mirroring the existing `LifetimeUsage` / workspace re-export pattern.
20
+ - Acceptance-grep catch: the issue's `grep src/lifecycle/` for bare `Agent` matches comments and string literals (e.g. the two `"Agent not configured …"` error messages), not just symbols.
21
+ The language-service rename does not touch those, so each step must sweep residual comment/string occurrences; step 7 has a final grep gate.
22
+ - Compound names (`AgentSession`, `AgentInvocation`, `AgentTypeRegistry`, `AgentTool`, `AgentSpawnConfig`) are not bare-word matches and are explicitly out of scope.
23
+ - Non-breaking — `refactor:` commits throughout; `verify:public-types` runs after the status consolidation and the final step since the public bundle (`dist/public.d.ts`) is rolled from `src/service/service.ts`.
24
+ - Also flagged the `package-pi-subagents` SKILL.md for an internals-naming update (it references `AgentManager`, `Agent`, `make-agent`).
25
+
26
+ ## Stage: Implementation — TDD (2026-05-31T00:38:55Z)
27
+
28
+ ### Session summary
29
+
30
+ Completed 6 refactor commits (steps 1–6 from the plan, with step 5 folded into step 3) plus a `test:` commit for the helper rename and a `docs:` commit for the architecture doc update.
31
+ All 973 tests pass across 59 test files with no test count delta.
32
+ Pre-completion reviewer returned PASS with all 5 acceptance criteria verified.
33
+
34
+ ### Observations
35
+
36
+ - Step 5 (`AgentManagerLike` → `SubagentManagerLike`) was automatically folded into step 3 because the bulk-rename Python script replaced all `AgentManager*` compounds at once — no separate commit was needed or appropriate.
37
+ - The acceptance grep (`grep -rnE '\bAgent(Manager|Init)?\b' src/lifecycle/`) also flags bare `Agent` in comments and error strings; each rename step swept those manually since the language-service rename does not touch non-symbol text.
38
+ - `sed` with negative lookaheads failed on macOS for the `notification.ts` file; fell back to a two-pass approach (sed for the import, then `perl -i -0pe` with negative lookahead for the body).
39
+ - `describe("Agent — ...)` test block names used em-dashes (Unicode U+2014); `sed` with `\u2014` escape did not match on macOS — required Python `re.sub` with the literal character.
40
+ - The `SubagentStatus` type definition was kept in `src/lifecycle/subagent.ts` (single home) and re-exported from `src/service/service.ts`, matching the existing `LifetimeUsage` / workspace re-export pattern and avoiding a `lifecycle → service` cycle.
41
+ - Docs: the architecture doc's session-encapsulation table had misaligned Markdown table columns after the rename (cell widths changed); `rumdl fmt` auto-fixed them.
42
+ - Pre-completion reviewer: PASS — all deterministic checks, all 5 acceptance criteria, conventional commits, docs, Mermaid diagrams, and dead-code gate confirmed clean.
43
+
44
+ ## Stage: Final Retrospective (2026-05-31T01:05:57Z)
45
+
46
+ ### Session summary
47
+
48
+ Shipped the full Planning → TDD → Ship lifecycle for the internal `Agent` → `Subagent` rename across `@gotgenes/pi-subagents`: 7 implementation commits (6 `refactor:`/`test:` + 1 `docs:`), 973 tests green throughout, CI passed on `27abb5aa`, and issue #280 closed.
49
+ No release was triggered (all `refactor:`/`test:`/`docs:` commits), so no release-please PR appeared — expected.
50
+
51
+ ### Observations
52
+
53
+ #### What went well
54
+
55
+ - Two planning-stage catches prevented downstream rework: the `lifecycle → service` import cycle (resolved by keeping `SubagentStatus`'s single home in `subagent.ts` and re-exporting from `service.ts`) and the acceptance grep matching bare `Agent` in comments/strings (so each step swept non-symbol text).
56
+ Both were anticipated in the plan and held during execution.
57
+ - Incremental verification carried the rename safely: `pnpm run check` plus the affected test files ran after every step, so the text-based substitution mistakes were caught instantly and never reached a commit.
58
+ The pre-completion reviewer then returned a clean PASS with nothing to fix.
59
+ - The status re-export needed a follow-up `import type { SubagentStatus }` because `export type { … } from` does not create a local binding for `SubagentRecord` to reference — `pnpm run check` flagged it immediately, a one-edit fix.
60
+
61
+ #### What caused friction (agent side)
62
+
63
+ - `wrong-abstraction` — The plan specified each rename as a "scope-aware language-service pass" (`findRenameLocations`), but the execution toolkit has no LSP-rename tool — only `Edit`, `Bash` (`sed`/`perl`/`python`), and `grep`.
64
+ Execution fell back to text substitution, which is exactly why the comment/string sweep was needed and why the regex gymnastics below happened.
65
+ Impact: added friction but no rework — `tsc` + tests caught every gap; no incorrect commit landed.
66
+ - `other` (cross-platform tooling) — Repeated silent failures from BSD `sed` / `perl` one-liners: BSD `sed` lacks `\b` and lookahead; `perl -i ''` is malformed (the `-i ''` form is a `sed`-ism); neither interprets `\uXXXX` escapes, so em-dash `describe("Agent — …")` blocks and `notification.ts` body renames did not match.
67
+ Each failure forced a grep-verify-redo loop, ultimately resolved by switching to Python `re.sub`.
68
+ Impact: roughly 5–8 extra tool calls across the TDD stage; no rework beyond the redo cycles.
69
+
70
+ #### What caused friction (user side)
71
+
72
+ - None.
73
+ The user's two `ask_user` answers at planning time (file renames + full-consistency adjacent identifiers) front-loaded every scope decision, so the TDD and Ship stages ran without a single mid-course correction.
74
+
75
+ ### Diagnostic details
76
+
77
+ - **Model-performance correlation** — One subagent dispatched (`pre-completion-reviewer`) on judgment-heavy review work (acceptance verification, Mermaid validation via `mmdc`, dead-code gate); appropriate match, no mismatch.
78
+ - **Escalation-delay tracking** — The `notification.ts` body rename cycled ~4–5 consecutive `sed`/`perl`/`grep` calls on the same substitution before switching to `perl -i -0pe`; under the 5-call threshold but the closest the session came.
79
+ The general lesson (prefer Python `re.sub`) generalizes the fix.
80
+ - **Unused-tool detection** — No Explore/`colgrep` gap: the codebase was already understood from planning, and an exact symbol rename is not a semantic-search task.
81
+ The only "missing capability" is a language-service rename tool, which is not in the toolkit — a genuine gap, not an unused option.
82
+ - **Feedback-loop gap analysis** — No gap: `pnpm run check` and affected tests ran after each step (incremental); `pnpm run lint`, `pnpm fallow dead-code`, and `verify:public-types` ran as the final batch.
83
+ Verification cadence was correct.
84
+
85
+ ### Changes made
86
+
87
+ 1. Appended this Final Retrospective stage entry to `packages/pi-subagents/docs/retro/0280-rename-agent-to-subagent.md`.
88
+ 2. Strengthened the global `APPEND_SYSTEM.md` "Shell Commands" rule to name the literal absolute-path `cd` form (e.g. `cd /Users/you/project &&`), not just `cd $CWD &&` — the existing rule failed to catch the literal-path form that agents actually emit.
89
+ This file is global (`~/.pi/agent/APPEND_SYSTEM.md`), outside the repo, so it is not committed here.
90
+ 3. Added a monorepo-specific line to `AGENTS.md` § Monorepo Structure: prefer `pnpm --filter @gotgenes/<pkg> run <script>` (or `pnpm -C packages/<pkg> run <script>`) from the root over `cd packages/<pkg> && pnpm run <script>`.
91
+ Prompted by excessive `cd` chaining observed across this session's Ship and Retro stages.
92
+
93
+ ### Follow-ups considered (not applied)
94
+
95
+ 1. Proposed adding a bulk-substitution tooling rule (prefer Python `re.sub` over `sed`/`perl` one-liners, since BSD `sed` lacks `\b`/lookahead and `\uXXXX` is uninterpreted) to the `code-design` skill's Tooling section.
96
+ The user opted to record the observation here only and skip the skill change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "13.2.0",
3
+ "version": "13.2.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/index.ts CHANGED
@@ -23,11 +23,11 @@ import {
23
23
  import { AgentTypeRegistry } from "#src/config/agent-types";
24
24
  import { loadCustomAgents } from "#src/config/custom-agents";
25
25
  import { SessionLifecycleHandler, ToolStartHandler } from "#src/handlers/index";
26
- import { AgentManager, type AgentManagerObserver } from "#src/lifecycle/agent-manager";
27
26
  import { createChildLifecyclePublisher } from "#src/lifecycle/child-lifecycle";
28
27
  import { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
29
28
  import { createSubagentSession, type SubagentSessionDeps } from "#src/lifecycle/create-subagent-session";
30
29
  import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
30
+ import { SubagentManager, type SubagentManagerObserver } from "#src/lifecycle/subagent-manager";
31
31
  import { buildEventData, type NotificationDetails, NotificationManager } from "#src/observation/notification";
32
32
  import { createNotificationRenderer } from "#src/observation/renderer";
33
33
  import { createSubagentRuntime } from "#src/runtime";
@@ -56,7 +56,7 @@ export default function (pi: ExtensionAPI) {
56
56
  const runtime = createSubagentRuntime();
57
57
 
58
58
  // ---- Notification system ----
59
- // runtime.widget is assigned after AgentManager construction; arrow closures
59
+ // runtime.widget is assigned after SubagentManager construction; arrow closures
60
60
  // capture `runtime` by reference so they always read the current value.
61
61
  const notifications = new NotificationManager(
62
62
  (msg, opts) => pi.sendMessage(msg, opts),
@@ -76,8 +76,8 @@ export default function (pi: ExtensionAPI) {
76
76
  settings.load();
77
77
 
78
78
  // Observer: receives agent lifecycle notifications and dispatches events/notifications.
79
- const observer: AgentManagerObserver = {
80
- onAgentStarted(record) {
79
+ const observer: SubagentManagerObserver = {
80
+ onSubagentStarted(record) {
81
81
  // Emit started event when agent transitions to running (including from queue).
82
82
  pi.events.emit("subagents:started", {
83
83
  id: record.id,
@@ -85,7 +85,7 @@ export default function (pi: ExtensionAPI) {
85
85
  description: record.description,
86
86
  });
87
87
  },
88
- onAgentCompleted(record) {
88
+ onSubagentCompleted(record) {
89
89
  // Emit lifecycle event based on terminal status.
90
90
  const isError = record.status === "error" || record.status === "stopped" || record.status === "aborted";
91
91
  const eventData = buildEventData(record);
@@ -110,7 +110,7 @@ export default function (pi: ExtensionAPI) {
110
110
 
111
111
  notifications.sendCompletion(record);
112
112
  },
113
- onAgentCompacted(record, info) {
113
+ onSubagentCompacted(record, info) {
114
114
  // Emit compacted event when agent's session compacts (preserves count on record).
115
115
  pi.events.emit("subagents:compacted", {
116
116
  id: record.id,
@@ -121,7 +121,7 @@ export default function (pi: ExtensionAPI) {
121
121
  compactionCount: record.compactionCount,
122
122
  });
123
123
  },
124
- onAgentCreated(record) {
124
+ onSubagentCreated(record) {
125
125
  // Emit created event for background agents (before startAgent / queue drain).
126
126
  pi.events.emit("subagents:created", {
127
127
  id: record.id,
@@ -150,7 +150,7 @@ export default function (pi: ExtensionAPI) {
150
150
  lifecycle: createChildLifecyclePublisher((channel, data) => pi.events.emit(channel, data)),
151
151
  };
152
152
 
153
- // ConcurrencyQueue: scheduling extracted from AgentManager.
153
+ // ConcurrencyQueue: scheduling extracted from SubagentManager.
154
154
  // startAgent callback forward-references manager via closure (safe — drain is never called during construction).
155
155
  const queue = new ConcurrencyQueue(
156
156
  () => settings.maxConcurrent,
@@ -161,7 +161,7 @@ export default function (pi: ExtensionAPI) {
161
161
  },
162
162
  );
163
163
 
164
- const manager = new AgentManager({
164
+ const manager = new SubagentManager({
165
165
  createSubagentSession: (params) => createSubagentSession(params, subagentSessionDeps),
166
166
  baseCwd: process.cwd(),
167
167
  observer,
@@ -5,7 +5,7 @@
5
5
  * `runAgent()` did up front: detect the environment, assemble the session config,
6
6
  * create the SDK session, publish `spawning`/`session-created`, bind extensions,
7
7
  * and apply the recursion guard. It returns a fully usable `SubagentSession` —
8
- * `Agent` then only coordinates (turn loop, steer, dispose).
8
+ * `Subagent` then only coordinates (turn loop, steer, dispose).
9
9
  *
10
10
  * The factory takes a resolved `cwd` value, never the WorkspaceProvider: `cwd`
11
11
  * is a value the factory consumes directly (detectEnv, assembleSessionConfig,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * agent-manager.ts - Tracks agents, background execution, resume support.
2
+ * subagent-manager.ts - Tracks subagents, background execution, resume support.
3
3
  *
4
4
  * Background agents are subject to a configurable concurrency limit (default: 4).
5
5
  * Excess agents are queued and auto-started as running agents complete.
@@ -9,10 +9,10 @@
9
9
  import { randomUUID } from "node:crypto";
10
10
  import type { Model } from "@earendil-works/pi-ai";
11
11
  import { debugLog } from "#src/debug";
12
- import { Agent, type AgentLifecycleObserver } from "#src/lifecycle/agent";
13
12
  import type { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
14
13
  import type { CreateSubagentSessionParams } from "#src/lifecycle/create-subagent-session";
15
14
  import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
15
+ import { Subagent, type SubagentLifecycleObserver } from "#src/lifecycle/subagent";
16
16
  import type { SubagentSession } from "#src/lifecycle/subagent-session";
17
17
  import type { WorkspaceProvider } from "#src/lifecycle/workspace";
18
18
 
@@ -20,15 +20,15 @@ import type { RunConfig } from "#src/runtime";
20
20
  import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
21
21
 
22
22
  /** Observer interface for agent lifecycle notifications. */
23
- export interface AgentManagerObserver {
24
- onAgentStarted(record: Agent): void;
25
- onAgentCompleted(record: Agent): void;
26
- onAgentCompacted(record: Agent, info: CompactionInfo): void;
23
+ export interface SubagentManagerObserver {
24
+ onSubagentStarted(record: Subagent): void;
25
+ onSubagentCompleted(record: Subagent): void;
26
+ onSubagentCompacted(record: Subagent, info: CompactionInfo): void;
27
27
  /** Fires synchronously after a background agent record is created (before run). */
28
- onAgentCreated(record: Agent): void;
28
+ onSubagentCreated(record: Subagent): void;
29
29
  }
30
30
 
31
- export interface AgentManagerOptions {
31
+ export interface SubagentManagerOptions {
32
32
  /** Assembly factory that produces a born-complete SubagentSession per spawn. */
33
33
  createSubagentSession: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
34
34
  /** Concurrency queue — owns scheduling, limit checks, and drain logic. */
@@ -36,7 +36,7 @@ export interface AgentManagerOptions {
36
36
  /** Base working directory handed to a workspace provider (the parent cwd). */
37
37
  baseCwd: string;
38
38
  getRunConfig?: () => RunConfig;
39
- observer?: AgentManagerObserver;
39
+ observer?: SubagentManagerObserver;
40
40
  }
41
41
 
42
42
  export interface AgentSpawnConfig {
@@ -56,16 +56,16 @@ export interface AgentSpawnConfig {
56
56
  invocation?: AgentInvocation;
57
57
  /** Parent abort signal - when aborted, the subagent is also stopped. */
58
58
  signal?: AbortSignal;
59
- /** Per-agent lifecycle observer — replaces onSessionCreated callback. */
60
- observer?: AgentLifecycleObserver;
59
+ /** Per-subagent lifecycle observer — replaces onSessionCreated callback. */
60
+ observer?: SubagentLifecycleObserver;
61
61
  /** Parent session identity - grouped fields that travel together from the tool boundary. */
62
62
  parentSession?: ParentSessionInfo;
63
63
  }
64
64
 
65
- export class AgentManager {
66
- private agents = new Map<string, Agent>();
65
+ export class SubagentManager {
66
+ private agents = new Map<string, Subagent>();
67
67
  private cleanupInterval: ReturnType<typeof setInterval>;
68
- private readonly observer?: AgentManagerObserver;
68
+ private readonly observer?: SubagentManagerObserver;
69
69
  private readonly createSubagentSession: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
70
70
  private readonly queue: ConcurrencyQueue;
71
71
  private readonly baseCwd: string;
@@ -77,7 +77,7 @@ export class AgentManager {
77
77
  return this._workspaceProvider;
78
78
  }
79
79
 
80
- constructor(options: AgentManagerOptions) {
80
+ constructor(options: SubagentManagerOptions) {
81
81
  this.createSubagentSession = options.createSubagentSession;
82
82
  this.queue = options.queue;
83
83
  this.baseCwd = options.baseCwd;
@@ -106,11 +106,11 @@ export class AgentManager {
106
106
  }
107
107
 
108
108
  /** Compose a per-agent lifecycle observer from manager and spawn-config concerns. */
109
- private buildObserver(options: AgentSpawnConfig): AgentLifecycleObserver {
109
+ private buildObserver(options: AgentSpawnConfig): SubagentLifecycleObserver {
110
110
  return {
111
111
  onStarted: (agent) => {
112
112
  if (options.isBackground) this.queue.markStarted();
113
- this.observer?.onAgentStarted(agent);
113
+ this.observer?.onSubagentStarted(agent);
114
114
  },
115
115
  onSessionCreated: options.observer?.onSessionCreated
116
116
  ? (agent) => options.observer!.onSessionCreated!(agent)
@@ -118,11 +118,11 @@ export class AgentManager {
118
118
  onRunFinished: (agent) => {
119
119
  if (options.isBackground) {
120
120
  this.queue.markFinished();
121
- try { this.observer?.onAgentCompleted(agent); } catch (err) { debugLog("onAgentCompleted observer", err); }
121
+ try { this.observer?.onSubagentCompleted(agent); } catch (err) { debugLog("onSubagentCompleted observer", err); }
122
122
  }
123
123
  },
124
124
  onCompacted: (agent, info) => {
125
- this.observer?.onAgentCompacted(agent, info);
125
+ this.observer?.onSubagentCompacted(agent, info);
126
126
  },
127
127
  };
128
128
  }
@@ -138,7 +138,7 @@ export class AgentManager {
138
138
  options: AgentSpawnConfig,
139
139
  ): string {
140
140
  const id = randomUUID().slice(0, 17);
141
- const record = new Agent({
141
+ const record = new Subagent({
142
142
  id,
143
143
  type,
144
144
  description: options.description,
@@ -163,7 +163,7 @@ export class AgentManager {
163
163
  this.agents.set(id, record);
164
164
 
165
165
  if (options.isBackground) {
166
- this.observer?.onAgentCreated(record);
166
+ this.observer?.onSubagentCreated(record);
167
167
  }
168
168
 
169
169
  if (options.isBackground && !options.bypassQueue && this.queue.isFull()) {
@@ -185,7 +185,7 @@ export class AgentManager {
185
185
  type: SubagentType,
186
186
  prompt: string,
187
187
  options: Omit<AgentSpawnConfig, "isBackground">,
188
- ): Promise<Agent> {
188
+ ): Promise<Subagent> {
189
189
  const id = this.spawn(snapshot, type, prompt, { ...options, isBackground: false });
190
190
  const record = this.agents.get(id)!;
191
191
  await record.promise;
@@ -194,24 +194,24 @@ export class AgentManager {
194
194
 
195
195
  /**
196
196
  * Resume an existing agent session with a new prompt.
197
- * Delegates to Agent.resume(), which owns the observer subscription lifecycle.
197
+ * Delegates to Subagent.resume(), which owns the observer subscription lifecycle.
198
198
  */
199
199
  async resume(
200
200
  id: string,
201
201
  prompt: string,
202
202
  signal?: AbortSignal,
203
- ): Promise<Agent | undefined> {
203
+ ): Promise<Subagent | undefined> {
204
204
  const agent = this.agents.get(id);
205
205
  if (!agent?.isSessionReady()) return undefined;
206
206
  await agent.resume(prompt, signal);
207
207
  return agent;
208
208
  }
209
209
 
210
- getRecord(id: string): Agent | undefined {
210
+ getRecord(id: string): Subagent | undefined {
211
211
  return this.agents.get(id);
212
212
  }
213
213
 
214
- listAgents(): Agent[] {
214
+ listAgents(): Subagent[] {
215
215
  return [...this.agents.values()].sort(
216
216
  (a, b) => b.startedAt - a.startedAt,
217
217
  );
@@ -232,7 +232,7 @@ export class AgentManager {
232
232
  }
233
233
 
234
234
  /** Dispose a record's session and remove it from the map. */
235
- private removeRecord(id: string, record: Agent): void {
235
+ private removeRecord(id: string, record: Subagent): void {
236
236
  record.disposeSession();
237
237
  this.agents.delete(id);
238
238
  }
@@ -4,10 +4,10 @@
4
4
  * A SubagentSession wraps one SDK AgentSession plus its turn-driving and teardown.
5
5
  * It is born complete: `createSubagentSession()` returns a fully usable instance
6
6
  * (session created, extensions bound, recursion guard applied), so the only thing
7
- * left for `Agent` to do is coordinate — drive the turn loop, steer, dispose.
7
+ * left for `Subagent` to do is coordinate — drive the turn loop, steer, dispose.
8
8
  *
9
9
  * Turn driving lives here, on the object that owns the AgentSession, rather than
10
- * reaching through `subagentSession.session` from `Agent` (Law of Demeter).
10
+ * reaching through `subagentSession.session` from `Subagent` (Law of Demeter).
11
11
  */
12
12
 
13
13
  import {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * agent.ts — Agent class with encapsulated status-transition logic and per-agent behavior.
2
+ * subagent.ts — Subagent class with encapsulated status-transition logic and per-subagent behavior.
3
3
  *
4
4
  * Status transitions (status, result, error, startedAt, completedAt) are owned
5
5
  * by the class and exposed via transition methods. External code reads these
@@ -8,8 +8,8 @@
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) lives on the agent rather than on
12
- * AgentManager — each agent manages its own lifecycle concerns.
11
+ * Behavior (abort, steer buffering) lives on the subagent rather than on
12
+ * SubagentManager — each subagent manages its own lifecycle concerns.
13
13
  *
14
14
  * The child's working directory is supplied by a registered WorkspaceProvider
15
15
  * (the workspace seam); with no provider the child runs in the parent cwd.
@@ -28,23 +28,23 @@ 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
30
  import { NotificationState } from "#src/observation/notification-state";
31
- import { subscribeAgentObserver } from "#src/observation/record-observer";
31
+ import { subscribeSubagentObserver } from "#src/observation/record-observer";
32
32
  import type { RunConfig } from "#src/runtime";
33
33
  import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType, ThinkingLevel } from "#src/types";
34
34
 
35
- /** Per-agent lifecycle observer — created by AgentManager for each spawn. */
36
- export interface AgentLifecycleObserver {
37
- /** Fires when the agent transitions to running (inside run(), after markRunning). */
38
- onStarted?(agent: Agent): void;
39
- /** Fires once the session is created — the agent's subagentSession is now available. */
40
- onSessionCreated?(agent: Agent): void;
35
+ /** Per-subagent lifecycle observer — created by SubagentManager for each spawn. */
36
+ export interface SubagentLifecycleObserver {
37
+ /** Fires when the subagent transitions to running (inside run(), after markRunning). */
38
+ onStarted?(agent: Subagent): void;
39
+ /** Fires once the session is created — the subagent's subagentSession is now available. */
40
+ onSessionCreated?(agent: Subagent): void;
41
41
  /** Fires once when the run completes or fails (for concurrency drain). */
42
- onRunFinished?(agent: Agent): void;
42
+ onRunFinished?(agent: Subagent): void;
43
43
  /** Fires on compaction events during the run. */
44
- onCompacted?(agent: Agent, info: CompactionInfo): void;
44
+ onCompacted?(agent: Subagent, info: CompactionInfo): void;
45
45
  }
46
46
 
47
- export type AgentStatus =
47
+ export type SubagentStatus =
48
48
  | "queued"
49
49
  | "running"
50
50
  | "completed"
@@ -53,7 +53,7 @@ export type AgentStatus =
53
53
  | "stopped"
54
54
  | "error";
55
55
 
56
- export interface AgentInit {
56
+ export interface SubagentInit {
57
57
  // Identity
58
58
  id: string;
59
59
  type: SubagentType;
@@ -61,7 +61,7 @@ export interface AgentInit {
61
61
  invocation?: AgentInvocation;
62
62
 
63
63
  // Status (for tests and restore scenarios)
64
- status?: AgentStatus;
64
+ status?: SubagentStatus;
65
65
  startedAt?: number;
66
66
  completedAt?: number;
67
67
  result?: string;
@@ -70,7 +70,7 @@ export interface AgentInit {
70
70
  // Shared deps (required for run(), optional for tests)
71
71
  /** Assembly factory that produces a born-complete SubagentSession. */
72
72
  createSubagentSession?: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
73
- observer?: AgentLifecycleObserver;
73
+ observer?: SubagentLifecycleObserver;
74
74
  getRunConfig?: () => RunConfig;
75
75
  /** Resolves the registered workspace provider (if any) at run-start. */
76
76
  getWorkspaceProvider?: () => WorkspaceProvider | undefined;
@@ -88,7 +88,7 @@ export interface AgentInit {
88
88
  signal?: AbortSignal;
89
89
  }
90
90
 
91
- export class Agent {
91
+ export class Subagent {
92
92
  // Identity — set once at construction
93
93
  readonly id: string;
94
94
  readonly type: SubagentType;
@@ -96,8 +96,8 @@ export class Agent {
96
96
  readonly invocation?: AgentInvocation;
97
97
 
98
98
  // Transition state — encapsulated behind getters, mutated only via transition methods
99
- private _status: AgentStatus;
100
- get status(): AgentStatus { return this._status; }
99
+ private _status: SubagentStatus;
100
+ get status(): SubagentStatus { return this._status; }
101
101
 
102
102
  private _result?: string;
103
103
  get result(): string | undefined { return this._result; }
@@ -128,7 +128,7 @@ export class Agent {
128
128
 
129
129
  // Shared deps — optional (required for run())
130
130
  private readonly _createSubagentSession?: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
131
- readonly observer?: AgentLifecycleObserver;
131
+ readonly observer?: SubagentLifecycleObserver;
132
132
  private readonly _getRunConfig?: () => RunConfig;
133
133
  private readonly _getWorkspaceProvider?: () => WorkspaceProvider | undefined;
134
134
  private readonly _baseCwd: string;
@@ -200,7 +200,7 @@ export class Agent {
200
200
  return this.subagentSession?.messages ?? [];
201
201
  }
202
202
 
203
- constructor(init: AgentInit) {
203
+ constructor(init: SubagentInit) {
204
204
  // Identity
205
205
  this.id = init.id;
206
206
  this.type = init.type;
@@ -254,10 +254,10 @@ export class Agent {
254
254
  */
255
255
  async run(): Promise<void> {
256
256
  if (!this._createSubagentSession) {
257
- throw new Error("Agent not configured for execution — missing session factory");
257
+ throw new Error("Subagent not configured for execution — missing session factory");
258
258
  }
259
259
  if (!this._snapshot || !this._prompt) {
260
- throw new Error("Agent not configured for execution — missing snapshot or prompt");
260
+ throw new Error("Subagent not configured for execution — missing snapshot or prompt");
261
261
  }
262
262
 
263
263
  this.markRunning(Date.now());
@@ -301,7 +301,7 @@ export class Agent {
301
301
  }
302
302
 
303
303
  this.flushPendingSteers();
304
- this.attachObserver(subscribeAgentObserver(this.subagentSession, this, {
304
+ this.attachObserver(subscribeSubagentObserver(this.subagentSession, this, {
305
305
  onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
306
306
  }));
307
307
  this.observer?.onSessionCreated?.(this);
@@ -332,11 +332,11 @@ export class Agent {
332
332
  async resume(prompt: string, signal?: AbortSignal): Promise<void> {
333
333
  const subagentSession = this.subagentSession;
334
334
  if (!subagentSession) {
335
- throw new Error("Agent not configured for resume — missing session");
335
+ throw new Error("Subagent not configured for resume — missing session");
336
336
  }
337
337
 
338
338
  this.resetForResume(Date.now());
339
- this.attachObserver(subscribeAgentObserver(subagentSession, this, {
339
+ this.attachObserver(subscribeSubagentObserver(subagentSession, this, {
340
340
  onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
341
341
  }));
342
342
 
@@ -428,7 +428,7 @@ export class Agent {
428
428
  /**
429
429
  * Abort a running agent: fire AbortController and transition to stopped.
430
430
  * Returns false if the agent is not running.
431
- * Queue removal is handled by AgentManager via ConcurrencyQueue.dequeue().
431
+ * Queue removal is handled by SubagentManager via ConcurrencyQueue.dequeue().
432
432
  */
433
433
  abort(): boolean {
434
434
  if (this._status !== "running") return false;
@@ -497,7 +497,7 @@ export class Agent {
497
497
 
498
498
  let finalResult = result.responseText;
499
499
  if (this._workspace) {
500
- const finalStatus: AgentStatus = result.aborted
500
+ const finalStatus: SubagentStatus = result.aborted
501
501
  ? "aborted"
502
502
  : result.steered
503
503
  ? "steered"
@@ -2,7 +2,7 @@
2
2
  * turn-limits.ts — Pure turn-limit normalization for subagent execution.
3
3
  *
4
4
  * Extracted from agent-runner.ts (issue #265) so the turn-counting policy has a
5
- * focused home independent of session assembly. Consumed by the Agent tool's
5
+ * focused home independent of session assembly. Consumed by the subagent tool's
6
6
  * spawn-config resolution and by the turn loop in SubagentSession.
7
7
  */
8
8