@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.
- package/CHANGELOG.md +14 -0
- package/README.md +21 -10
- package/dist/public.d.ts +34 -35
- package/docs/architecture/architecture.md +58 -148
- package/docs/architecture/history/phase-16-invert-dependencies.md +144 -0
- package/docs/decisions/0003-publish-bundled-type-declarations.md +3 -1
- package/docs/plans/0051-update-adr-0001-hard-fork.md +8 -6
- package/docs/plans/0257-extract-child-session-factory.md +3 -1
- package/docs/plans/0262-add-workspace-provider-seam.md +41 -39
- package/docs/plans/0264-remove-extension-lifecycle-control.md +100 -98
- package/docs/plans/0265-born-complete-subagent-session.md +5 -2
- package/docs/plans/0270-type-consumable-public-surface.md +3 -1
- package/docs/plans/0280-rename-agent-to-subagent.md +197 -0
- package/docs/retro/0051-update-adr-0001-hard-fork.md +4 -2
- package/docs/retro/0257-extract-child-session-factory.md +3 -1
- package/docs/retro/0262-add-workspace-provider-seam.md +3 -1
- package/docs/retro/0264-remove-extension-lifecycle-control.md +3 -1
- package/docs/retro/0270-type-consumable-public-surface.md +4 -2
- package/docs/retro/0277-encapsulate-agent-session.md +41 -0
- package/docs/retro/0280-rename-agent-to-subagent.md +96 -0
- package/package.json +1 -1
- package/src/index.ts +9 -9
- package/src/lifecycle/create-subagent-session.ts +1 -1
- package/src/lifecycle/{agent-manager.ts → subagent-manager.ts} +27 -27
- package/src/lifecycle/subagent-session.ts +2 -2
- package/src/lifecycle/{agent.ts → subagent.ts} +28 -28
- package/src/lifecycle/turn-limits.ts +1 -1
- package/src/lifecycle/workspace.ts +2 -2
- package/src/observation/notification.ts +9 -9
- package/src/observation/record-observer.ts +9 -9
- package/src/runtime.ts +1 -1
- package/src/service/service-adapter.ts +10 -10
- package/src/service/service.ts +5 -9
- package/src/tools/agent-tool.ts +5 -5
- package/src/tools/background-spawner.ts +3 -3
- package/src/tools/foreground-runner.ts +5 -5
- package/src/tools/get-result-tool.ts +2 -2
- package/src/tools/steer-tool.ts +2 -2
- package/src/types.ts +1 -1
- package/src/ui/agent-creation-wizard.ts +2 -2
- package/src/ui/agent-menu.ts +5 -5
- package/src/ui/agent-widget.ts +2 -2
- 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
|
|
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
|
|
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
|
|
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
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
|
|
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:
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
* `
|
|
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
|
-
*
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
28
|
+
onSubagentCreated(record: Subagent): void;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export interface
|
|
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?:
|
|
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-
|
|
60
|
-
observer?:
|
|
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
|
|
66
|
-
private agents = new Map<string,
|
|
65
|
+
export class SubagentManager {
|
|
66
|
+
private agents = new Map<string, Subagent>();
|
|
67
67
|
private cleanupInterval: ReturnType<typeof setInterval>;
|
|
68
|
-
private readonly observer?:
|
|
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:
|
|
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):
|
|
109
|
+
private buildObserver(options: AgentSpawnConfig): SubagentLifecycleObserver {
|
|
110
110
|
return {
|
|
111
111
|
onStarted: (agent) => {
|
|
112
112
|
if (options.isBackground) this.queue.markStarted();
|
|
113
|
-
this.observer?.
|
|
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?.
|
|
121
|
+
try { this.observer?.onSubagentCompleted(agent); } catch (err) { debugLog("onSubagentCompleted observer", err); }
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
onCompacted: (agent, info) => {
|
|
125
|
-
this.observer?.
|
|
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
|
|
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?.
|
|
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<
|
|
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
|
|
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<
|
|
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):
|
|
210
|
+
getRecord(id: string): Subagent | undefined {
|
|
211
211
|
return this.agents.get(id);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
listAgents():
|
|
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:
|
|
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 `
|
|
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 `
|
|
10
|
+
* reaching through `subagentSession.session` from `Subagent` (Law of Demeter).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
12
|
-
*
|
|
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 {
|
|
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-
|
|
36
|
-
export interface
|
|
37
|
-
/** Fires when the
|
|
38
|
-
onStarted?(agent:
|
|
39
|
-
/** Fires once the session is created — the
|
|
40
|
-
onSessionCreated?(agent:
|
|
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:
|
|
42
|
+
onRunFinished?(agent: Subagent): void;
|
|
43
43
|
/** Fires on compaction events during the run. */
|
|
44
|
-
onCompacted?(agent:
|
|
44
|
+
onCompacted?(agent: Subagent, info: CompactionInfo): void;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export type
|
|
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
|
|
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?:
|
|
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?:
|
|
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
|
|
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:
|
|
100
|
-
get 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?:
|
|
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:
|
|
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("
|
|
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("
|
|
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(
|
|
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("
|
|
335
|
+
throw new Error("Subagent not configured for resume — missing session");
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
this.resetForResume(Date.now());
|
|
339
|
-
this.attachObserver(
|
|
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
|
|
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:
|
|
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
|
|
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
|
|