@gotgenes/pi-subagents 7.8.1 → 9.0.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,52 @@ 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
+ ## [9.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v8.0.0...pi-subagents-v9.0.0) (2026-05-27)
9
+
10
+
11
+ ### ⚠ BREAKING CHANGES
12
+
13
+ * extensions field in agent frontmatter no longer supports a comma-separated allowlist. Values like `extensions: pi-github-tools, colgrep` are coerced to `true` (inherit all). Users who relied on per-extension filtering should migrate to `permission:` frontmatter in pi-permission-system.
14
+
15
+ ### Features
16
+
17
+ * narrow extensions type from union to boolean ([90350ab](https://github.com/gotgenes/pi-packages/commit/90350abef259f5494990c5796a005f2edba2163d))
18
+
19
+
20
+ ### Documentation
21
+
22
+ * mark Phase 14 Step 2 complete in architecture ([fc8bc57](https://github.com/gotgenes/pi-packages/commit/fc8bc57788a3ade9ec2f74200973e5a811c48bb3))
23
+ * plan remove extensions filtering ([#238](https://github.com/gotgenes/pi-packages/issues/238)) ([1f4046a](https://github.com/gotgenes/pi-packages/commit/1f4046a84a308e80472425b5ca9e83263ecc310a))
24
+ * **retro:** add planning stage notes for issue [#238](https://github.com/gotgenes/pi-packages/issues/238) ([b2ce842](https://github.com/gotgenes/pi-packages/commit/b2ce842dcc999fd48f6aeb67c6d464750d6d87aa))
25
+ * **retro:** add retro notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([6be215e](https://github.com/gotgenes/pi-packages/commit/6be215e6f4d9307f94b5bb61c024292a8eb77469))
26
+ * **retro:** add TDD stage notes for issue [#238](https://github.com/gotgenes/pi-packages/issues/238) ([87256db](https://github.com/gotgenes/pi-packages/commit/87256dbd97ba4a69267f2091896d959bef9ff9df))
27
+ * simplify extensions field to boolean in README ([b4028d7](https://github.com/gotgenes/pi-packages/commit/b4028d7c203fce792238acb147709ad3c3d4d28e))
28
+
29
+ ## [8.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.8.1...pi-subagents-v8.0.0) (2026-05-27)
30
+
31
+
32
+ ### ⚠ BREAKING CHANGES
33
+
34
+ * The `disallowed_tools` frontmatter field is no longer supported. Users should migrate to pi-permission-system's `permission:`
35
+
36
+ ### Features
37
+
38
+ * remove disallowedTools from AgentConfig ([6044552](https://github.com/gotgenes/pi-packages/commit/6044552db68180bf8a151c0f64c13f264f3dacb9))
39
+
40
+
41
+ ### Documentation
42
+
43
+ * add Phase 14 issue links, anemic-model and lifecycle-object smells to discovery skill ([9831176](https://github.com/gotgenes/pi-packages/commit/983117652937dce08e61a6cf7624e35ccf8a0dcb))
44
+ * mark Phase 14 Step 1 complete in architecture ([d24e3dd](https://github.com/gotgenes/pi-packages/commit/d24e3dd5c664e63a372f399f9e325f1bdf877022))
45
+ * **pi-subagents:** add Phase 14 issue links ([#237](https://github.com/gotgenes/pi-packages/issues/237), [#238](https://github.com/gotgenes/pi-packages/issues/238), [#239](https://github.com/gotgenes/pi-packages/issues/239)) ([b152a75](https://github.com/gotgenes/pi-packages/commit/b152a75dcd6596468c5c0aeca1b36ac33216dfa8))
46
+ * **pi-subagents:** add target architecture vision and renumber phases 14-17 ([f7e5315](https://github.com/gotgenes/pi-packages/commit/f7e5315bbfb088d7a40637deead1811691dec1bd))
47
+ * **pi-subagents:** archive Phase 13, update metrics for Phase 14 ([9d473db](https://github.com/gotgenes/pi-packages/commit/9d473db1b0e420fa52ce9d09bea7afff75331e58))
48
+ * plan removal of disallowed_tools ([#237](https://github.com/gotgenes/pi-packages/issues/237)) ([9cb600c](https://github.com/gotgenes/pi-packages/commit/9cb600cad9c8cc7b1081b021f039b0f36b9f9a44))
49
+ * remove disallowed_tools from README and add migration note ([9b3905f](https://github.com/gotgenes/pi-packages/commit/9b3905fbaa81db47633c0c8edf21ed3b766d5507))
50
+ * **retro:** add planning stage notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([85460dd](https://github.com/gotgenes/pi-packages/commit/85460dd72949d7da83eaf9ed2196ff5c41d92f7f))
51
+ * **retro:** add retro notes for issue [#219](https://github.com/gotgenes/pi-packages/issues/219) ([2d92af5](https://github.com/gotgenes/pi-packages/commit/2d92af577c0cfb71c575a5c334df2347840b4a8d))
52
+ * **retro:** add TDD stage notes for issue [#237](https://github.com/gotgenes/pi-packages/issues/237) ([f334538](https://github.com/gotgenes/pi-packages/commit/f3345383a76975f09c0ee689edcb5df286b76fc7))
53
+
8
54
  ## [7.8.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v7.8.0...pi-subagents-v7.8.1) (2026-05-26)
9
55
 
10
56
 
package/README.md CHANGED
@@ -31,7 +31,6 @@ Run them in foreground or background, steer them mid-run, resume completed sessi
31
31
  - **Persistent agent memory** — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
32
32
  - **Git worktree isolation** — run agents in isolated repo copies; changes auto-committed to branches on completion
33
33
  - **Skill preloading** — inject named skills into agent system prompts, discovered from `.pi/skills/`, `.agents/skills/`, and global locations (Pi-standard `<name>/SKILL.md` directory layout supported)
34
- - **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
35
34
  - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML.
36
35
  Expandable to show full output
37
36
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
@@ -176,10 +175,9 @@ All fields are optional — sensible defaults for everything.
176
175
  | `description` | filename | Agent description shown in tool listings |
177
176
  | `display_name` | — | Display name for UI (e.g. widget, agent list) |
178
177
  | `tools` | all 7 | Comma-separated built-in tools: read, bash, edit, write, grep, find, ls. `none` for no tools |
179
- | `extensions` | `true` | Inherit MCP/extension tools. `false` to disable |
178
+ | `extensions` | `true` | `true` to inherit all MCP/extension tools, `false` to disable |
180
179
  | `skills` | `true` | Inherit skills from parent. Can be a comma-separated list of skill names to preload (see [Skill Preloading](#skill-preloading) for discovery locations) |
181
180
  | `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
182
- | `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
183
181
  | `isolation` | — | Set to `worktree` to run in an isolated git worktree |
184
182
  | `model` | inherit parent | Model — `provider/modelId` or fuzzy name (`"haiku"`, `"sonnet"`) |
185
183
  | `thinking` | inherit | off, minimal, low, medium, high, xhigh |
@@ -356,8 +354,6 @@ Agents with write tools get full read-write access.
356
354
  **Read-only agents** (no `write`/`edit` tools) automatically get read-only memory — they can consume memories written by other agents but cannot modify them.
357
355
  This prevents unintended tool escalation.
358
356
 
359
- The `disallowed_tools` field is respected when determining write capability — an agent with `tools: write` + `disallowed_tools: write` correctly gets read-only memory.
360
-
361
357
  ## Worktree Isolation
362
358
 
363
359
  Set `isolation: worktree` to run an agent in a temporary git worktree:
@@ -408,18 +404,19 @@ Traversal is byte-order sorted for deterministic resolution across filesystems.
408
404
  **Security:** symlinks are rejected at every layer (root, flat file, skill directory, `SKILL.md` inside a skill directory) — intentional deviation from Pi, which follows symlinks.
409
405
  Skill names with path-traversal characters (`..`, `/`, `\`, spaces, leading dot, >128 chars) are rejected.
410
406
 
411
- ## Tool Denylist
407
+ ## Migrating from `disallowed_tools`
412
408
 
413
- Block specific tools from an agent even if extensions provide them:
409
+ The `disallowed_tools` frontmatter field has been removed.
410
+ Use [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system)'s `permission:` frontmatter instead — it provides richer semantics (allow/ask/deny vs. binary hide):
414
411
 
415
412
  ```yaml
416
- ---
417
- tools: read, bash, grep, write
418
- disallowed_tools: write, edit
419
- ---
420
- ```
413
+ # Before (no longer supported)
414
+ disallowed_tools: bash
421
415
 
422
- This is useful for creating agents that inherit extension tools but should not have write access.
416
+ # After
417
+ permission:
418
+ bash: deny
419
+ ```
423
420
 
424
421
  ## Permission System Integration
425
422
 
@@ -482,8 +479,7 @@ Each has a corresponding upstream PR:
482
479
  1. **Peer-dep migration to `@earendil-works/pi-*`** — `peerDependencies` and all imports point at `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` (the active scope on npm) instead of the deprecated `@mariozechner/pi-*` scope.
483
480
  Also fixes a latent bug where `ThinkingLevel` was imported from `pi-agent-core` (an undeclared transitive dep that breaks under pnpm).
484
481
  Upstream PR: [tintinweb/pi-subagents#71](https://github.com/tintinweb/pi-subagents/pull/71).
485
- 2. **Post-`bindExtensions` active-tool re-filter** (`src/agent-runner.ts`) — `runAgent` re-runs its active-tool filter after `session.bindExtensions(...)` so extension-registered tools join the child's active tool set.
486
- Without this, the `extensions: string[]` allowlist branch was functionally dead for extension tools, and `extensions: true` with a `disallowedTools` denylist let denylisted extension tools slip through.
482
+ 2. **Post-`bindExtensions` active-tool re-filter** (`src/agent-runner.ts`) — `runAgent` re-runs its active-tool filter after `session.bindExtensions(...)` so the `EXCLUDED_TOOL_NAMES` recursion guard applies to extension-registered tools (which join the active set during `bindExtensions`).
487
483
  Upstream PR: [tintinweb/pi-subagents#72](https://github.com/tintinweb/pi-subagents/pull/72).
488
484
  3. **`<active_agent>` system-prompt tag** (`src/prompts.ts`) — `buildAgentPrompt` prepends `<active_agent name="${config.name}"/>` to every assembled child system prompt (both `replace` and `append` modes).
489
485
  Downstream extensions like [`@gotgenes/pi-permission-system`](https://github.com/gotgenes/pi-permission-system) parse this tag to resolve per-agent `permission:` frontmatter inside the child session.
@@ -22,6 +22,9 @@ This document describes the architecture of the pi-subagents fork: a focused, co
22
22
  No post-construction field writes from external code — if an object can't be instantiated ready-to-go, the prep work hasn't been done and the right dependencies haven't been identified.
23
23
  9. **State owns its mutations** — mutable state lives in a class whose methods enforce valid transitions and invariants.
24
24
  Free functions that mutate module-scoped variables, closure-captured bags-of-functions, and external writes to shared interfaces are replaced by classes that encapsulate the state they manage.
25
+ 10. **Open for extension, closed for modification** — pi-subagents is a minimal core that publishes events and a service API.
26
+ Other packages (pi-permission-system, a future UI extension, hypothetical OTel integration) hook into these events to add permissions, rendering, or telemetry.
27
+ Pi-subagents has zero knowledge of its consumers — dependency arrows point inward, never outward.
25
28
 
26
29
  ## Domain model
27
30
 
@@ -222,7 +225,7 @@ sequenceDiagram
222
225
 
223
226
  ## Module organization
224
227
 
225
- The extension has 53 source files organized into six domains plus entry-point wiring.
228
+ The extension has 56 source files organized into six domains plus entry-point wiring.
226
229
  All eight domains have directories: `config/`, `session/`, `lifecycle/`, `observation/`, `service/`, `tools/`, `ui/`, and `handlers/`.
227
230
  Issue #164 moved the 26 previously flat root-level files into five new domain directories, reducing the root to 5 files + 8 directories.
228
231
 
@@ -260,6 +263,7 @@ src/
260
263
  │ ├── parent-snapshot.ts immutable spawn-time parent state
261
264
  │ ├── execution-state.ts session/output phase state
262
265
  │ ├── permission-bridge.ts optional bridge to pi-permission-system registry
266
+ │ ├── run-handle.ts per-run cleanup lifecycle
263
267
  │ ├── worktree.ts git worktree isolation
264
268
  │ ├── worktree-state.ts worktree phase state
265
269
  │ └── usage.ts token usage tracking
@@ -272,7 +276,7 @@ src/
272
276
 
273
277
  ├── service/ cross-extension API boundary
274
278
  │ ├── service.ts SubagentsService interface + Symbol.for() accessors
275
- │ └── service-adapter.ts SubagentsService wrapper around AgentManager
279
+ │ └── service-adapter.ts SubagentsServiceAdapter class wrapping AgentManager
276
280
 
277
281
  ├── tools/ LLM-facing tool implementations
278
282
  │ ├── agent-tool.ts Agent tool definition, validation, dispatch
@@ -288,8 +292,8 @@ src/
288
292
  │ ├── agent-widget.ts above-editor live status widget
289
293
  │ ├── widget-renderer.ts pure rendering for widget
290
294
  │ ├── agent-menu.ts /agents slash command menu
291
- │ ├── agent-config-editor.ts agent detail/edit view
292
- │ ├── agent-creation-wizard.ts agent creation (AI + manual)
295
+ │ ├── agent-config-editor.ts agent detail/edit view (AgentConfigEditor class)
296
+ │ ├── agent-creation-wizard.ts agent creation (AgentCreationWizard class)
293
297
  │ ├── conversation-viewer.ts scrollable session overlay
294
298
  │ ├── message-formatters.ts pure per-message-type formatters (extracted from conversation-viewer)
295
299
  │ ├── agent-activity-tracker.ts live activity state tracker
@@ -335,8 +339,9 @@ They declare this package as an optional peer dependency and use dynamic import
335
339
 
336
340
  - The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
337
341
  - `AgentManager` — spawn, queue, abort, resume, concurrency control.
338
- - `agent-runner` — session creation, turn loop, tool filtering, extension binding (Patches 2 and 3), permission-system registration.
342
+ - `agent-runner` — session creation, turn loop, extension binding.
339
343
  - `permission-bridge` — optional cross-extension bridge to `@gotgenes/pi-permission-system`; registers each child session with `SubagentSessionRegistry` before `bindExtensions()` so the permission system detects in-process children deterministically.
344
+ Scheduled for removal in Phase 16 — replaced by lifecycle events that consumers listen for.
340
345
  - `session-config` — pure configuration assembler (extracted from `agent-runner`).
341
346
  - `SubagentRuntime` — session-scoped state bag with methods.
342
347
  - `ParentSnapshot` — immutable snapshot of parent session state, captured once at spawn time.
@@ -430,22 +435,56 @@ The core emits events on `pi.events` that any extension can observe:
430
435
 
431
436
  These are fire-and-forget broadcast events — no request IDs, no reply channels.
432
437
 
438
+ ## Target architecture
439
+
440
+ The long-term architectural direction is to make pi-subagents a **minimal core** with inverted dependencies.
441
+ Today, pi-subagents reaches outward to pi-permission-system via a bridge module and owns tool/extension filtering logic that duplicates permission-system responsibilities.
442
+ The target state eliminates this overlap and flips the dependency direction.
443
+
444
+ ### Core responsibilities (keep)
445
+
446
+ - **Agent definitions** — name, model, thinking, system prompt, tools list.
447
+ - **Prompt composition** — system prompt assembly, skill preloading into prompt.
448
+ - **Session lifecycle** — create child sessions, bind extensions, run conversation loop, track results.
449
+ - **Concurrency management** — queue, abort, resume, max concurrency.
450
+ - **Recursion guard** — remove pi-subagents' own three tools from child sessions (prevent infinite nesting).
451
+ - **Lifecycle events** — emit events on `pi.events` when child sessions are created, completed, etc.
452
+ - **Service API** — publish `SubagentsService` via `Symbol.for()` for cross-extension access.
453
+
454
+ ### Responsibilities to remove
455
+
456
+ - **Tool policy** (`disallowed_tools`, `ToolFilterConfig.disallowedSet`) — access control belongs in pi-permission-system's `permission:` frontmatter.
457
+ - **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
458
+ - **Permission bridge** (`permission-bridge.ts`) — outbound coupling to pi-permission-system.
459
+ Replaced by lifecycle events that pi-permission-system listens for.
460
+ - **Extension lifecycle control** (`extensions: false`, `isolated`) — extensions provide behavioral layers (permissions, formatting, context management) that benefit all agents.
461
+ Blanket-disabling them is a blunt instrument with no clear use case; tool restrictions belong in the permission system.
462
+
463
+ ### Composition model
464
+
465
+ In the target state, pi-subagents publishes events and other packages hook in:
466
+
467
+ - **pi-permission-system** listens for child session lifecycle events, applies per-agent policy (allow/ask/deny), gates tool calls at runtime.
468
+ - **pi-subagents-ui** (future) subscribes to the service API, renders the widget, conversation viewer, and `/agents` menu.
469
+ - **Any future extension** (OTel, auditing, cost tracking) hooks into the same events without pi-subagents knowing.
470
+
471
+ This is achieved across three phases: Phase 14 (strip policy), Phase 16 (invert dependencies), and Phase 17 (extract UI).
472
+
433
473
  ## Current structural analysis
434
474
 
435
475
  ### Health metrics
436
476
 
437
- | Metric | Value |
438
- | -------------------------- | ------------------------------------ |
439
- | Health score | 78/100 (B) |
440
- | Total LOC | 8,180 (53 files) |
441
- | Test LOC | 12,026 |
442
- | Dead code | 0 files, 0 exports |
443
- | Maintainability index | 90.7 (good) |
444
- | Avg cyclomatic complexity | 1.5 |
445
- | P90 cyclomatic complexity | 2 |
446
- | Production duplication | 20 lines (1 clone group) |
447
- | Test duplication | 59 clone groups, 1,046 lines |
448
- | Fallow refactoring targets | 1 (buildParentContext, cognitive 30) |
477
+ | Metric | Value |
478
+ | -------------------------- | --------------------------------- |
479
+ | Health score | 78/100 (B) |
480
+ | Total LOC | 8,382 (56 files) |
481
+ | Dead code | 0 files, 0 exports |
482
+ | Maintainability index | 90.8 (good) |
483
+ | Avg cyclomatic complexity | 1.4 |
484
+ | P90 cyclomatic complexity | 2 |
485
+ | Production duplication | 11 lines (1 internal clone group) |
486
+ | Test duplication | 38 clone groups, 634 lines |
487
+ | Fallow refactoring targets | 0 |
449
488
 
450
489
  ### Dependency bag inventory
451
490
 
@@ -471,28 +510,29 @@ Bags with 10+ fields are the highest priority for decomposition.
471
510
 
472
511
  Functions with cyclomatic complexity ≥ 21 (critical threshold):
473
512
 
474
- No functions remain above the critical threshold — all hotspots resolved in Phase 12.
513
+ No functions remain above the critical threshold — all hotspots resolved in Phase 12. 6 functions remain at HIGH severity (CRAP ≥ 65); 13 at moderate.
475
514
 
476
515
  ### Churn hotspots
477
516
 
478
- Files with highest commit frequency × complexity (accelerating trend):
517
+ Files with highest commit frequency × complexity:
479
518
 
480
519
  | Score | File | Commits | Trend |
481
520
  | ----- | --------------------------- | ------- | -------------- |
482
- | 43.3 | `index.ts` | 81 | ▲ accelerating |
483
- | 26.0 | `ui/agent-menu.ts` | 33 | accelerating |
484
- | 13.6 | `tools/agent-tool.ts` | 41 | accelerating |
485
- | 13.3 | `ui/conversation-viewer.ts` | 16 | accelerating |
486
- | 12.6 | `ui/agent-config-editor.ts` | 10 | cooling |
487
- | 11.7 | `ui/agent-widget.ts` | 14 | accelerating |
521
+ | 65.0 | `index.ts` | 128 | ▲ accelerating |
522
+ | 9.1 | `ui/agent-widget.ts` | 13 | cooling |
523
+ | 8.4 | `ui/conversation-viewer.ts` | 11 | stable |
524
+ | 6.4 | `runtime.ts` | 12 | stable |
525
+ | 3.3 | `settings.ts` | 4 | stable |
526
+ | 2.9 | `handlers/lifecycle.ts` | 11 | stable |
488
527
 
489
- Note: accelerating trends reflect recent refactoring phases, not feature churn.
490
- Once structural work stabilizes, these are expected to cool.
528
+ Most files have cooled to stable after 13 phases of structural work.
529
+ `index.ts` remains the sole accelerating hotspot expected as the wiring entry point for each refactoring phase.
491
530
 
492
531
  ### Production duplication
493
532
 
494
533
  The prior clone group between `agent-runner.ts` and `message-formatters.ts` was resolved in #172.
495
- The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 — extracted into `ui/agent-file-writer.ts` (`writeAgentFile`). 0 production clone groups remain.
534
+ The 20-line clone group between `agent-config-editor.ts` and `agent-creation-wizard.ts` was resolved in #217 — extracted into `ui/agent-file-writer.ts` (`writeAgentFile`).
535
+ One 11-line internal clone group remains within `agent-config-editor.ts` (lines 135–145 / 173–183).
496
536
 
497
537
  ### Proposed bag decompositions
498
538
 
@@ -619,123 +659,97 @@ See [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) for det
619
659
  Phase 12 decomposed the three remaining high-complexity UI functions and extracted shared test fixtures.
620
660
  All four steps are closed: [#205], [#206], [#207], [#208].
621
661
 
622
- ## Improvement roadmap (Phase 13)
623
-
624
- Phase 13 addresses the remaining fallow refactoring target, `agent-manager.ts` oversized method, production duplication, SDK boundary coupling, and the heaviest test clone families.
625
- Health score target: 80+ (A).
626
-
627
- ### Findings summary
628
-
629
- | Finding | Category | Impact | Risk | Priority |
630
- | ---------------------------------------------------------------- | -------------- | ------ | ---- | -------- |
631
- | 3 remaining closure factories (Phase 11 survivors) | C: Coupling | 4 | 2 | 16 |
632
- | `buildParentContext` cognitive 30 (only fallow target) | B: Oversized | 3 | 1 | 15 |
633
- | `startAgent` in agent-manager.ts (~130 LOC method) | B: Oversized | 4 | 3 | 12 |
634
- | Test duplication: 59 clone groups, 1,046 lines | D: Testability | 3 | 2 | 12 |
635
- | Overwrite guard duplicated across UI modules (20 lines) | A: Redundant | 2 | 1 | 10 |
636
- | `settings.ts` calls SDK function `getAgentDir()` at module level | C: Coupling | 2 | 1 | 10 |
637
-
638
- ### Step 1: Convert remaining closure factories to classes — [#214] ✓
662
+ ## Phase 13 (complete)
639
663
 
640
- Three closure factories converted to classes in [#214].
664
+ Phase 13 addressed remaining closure factories, the last fallow refactoring target, oversized methods, production duplication, SDK boundary coupling, and test clone families.
665
+ All six steps are closed: [#214], [#215], [#216], [#217], [#218], [#219].
666
+ See [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) for details.
641
667
 
642
- | Factory Class | File | Captures |
643
- | ------------------------------------------------------ | ----------------------------- | ---------------------------------------- |
644
- | `createAgentConfigEditor()` → `AgentConfigEditor` | `ui/agent-config-editor.ts` | `fileOps`, `registry`, 2 dirs |
645
- | `createAgentCreationWizard()` → `AgentCreationWizard` | `ui/agent-creation-wizard.ts` | `fileOps`, `manager`, `registry`, 2 dirs |
646
- | `createSubagentsService()` → `SubagentsServiceAdapter` | `service/service-adapter.ts` | `manager`, `resolveModel`, `runtime` |
668
+ ## Improvement roadmap (Phase 14 — strip policy from core)
647
669
 
648
- - Smell: C (coupling deps hidden in closure scope instead of explicit on class)
649
- - Outcome: 0 remaining closure factories (excluding pure-function factories), deps visible as constructor parameters
670
+ Phase 14 removes tool and extension policy enforcement from pi-subagents.
671
+ This code duplicates what pi-permission-system already provides with richer semantics (allow/ask/deny vs. binary hide).
672
+ Removing it simplifies `runAgent`, shrinks `AgentConfig` and `SessionConfig`, and makes the Phase 15 domain-model work operate on a cleaner codebase.
650
673
 
651
- ### Step 2: Decompose `buildParentContext` (cognitive 30) — [#215] ✓
652
-
653
- `buildParentContext` in `session/context.ts` is the only remaining fallow refactoring target.
654
- The function loops over branch entries with 3 type-check branches, each with sub-branches for role or summary.
655
- Extract per-entry-type formatters: `formatMessageEntry(entry)` and `formatCompactionEntry(entry)`.
656
-
657
- - Target: `src/session/context.ts`
658
- - Smell: B (oversized function)
659
- - Outcome: cognitive complexity < 10, function < 15 LOC
660
-
661
- ### Step 3: Decompose `startAgent` in `agent-manager.ts` — [#216] ✓
662
-
663
- `startAgent` had two mutable closure variables (`unsubRecordObserver`, `detachParentSignal`) shared across three callbacks with duplicated finalization logic in `.then()`/`.catch()`.
664
- The fix introduced a `RunHandle` lifecycle object (private to `agent-manager.ts`) that owns the per-run cleanup state and exposes `complete()`/`fail()` as Tell-Don't-Ask methods.
665
- `WorktreeState` gained `performCleanup(worktrees, description)` to eliminate the ask-tell dance at cleanup sites.
674
+ ### Findings summary
666
675
 
667
- Extracted:
676
+ | Finding | Category | Impact | Risk | Priority |
677
+ | ----------------------------------------------------------------------------------------- | ------------- | ------ | ---- | -------- |
678
+ | `disallowed_tools` duplicates pi-permission-system's `permission:` frontmatter | A: Overlap | 4 | 2 | 12 |
679
+ | `extensions: string[]` allowlist is tool filtering disguised as lifecycle control | A: Overlap | 3 | 2 | 9 |
680
+ | `filterActiveTools` runs twice (pre-bind + post-bind) to catch extension-registered tools | B: Complexity | 3 | 1 | 6 |
681
+ | `ToolFilterConfig` exists solely to carry filtering state through the runner | C: Accidental | 2 | 1 | 4 |
668
682
 
669
- 1. `RunHandle` class owns `unsub`/`detachFn`, `wireSignal()`, `attachObserver()`, `complete()`, `fail()`, idempotent `fireOnFinished()`.
670
- 2. `finalizeBackgroundRun(record)` — shared `runningBackground--`, crash-safe observer notification, `drainQueue()`.
671
- 3. `setupWorktree(id, record, isolation)` — worktree creation with strict failure.
672
- 4. `flushPendingSteers(id, session)` — drain buffered steers on session creation.
673
- 5. `WorktreeState.performCleanup(worktrees, description)` — self-cleanup eliminating ask-tell.
683
+ ### Step 1: Remove `disallowed_tools` [#237] Complete
674
684
 
675
- - Target: `src/lifecycle/agent-manager.ts`, `src/lifecycle/worktree-state.ts`
676
- - Smell: B (oversized method) + A (duplicated finalization logic in then/catch)
677
- - Outcome: `startAgent` reduced to ~40 LOC coordinator with zero mutable `let` bindings; `.then()`/`.catch()` are one-liners
685
+ Remove the `disallowedTools` field from `AgentConfig` and all code that processes it.
678
686
 
679
- ### Step 4: Extract overwrite guard from UI — [#217] ✓
687
+ 1. Remove `disallowedTools` from the `AgentConfig` interface in `types.ts`.
688
+ 2. Remove `disallowed_tools` parsing from custom agent frontmatter in `custom-agents.ts`.
689
+ 3. Remove `disallowedSet` from `ToolFilterConfig` in `session-config.ts`.
690
+ 4. Remove the `disallowedSet` construction in `assembleSessionConfig`.
691
+ 5. Remove the `disallowedSet` branch from `filterActiveTools` in `agent-runner.ts`.
692
+ 6. Remove `disallowed_tools` from the agent config editor UI.
693
+ 7. Remove `disallowed_tools` from the agent creation wizard.
694
+ 8. Update tests.
680
695
 
681
- The 20-line pattern duplicated between `agent-config-editor.ts:138–151` and `agent-creation-wizard.ts:231–250` checks file existence, prompts for confirmation, writes the file, reloads the registry, and notifies the user.
682
- Extract a shared `writeAgentFile(fileOps, ui, registry, targetPath, content, label)` function.
696
+ - Target: `types.ts`, `custom-agents.ts`, `session-config.ts`, `agent-runner.ts`, `ui/agent-config-editor.ts`, `ui/agent-creation-wizard.ts`
697
+ - Smell: A (responsibility overlap with pi-permission-system)
698
+ - Outcome: users migrate to `permission:` frontmatter for tool restrictions; single source of truth for access control
683
699
 
684
- - Target: new `src/ui/agent-file-writer.ts`, consumers `src/ui/agent-config-editor.ts` and `src/ui/agent-creation-wizard.ts`
685
- - Smell: A (production duplication)
686
- - Outcome: 0 production clone groups
700
+ ### Step 2: Remove `extensions` filtering [#238] ✅ Complete
687
701
 
688
- ### Step 5: Push SDK boundary in `settings.ts` [#218]
702
+ Remove the `extensions: string[]` allowlist and simplify the field to a boolean.
703
+ The `extensions: false` case (used by `isolated`) is retained in this step and removed in Phase 16.
689
704
 
690
- `globalPath()` calls `getAgentDir()` (a Pi SDK function) at invocation time.
691
- This hides a platform dependency inside a module that is otherwise pure configuration logic.
692
- Inject `agentDir: string` as a constructor parameter to `SettingsManager` and pass the global settings path from the boundary in `index.ts`.
705
+ 1. Change `extensions` type from `true | string[] | false` to `boolean` in `AgentConfig`.
706
+ 2. Remove the `extensions` array branch from `filterActiveTools` in `agent-runner.ts`.
707
+ 3. Remove `extensions` from the agent config editor and creation wizard.
708
+ 4. Update custom agent frontmatter parsing to treat array values as `true` (with a warning).
709
+ 5. Update tests.
693
710
 
694
- - Target: `src/settings.ts`, `src/index.ts`
695
- - Smell: C (platform type threading)
696
- - Outcome: `settings.ts` has 0 Pi SDK imports, `loadSettings`/`saveSettings` become fully testable without SDK stubs
711
+ - Target: `types.ts`, `agent-runner.ts`, `session-config.ts`, `ui/agent-config-editor.ts`, `ui/agent-creation-wizard.ts`
712
+ - Smell: A (tool filtering disguised as extension lifecycle control)
713
+ - Outcome: `filterActiveTools` reduces to two concerns: recursion guard and `extensions: false` passthrough
697
714
 
698
- ### Step 6: Reduce test duplication top 3 clone families — [#219]
715
+ ### Step 3: Collapse `filterActiveTools` to recursion guard — [#239]
699
716
 
700
- The three heaviest remaining clone families after Phase 12:
717
+ With Steps 1–2 complete, `filterActiveTools` has only two remaining branches: the `EXCLUDED_TOOL_NAMES` recursion guard and the `extensions === false` passthrough.
718
+ Inline the `extensions === false` passthrough into the callsite and reduce the function to its essential purpose.
701
719
 
702
- 1. `agent-manager.test.ts` 16 clone groups, 160 duplicated lines.
703
- Extract shared setup/assertion helpers into `test/helpers/manager-stubs.ts`.
704
- 2. `conversation-viewer.test.ts`8 clone groups, 91 duplicated lines.
705
- Extract entry-builder helpers into existing `test/helpers/` or inline factory.
706
- 3. `agent-config-editor.test.ts` — 5 clone groups, 42 duplicated lines.
707
- Extract shared setup helpers.
720
+ 1. Simplify `filterActiveTools` to filter only `EXCLUDED_TOOL_NAMES`.
721
+ 2. Remove `ToolFilterConfig` the function no longer needs a config bag.
722
+ 3. Remove the pre-bind filter call extension tools aren't in the active set yet, so filtering built-in tools pre-bind is only needed for denylist/allowlist logic that no longer exists.
723
+ 4. Keep a single post-bind filter call for the recursion guard.
724
+ 5. Simplify `SessionConfig` — remove the `toolFilter` field, keep `toolNames` as a flat field.
725
+ 6. Update tests.
708
726
 
709
- - Target: `test/lifecycle/agent-manager.test.ts`, `test/conversation-viewer.test.ts`, `test/ui/agent-config-editor.test.ts`
710
- - Smell: D (test duplication)
711
- - Outcome: test duplication reduced by ~200 lines (from 1,046 to < 850)
727
+ - Target: `agent-runner.ts`, `session-config.ts`
728
+ - Smell: B (accidental complexity), C (two-pass filter dance)
729
+ - Outcome: `filterActiveTools` is a one-liner; `SessionConfig` loses one nested type; the pre-bind/post-bind dance is gone
712
730
 
713
731
  ### Step dependency diagram
714
732
 
715
733
  ```mermaid
716
734
  flowchart LR
717
- S1["Step 1\nclosure to class"]
718
- S2["Step 2\nbuildParentContext"]
719
- S3["Step 3\nstartAgent decomp"]
720
- S4["Step 4\noverwrite guard"]
721
- S5["Step 5\nsettings SDK"]
722
- S6["Step 6\ntest duplication"]
723
-
724
- S1 --> S4
725
- S1 --> S6
726
- S3 --> S6
727
- S2 ~~~ S5
735
+ S1["Step 1\nRemove disallowed_tools"]
736
+ S2["Step 2\nRemove extensions filtering"]
737
+ S3["Step 3\nCollapse filterActiveTools"]
738
+
739
+ S1 --> S3
740
+ S2 --> S3
728
741
  ```
729
742
 
730
- ### Tracks
743
+ Steps 1 and 2 are independent and can proceed in parallel.
744
+ Step 3 depends on both.
731
745
 
732
- 1. **Track A — Structural** (Steps 1, 3): closure-to-class conversions and method decomposition.
733
- 2. **Track B — Complexity and coupling** (Steps 2, 5): independent, can proceed in parallel with Track A.
734
- 3. **Track C — Duplication** (Steps 4, 6): Step 4 depends on Step 1 (overwrite guard lives in files being converted); Step 6 depends on Steps 1 and 3 (production code they test changes first).
746
+ [#237]: https://github.com/gotgenes/pi-packages/issues/237
747
+ [#238]: https://github.com/gotgenes/pi-packages/issues/238
748
+ [#239]: https://github.com/gotgenes/pi-packages/issues/239
735
749
 
736
- ## Improvement roadmap (Phase 14)
750
+ ## Improvement roadmap (Phase 15 — domain model evolution)
737
751
 
738
- Phase 14 addresses the anemic domain model in the lifecycle layer.
752
+ Phase 15 addresses the anemic domain model in the lifecycle layer.
739
753
  `AgentRecord` is a data bag — identity, status transitions, and stats — but no behavior.
740
754
  `AgentManager` reaches into records 37 times, doing work that belongs on the agent.
741
755
  Per-agent state (pending steers, abort logic, run lifecycle) is scattered across the manager, `RunHandle`, and a manager-level Map.
@@ -839,26 +853,51 @@ flowchart LR
839
853
  Sequential — each depends on the previous.
840
854
  2. **Track B — Decoupling** (Steps 3, 4, 5): independent, can proceed in parallel with Track A.
841
855
 
856
+ ## Improvement roadmap (Phase 16 — invert dependencies)
857
+
858
+ Phase 16 completes the architectural inversion by removing the outbound permission bridge and the `extensions: false` / `isolated` concepts.
859
+ It depends on Phase 15's observer pattern (#229) as the replacement mechanism.
860
+
861
+ Phase 16 is scoped but not yet broken into steps.
862
+ Key changes:
863
+
864
+ 1. Remove `permission-bridge.ts` — the outbound coupling to pi-permission-system.
865
+ 2. Emit child session lifecycle events via the observer — pi-permission-system and other consumers listen for these events instead of being called.
866
+ 3. Remove `extensions: false` — all child sessions load all extensions.
867
+ 4. Dissolve or redefine `isolated` — without extension control and tool filtering, the concept either disappears or becomes purely about prompt composition (no skill preloading, no parent context inheritance).
868
+ 5. Update pi-permission-system to listen for child session events instead of being registered by the bridge.
869
+
870
+ ## Improvement roadmap (Phase 17 — extract UI)
871
+
872
+ Phase 17 is the long-deferred UI extraction (originally Phase 6).
873
+ The widget, conversation viewer, and `/agents` command menu move to a separate `pi-subagents-ui` extension that consumes the `SubagentsService` API.
874
+ By this point the core is minimal and stable — the API boundary has been proven across Phases 14–16.
875
+
842
876
  ## Refactoring history
843
877
 
844
878
  Phases 1–5 and 7–12 are complete.
845
879
  Phase 6 (UI extraction to a separate package) is deferred.
846
880
  Detailed records are preserved in per-phase history files:
847
881
 
848
- | Phase | Title | Status | History |
849
- | ----- | --------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
850
- | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
851
- | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
852
- | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
853
- | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
854
- | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
855
- | 6 | Extract UI to separate package | Deferred | — |
856
- | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
857
- | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
858
- | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
859
- | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
860
- | 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
861
- | 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
882
+ | Phase | Title | Status | History |
883
+ | -------- | --------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
884
+ | 1 | Export SubagentsService API boundary | Complete | [phase-1-api-boundary.md](history/phase-1-api-boundary.md) |
885
+ | 2 | Remove scheduling subsystem | Complete | [phase-2-remove-scheduling.md](history/phase-2-remove-scheduling.md) |
886
+ | 3 | Remove group-join, RPC; replace output-file | Complete | [phase-3-remove-rpc-groupjoin.md](history/phase-3-remove-rpc-groupjoin.md) |
887
+ | 4 | Implement and publish SubagentsService | Complete | [phase-4-implement-service.md](history/phase-4-implement-service.md) |
888
+ | 5 | Decompose index.ts | Complete | [phase-5-decompose-index.md](history/phase-5-decompose-index.md) |
889
+ | 6 | Extract UI to separate package | Deferred → Phase 17 | — |
890
+ | 7 | Encapsulation and dependency narrowing | Complete | [phase-7-encapsulation.md](history/phase-7-encapsulation.md) |
891
+ | 8 | Testability, display extraction, menu decomposition | Complete | [phase-8-testability.md](history/phase-8-testability.md) |
892
+ | 9 | Observation consolidation, ctx elimination | Complete | [phase-9-observation-ctx.md](history/phase-9-observation-ctx.md) |
893
+ | 10 | Domain organization, bag decomposition, complexity | Complete | [phase-10-structural-decomposition.md](history/phase-10-structural-decomposition.md) |
894
+ | 11 | Closure factories to classes | Complete | [phase-11-closure-to-class.md](history/phase-11-closure-to-class.md) |
895
+ | 12 | Complexity reduction and test fixture extraction | Complete | [phase-12-complexity-test-fixtures.md](history/phase-12-complexity-test-fixtures.md) |
896
+ | 13 | Remaining structural smells | Complete | [phase-13-remaining-smells.md](history/phase-13-remaining-smells.md) |
897
+ | 14 | Strip policy from core | Planned | — |
898
+ | 15 | Domain model evolution | Planned | — |
899
+ | 16 | Invert dependencies | Planned | — |
900
+ | 17 | Extract UI to separate package | Planned | — |
862
901
 
863
902
  ### Structural refactoring issues
864
903
 
@@ -876,7 +915,8 @@ Detailed records are preserved in per-phase history files:
876
915
  | Phase 11 | #192, #193, #194, #195, #196 | SessionContext, runtime queries, interface alignment, tool classes, runner/menu classes, index.ts simplification |
877
916
  | Phase 12 | #205, #206, #207, #208 | renderWidgetLines, showAgentDetail, widget update, shared test fixtures |
878
917
  | Phase 13 | #214, #215, #216, #217, #218, #219 | Closure-to-class, buildParentContext, startAgent decomp, overwrite guard, settings SDK, test duplication |
879
- | Phase 14 | #227, #228, #229, #230, #231, #232 | Agent domain model, async startAgent, onSessionCreated observer, ConcurrencyQueue, relay deps, resume unification |
918
+ | Phase 14 | #237, #238, #239 | Remove disallowed_tools, remove extensions filtering, collapse filterActiveTools |
919
+ | Phase 15 | #227, #228, #229, #230, #231, #232 | Agent domain model, async startAgent, onSessionCreated observer, ConcurrencyQueue, relay deps, resume unification |
880
920
 
881
921
  The remaining open issue is #22 (parent-session resolution), a cross-extension track that does not gate the structural work.
882
922
 
@@ -909,3 +949,9 @@ The upstream test suite is run periodically as a regression canary for the agent
909
949
  [#217]: https://github.com/gotgenes/pi-packages/issues/217
910
950
  [#218]: https://github.com/gotgenes/pi-packages/issues/218
911
951
  [#219]: https://github.com/gotgenes/pi-packages/issues/219
952
+ [#227]: https://github.com/gotgenes/pi-packages/issues/227
953
+ [#228]: https://github.com/gotgenes/pi-packages/issues/228
954
+ [#229]: https://github.com/gotgenes/pi-packages/issues/229
955
+ [#230]: https://github.com/gotgenes/pi-packages/issues/230
956
+ [#231]: https://github.com/gotgenes/pi-packages/issues/231
957
+ [#232]: https://github.com/gotgenes/pi-packages/issues/232