@gotgenes/pi-subagents 6.19.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,47 @@ 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
+ ## [7.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.0.0...pi-subagents-v7.1.0) (2026-05-24)
9
+
10
+
11
+ ### Features
12
+
13
+ * **pi-subagents:** define SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([a043d4d](https://github.com/gotgenes/pi-packages/commit/a043d4d970a8aacc084a125d9a07b860a7fb6e9b))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * **pi-subagents:** archive Phase 10, propose Phase 11 roadmap ([d474eef](https://github.com/gotgenes/pi-packages/commit/d474eef98c1a39757d933da291725ff126f1b8ac))
19
+ * **pi-subagents:** revise Phase 11 roadmap — layered class conversion ([35d1083](https://github.com/gotgenes/pi-packages/commit/35d1083445aece783e344c37fc949395e190f9e5))
20
+ * plan SessionContext narrow interface ([#192](https://github.com/gotgenes/pi-packages/issues/192)) ([95cb16e](https://github.com/gotgenes/pi-packages/commit/95cb16e46aa630ecc34f423aaaa4ff02845ed5b5))
21
+ * **retro:** add planning stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([31fd729](https://github.com/gotgenes/pi-packages/commit/31fd7290985b0ef1d240cd5afd5e0e0e7eec9131))
22
+ * **retro:** add retro notes for issue [#185](https://github.com/gotgenes/pi-packages/issues/185) ([66e49cf](https://github.com/gotgenes/pi-packages/commit/66e49cfb4129b9bba3b78e0850402bc61d99dda8))
23
+ * **retro:** add TDD stage notes for issue [#192](https://github.com/gotgenes/pi-packages/issues/192) ([6cf3f95](https://github.com/gotgenes/pi-packages/commit/6cf3f95f6c39f3f81c1d335348d5abd74d948ff3))
24
+
25
+ ## [7.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.19.1...pi-subagents-v7.0.0) (2026-05-24)
26
+
27
+
28
+ ### ⚠ BREAKING CHANGES
29
+
30
+ * `src/session/memory.ts` and all persistent agent memory functionality (MEMORY.md, agent-memory directories) are removed from pi-subagents. This is scope reduction — agent spawning, execution, and result retrieval remain.
31
+ * `MemoryScope` is no longer exported from the package. The `memory` field is removed from `AgentConfig`. Custom agent .md files with a `memory:` frontmatter key will have it silently ignored.
32
+ * The `memory` field in agent configuration no longer has any effect. Memory block injection, memory tool augmentation, and the `AssemblerIO.buildMemoryBlock` / `buildReadOnlyMemoryBlock` collaborators are removed from the session assembler.
33
+
34
+ ### Features
35
+
36
+ * delete memory module ([78ace55](https://github.com/gotgenes/pi-packages/commit/78ace558b1988bce8e002b28fe3152c5de708b84))
37
+ * remove memory from session assembly and config layers ([6ebeb91](https://github.com/gotgenes/pi-packages/commit/6ebeb91f28c56c1d21fc4e1a7b6bededa8eba025))
38
+ * remove MemoryScope type and memory config field ([d6e3bcb](https://github.com/gotgenes/pi-packages/commit/d6e3bcbb58902133a3704d89d88b548f8f2a4769))
39
+
40
+
41
+ ### Documentation
42
+
43
+ * plan remove persistent agent memory ([#185](https://github.com/gotgenes/pi-packages/issues/185)) ([0f6b3ad](https://github.com/gotgenes/pi-packages/commit/0f6b3adb6c35c3879c5d1e176e1c6d9da36a5cf1))
44
+ * **retro:** add planning stage notes for issue [#185](https://github.com/gotgenes/pi-packages/issues/185) ([58dcfbc](https://github.com/gotgenes/pi-packages/commit/58dcfbc3124ec7695048ad3df8fc6f397a883d1a))
45
+ * **retro:** add retro notes for issue [#188](https://github.com/gotgenes/pi-packages/issues/188) ([8eeaf6b](https://github.com/gotgenes/pi-packages/commit/8eeaf6b52f7b40a2126f3ffa3ca01a8e3b84f338))
46
+ * **retro:** add TDD stage notes for issue [#185](https://github.com/gotgenes/pi-packages/issues/185) ([8e75b18](https://github.com/gotgenes/pi-packages/commit/8e75b18b2c87b47f1a29df9d2c544a8ad2023f9f))
47
+ * update architecture after memory removal ([52716d5](https://github.com/gotgenes/pi-packages/commit/52716d5f89b729ce183d4a450d8539e6cabdbadc))
48
+
8
49
  ## [6.19.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v6.19.0...pi-subagents-v6.19.1) (2026-05-24)
9
50
 
10
51
 
@@ -42,7 +42,7 @@ flowchart TB
42
42
  SessionConfig["assembleSessionConfig\n(pure assembler)"]
43
43
  Prompts["prompts\n(system prompt)"]
44
44
  Context["context\n(parent history)"]
45
- Memory["memory\n(MEMORY.md)"]
45
+ SafeFs["safe-fs\n(symlink/name guards)"]
46
46
  SkillLoader["skill-loader\n(preload skills)"]
47
47
  Env["env\n(git/platform)"]
48
48
  ModelResolver["model-resolver\n(fuzzy match)"]
@@ -86,7 +86,8 @@ flowchart TB
86
86
  AgentManager --> AgentRunner
87
87
  AgentRunner --> SessionConfig
88
88
  SessionConfig --> AgentTypeRegistry
89
- SessionConfig --> Prompts & Memory & SkillLoader & Env
89
+ SessionConfig --> Prompts & SkillLoader & Env
90
+ SkillLoader --> SafeFs
90
91
  AgentTypeRegistry --> DefaultAgents & CustomAgents
91
92
  RecordObserver -.->|subscribes| AgentRunner
92
93
  UIObserver -.->|subscribes| AgentRunner
@@ -246,7 +247,7 @@ src/
246
247
  │ ├── prompts.ts system prompt building
247
248
  │ ├── content-items.ts shared message content parsing (tool-call names, assistant content)
248
249
  │ ├── context.ts parent conversation extraction
249
- │ ├── memory.ts persistent MEMORY.md per agent
250
+ │ ├── safe-fs.ts symlink rejection and safe file reads
250
251
  │ ├── skill-loader.ts skill preloading
251
252
  │ ├── env.ts git/platform detection
252
253
  │ ├── model-resolver.ts fuzzy model name resolution
@@ -313,16 +314,16 @@ The widget reads agent state by polling a shared `Map<string, AgentActivityTrack
313
314
 
314
315
  ```mermaid
315
316
  flowchart TD
316
- subgraph core["@gotgenes/pi-subagents (this package)"]
317
+ subgraph core["@gotgenes/pi-subagents"]
317
318
  direction TB
318
- exports["SubagentsService interface\npublish / getSubagentsService()\nSubagentRecord, SubagentStatus, LifetimeUsage\nSUBAGENT_EVENTS constants"]
319
- engine["Agent + get_subagent_result + steer_subagent tools\nAgentManager, agent-runner, agent-types\npublishSubagentsService() called at init"]
320
- ui_int["Internal UI: widget, viewer, /agents menu\n(candidate for extraction to pi-subagents-ui)"]
319
+ exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
320
+ engine["Tools: Agent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, agent-runner"]
321
+ ui_int["Internal UI: widget, viewer,<br/>/agents menu"]
321
322
  end
322
323
 
323
- core -- "Symbol.for() on globalThis" --> sched["scheduling extension\n(hypothetical)"]
324
- core -- "Symbol.for() on globalThis" --> subui["pi-subagents-ui\n(deferred)"]
325
- core -- "Symbol.for() on globalThis" --> future["any future extension"]
324
+ core -- "Symbol.for on globalThis" --> sched["scheduling extension<br/>(hypothetical)"]
325
+ core -- "Symbol.for on globalThis" --> subui["pi-subagents-ui<br/>(deferred)"]
326
+ core -- "Symbol.for on globalThis" --> future["any future extension"]
326
327
  ```
327
328
 
328
329
  Consumers call `getSubagentsService()?.spawn(...)` at runtime.
@@ -338,7 +339,7 @@ They declare this package as an optional peer dependency and use dynamic import
338
339
  - `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
339
340
  - `record-observer` — session-event observer that updates record statistics without callback threading.
340
341
  - Agent type registry — default agents, custom `.md` file loading.
341
- - Prompt assembly, context extraction, memory, skills, environment.
342
+ - Prompt assembly, context extraction, skills, environment.
342
343
  - Worktree isolation.
343
344
  - Token usage tracking.
344
345
  - Session directory derivation and persisted `SessionManager` for subagent transcripts.
@@ -598,121 +599,218 @@ export type RunnerIO = EnvironmentIO & SessionFactoryIO;
598
599
  `RunnerIO` is kept as a type alias for the intersection.
599
600
  All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
600
601
 
601
- ## Improvement roadmap (Phase 10)
602
+ ## Improvement roadmap (Phase 11)
602
603
 
603
- Phase 10 addresses the structural gaps identified in this analysis: flat code organization, oversized dependency bags, and complexity hotspots.
604
+ Phase 11 converts closure factories to classes, eliminating the adapter closure density in `index.ts` (the package's #1 churn hotspot: 128 commits, accelerating, fan-out 25).
605
+ The approach is layered: each step makes the next step trivial.
604
606
 
605
- ### Step 1: Reorganize source into domain directories ([#164][164]) Done
607
+ > "Make the change that makes the change easy." —Kent Beck
606
608
 
607
- Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
608
- All `src/` internal imports now use `#src/` path aliases (same style as `test/` files), eliminating relative depth arithmetic for future moves.
609
+ ### Findings
609
610
 
610
- - Domain model is now visible in the filesystem.
611
- - Root reduced to 5 files + 8 directories (was 31 files + 3 directories).
612
- - All subsequent steps can move or extract files without `../` import churn.
611
+ | Metric | Value |
612
+ | ------------------------- | --------------------------------------- |
613
+ | Health score | 75/100 (B) |
614
+ | #1 hotspot | `index.ts` (128 commits, accelerating) |
615
+ | Dead exports | 1 (`getToolCallName` re-export) |
616
+ | Production duplication | 0 |
617
+ | Test duplication | 1,396 lines (69 clone groups, 22 files) |
618
+ | `as any` casts in index | 5 |
619
+ | Adapter closures in index | 44 |
620
+ | Index fan-out | 25 imports |
613
621
 
614
- ### Step 2: Decompose ResolvedSpawnConfig ([#165][165])
622
+ ### Root cause
615
623
 
616
- Split the 15-field bag into `SpawnIdentity`, `SpawnExecution`, and `SpawnPresentation`.
617
- Each consumer declares its real dependencies.
618
- Enables Step 3 (narrowing AgentSpawnConfig, [#166][166]).
624
+ The 44 adapter closures in `index.ts` exist because the tool factories accept narrow interfaces that don't structurally match the real objects.
625
+ The real objects can't satisfy the interfaces because:
619
626
 
620
- ### Step 3: Extract ParentSessionInfo from AgentSpawnConfig ([#166][166]) Complete
627
+ 1. `SubagentRuntime.currentCtx` is typed `{ pi: unknown; ctx: unknown }`so every consumer must `as any` cast to read fields.
628
+ 2. Context queries (`buildSnapshot`, `getModelInfo`, `getSessionInfo`) live as closures in index.ts instead of methods on the state holder.
629
+ 3. `AgentToolManager` mixes fields from `AgentManager` and `SettingsManager` (source mismatch).
630
+ 4. `AgentToolWidget` uses different method names than `SubagentRuntime` (name mismatch).
621
631
 
622
- Extracted `parentSessionFile`, `parentSessionId`, `toolCallId` into `ParentSessionInfo`.
623
- `AgentSpawnConfig`, `BackgroundParams`, `ForegroundParams`, and `RunOptions` all carry the nested group.
632
+ Fix these structural misalignments and the class conversions become mechanical.
624
633
 
625
- ### Step 4: Narrow RunnerIO ([#167][167]) ✓ Done
634
+ ### Layer 0: Define `SessionContext` narrow interface ([#192][192])
626
635
 
627
- `RunnerIO` split into `EnvironmentIO` (3 methods: environment discovery) and `SessionFactoryIO` (5 methods + `assemblerIO`: SDK object creation).
628
- `RunnerIO` kept as a backward-compatible type alias for the intersection.
629
- All existing consumers satisfy both sub-interfaces via structural typing with no call-site changes.
636
+ `SubagentRuntime` currently types its context as `unknown` to avoid SDK coupling.
637
+ But `ExtensionContext` is exported by the SDK — the `unknown` is a historical choice, not a constraint.
638
+ Define a narrow `SessionContext` interface capturing the 5 fields runtime actually needs:
630
639
 
631
- ### Step 5: Extract ToolFilterConfig from SessionConfig ([#168][168]) ✓ Done
640
+ ```typescript
641
+ export interface SessionContext {
642
+ readonly cwd: string;
643
+ readonly model: unknown;
644
+ readonly modelRegistry: ModelRegistry | undefined;
645
+ getSystemPrompt(): string;
646
+ readonly sessionManager: {
647
+ getSessionFile(): string | undefined;
648
+ getSessionId(): string;
649
+ getBranch(): unknown[];
650
+ };
651
+ }
652
+ ```
632
653
 
633
- `ToolFilterConfig` extracted from `SessionConfig`, grouping `toolNames`, `disallowedSet`, and `extensions`.
634
- `filterActiveTools` now accepts a single `ToolFilterConfig` argument.
635
- `SessionConfig` reduced from 10 to 8 top-level fields.
654
+ - Target: `src/types.ts`
655
+ - Smell: Category C (platform type threading)
656
+ - Outcome: typed foundation for Layers 1–4; no `as any` needed by consumers of `SubagentRuntime`
657
+
658
+ ### Layer 1: `SubagentRuntime` stores typed context, owns its queries ([#193][193])
659
+
660
+ Change `currentCtx` from `{ pi: unknown; ctx: unknown }` to `SessionContext | undefined`.
661
+ The single `as SessionContext` cast moves into `handleSessionStart` — the boundary where the SDK hands us the value.
662
+ Add typed methods: `buildSnapshot(inheritContext)`, `getModelInfo()`, `getSessionInfo()`.
663
+
664
+ - Target: `src/runtime.ts`, `src/handlers/lifecycle.ts`
665
+ - Smell: Category C (closure queries on mutable field → methods on state owner)
666
+ - Outcome: 3 closure queries in index.ts → 0; `SubagentRuntime` is self-sufficient for tool deps
667
+ - Enables: Layer 3 (tools accept `SubagentRuntime` directly)
668
+
669
+ ### Layer 2: Align interfaces so real objects satisfy tool deps structurally ([#194][194])
636
670
 
637
- ### Step 6: Extract RunContext from RunOptions ([#169][169]) ✓ Done
671
+ Three alignment changes:
638
672
 
639
- Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`, nested as `RunOptions.context`.
640
- `RunOptions` reduced from 12 to 9 fields (1 nested `context` + 8 flat execution fields).
673
+ 1. **Move `getMaxConcurrent` off `AgentToolManager`** it reads from `SettingsManager`, not `AgentManager`.
674
+ The tool already receives `settings`; read it from there.
675
+ 2. **Rename widget methods** — align `SubagentRuntime` method names with `AgentToolWidget` (either rename `updateWidget()` → `update()` on runtime, or rename the interface to match).
676
+ 3. **Remove dead re-export** — `getToolCallName` in `ui/message-formatters.ts` (fallow finding).
641
677
 
642
- ### Step 7: Reduce buildContentLines complexity ([#170][170]) Done
678
+ After this step, `AgentManager` structurally satisfies `AgentToolManager` and `SubagentRuntime` structurally satisfies `AgentToolWidget`.
643
679
 
644
- Extracted formatting sub-functions for each content type (user, assistant, tool result, bash execution, streaming indicator) into `ui/message-formatters.ts`.
645
- `buildContentLines` in `conversation-viewer.ts` is now a ~30-line dispatch loop delegating to `formatMessage` and `formatStreamingIndicator`.
680
+ - Target: `src/tools/agent-tool.ts` (interface), `src/runtime.ts` (method names), `src/ui/message-formatters.ts`
681
+ - Smell: Category C (source mismatch, name mismatch) + Category A (dead export)
682
+ - Outcome: structural typing connects real objects to tool interfaces without adapters
683
+ - Enables: Layer 3 (class constructors accept real objects directly)
646
684
 
647
- ### Step 8: Reduce renderResult complexity ([#171][171]) ✓ Done
685
+ ### Layer 3: Convert closure factories to classes ([#195][195], [#196][196])
648
686
 
649
- Extracted per-status result formatting from `renderResult` in `agent-tool.ts` into `tools/result-renderer.ts`.
650
- `renderResult` reduced from ~80 lines (cognitive complexity 43) to a 10-line guard + `renderAgentResult` dispatcher.
651
- The inline `stats()` closure became the exported `renderStats` helper, shared by all status renderers.
687
+ With Layers 0–2 complete, each factory is a mechanical conversion:
652
688
 
653
- ### Step 9: Extract shared turn-formatting logic ([#172][172]) ✓ Done
689
+ | Factory | Class | Constructor params |
690
+ | -------------------------------- | ------------------------------ | --------------------------------------------------- |
691
+ | `createAgentTool({...})` | `AgentTool` | `manager`, `runtime`, `settings`, `registry` |
692
+ | `createGetResultTool(...)` | `GetResultTool` | `manager`, `notifications`, `registry` |
693
+ | `createSteerTool(...)` | `SteerTool` | `manager`, `events` |
694
+ | `createAgentRunner(runnerIO)` | `AgentRunner` (concrete class) | `io: RunnerIO` |
695
+ | `createAgentsMenuHandler({...})` | `AgentsMenuHandler` | `manager`, `registry`, `settings`, `fileOps`, paths |
654
696
 
655
- Extracted `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` into `session/content-items.ts`.
656
- Both `lifecycle/agent-runner.ts` (`getAgentConversation`) and `ui/message-formatters.ts` (`formatAssistantMessage`) now import from the shared module.
657
- Eliminates the 18-line production-duplication finding.
697
+ Each class satisfies the existing interface via structural typing.
698
+ The `defineTool()` wrapper moves into a `toToolDefinition()` method on each tool class.
699
+
700
+ - Target: `src/tools/*.ts`, `src/lifecycle/agent-runner.ts`, `src/ui/agent-menu.ts`
701
+ - Smell: Category C (closure factories masquerading as classes)
702
+ - Outcome: deps are constructor params (inspectable, testable); no captured closures
703
+ - Enables: Layer 4 (index.ts simplification)
704
+
705
+ ### Layer 4: Simplify index.ts (included in [#196][196])
706
+
707
+ With real objects satisfying tool interfaces and queries living on `SubagentRuntime`, the composition root becomes pure construction:
708
+
709
+ ```typescript
710
+ const runtime = new SubagentRuntime();
711
+ const settings = new SettingsManager(...);
712
+ const manager = new AgentManager(...);
713
+ const agentTool = new AgentTool(manager, runtime, settings, registry);
714
+ pi.registerTool(agentTool.toToolDefinition());
715
+ ```
716
+
717
+ No adapter closures.
718
+ No `as any`.
719
+ Fan-out drops from 25 to ~15 (internal factories eliminated).
720
+
721
+ - Target: `src/index.ts`
722
+ - Smell: Category B (god file) + Category C (adapter closure density)
723
+ - Outcome: index.ts shrinks from 280 to ~150 lines; churn hotspot stabilizes
658
724
 
659
725
  ### Step dependencies
660
726
 
661
727
  ```mermaid
662
728
  flowchart LR
663
- subgraph organization["Code organization"]
664
- S1["#164: Domain directories"]
665
- end
666
- subgraph bags["Dependency bags"]
667
- S2["#165: ResolvedSpawnConfig"] --> S3["#166: AgentSpawnConfig"]
668
- S4["#167: RunnerIO"]
669
- S5["#168: SessionConfig"]
670
- S6["#169: RunOptions"]
671
- end
672
- subgraph complexity["Complexity reduction"]
673
- S7["#170: buildContentLines"]
674
- S8["#171: renderResult"]
675
- S9["#172: Shared turn-formatting"]
676
- end
677
- S1 --> S2 & S4 & S5 & S6
678
- S1 --> S7 & S8 & S9
729
+ L0["Layer 0: SessionContext interface"] --> L1["Layer 1: Runtime owns queries"]
730
+ L1 --> L3["Layer 3: Classes replace factories"]
731
+ L2["Layer 2: Align interfaces"] --> L3
732
+ L3 --> L4["Layer 4: Simplify index.ts"]
679
733
  ```
680
734
 
681
- Step 1 ([#164][164], directory restructuring) unblocks all other steps by co-locating related files.
682
- Steps 2–6 (bag decomposition) and Steps 7–9 (complexity reduction) are independent tracks that can proceed in parallel.
683
- Within the bag track, Step 2 ([#165][165], ResolvedSpawnConfig) enables Step 3 ([#166][166], AgentSpawnConfig).
735
+ Layers 0 and 2 are independent of each other.
736
+ Layer 1 depends on Layer 0.
737
+ Layer 3 depends on both Layer 1 and Layer 2.
738
+ Layer 4 depends on Layer 3.
739
+
740
+ ## Improvement roadmap (Phase 12)
741
+
742
+ Phase 12 addresses the remaining fallow refactoring targets and test duplication.
743
+ These are independent of Phase 11 and can proceed in parallel if desired.
744
+
745
+ ### Step 1: Decompose `renderWidgetLines` (cognitive 44)
746
+
747
+ `renderWidgetLines` in `ui/widget-renderer.ts` handles agent-status formatting, tree connectors, overflow, and empty states.
748
+ Extract per-status renderers and a tree-connector utility.
749
+
750
+ - Target: `src/ui/widget-renderer.ts`
751
+ - Outcome: cognitive complexity < 10
752
+
753
+ ### Step 2: Decompose `showAgentDetail` (cognitive 33)
754
+
755
+ `showAgentDetail` in `ui/agent-config-editor.ts` handles display, edit, eject, and delete flows.
756
+ Extract sub-functions per menu action.
757
+
758
+ - Target: `src/ui/agent-config-editor.ts`
759
+ - Outcome: cognitive complexity < 10
760
+
761
+ ### Step 3: Decompose `update` in `agent-widget.ts` (cognitive 31)
762
+
763
+ `update` mixes timer lifecycle, agent list assembly, render delegation, and visibility state.
764
+ Extract `assembleWidgetState` (pure) and timer management.
765
+
766
+ - Target: `src/ui/agent-widget.ts`
767
+ - Outcome: cognitive complexity < 10
768
+
769
+ ### Step 4: Extract shared test fixtures
770
+
771
+ The 3 heaviest clone families:
772
+
773
+ - `agent-runner.test.ts` + `agent-runner-extension-tools.test.ts` (60-line shared setup)
774
+ - `agent-menu.test.ts` + `agent-creation-wizard.test.ts` + `agent-config-editor.test.ts` (54+51+24 lines)
775
+ - `agent-manager.test.ts` (18 internal clone groups, 210 duplicated lines)
776
+
777
+ Extract shared factories into `test/fixtures/` modules.
778
+
779
+ - Target: new `test/fixtures/` modules
780
+ - Outcome: test duplication reduced by ~400 lines
684
781
 
685
782
  ## Refactoring history
686
783
 
687
- Phases 1–5 and 7–9 are complete.
784
+ Phases 1–5 and 7–10 are complete.
688
785
  Phase 6 (UI extraction to a separate package) is deferred.
689
786
  Detailed records are preserved in per-phase history files:
690
787
 
691
- | Phase | Title | Status | History |
692
- | ----- | --------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
693
- | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
694
- | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
695
- | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
696
- | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
697
- | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
698
- | 6 | Extract UI to separate package | Deferred | — |
699
- | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
700
- | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
701
- | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
788
+ | Phase | Title | Status | History |
789
+ | ----- | --------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
790
+ | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
791
+ | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
792
+ | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
793
+ | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
794
+ | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
795
+ | 6 | Extract UI to separate package | Deferred | — |
796
+ | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
797
+ | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
798
+ | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
799
+ | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
702
800
 
703
801
  ### Structural refactoring issues
704
802
 
705
- | Phase | Issue | Summary |
706
- | ------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
707
- | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
708
- | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
709
- | Interface polish | #66, #77 | SDK types, projectAgentsDir |
710
- | Features | #61 | JSONL session transcripts |
711
- | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
712
- | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
713
- | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
714
- | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
715
- | Phase 10 | #164, #166, #167, #168, #169, #170, #171 | Domain directories, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult |
803
+ | Phase | Issue | Summary |
804
+ | ------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
805
+ | Foundation | #69, #71, #76, #80 | SubagentRuntime, pure assembler, cwd injection, config consolidation |
806
+ | Core decomposition | #84, #72, #87, #70 | WorktreeManager, AgentManager DI, runtime methods, handler extraction |
807
+ | Interface polish | #66, #77 | SDK types, projectAgentsDir |
808
+ | Features | #61 | JSONL session transcripts |
809
+ | AgentManager | #98, #99, #100, #102 | Record state machine, ParentSnapshot, session-event observation, test factory |
810
+ | Encapsulation | #108, #109, #110, #111, #112, #113, #114, #115, #116, #118 | Registry, settings, activity tracker, record lifecycle, observer, spawn options, deps narrowing, tool split, type housekeeping |
811
+ | Testability | #131, #132, #133, #134, #135, #136 | Shared fixtures, session-config IO, runner SDK boundary, as-any reduction, display extraction, menu decomposition |
812
+ | Observation/ctx | #144, #145, #146, #147, #148 | Observation consolidation, execute decomposition, UI context, text wrapping injection, widget rendering split |
813
+ | Phase 10 | #164, #165, #166, #167, #168, #169, #170, #171, #172 | Domain directories, ResolvedSpawnConfig, ParentSessionInfo, RunnerIO split, ToolFilterConfig, RunContext, buildContentLines, renderResult, content-items |
716
814
 
717
815
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
718
816
 
@@ -732,12 +830,12 @@ The upstream test suite is run periodically as a regression canary for the agent
732
830
  [gotgenes/pi-packages]: https://github.com/gotgenes/pi-packages
733
831
  [tintinweb/pi-subagents]: https://github.com/tintinweb/pi-subagents
734
832
 
735
- [164]: https://github.com/gotgenes/pi-packages/issues/164
736
- [165]: https://github.com/gotgenes/pi-packages/issues/165
737
833
  [166]: https://github.com/gotgenes/pi-packages/issues/166
738
834
  [167]: https://github.com/gotgenes/pi-packages/issues/167
739
835
  [168]: https://github.com/gotgenes/pi-packages/issues/168
740
836
  [169]: https://github.com/gotgenes/pi-packages/issues/169
741
- [170]: https://github.com/gotgenes/pi-packages/issues/170
742
- [171]: https://github.com/gotgenes/pi-packages/issues/171
743
- [172]: https://github.com/gotgenes/pi-packages/issues/172
837
+ [192]: https://github.com/gotgenes/pi-packages/issues/192
838
+ [193]: https://github.com/gotgenes/pi-packages/issues/193
839
+ [194]: https://github.com/gotgenes/pi-packages/issues/194
840
+ [195]: https://github.com/gotgenes/pi-packages/issues/195
841
+ [196]: https://github.com/gotgenes/pi-packages/issues/196
@@ -0,0 +1,141 @@
1
+ # Phase 10: Domain organization, bag decomposition, and complexity reduction
2
+
3
+ Target: reorganize source into domain directories; decompose remaining wide parameter bags into focused value objects; reduce cyclomatic complexity in rendering functions; eliminate production code duplication.
4
+
5
+ ## Current smells
6
+
7
+ | Smell | Location | Evidence | Severity |
8
+ | ------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------- |
9
+ | Flat `src/` directory | `src/` root | 20+ files in a single directory with no domain grouping; hard to see module boundaries | Medium |
10
+ | `ResolvedSpawnConfig` is a 15-field bag | `spawn-config.ts` | Mixes identity (name, slug), execution (model, permissions), and presentation (icon, color) concerns in one interface | Medium |
11
+ | `AgentSpawnConfig` threads parent session fields | `agent-manager.ts` | `parentSessionFile`, `parentSessionId`, `toolCallId` always travel together but are separate parameters | Low |
12
+ | `RunnerIO` is too wide | `foreground-runner.ts` | 9 methods spanning environment concerns (cwd, env) and session factory concerns (create, attach, resume) | Medium |
13
+ | `SessionConfig` mixes tool filtering with session setup | `session-config.ts` | `toolNames`, `disallowedSet`, `extensions` are a cohesive filter group buried in a larger config | Low |
14
+ | `RunOptions` is a 12-field bag | `foreground-runner.ts` | Mixes execution context (exec, registry, cwd) with per-run options (model, prompt, permissions) | Medium |
15
+ | `buildContentLines` high complexity | `ui/build-content-lines.ts` | ~60-line function with a switch over every content type; each branch is an independent formatter | Medium |
16
+ | `renderResult` high complexity | `tools/agent-tool.ts` | ~80-line function with status-based branching; each status is an independent rendering concern | Medium |
17
+ | Duplicated turn-formatting logic | `session/session-runner.ts`, `tools/agent-tool.ts` | 18 lines of identical `ToolCallContent` extraction and `getToolCallName` logic in two production files | Low |
18
+
19
+ ## Step 1: Reorganize source into domain directories (#164)
20
+
21
+ Moved files into `config/`, `session/`, `lifecycle/`, `observation/`, and `service/` subdirectories.
22
+ All `src/` internal imports now use `#src/` path aliases.
23
+ Root `src/` reduced to 5 files + 8 directories.
24
+
25
+ Impact: domain boundaries are visible in the directory tree; imports communicate intent via path alias.
26
+
27
+ ## Step 2: Decompose ResolvedSpawnConfig (#165)
28
+
29
+ Split the 15-field `ResolvedSpawnConfig` into three focused value objects:
30
+
31
+ - `SpawnIdentity` — name, slug, key
32
+ - `SpawnExecution` — model, permissions, tools, prompt, systemPrompt
33
+ - `SpawnPresentation` — icon, color, description
34
+
35
+ `ResolvedSpawnConfig` composes these three plus the remaining spawn-level fields.
36
+
37
+ Impact: each consumer receives only the fields it needs; adding a presentation field no longer touches execution code.
38
+
39
+ ## Step 3: Extract ParentSessionInfo from AgentSpawnConfig (#166)
40
+
41
+ Extracted `parentSessionFile`, `parentSessionId`, and `toolCallId` into `ParentSessionInfo`.
42
+ `AgentSpawnConfig` and `AgentManager.spawn()` accept the single value object instead of three separate parameters.
43
+
44
+ Impact: eliminated parameter co-travel; the three fields that always move together are now a single concept.
45
+
46
+ ## Step 4: Narrow RunnerIO (#167)
47
+
48
+ Split `RunnerIO` into two focused interfaces:
49
+
50
+ - `EnvironmentIO` (3 methods) — cwd, env, platform concerns
51
+ - `SessionFactoryIO` (5+1 methods) — create, attach, resume, and related session lifecycle
52
+
53
+ `RunnerIO` kept as a backward-compatible type alias (`EnvironmentIO & SessionFactoryIO`).
54
+
55
+ Impact: consumers declare which IO surface they actually need; test doubles shrink to the relevant subset.
56
+
57
+ ## Step 5: Extract ToolFilterConfig from SessionConfig (#168)
58
+
59
+ Grouped `toolNames`, `disallowedSet`, and `extensions` into `ToolFilterConfig`.
60
+ `filterActiveTools` accepts a single `ToolFilterConfig` argument instead of three separate parameters.
61
+
62
+ Impact: tool-filtering concern is encapsulated; `SessionConfig` is narrower and more focused.
63
+
64
+ ## Step 6: Extract RunContext from RunOptions (#169)
65
+
66
+ Extracted `exec`, `registry`, `cwd`, and `parentSession` into `RunContext`.
67
+ `RunOptions` reduced from 12 fields to 9 (the 4 extracted fields replaced by a single `context` field, plus the remaining per-run options).
68
+
69
+ Impact: execution context is a reusable value object; `RunOptions` focuses on per-run configuration.
70
+
71
+ ## Step 7: Reduce buildContentLines complexity (#170)
72
+
73
+ Extracted per-content-type formatters into `ui/message-formatters.ts`.
74
+ Each content type (text, tool-use, tool-result, image, etc.) has a dedicated pure function.
75
+ `buildContentLines` is now a ~30-line dispatch loop that delegates to the appropriate formatter.
76
+
77
+ Impact: cyclomatic complexity of `buildContentLines` dropped from ~15 to ~5; each formatter is independently testable.
78
+
79
+ ## Step 8: Reduce renderResult complexity (#171)
80
+
81
+ Extracted per-status formatters into `tools/result-renderer.ts`.
82
+ Each result status (success, error, timeout, cancelled) has a dedicated pure function.
83
+ `renderResult` reduced from ~80 lines to a 10-line guard that dispatches to the appropriate renderer.
84
+
85
+ Impact: cyclomatic complexity of `renderResult` dropped from ~10 to ~3; status rendering is independently testable.
86
+
87
+ ## Step 9: Extract shared turn-formatting logic (#172)
88
+
89
+ Extracted `ToolCallContent`, `getToolCallName`, and `extractAssistantContent` into `session/content-items.ts`.
90
+ Both `session/session-runner.ts` and `tools/agent-tool.ts` import from the shared module.
91
+
92
+ Impact: eliminated 18 lines of production code duplication; single source of truth for turn-content extraction.
93
+
94
+ ## Step dependencies
95
+
96
+ ```mermaid
97
+ flowchart LR
98
+ subgraph bag["Bag decomposition track"]
99
+ S2["2: Decompose ResolvedSpawnConfig #165"] --> S3["3: Extract ParentSessionInfo #166"]
100
+ S4["4: Narrow RunnerIO #167"]
101
+ S5["5: Extract ToolFilterConfig #168"]
102
+ S6["6: Extract RunContext #169"]
103
+ end
104
+ subgraph complexity["Complexity reduction track"]
105
+ S7["7: Reduce buildContentLines #170"]
106
+ S8["8: Reduce renderResult #171"]
107
+ S9["9: Extract turn-formatting #172"]
108
+ end
109
+ S1["1: Reorganize into domain dirs #164"] --> bag
110
+ S1 --> complexity
111
+ ```
112
+
113
+ Step 1 unblocked all other steps.
114
+ Within the bag decomposition track, Step 2 enabled Step 3.
115
+ The bag decomposition and complexity reduction tracks were independent of each other.
116
+
117
+ ## Impact
118
+
119
+ | Metric | Before | After |
120
+ | ------------------------------ | ------------------------------ | --------------------------------------------- |
121
+ | Health score | ~65 | 75 |
122
+ | `src/` root files | 20+ | 5 files + 8 directories |
123
+ | `ResolvedSpawnConfig` fields | 15 | 3 composed value objects |
124
+ | `RunOptions` fields | 12 | 9 |
125
+ | `RunnerIO` methods | 9 | 3 (`EnvironmentIO`) + 6 (`SessionFactoryIO`) |
126
+ | `buildContentLines` complexity | ~15 | ~5 |
127
+ | `renderResult` lines | ~80 | ~10 |
128
+ | Production duplication | 18 lines | 0 |
129
+ | Test duplication | ~1,400 lines (69 clone groups) | ~1,400 lines (69 clone groups) — not targeted |
130
+
131
+ ## Related issues
132
+
133
+ - #164 — Reorganize source into domain directories
134
+ - #165 — Decompose ResolvedSpawnConfig
135
+ - #166 — Extract ParentSessionInfo from AgentSpawnConfig
136
+ - #167 — Narrow RunnerIO
137
+ - #168 — Extract ToolFilterConfig from SessionConfig
138
+ - #169 — Extract RunContext from RunOptions
139
+ - #170 — Reduce buildContentLines complexity
140
+ - #171 — Reduce renderResult complexity
141
+ - #172 — Extract shared turn-formatting logic