@gotgenes/pi-subagents 6.18.2 → 6.18.4

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,32 @@ 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
+ ## [6.18.4](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.3...pi-subagents-v6.18.4) (2026-05-24)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * mark RunnerIO split done in architecture ([#167](https://github.com/gotgenes/pi-packages/issues/167)) ([824fd72](https://github.com/gotgenes/pi-packages/commit/824fd726361f62b6696dcc62de3b0bbb9cf45711))
14
+ * plan narrow RunnerIO into EnvironmentIO + SessionFactoryIO ([#167](https://github.com/gotgenes/pi-packages/issues/167)) ([8110fec](https://github.com/gotgenes/pi-packages/commit/8110fec44dfaf08bd93d9cbc59ad04c6cba62a84))
15
+ * **retro:** add planning stage notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([1aceff7](https://github.com/gotgenes/pi-packages/commit/1aceff77c8177093c60b90b87a3f991cb0186602))
16
+ * **retro:** add retro notes for issue [#180](https://github.com/gotgenes/pi-packages/issues/180) ([1fcd0ac](https://github.com/gotgenes/pi-packages/commit/1fcd0ace6fd7f5ec90a8d44423b276eb351875af))
17
+ * **retro:** add TDD stage notes for issue [#167](https://github.com/gotgenes/pi-packages/issues/167) ([870c767](https://github.com/gotgenes/pi-packages/commit/870c7670fdab831d408232c126312d0b5010d6f4))
18
+
19
+ ## [6.18.3](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.2...pi-subagents-v6.18.3) (2026-05-24)
20
+
21
+
22
+ ### Performance Improvements
23
+
24
+ * reorder append-mode prompt for KV cache reuse ([#180](https://github.com/gotgenes/pi-packages/issues/180)) ([5f688bd](https://github.com/gotgenes/pi-packages/commit/5f688bd1d008e20987d28626c5f5d0df0f66b854))
25
+
26
+
27
+ ### Documentation
28
+
29
+ * plan reorder append-mode prompt for KV cache reuse ([#180](https://github.com/gotgenes/pi-packages/issues/180)) ([bb0ddec](https://github.com/gotgenes/pi-packages/commit/bb0ddec8a7beb37baace5698e4fa4d09e61497d6))
30
+ * **retro:** add planning stage notes for issue [#180](https://github.com/gotgenes/pi-packages/issues/180) ([3413158](https://github.com/gotgenes/pi-packages/commit/341315898baa09652df18731ad318c89861ec62c))
31
+ * **retro:** add retro notes for issue [#166](https://github.com/gotgenes/pi-packages/issues/166) ([fae30ce](https://github.com/gotgenes/pi-packages/commit/fae30cec3dd99bbac490a2764a8340aa12fc171c))
32
+ * **retro:** add TDD stage notes for issue [#180](https://github.com/gotgenes/pi-packages/issues/180) ([1560f2d](https://github.com/gotgenes/pi-packages/commit/1560f2d6f7029cbbe0cc7b1efe1aba2a243e8357))
33
+
8
34
  ## [6.18.2](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.18.1...pi-subagents-v6.18.2) (2026-05-24)
9
35
 
10
36
 
@@ -500,20 +500,20 @@ These are fire-and-forget broadcast events — no request IDs, no reply channels
500
500
  These interfaces carry hidden dependencies that obscure true coupling.
501
501
  Bags with 10+ fields are the highest priority for decomposition.
502
502
 
503
- | Interface | Fields | Consumers | Severity |
504
- | --------------------------- | ---------------------------------- | ------------------------------------------------- | -------- |
505
- | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
506
- | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
507
- | `RunOptions` | 12 | agent-runner | High |
508
- | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
509
- | `NotificationDetails` | 10 | notification | Medium |
510
- | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
511
- | `RunnerIO` | 9 methods | agent-runner | Medium |
512
- | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
513
- | `AgentToolDeps` | 8 | agent-tool | Low |
514
- | `AgentMenuDeps` | 8 | agent-menu | Low |
515
- | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
516
- | `AgentRecordInit` | 8 | agent-record | Low |
503
+ | Interface | Fields | Consumers | Severity |
504
+ | --------------------------- | ------------------------------------------------------ | ------------------------------------------------- | -------- |
505
+ | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
506
+ | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
507
+ | `RunOptions` | 12 | agent-runner | High |
508
+ | `SessionConfig` | 11 | agent-runner (output of assembler) | High |
509
+ | `NotificationDetails` | 10 | notification | Medium |
510
+ | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Medium |
511
+ | `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
512
+ | `CreateSessionOptions` | 9 | agent-runner (SDK bridge) | Medium |
513
+ | `AgentToolDeps` | 8 | agent-tool | Low |
514
+ | `AgentMenuDeps` | 8 | agent-menu | Low |
515
+ | `ConversationViewerOptions` | 8 | conversation-viewer | Low |
516
+ | `AgentRecordInit` | 8 | agent-record | Low |
517
517
 
518
518
  ### Complexity hotspots
519
519
 
@@ -681,10 +681,11 @@ Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
681
681
  Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessionInfo`.
682
682
  `AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
683
683
 
684
- ### Step 4: Narrow RunnerIO ([#167][167])
684
+ ### Step 4: Narrow RunnerIO ([#167][167]) ✓ Done
685
685
 
686
- Split into `EnvironmentIO` and `SessionFactoryIO`.
687
- Each half can be tested independently.
686
+ `RunnerIO` split into `EnvironmentIO` (3 methods: environment discovery) and `SessionFactoryIO` (5 methods + `assemblerIO`: SDK object creation).
687
+ `RunnerIO` kept as a backward-compatible type alias for the intersection.
688
+ All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
688
689
 
689
690
  ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168])
690
691
 
@@ -0,0 +1,150 @@
1
+ ---
2
+ issue: 167
3
+ issue_title: "refactor(pi-subagents): narrow RunnerIO (9 methods → 2 focused interfaces)"
4
+ ---
5
+
6
+ # Narrow RunnerIO into EnvironmentIO + SessionFactoryIO
7
+
8
+ ## Problem Statement
9
+
10
+ `RunnerIO` in `agent-runner.ts` bundles 8 members (7 methods + 1 sub-interface) into a single IO boundary.
11
+ The methods split naturally into two concerns — environment discovery vs. SDK object creation — but the current monolithic interface forces every consumer (and every test mock) to provide all members regardless of which subset they actually use.
12
+ This violates Interface Segregation (ISP) and inflates test factory helpers.
13
+
14
+ ## Goals
15
+
16
+ - Split `RunnerIO` into two focused interfaces: `EnvironmentIO` (3 methods) and `SessionFactoryIO` (5 methods + `assemblerIO`).
17
+ - Keep `RunnerIO` as a type alias (`EnvironmentIO & SessionFactoryIO`) so the change is fully backward-compatible at the type level.
18
+ - Update both test `createRunnerIO()` factories to use the new sub-interfaces in their comments/structure (no behavioral test changes needed — factories already return plain objects).
19
+ - Zero runtime behavior change.
20
+
21
+ ## Non-Goals
22
+
23
+ - Splitting the `runAgent()` function itself — that's a separate concern.
24
+ - Changing how `createAgentRunner()` accepts its IO parameter — it keeps taking `RunnerIO` (the intersection).
25
+ - Refactoring `index.ts` wiring — the construction site already builds a plain object; it will continue to satisfy `RunnerIO`.
26
+ - Extracting `AssemblerIO` further — it already has its own interface in `session-config.ts`.
27
+
28
+ ## Background
29
+
30
+ `RunnerIO` was introduced in issue #133 to decouple `agent-runner.ts` from direct Pi SDK imports.
31
+ It succeeded at making the runner testable via plain stubs, but bundled all IO into one wide interface.
32
+ Issue #164 (closed) reorganized source into domain directories; the current file path is `src/lifecycle/agent-runner.ts`.
33
+
34
+ The 8 members group into two cohesive clusters:
35
+
36
+ | Cluster | Members | Responsibility |
37
+ | --------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
38
+ | Environment discovery | `detectEnv`, `getAgentDir`, `deriveSessionDir` | Discover runtime environment, resolve directories |
39
+ | Session factory | `createResourceLoader`, `createSessionManager`, `createSettingsManager`, `createSession`, `assemblerIO` | Create SDK objects for a child session |
40
+
41
+ In `runAgent()`, the environment methods are called first (lines ~265–275), then the factory methods build SDK objects (lines ~280–320).
42
+ The two groups have no cross-dependencies within `runAgent()`.
43
+
44
+ ## Design Overview
45
+
46
+ ### New interfaces
47
+
48
+ ```typescript
49
+ /** Environment discovery — detect runtime context and resolve directories. */
50
+ export interface EnvironmentIO {
51
+ detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
52
+ getAgentDir: () => string;
53
+ deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
54
+ }
55
+
56
+ /** Session factory — create SDK objects for a child agent session. */
57
+ export interface SessionFactoryIO {
58
+ createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
59
+ createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
60
+ createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
61
+ createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
62
+ assemblerIO: AssemblerIO;
63
+ }
64
+
65
+ /**
66
+ * IO boundary injected into runAgent().
67
+ * Backward-compatible intersection of the two focused interfaces.
68
+ */
69
+ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
70
+ ```
71
+
72
+ ### Backward compatibility
73
+
74
+ - `RunnerIO` becomes a type alias for the intersection.
75
+ Any code that imports `RunnerIO` continues to compile unchanged.
76
+ - `index.ts` builds a plain object literal that satisfies `RunnerIO` — no change needed.
77
+ - Test factories return unannotated objects that are structurally compatible — no change needed for compilation, but comments can be updated to reference the sub-interfaces.
78
+
79
+ ### Consumer call-site verification
80
+
81
+ The call site in `createAgentRunner()` (3 lines):
82
+
83
+ ```typescript
84
+ export function createAgentRunner(io: RunnerIO): AgentRunner {
85
+ return {
86
+ run: (snapshot, type, prompt, options) => runAgent(snapshot, type, prompt, options, io),
87
+ resume: resumeAgent,
88
+ };
89
+ }
90
+ ```
91
+
92
+ This passes the full `io` to `runAgent()`, which continues to accept `RunnerIO`.
93
+ No Tell-Don't-Ask or LoD violations introduced.
94
+
95
+ ## Module-Level Changes
96
+
97
+ ### `src/lifecycle/agent-runner.ts`
98
+
99
+ 1. Add `EnvironmentIO` interface (3 members) before the current `RunnerIO` definition.
100
+ 2. Add `SessionFactoryIO` interface (5 members) after `EnvironmentIO`.
101
+ 3. Change `RunnerIO` from an `interface` to a `type` alias: `type RunnerIO = EnvironmentIO & SessionFactoryIO`.
102
+ 4. Export the two new interfaces alongside `RunnerIO`.
103
+ 5. Move existing JSDoc from `RunnerIO` members to the sub-interfaces.
104
+ 6. No changes to function signatures — `runAgent()` and `createAgentRunner()` keep accepting `RunnerIO`.
105
+
106
+ ### `src/index.ts`
107
+
108
+ No changes.
109
+ The construction site builds a plain object satisfying all 8 members — TypeScript's structural typing ensures it satisfies `EnvironmentIO & SessionFactoryIO` without annotation changes.
110
+
111
+ ### Test files
112
+
113
+ No behavioral changes.
114
+ The `createRunnerIO()` factories in both test files return unannotated plain objects that structurally satisfy the intersection.
115
+ Comments referencing `RunnerIO` can be updated to mention the sub-interfaces for documentation clarity.
116
+
117
+ Files affected:
118
+
119
+ - `test/lifecycle/agent-runner.test.ts` — update comment (line ~23–27).
120
+ - `test/lifecycle/agent-runner-extension-tools.test.ts` — update comment (line ~46).
121
+
122
+ ## Test Impact Analysis
123
+
124
+ 1. **New unit tests enabled:** The split enables future tests that inject only `EnvironmentIO` or only `SessionFactoryIO` — useful when testing environment-only or factory-only code paths in future extractions.
125
+ No new tests are needed in this issue because `runAgent()` still consumes the full intersection.
126
+ 2. **Redundant tests:** None — existing tests already test through `runAgent()` which uses all members.
127
+ 3. **Tests that stay as-is:** All existing tests in both `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` remain valid; the factories produce objects compatible with the new type alias.
128
+
129
+ ## TDD Order
130
+
131
+ 1. **Red → Green: export `EnvironmentIO` and `SessionFactoryIO`, convert `RunnerIO` to type alias.**
132
+ Test surface: existing `agent-runner.test.ts` and `agent-runner-extension-tools.test.ts` suites (must still pass).
133
+ Run `pnpm run check` to verify type compatibility.
134
+ Commit: `refactor: split RunnerIO into EnvironmentIO and SessionFactoryIO (#167)`
135
+
136
+ 2. **Update test comments to reference sub-interfaces.**
137
+ Test surface: no behavioral test changes — comment-only updates.
138
+ Commit: `refactor: update test comments for RunnerIO sub-interfaces (#167)`
139
+
140
+ ## Risks and Mitigations
141
+
142
+ | Risk | Mitigation |
143
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------- |
144
+ | External consumers importing `RunnerIO` break | `RunnerIO` remains exported as a type alias for the intersection — fully backward-compatible |
145
+ | Test factories need updating | Factories return unannotated objects — structural typing handles the new type alias without changes |
146
+ | Future extractions assume wrong interface | Each sub-interface has a clear JSDoc explaining its scope |
147
+
148
+ ## Open Questions
149
+
150
+ None — the split follows the natural cohesion boundary identified in the issue body.
@@ -0,0 +1,100 @@
1
+ ---
2
+ issue: 180
3
+ issue_title: "perf(pi-subagents): reorder append-mode system prompt to enable KV cache reuse"
4
+ ---
5
+
6
+ # Reorder append-mode system prompt for KV cache reuse
7
+
8
+ ## Problem Statement
9
+
10
+ In append mode, `buildAgentPrompt()` places varying, agent-specific content (the `<active_agent>` tag and env block) *before* the large shared inherited system prompt (~8k tokens).
11
+ LLM KV caching works on prefixes — the cache is only reusable when the beginning of the prompt matches.
12
+ Every subagent spawn reprocesses the entire inherited prompt from scratch because the prefix differs per agent.
13
+
14
+ ## Goals
15
+
16
+ - Reorder the append-mode system prompt so shared/stable content comes first and varying content follows.
17
+ - Preserve the `<active_agent>` tag at any position — pi-permission-system's `ACTIVE_AGENT_TAG_REGEX.exec()` searches the full string.
18
+ - Keep replace-mode prompt ordering unchanged (it has no shared inherited content to cache).
19
+ - Update tests and JSDoc to reflect the new ordering.
20
+
21
+ ## Non-Goals
22
+
23
+ - Changing replace-mode prompt assembly (no shared prefix to cache).
24
+ - Modifying pi-permission-system (its regex parsing is already position-independent).
25
+ - Changing the *content* of any prompt section — only reordering.
26
+
27
+ ## Background
28
+
29
+ `buildAgentPrompt()` in `src/session/prompts.ts` assembles the system prompt for subagents.
30
+ In append mode, the current ordering is:
31
+
32
+ ```text
33
+ 1. <active_agent name="${name}"/> ← VARIES per agent
34
+ 2. # Environment ... ← VARIES per runtime
35
+ 3. <inherited_system_prompt> ← SHARED (~8k tokens)
36
+ 4. <sub_agent_context> ← SHARED (static)
37
+ 5. <agent_instructions> ← VARIES per agent
38
+ 6. memory / skills ← VARIES
39
+ ```
40
+
41
+ pi-permission-system's `getActiveAgentNameFromSystemPrompt()` in `src/active-agent.ts` uses `ACTIVE_AGENT_TAG_REGEX.exec(systemPrompt)` — a regex search that finds the tag at any position, confirmed by reading the source.
42
+
43
+ ## Design Overview
44
+
45
+ Move shared/stable sections to the front of the append-mode prompt:
46
+
47
+ ```text
48
+ 1. <inherited_system_prompt> ← SHARED (~8k tokens, NOW CACHEABLE)
49
+ 2. <sub_agent_context> ← SHARED (static)
50
+ 3. <active_agent name="${name}"/> ← VARIES (after cached prefix)
51
+ 4. # Environment ... ← VARIES
52
+ 5. <agent_instructions> ← VARIES per agent
53
+ 6. memory / skills ← VARIES
54
+ ```
55
+
56
+ This is a pure reordering — no content changes.
57
+ The `<active_agent>` tag remains in the system prompt for pi-permission-system to find via regex.
58
+ The env block and agent instructions still provide context to the model; their position relative to the inherited prompt is not semantically significant.
59
+
60
+ ## Module-Level Changes
61
+
62
+ ### `src/session/prompts.ts`
63
+
64
+ 1. Reorder the return statement in the `config.promptMode === "append"` branch to place `identity` (wrapped in `<inherited_system_prompt>`) and `bridge` before `activeAgentTag` and `envBlock`.
65
+ 2. Update the JSDoc comment on `buildAgentPrompt()` — replace "Both modes prepend" language with a description that notes the tag is included (not necessarily prepended) in append mode.
66
+
67
+ ### `test/session/prompts.test.ts`
68
+
69
+ 1. Update "prepends `<active_agent>` tag in append mode" — change from asserting `prompt.startsWith()` to asserting the tag appears *after* the inherited system prompt.
70
+ 2. Update "active_agent tag appears before envBlock in both modes" — the append-mode assertions change: the tag should still appear before the env block, but no longer at index 0.
71
+ The replace-mode assertions remain unchanged (`tagIdx === 0`).
72
+
73
+ ## Test Impact Analysis
74
+
75
+ - Two existing tests assert `<active_agent>` is prepended (index 0) in append mode — these must change to assert the new ordering.
76
+ - All other prompt tests use `toContain()` and are position-independent — they pass without changes.
77
+ - No new test files or test surfaces are needed; the existing test suite covers the reordering adequately once the positional assertions are updated.
78
+
79
+ ## TDD Order
80
+
81
+ 1. **Red: update positional assertions for append mode.**
82
+ Change the two append-mode tests to assert the new ordering: `<inherited_system_prompt>` appears before `<active_agent>`, and the tag appears before the env block but not at index 0.
83
+ Commit: `test: assert cache-friendly prompt ordering in append mode (#180)`
84
+
85
+ 2. **Green: reorder the append-mode return statement.**
86
+ Move `identity` + `<inherited_system_prompt>` wrapper and `bridge` before `activeAgentTag` + `envBlock` in the return expression.
87
+ Update the JSDoc on `buildAgentPrompt()`.
88
+ Commit: `perf: reorder append-mode prompt for KV cache reuse (#180)`
89
+
90
+ ## Risks and Mitigations
91
+
92
+ | Risk | Mitigation |
93
+ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | pi-permission-system depends on tag position | Confirmed `ACTIVE_AGENT_TAG_REGEX.exec()` searches the full string — position-independent. |
95
+ | Model behavior changes with reordered prompt | The same content is present; only ordering changes. The inherited system prompt as the "base" followed by specialization is arguably more natural. |
96
+ | Replace mode accidentally affected | Replace mode has its own code path and is not touched by this change. |
97
+
98
+ ## Open Questions
99
+
100
+ None — the design is straightforward and confirmed safe by code inspection.
@@ -37,3 +37,30 @@ Type check and lint both clean after all steps.
37
37
  - `RunOptions` in `agent-runner.ts` needed a new import of `ParentSessionInfo` from `agent-manager.ts`; no circular dependency since `agent-runner.ts` already imports from `agent-manager.ts`.
38
38
  - `agent-tool.ts` still imports `AgentSpawnConfig` (needed by `AgentToolManager` interface) — the new `ParentSessionInfo` import was added alongside it.
39
39
  - All 5 commits are clean `refactor:` messages; architecture doc update is a separate `docs:` commit.
40
+
41
+ ## Stage: Final Retrospective (2026-05-24T18:00:00Z)
42
+
43
+ ### Session summary
44
+
45
+ Planning, TDD implementation (5 steps), shipping, and CI verification all completed in a single session.
46
+ Released as `pi-subagents-v6.18.2`.
47
+ Zero rework — every TDD step went green on first attempt.
48
+
49
+ ### Observations
50
+
51
+ #### What went well
52
+
53
+ - The planning session's identification of the deep-merge trap in `background-spawner.test.ts`'s `makeParams` factory paid off — the TDD implementation handled it without friction because the risk was anticipated.
54
+ - The 5-step inside-out TDD order (manager → runner → background → foreground → agent-tool) was the right sequence.
55
+ Each step only introduced type errors in files that subsequent steps would fix, with no circular breakage.
56
+ - Clean mechanical execution — 805 tests before and after, zero rework commits, lint and type-check clean throughout.
57
+
58
+ #### What caused friction (agent side)
59
+
60
+ - `missing-context` — The plan repeated the issue body's stale "13 fields" count without verifying against the actual `AgentSpawnConfig` interface (which had 15 fields after `bypassQueue` was added in a prior issue).
61
+ The plan also inconsistently claimed the extraction would reduce the count to both "11" and "10" in different places.
62
+ Impact: required corrections in the architecture doc update, but no implementation rework.
63
+
64
+ #### What caused friction (user side)
65
+
66
+ - None observed — the user let the session run autonomously through all stages without intervention.
@@ -0,0 +1,35 @@
1
+ ---
2
+ issue: 167
3
+ issue_title: "refactor(pi-subagents): narrow RunnerIO (9 methods → 2 focused interfaces)"
4
+ ---
5
+
6
+ # Retro: #167 — narrow RunnerIO (9 methods → 2 focused interfaces)
7
+
8
+ ## Stage: Planning (2026-05-24T20:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a plan to split the `RunnerIO` interface in `agent-runner.ts` into two focused sub-interfaces (`EnvironmentIO` and `SessionFactoryIO`) while keeping `RunnerIO` as a backward-compatible type alias for their intersection.
13
+ The change is a pure refactoring with zero runtime behavior change.
14
+
15
+ ### Observations
16
+
17
+ - The split is mechanical and low-risk: `RunnerIO` becomes `type RunnerIO = EnvironmentIO & SessionFactoryIO`, and all existing consumers (production code and test factories) continue to compile via structural typing.
18
+ - Dependency #164 (reorganize into domain directories) is already closed, so file paths are current.
19
+ - The two test `createRunnerIO()` factories are unannotated (intentionally, per testing skill guidelines), so they don't need type-level updates — only comment updates for documentation.
20
+ - This is a two-commit TDD plan, suitable for `/build-plan` rather than full TDD cycles since no new tests are required.
21
+
22
+ ## Stage: Implementation — TDD (2026-05-24T20:45:00Z)
23
+
24
+ ### Session summary
25
+
26
+ Completed both TDD steps in full.
27
+ Step 1 added `EnvironmentIO` and `SessionFactoryIO` interfaces and converted `RunnerIO` to a type alias in `agent-runner.ts`; step 2 updated comments in both test factories.
28
+ Test count held steady at 805/805 (50 files) — no behavioral changes.
29
+
30
+ ### Observations
31
+
32
+ - The type check (`pnpm run check`) passed immediately after the interface split — structural typing meant zero call-site changes in `index.ts` or test factories.
33
+ - `RunnerIO` JSDoc was split: `EnvironmentIO` got the environment-discovery description, `SessionFactoryIO` got the original "decouples from Pi SDK imports" description, and `RunnerIO` itself got a short backward-compatibility note.
34
+ - Architecture doc updated: wide-interface table row and Step 4 roadmap entry both marked done.
35
+ - No deviations from the plan.
@@ -0,0 +1,62 @@
1
+ ---
2
+ issue: 180
3
+ issue_title: "perf(pi-subagents): reorder append-mode system prompt to enable KV cache reuse"
4
+ ---
5
+
6
+ # Retro: #180 — Reorder append-mode system prompt for KV cache reuse
7
+
8
+ ## Stage: Planning (2026-05-24T20:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a plan to reorder the append-mode system prompt in `buildAgentPrompt()` so the shared inherited content (~8k tokens) comes before the varying `<active_agent>` tag and env block, enabling LLM KV cache prefix reuse across subagent invocations.
13
+
14
+ ### Observations
15
+
16
+ - Confirmed pi-permission-system's `ACTIVE_AGENT_TAG_REGEX.exec()` is position-independent — no changes needed in that package despite the `pkg:pi-permission-system` label on the issue.
17
+ - Only two tests assert positional ordering in append mode (`startsWith` and `tagIdx === 0`); all other prompt tests use `toContain()` and are unaffected.
18
+ - Replace mode is a separate code path and is not touched.
19
+ - The TDD cycle is minimal: one red step (update two positional assertions), one green step (reorder the return statement + update JSDoc).
20
+
21
+ ## Stage: Implementation — TDD (2026-05-24T20:15:00Z)
22
+
23
+ ### Session summary
24
+
25
+ Completed both TDD cycles in `buildAgentPrompt()` in `src/session/prompts.ts`.
26
+ Two positional assertions in `test/session/prompts.test.ts` were updated to expect the new ordering (red), then the append-mode return statement was reordered and the JSDoc updated (green).
27
+ Test count unchanged at 805 across 50 files.
28
+
29
+ ### Observations
30
+
31
+ - The JSDoc bullet for append mode also described the old ordering ("env header + parent system prompt + ...") and was corrected as part of the green step.
32
+ - The `<active_agent>` tag is followed by a `\n\n`, so when it moves after `<sub_agent_context>`, a `\n\n` separator between the bridge and the tag was needed to maintain clean section boundaries.
33
+ - No deviations from the plan; both steps were exactly as described.
34
+
35
+ ## Stage: Final Retrospective (2026-05-24T21:00:00Z)
36
+
37
+ ### Session summary
38
+
39
+ Issue #180 went from external community observation through release (`pi-subagents-v6.18.3`) in a single continuous session.
40
+ The plan predicted exactly two TDD steps; both executed without deviation or rework.
41
+
42
+ ### Observations
43
+
44
+ #### What went well
45
+
46
+ - End-to-end lifecycle in one session: external comment → issue → plan → TDD → ship → release.
47
+ No corrections, no scope drift, no rework across any stage.
48
+ - The plan's test impact analysis was accurate — only two positional assertions needed updating; all `toContain()` tests passed untouched.
49
+ - Confirming pi-permission-system's `ACTIVE_AGENT_TAG_REGEX.exec()` is position-independent during planning eliminated the second `pkg:*` label's scope entirely, keeping the change to a single file.
50
+
51
+ #### What caused friction (agent side)
52
+
53
+ - `wrong-abstraction` — Launched an Explore agent (75.9s, 18 tool uses) to map the prompt assembly flow when the `package-pi-subagents` skill already listed the file layout and `prompts.ts` is 107 lines.
54
+ Direct `read` + `grep` achieved the same confirmation in ~3 seconds during the planning phase.
55
+ Impact: added ~75 seconds of latency but no rework.
56
+ - `missing-context` — The plan listed "Update the JSDoc comment" but missed that the mode-description bullet ("env header + parent system prompt + ...") also encoded the old ordering.
57
+ Caught during the green step and fixed in the same commit.
58
+ Impact: added friction but no rework.
59
+
60
+ #### What caused friction (user side)
61
+
62
+ - Nothing notable — the user's prompts were well-scoped and the issue description was unambiguous.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "6.18.2",
3
+ "version": "6.18.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"
@@ -91,7 +91,7 @@ export interface SessionManagerLike {
91
91
  getSessionFile(): string | undefined;
92
92
  }
93
93
 
94
- /** Options passed to RunnerIO.createResourceLoader. */
94
+ /** Options passed to EnvironmentIO/SessionFactoryIO methods. */
95
95
  export interface ResourceLoaderOptions {
96
96
  cwd: string;
97
97
  agentDir: string;
@@ -105,7 +105,7 @@ export interface ResourceLoaderOptions {
105
105
  appendSystemPromptOverride?: (base: string[]) => string[];
106
106
  }
107
107
 
108
- /** Options passed to RunnerIO.createSession. */
108
+ /** Options passed to SessionFactoryIO.createSession. */
109
109
  export interface CreateSessionOptions {
110
110
  cwd: string;
111
111
  agentDir: string;
@@ -119,22 +119,40 @@ export interface CreateSessionOptions {
119
119
  }
120
120
 
121
121
  /**
122
- * IO boundary injected into runAgent().
122
+ * Environment discovery detect runtime context and resolve directories.
123
123
  *
124
- * Decouples the runner from direct Pi SDK imports and sibling-module IO,
125
- * making it testable via plain stub objects without vi.mock().
124
+ * Decouples the runner from direct process/SDK reads so each can be stubbed
125
+ * independently in tests.
126
126
  */
127
- export interface RunnerIO {
127
+ export interface EnvironmentIO {
128
128
  detectEnv: (exec: ShellExec, cwd: string) => Promise<EnvInfo>;
129
129
  getAgentDir: () => string;
130
- createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
131
130
  deriveSessionDir: (parentSessionFile: string | undefined, effectiveCwd: string) => string;
131
+ }
132
+
133
+ /**
134
+ * Session factory — create SDK objects for a child agent session.
135
+ *
136
+ * Decouples the runner from direct Pi SDK imports and sibling-module IO,
137
+ * making it testable via plain stub objects without vi.mock().
138
+ */
139
+ export interface SessionFactoryIO {
140
+ createResourceLoader: (opts: ResourceLoaderOptions) => ResourceLoaderLike;
132
141
  createSessionManager: (cwd: string, sessionDir: string) => SessionManagerLike;
133
142
  createSettingsManager: (cwd: string, agentDir: string) => SettingsManager;
134
143
  createSession: (opts: CreateSessionOptions) => Promise<{ session: AgentSession }>;
135
144
  assemblerIO: AssemblerIO;
136
145
  }
137
146
 
147
+ /**
148
+ * IO boundary injected into runAgent().
149
+ *
150
+ * Backward-compatible intersection of EnvironmentIO and SessionFactoryIO.
151
+ * Callers that previously constructed a RunnerIO object continue to satisfy
152
+ * both sub-interfaces via TypeScript's structural typing.
153
+ */
154
+ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
155
+
138
156
  // ── Public interfaces ─────────────────────────────────────────────────────────
139
157
 
140
158
  export interface RunOptions {
@@ -17,12 +17,14 @@ export interface PromptExtras {
17
17
  * Build the system prompt for an agent from its config.
18
18
  *
19
19
  * - "replace" mode: env header + config.systemPrompt (full control, no parent identity)
20
- * - "append" mode: env header + parent system prompt + sub-agent context + config.systemPrompt
20
+ * - "append" mode: parent system prompt + sub-agent context + env header + config.systemPrompt
21
21
  * - "append" with empty systemPrompt: pure parent clone
22
22
  *
23
- * Both modes prepend an `<active_agent name="${config.name}"/>` tag so downstream
23
+ * Both modes include an `<active_agent name="${config.name}"/>` tag so downstream
24
24
  * extensions (e.g. `@gotgenes/pi-permission-system`) can resolve per-agent policy
25
25
  * inside the child session by parsing the system prompt.
26
+ * In replace mode the tag is prepended; in append mode it follows the shared
27
+ * inherited content so the stable prefix is cacheable by the LLM's KV cache.
26
28
  *
27
29
  * @param parentSystemPrompt The parent agent's effective system prompt (for append mode).
28
30
  * @param extras Optional extra sections to inject (memory, preloaded skills).
@@ -76,13 +78,17 @@ You are operating as a sub-agent invoked to handle a specific task.
76
78
  ? `\n\n<agent_instructions>\n${config.systemPrompt}\n</agent_instructions>`
77
79
  : "";
78
80
 
81
+ // Place shared/stable content first so the LLM's KV cache can reuse the
82
+ // inherited prefix across all subagent invocations. The <active_agent> tag
83
+ // and env block vary per call and are placed after the cacheable prefix.
79
84
  return (
80
- activeAgentTag +
81
- envBlock +
82
- "\n\n<inherited_system_prompt>\n" +
85
+ "<inherited_system_prompt>\n" +
83
86
  identity +
84
87
  "\n</inherited_system_prompt>\n\n" +
85
88
  bridge +
89
+ "\n\n" +
90
+ activeAgentTag +
91
+ envBlock +
86
92
  customSection +
87
93
  extrasSuffix
88
94
  );