@gotgenes/pi-subagents 9.0.1 → 10.0.1

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,37 @@ 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
+ ## [10.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v10.0.0...pi-subagents-v10.0.1) (2026-05-27)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * mark Phase 14 Step 3 complete in architecture ([fb374ba](https://github.com/gotgenes/pi-packages/commit/fb374ba5829945a4d2f71d8a611bc5128cdd3bf1))
14
+ * plan collapse filterActiveTools to recursion guard ([#239](https://github.com/gotgenes/pi-packages/issues/239)) ([411b22e](https://github.com/gotgenes/pi-packages/commit/411b22ef8186e5fe73bfa81672edfe473ad9d76a))
15
+ * **retro:** add planning stage notes for issue [#239](https://github.com/gotgenes/pi-packages/issues/239) ([c0383b1](https://github.com/gotgenes/pi-packages/commit/c0383b1d9333b70d61a80b75cd1e6e2724d91ad3))
16
+ * **retro:** add retro notes for issue [#242](https://github.com/gotgenes/pi-packages/issues/242) ([69c8cc2](https://github.com/gotgenes/pi-packages/commit/69c8cc269f6dfd6552aecb6d073ea86bb22267dd))
17
+ * **retro:** add TDD stage notes for issue [#239](https://github.com/gotgenes/pi-packages/issues/239) ([f4098a0](https://github.com/gotgenes/pi-packages/commit/f4098a084564dd75bd683449a2aa3926ae36cba3))
18
+
19
+ ## [10.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v9.0.1...pi-subagents-v10.0.0) (2026-05-27)
20
+
21
+
22
+ ### ⚠ BREAKING CHANGES
23
+
24
+ * The tool name changes from "Agent" to "subagent". Any AGENTS.md files, prompt templates, or custom agent configs that reference the tool by name will need updating.
25
+
26
+ ### Features
27
+
28
+ * rename Agent tool to subagent ([#242](https://github.com/gotgenes/pi-packages/issues/242)) ([8b1c310](https://github.com/gotgenes/pi-packages/commit/8b1c310d8fd2d797b0d116fdaf2d4b28ea5b41ce))
29
+
30
+
31
+ ### Documentation
32
+
33
+ * **pi-subagents:** add Phase 14 Step 4 — rename Agent tool to subagent ([#242](https://github.com/gotgenes/pi-packages/issues/242)) ([2a3cd9f](https://github.com/gotgenes/pi-packages/commit/2a3cd9f25dce042a77d28da1d17a6d8c7870dddf))
34
+ * plan rename Agent tool to subagent ([#242](https://github.com/gotgenes/pi-packages/issues/242)) ([41fe2a4](https://github.com/gotgenes/pi-packages/commit/41fe2a4033a49b39fbbe3938580c6afaeefa9d47))
35
+ * **retro:** add planning stage notes for issue [#242](https://github.com/gotgenes/pi-packages/issues/242) ([6211bed](https://github.com/gotgenes/pi-packages/commit/6211bede3cde8e283b05ed81cc1652e2e370cd9b))
36
+ * **retro:** add TDD stage notes for issue [#242](https://github.com/gotgenes/pi-packages/issues/242) ([7ec9ead](https://github.com/gotgenes/pi-packages/commit/7ec9eadc3544a4c73ce1d3e491cc29da3fddbe16))
37
+ * update tool name references after Agent → subagent rename ([#242](https://github.com/gotgenes/pi-packages/issues/242)) ([0b6774d](https://github.com/gotgenes/pi-packages/commit/0b6774dbe049320bb7919f1f09c6f4c090eb91c5))
38
+
8
39
  ## [9.0.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v9.0.0...pi-subagents-v9.0.1) (2026-05-27)
9
40
 
10
41
 
package/README.md CHANGED
@@ -16,7 +16,7 @@ Run them in foreground or background, steer them mid-run, resume completed sessi
16
16
 
17
17
  ## Features
18
18
 
19
- - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
19
+ - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`subagent`, `get_subagent_result`, `steer_subagent`) — feels native
20
20
  - **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 4) and individual completion notifications
21
21
  - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons
22
22
  - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause)
@@ -49,10 +49,10 @@ pi -e ./src/index.ts
49
49
 
50
50
  ## Quick Start
51
51
 
52
- The parent agent spawns sub-agents using the `Agent` tool:
52
+ The parent agent spawns sub-agents using the `subagent` tool:
53
53
 
54
54
  ```text
55
- Agent({
55
+ subagent({
56
56
  subagent_type: "Explore",
57
57
  prompt: "Find all files that handle authentication",
58
58
  description: "Find auth files",
@@ -163,7 +163,7 @@ Report findings with file paths, line numbers, severity, and remediation advice.
163
163
  Then spawn it like any built-in type:
164
164
 
165
165
  ```text
166
- Agent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
166
+ subagent({ subagent_type: "auditor", prompt: "Review the auth module", description: "Security audit" })
167
167
  ```
168
168
 
169
169
  ### Frontmatter Fields
@@ -190,11 +190,11 @@ All fields are optional — sensible defaults for everything.
190
190
 
191
191
  Frontmatter is authoritative.
192
192
  If an agent file sets `model`, `thinking`, `max_turns`, `inherit_context`, `run_in_background`, `isolated`, or `isolation`, those values are locked for that agent.
193
- `Agent` tool parameters only fill fields the agent config leaves unspecified.
193
+ `subagent` tool parameters only fill fields the agent config leaves unspecified.
194
194
 
195
195
  ## Tools
196
196
 
197
- ### `Agent`
197
+ ### `subagent`
198
198
 
199
199
  Launch a sub-agent.
200
200
 
@@ -359,7 +359,7 @@ This prevents unintended tool escalation.
359
359
  Set `isolation: worktree` to run an agent in a temporary git worktree:
360
360
 
361
361
  ```text
362
- Agent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
362
+ subagent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
363
363
  ```
364
364
 
365
365
  The agent gets a full, isolated copy of the repository.
@@ -368,7 +368,7 @@ On completion:
368
368
  - **No changes:** worktree is cleaned up automatically
369
369
  - **Changes made:** changes are committed to a new branch (`pi-agent-<id>`) and returned in the result
370
370
 
371
- If the worktree cannot be created (not a git repo, no commits, or `git worktree add` fails), the `Agent` tool returns a clear error instead of running unisolated — `isolation: "worktree"` is a strict guarantee, not a hint.
371
+ If the worktree cannot be created (not a git repo, no commits, or `git worktree add` fails), the `subagent` tool returns a clear error instead of running unisolated — `isolation: "worktree"` is a strict guarantee, not a hint.
372
372
  Initialize git and commit at least once, or omit `isolation`.
373
373
 
374
374
  ## Skill Preloading
@@ -69,7 +69,7 @@ flowchart TB
69
69
 
70
70
  subgraph tools["Tools domain"]
71
71
  direction TB
72
- AgentTool["Agent tool\n(dispatch)"]
72
+ AgentTool["subagent tool\n(dispatch)"]
73
73
  ResultRenderer["result-renderer\n(pure rendering)"]
74
74
  SpawnConfig["spawn-config\n(resolve params)"]
75
75
  FgRunner["foreground-runner"]
@@ -200,14 +200,14 @@ Other terminal transitions guard against overwriting `stopped` — once an agent
200
200
  ```mermaid
201
201
  sequenceDiagram
202
202
  participant LLM as Parent LLM
203
- participant Tool as Agent tool
203
+ participant Tool as subagent tool
204
204
  participant Spawn as spawn-config
205
205
  participant Mgr as AgentManager
206
206
  participant Runner as agent-runner
207
207
  participant Asm as assembleSessionConfig
208
208
  participant Child as Child session
209
209
 
210
- LLM->>Tool: Agent(type, prompt, ...)
210
+ LLM->>Tool: subagent(type, prompt, ...)
211
211
  Tool->>Spawn: resolveSpawnConfig(params)
212
212
  Spawn-->>Tool: ResolvedSpawnConfig
213
213
  Tool->>Mgr: spawn(snapshot, type, prompt, config)
@@ -279,7 +279,7 @@ src/
279
279
  │ └── service-adapter.ts SubagentsServiceAdapter class wrapping AgentManager
280
280
 
281
281
  ├── tools/ LLM-facing tool implementations
282
- │ ├── agent-tool.ts Agent tool definition, validation, dispatch
282
+ │ ├── agent-tool.ts subagent tool definition, validation, dispatch
283
283
  │ ├── result-renderer.ts pure per-status result rendering
284
284
  │ ├── spawn-config.ts pure config resolution
285
285
  │ ├── foreground-runner.ts foreground execution loop
@@ -323,7 +323,7 @@ flowchart TD
323
323
  subgraph core["@gotgenes/pi-subagents"]
324
324
  direction TB
325
325
  exports["SubagentsService API<br/>publish / getSubagentsService<br/>SubagentRecord, SubagentStatus"]
326
- engine["Tools: Agent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, agent-runner"]
326
+ engine["Tools: subagent, get_subagent_result,<br/>steer_subagent<br/>AgentManager, agent-runner"]
327
327
  ui_int["Internal UI: widget, viewer,<br/>/agents menu"]
328
328
  end
329
329
 
@@ -337,7 +337,7 @@ They declare this package as an optional peer dependency and use dynamic import
337
337
 
338
338
  ### What the core owns
339
339
 
340
- - The three tools: `Agent`, `get_subagent_result`, `steer_subagent`.
340
+ - The three tools: `subagent` (née `Agent`), `get_subagent_result`, `steer_subagent`.
341
341
  - `AgentManager` — spawn, queue, abort, resume, concurrency control.
342
342
  - `agent-runner` — session creation, turn loop, extension binding.
343
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.
@@ -453,7 +453,7 @@ The target state eliminates this overlap and flips the dependency direction.
453
453
 
454
454
  ### Responsibilities to remove
455
455
 
456
- - **Tool policy** (`disallowed_tools`, `ToolFilterConfig.disallowedSet`) — access control belongs in pi-permission-system's `permission:` frontmatter.
456
+ - **Tool policy** (`disallowed_tools`) — access control belongs in pi-permission-system's `permission:` frontmatter.
457
457
  - **Extension filtering** (`extensions: string[]` allowlist) — tool visibility is pi-permission-system's job.
458
458
  - **Permission bridge** (`permission-bridge.ts`) — outbound coupling to pi-permission-system.
459
459
  Replaced by lifecycle events that pi-permission-system listens for.
@@ -496,7 +496,7 @@ Bags with 10+ fields are the highest priority for decomposition.
496
496
  | `ResolvedSpawnConfig` | 3 nested | foreground-runner, background-spawner, agent-tool | ✓ done |
497
497
  | `AgentSpawnConfig` | 13 → 13 (ParentSessionInfo nested) | agent-manager (internal) | ✓ done |
498
498
  | `RunOptions` | 9 (`RunContext` nested) | agent-runner | ✓ done |
499
- | `SessionConfig` | 8 (ToolFilterConfig nested) | agent-runner (output of assembler) | ✓ done |
499
+ | `SessionConfig` | 8 (flat fields, ToolFilterConfig removed) | agent-runner (output of assembler) | ✓ done |
500
500
  | `NotificationDetails` | 10 | notification | Low (DTO) |
501
501
  | `ResourceLoaderOptions` | 10 | agent-runner (SDK bridge) | Low (SDK) |
502
502
  | `RunnerIO` | split → `EnvironmentIO` (3) + `SessionFactoryIO` (5+1) | agent-runner | ✓ done |
@@ -677,8 +677,9 @@ Removing it simplifies `runAgent`, shrinks `AgentConfig` and `SessionConfig`, an
677
677
  | ----------------------------------------------------------------------------------------- | ------------- | ------ | ---- | -------- |
678
678
  | `disallowed_tools` duplicates pi-permission-system's `permission:` frontmatter | A: Overlap | 4 | 2 | 12 |
679
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 |
680
+ | `filterActiveTools` ran twice (pre-bind + post-bind) to catch extension-registered tools | B: Complexity | 3 | 1 | ✓ done |
681
+ | `ToolFilterConfig` existed solely to carry filtering state through the runner | C: Accidental | 2 | 1 | ✓ done |
682
+ | `Agent` tool name is PascalCase — misaligns with Pi's lowercase built-in convention | D: Convention | 2 | 1 | 3 |
682
683
 
683
684
  ### Step 1: Remove `disallowed_tools` — [#237] ✅ Complete
684
685
 
@@ -712,22 +713,40 @@ The `extensions: false` case (used by `isolated`) is retained in this step and r
712
713
  - Smell: A (tool filtering disguised as extension lifecycle control)
713
714
  - Outcome: `filterActiveTools` reduces to two concerns: recursion guard and `extensions: false` passthrough
714
715
 
715
- ### Step 3: Collapse `filterActiveTools` to recursion guard — [#239]
716
+ ### Step 3: Collapse `filterActiveTools` to recursion guard — [#239] ✅ Complete
716
717
 
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.
718
+ With Steps 1–2 complete, `filterActiveTools` had only two remaining branches: the `EXCLUDED_TOOL_NAMES` recursion guard and the `extensions === false` passthrough.
719
+ Inlined the `extensions === false` passthrough into the callsite and reduced the function to its essential purpose.
719
720
 
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.
721
+ 1. Simplified `filterActiveTools` to filter only `EXCLUDED_TOOL_NAMES`.
722
+ 2. Removed `ToolFilterConfig` — the function no longer needs a config bag.
723
+ 3. Removed the pre-bind filter call — extension tools aren't in the active set pre-bind, so the guard is a no-op there.
724
+ 4. Kept a single post-bind filter call for the recursion guard.
725
+ 5. Flattened `SessionConfig` — removed `toolFilter: ToolFilterConfig`; `toolNames` and `extensions` are top-level fields.
726
+ 6. Updated tests.
726
727
 
727
728
  - Target: `agent-runner.ts`, `session-config.ts`
728
729
  - Smell: B (accidental complexity), C (two-pass filter dance)
729
730
  - Outcome: `filterActiveTools` is a one-liner; `SessionConfig` loses one nested type; the pre-bind/post-bind dance is gone
730
731
 
732
+ ### Step 4: Rename `Agent` tool to `subagent` — [#242] ✅ Complete
733
+
734
+ Rename the `Agent` tool to `subagent` to align with Pi's built-in tool naming convention (all lowercase: `read`, `bash`, `write`, `edit`, `find`, `grep`, `ls`).
735
+ The PascalCase name was inherited from tintinweb/pi-subagents (mimicking Claude Code's convention), but Pi uses lowercase for all built-in tools.
736
+ The companion tools are already lowercase snake_case (`get_subagent_result`, `steer_subagent`), and nicobailon/pi-subagents already uses `subagent`.
737
+
738
+ 1. Rename tool name from `"Agent"` to `"subagent"` in `agent-tool.ts`.
739
+ 2. Update `label` and `promptSnippet` in the tool definition.
740
+ 3. Update `EXCLUDED_TOOL_NAMES` in `agent-runner.ts`.
741
+ 4. Update the fallback display name in `agent-tool.ts`.
742
+ 5. Update architecture docs (tool references, domain diagrams, cross-extension section).
743
+ 6. Update pi-permission-system docs that reference the `Agent` tool name.
744
+ 7. Update tests.
745
+
746
+ - Target: `tools/agent-tool.ts`, `lifecycle/agent-runner.ts`, `docs/`, `../pi-permission-system/docs/`
747
+ - Smell: D (convention mismatch — PascalCase in a lowercase ecosystem)
748
+ - Outcome: all three tools use consistent lowercase naming; aligns with Pi's built-in convention
749
+
731
750
  ### Step dependency diagram
732
751
 
733
752
  ```mermaid
@@ -735,17 +754,19 @@ flowchart LR
735
754
  S1["Step 1\nRemove disallowed_tools"]
736
755
  S2["Step 2\nRemove extensions filtering"]
737
756
  S3["Step 3\nCollapse filterActiveTools"]
757
+ S4["Step 4\nRename Agent to subagent"]
738
758
 
739
759
  S1 --> S3
740
760
  S2 --> S3
741
761
  ```
742
762
 
743
- Steps 1 and 2 are independent and can proceed in parallel.
744
- Step 3 depends on both.
763
+ Steps 1, 2, and 4 are independent and can proceed in parallel.
764
+ Step 3 depends on Steps 1 and 2.
745
765
 
746
766
  [#237]: https://github.com/gotgenes/pi-packages/issues/237
747
767
  [#238]: https://github.com/gotgenes/pi-packages/issues/238
748
768
  [#239]: https://github.com/gotgenes/pi-packages/issues/239
769
+ [#242]: https://github.com/gotgenes/pi-packages/issues/242
749
770
 
750
771
  ## Improvement roadmap (Phase 15 — domain model evolution)
751
772
 
@@ -0,0 +1,217 @@
1
+ ---
2
+ issue: 239
3
+ issue_title: "Collapse filterActiveTools to recursion guard (Phase 14, Step 3)"
4
+ ---
5
+
6
+ # Collapse `filterActiveTools` to recursion guard
7
+
8
+ ## Problem Statement
9
+
10
+ With `disallowed_tools` (#237) and `extensions` filtering (#238) removed, `filterActiveTools` retains two branches that no longer justify a config bag or two-pass pre-bind/post-bind dance:
11
+
12
+ 1. The `extensions === false` early return — a passthrough that belongs at the callsite.
13
+ 2. The `EXCLUDED_TOOL_NAMES` recursion guard — the function's sole essential purpose.
14
+
15
+ The `builtinToolNameSet` membership check always returns `true` now (no string-array `extensions` filtering remains), making it dead logic.
16
+ `ToolFilterConfig` exists only to carry two fields (`toolNames`, `extensions`) through the assembler→runner boundary, but after this change they travel independently: `toolNames` feeds `createSession`, `extensions` feeds the resource loader's `noExtensions` flag, and neither is consumed by the filter function.
17
+
18
+ ## Goals
19
+
20
+ - Reduce `filterActiveTools` to a one-liner: filter out `EXCLUDED_TOOL_NAMES`.
21
+ - Delete the `ToolFilterConfig` interface.
22
+ - Flatten `SessionConfig.toolFilter` back into two top-level fields: `toolNames` and `extensions`.
23
+ - Remove the pre-bind filter call — without denylist/allowlist logic, filtering before `bindExtensions` serves no purpose.
24
+ - Keep a single post-bind filter call for the recursion guard.
25
+ - Update tests to reflect the simplified flow.
26
+
27
+ ## Non-Goals
28
+
29
+ - Removing `extensions` from `SessionConfig` entirely — it's still needed for the `noExtensions` flag on the resource loader.
30
+ - Renaming `EXCLUDED_TOOL_NAMES` or moving it to a separate module.
31
+ - Phase 15 domain model changes (#227–#232) — those operate on the simplified codebase this change produces.
32
+
33
+ ## Background
34
+
35
+ `filterActiveTools` was extracted as part of Phase 10 (#168) to group `toolNames`, `disallowedSet`, and `extensions` into a `ToolFilterConfig` bag.
36
+ Issue #237 removed `disallowedSet`; #238 narrowed `extensions` from `true | string[] | false` to `boolean`.
37
+ Both are now closed.
38
+
39
+ The two-pass filter dance (Patch 2, RepOne #443) exists to catch extension-registered tools that join the active set during `bindExtensions`.
40
+ With the only remaining filter logic being the `EXCLUDED_TOOL_NAMES` guard, a single post-bind pass suffices.
41
+
42
+ Current `filterActiveTools` body:
43
+
44
+ ```typescript
45
+ function filterActiveTools(
46
+ activeTools: string[],
47
+ config: ToolFilterConfig,
48
+ ): string[] {
49
+ const { toolNames, extensions } = config;
50
+ if (!extensions) {
51
+ return activeTools;
52
+ }
53
+ const builtinToolNameSet = new Set(toolNames);
54
+ return activeTools.filter((t) => {
55
+ if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
56
+ if (builtinToolNameSet.has(t)) return true;
57
+ return true;
58
+ });
59
+ }
60
+ ```
61
+
62
+ The `builtinToolNameSet` check is dead — both branches return `true`.
63
+ The `!extensions` early return belongs at the callsite.
64
+
65
+ ### Affected files
66
+
67
+ - `src/lifecycle/agent-runner.ts` — `filterActiveTools`, pre-bind/post-bind calls, `ToolFilterConfig` import
68
+ - `src/session/session-config.ts` — `ToolFilterConfig` interface, `SessionConfig.toolFilter` field, assembler return literal
69
+ - `test/lifecycle/agent-runner-extension-tools.test.ts` — pre-bind/post-bind assertions
70
+ - `test/session/session-config.test.ts` — `toolFilter.*` assertions
71
+ - `test/lifecycle/agent-runner.test.ts` — session mock (`setActiveToolsByName` calls)
72
+ - `docs/architecture/architecture.md` — references to `ToolFilterConfig`, `filterActiveTools`, Phase 14 status
73
+
74
+ ## Design Overview
75
+
76
+ ### `filterActiveTools` simplification
77
+
78
+ The function reduces to:
79
+
80
+ ```typescript
81
+ function filterActiveTools(activeTools: string[]): string[] {
82
+ return activeTools.filter((t) => !EXCLUDED_TOOL_NAMES.includes(t));
83
+ }
84
+ ```
85
+
86
+ No config parameter.
87
+ The `extensions === false` guard moves to the callsite: `if (cfg.extensions)`.
88
+
89
+ ### `SessionConfig` flattening
90
+
91
+ ```typescript
92
+ export interface SessionConfig {
93
+ effectiveCwd: string;
94
+ systemPrompt: string;
95
+ toolNames: string[]; // was toolFilter.toolNames
96
+ extensions: boolean; // was toolFilter.extensions
97
+ model: unknown;
98
+ thinkingLevel: ThinkingLevel | undefined;
99
+ noSkills: boolean;
100
+ extras: PromptExtras;
101
+ agentMaxTurns: number | undefined;
102
+ }
103
+ ```
104
+
105
+ ### `runAgent` callsite changes
106
+
107
+ Before (two-pass):
108
+
109
+ ```typescript
110
+ if (cfg.toolFilter.extensions) {
111
+ const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
112
+ session.setActiveToolsByName(filtered);
113
+ }
114
+ // ... bindExtensions ...
115
+ if (cfg.toolFilter.extensions) {
116
+ const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
117
+ session.setActiveToolsByName(refiltered);
118
+ }
119
+ ```
120
+
121
+ After (single post-bind pass):
122
+
123
+ ```typescript
124
+ // ... bindExtensions ...
125
+ if (cfg.extensions) {
126
+ const filtered = filterActiveTools(session.getActiveToolNames());
127
+ session.setActiveToolsByName(filtered);
128
+ }
129
+ ```
130
+
131
+ Other `cfg.toolFilter.*` references update to `cfg.toolNames` and `cfg.extensions`.
132
+
133
+ ## Module-Level Changes
134
+
135
+ ### `src/session/session-config.ts`
136
+
137
+ 1. Delete the `ToolFilterConfig` interface.
138
+ 2. Replace `toolFilter: ToolFilterConfig` on `SessionConfig` with two flat fields: `toolNames: string[]` and `extensions: boolean`.
139
+ 3. Update the return literal in `assembleSessionConfig` from `toolFilter: { toolNames, extensions }` to `toolNames, extensions`.
140
+ 4. Update the JSDoc on `SessionConfig` — remove "Tool filtering cluster" comment.
141
+
142
+ ### `src/lifecycle/agent-runner.ts`
143
+
144
+ 1. Remove the `ToolFilterConfig` import.
145
+ 2. Simplify `filterActiveTools` to `(activeTools: string[]) => string[]` — just the `EXCLUDED_TOOL_NAMES` filter.
146
+ 3. Remove the pre-bind filter block (the first `if (cfg.toolFilter.extensions)` block).
147
+ 4. Update the post-bind filter block: `cfg.toolFilter.extensions` → `cfg.extensions`, remove the config argument from the `filterActiveTools` call.
148
+ 5. Update `noExtensions: !cfg.toolFilter.extensions` → `noExtensions: !cfg.extensions`.
149
+ 6. Update `tools: cfg.toolFilter.toolNames` → `tools: cfg.toolNames`.
150
+ 7. Update or remove the Patch 2 comments — the two-pass dance is gone; the remaining call is just a recursion guard.
151
+
152
+ ### `test/lifecycle/agent-runner-extension-tools.test.ts`
153
+
154
+ 1. Remove the pre-bind/post-bind ordering assertions — there is only one post-bind call now.
155
+ 2. Update `setActiveToolsByName` call count expectations from 2 to 1.
156
+ 3. Update assertions to check `setActiveToolsByName.mock.calls[0][0]` (was `calls[1][0]` for the second call).
157
+ 4. The `extensions: false` test continues to assert that `setActiveToolsByName` is not called.
158
+
159
+ ### `test/session/session-config.test.ts`
160
+
161
+ 1. Update `result.toolFilter.toolNames` → `result.toolNames`.
162
+ 2. Update `result.toolFilter.extensions` → `result.extensions`.
163
+
164
+ ### `test/lifecycle/agent-runner.test.ts`
165
+
166
+ 1. No structural changes needed — the session mock already has `setActiveToolsByName: vi.fn()`, and the default test config has `extensions: false` (so the filter doesn't run).
167
+ If any test asserts on `setActiveToolsByName` call counts, verify they still pass.
168
+
169
+ ### `docs/architecture/architecture.md`
170
+
171
+ 1. Update the Phase 14 Step 3 entry to mark it complete.
172
+ 2. Update the structural analysis table: `SessionConfig` field count changes, `ToolFilterConfig` is removed.
173
+ 3. Update the smell table to mark the two-pass filter and `ToolFilterConfig` smells as resolved.
174
+
175
+ ## Test Impact Analysis
176
+
177
+ 1. **New tests enabled:** None — the simplification doesn't introduce new testable surface.
178
+ 2. **Tests that become redundant:** The pre-bind/post-bind ordering test in `agent-runner-extension-tools.test.ts` — the pre-bind call is removed.
179
+ The test that the post-bind filter includes extension tools stays; it verifies the recursion guard runs after `bindExtensions`.
180
+ 3. **Tests that stay as-is:** The `extensions: false` skip test, the `EXCLUDED_TOOL_NAMES` exclusion test, all `session-config.test.ts` tests (with property path updates).
181
+
182
+ ## TDD Order
183
+
184
+ 1. **Flatten `SessionConfig` and delete `ToolFilterConfig`** — Replace `toolFilter: ToolFilterConfig` with `toolNames: string[]` and `extensions: boolean` on `SessionConfig`.
185
+ Delete the `ToolFilterConfig` interface.
186
+ Update the assembler return literal.
187
+ Update `session-config.test.ts` property paths (`result.toolFilter.toolNames` → `result.toolNames`, `result.toolFilter.extensions` → `result.extensions`).
188
+ Run `pnpm run check` to verify downstream compile errors (expected in `agent-runner.ts`).
189
+ Commit: `refactor: flatten SessionConfig and remove ToolFilterConfig`
190
+
191
+ 2. **Simplify `filterActiveTools` and remove pre-bind call** — Reduce `filterActiveTools` to `(activeTools: string[]) => string[]`.
192
+ Remove the `ToolFilterConfig` import.
193
+ Remove the pre-bind filter block.
194
+ Update the post-bind filter block to use `cfg.extensions` and pass no config to `filterActiveTools`.
195
+ Update `noExtensions` and `tools` references to `cfg.extensions` and `cfg.toolNames`.
196
+ Update `agent-runner-extension-tools.test.ts`: change `setActiveToolsByName` call count from 2 to 1, update assertion indices from `calls[1]` to `calls[0]`, remove the pre-bind/post-bind ordering test, update comments.
197
+ Verify `agent-runner.test.ts` still passes.
198
+ Commit: `refactor: simplify filterActiveTools to recursion guard`
199
+
200
+ 3. **Update architecture docs** — Mark Phase 14 Step 3 as complete.
201
+ Update `SessionConfig` field count and remove `ToolFilterConfig` references from the structural analysis.
202
+ Mark the two-pass filter and `ToolFilterConfig` smells as resolved.
203
+ Commit: `docs: mark Phase 14 Step 3 complete in architecture`
204
+
205
+ ## Risks and Mitigations
206
+
207
+ 1. **Pre-bind filter removal may miss a recursion-guard edge case** — If a subagent's own tools (`subagent`, `get_subagent_result`, `steer_subagent`) were in the built-in tool set before `bindExtensions`, removing the pre-bind filter could leave them active during the bind phase.
208
+ Mitigation: These tools are registered by this extension during `bindExtensions`, not before.
209
+ They cannot be in the pre-bind active set.
210
+ The post-bind filter is sufficient.
211
+
212
+ 2. **Flattening `SessionConfig` may break external consumers** — `SessionConfig` is not exported from the package entry point; it's an internal interface between `session-config.ts` and `agent-runner.ts`.
213
+ No external consumers exist.
214
+
215
+ ## Open Questions
216
+
217
+ None — the issue's changes are unambiguous and all dependencies are complete.
@@ -0,0 +1,165 @@
1
+ ---
2
+ issue: 242
3
+ issue_title: "Rename `Agent` tool to `subagent`"
4
+ ---
5
+
6
+ # Rename `Agent` tool to `subagent`
7
+
8
+ ## Problem Statement
9
+
10
+ The `Agent` tool is the only PascalCase tool name in the Pi ecosystem.
11
+ Pi's built-in tools are all lowercase (`read`, `bash`, `write`, `edit`, `find`, `grep`, `ls`), and the companion tools in this package already use lowercase snake_case (`get_subagent_result`, `steer_subagent`).
12
+ The PascalCase name was inherited from tintinweb/pi-subagents, which mimicked Claude Code's convention rather than Pi's.
13
+
14
+ ## Goals
15
+
16
+ - Rename the tool from `"Agent"` to `"subagent"` — a **breaking change** (`feat!:`).
17
+ - Update `label`, `promptSnippet`, and `description` text to reference `subagent` instead of `Agent`.
18
+ - Update `EXCLUDED_TOOL_NAMES` in `agent-runner.ts`.
19
+ - Update the fallback display name in `agent-tool.ts` `renderCall`.
20
+ - Update architecture docs that reference the `Agent` tool name.
21
+ - Update `README.md` tool-name references.
22
+ - Update all affected tests.
23
+
24
+ ## Non-Goals
25
+
26
+ - Renaming the `displayName` of the general-purpose agent type (`"Agent"` in `default-agents.ts` and `agent-types.ts` fallback) — that is a UI display name for the agent *type*, not the tool name.
27
+ The widget shows "Agent" as the display name for general-purpose agents; this is a separate concern.
28
+ - Renaming the companion tools `get_subagent_result` and `steer_subagent` — they already follow the lowercase convention.
29
+ - Updating the companion tools' `label` fields (`"Steer Agent"`, `"Get Agent Result"`) — these use "Agent" in the human-readable sense, not as the tool name.
30
+ - Collapsing `filterActiveTools` (#239) — that step is independent within Phase 14.
31
+ - Updating pi-permission-system docs — verified that no docs there reference the `Agent` tool name specifically.
32
+ All "Agent" references in that package's docs refer to the agent-name concept (per-agent overrides, agent frontmatter), not the tool.
33
+
34
+ ## Background
35
+
36
+ Phase 14 of the architecture roadmap strips policy enforcement from pi-subagents.
37
+ Steps 1 and 2 (#237, #238) are complete; Step 3 (#239, collapse `filterActiveTools`) is open but independent of this rename.
38
+ This is Step 4 — the final convention-alignment step in Phase 14.
39
+
40
+ ### Files affected
41
+
42
+ | File | Current `"Agent"` references |
43
+ | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
44
+ | `src/tools/agent-tool.ts` | `name`, `label`, `promptSnippet`, description text, `renderCall` fallback (line 247) |
45
+ | `src/lifecycle/agent-runner.ts` | `EXCLUDED_TOOL_NAMES` array (line 22) |
46
+ | `README.md` | Tool name references, usage examples, tool parameter table heading |
47
+ | `docs/architecture/architecture.md` | Directory listing comment, findings table, Step 4 description |
48
+ | `test/tools/agent-tool.test.ts` | `name` and `label` assertions |
49
+ | `test/lifecycle/agent-runner-extension-tools.test.ts` | `"Agent"` in active-tools arrays and `not.toContain` assertion |
50
+ | `test/print-mode.test.ts` | `tools.get("Agent")` lookup |
51
+ | `test/widget-renderer.test.ts` | `"Agent"` in display-name comment and assertion |
52
+ | `test/tools/spawn-config.test.ts` | `displayName: "Agent"` assertions (general-purpose type display name — **no change**, these test the agent type's displayName, not the tool name) |
53
+ | `test/tools/foreground-runner.test.ts` | `displayName: "Agent"` in fixture data (**no change** — tests general-purpose type displayName) |
54
+ | `test/tools/helpers.test.ts` | `displayName: "Agent"` in fixture data (**no change** — tests result rendering, not tool name) |
55
+ | `test/display.test.ts` | `"Agent"` display name assertion (**no change** — tests `getDisplayName` for general-purpose type) |
56
+
57
+ ## Design Overview
58
+
59
+ This is a straightforward string-replacement change with no logic or type changes.
60
+ The tool's `name` field drives Pi's tool registration, system-prompt toolbox, and LLM invocations.
61
+
62
+ ### Tool definition changes
63
+
64
+ ```typescript
65
+ // Before
66
+ name: "Agent" as const,
67
+ label: "Agent",
68
+ promptSnippet: "Agent: Launch a specialized agent for complex, multi-step tasks.",
69
+
70
+ // After
71
+ name: "subagent" as const,
72
+ label: "Subagent",
73
+ promptSnippet: "subagent: Launch a specialized agent for complex, multi-step tasks.",
74
+ ```
75
+
76
+ The description body changes `"The Agent tool launches"` → `"The subagent tool launches"` and `"Agent results are returned"` → `"Subagent results are returned"`.
77
+
78
+ ### renderCall fallback
79
+
80
+ ```typescript
81
+ // Before
82
+ : "Agent";
83
+ // After
84
+ : "Subagent";
85
+ ```
86
+
87
+ This fallback shows when no `subagent_type` is provided.
88
+ Using `"Subagent"` (capitalized for display) is appropriate — it mirrors the tool's identity without conflating with the general-purpose agent type's `displayName`.
89
+
90
+ ### EXCLUDED_TOOL_NAMES
91
+
92
+ ```typescript
93
+ // Before
94
+ const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
95
+ // After
96
+ const EXCLUDED_TOOL_NAMES = ["subagent", "get_subagent_result", "steer_subagent"];
97
+ ```
98
+
99
+ ## Module-Level Changes
100
+
101
+ ### `src/tools/agent-tool.ts`
102
+
103
+ 1. Change `name: "Agent" as const` → `name: "subagent" as const`.
104
+ 2. Change `label: "Agent"` → `label: "Subagent"`.
105
+ 3. Change `promptSnippet` to start with `subagent:` instead of `Agent:`.
106
+ 4. Change `"The Agent tool launches"` → `"The subagent tool launches"` in description.
107
+ 5. Change `"Agent results are returned"` → `"Subagent results are returned"` in description guidelines.
108
+ 6. Change fallback `"Agent"` → `"Subagent"` in `renderCall`.
109
+
110
+ ### `src/lifecycle/agent-runner.ts`
111
+
112
+ 1. Change `"Agent"` → `"subagent"` in `EXCLUDED_TOOL_NAMES`.
113
+
114
+ ### `README.md`
115
+
116
+ 1. Update tool name references in the "features" bullet (`` `Agent` `` → `` `subagent` ``).
117
+ 2. Update usage example heading and code block (`Agent({` → `subagent({`).
118
+ 3. Update tool parameter table heading (`### \`Agent\`` → `### \`subagent\``).
119
+ 4. Update prose references to the `Agent` tool ("`Agent` tool parameters", "`Agent` tool returns").
120
+ 5. Update widget example display — the widget shows `Agent` because the general-purpose type's `displayName` is `"Agent"`, so those lines stay as-is.
121
+
122
+ ### `docs/architecture/architecture.md`
123
+
124
+ 1. Update the directory listing comment: `agent-tool.ts` description → `subagent tool definition`.
125
+ 2. Update the findings table row to mark the item as resolved.
126
+ 3. Update Step 4 description to indicate completion.
127
+ 4. The `(née \`Agent\`)` parenthetical in the "What the core owns" section already anticipates the rename — keep it as-is for historical context.
128
+
129
+ ### Test files
130
+
131
+ 1. `test/tools/agent-tool.test.ts` — update `name` and `label` assertions.
132
+ 2. `test/lifecycle/agent-runner-extension-tools.test.ts` — update `"Agent"` in active-tools arrays and `.not.toContain("Agent")` → `.not.toContain("subagent")`.
133
+ 3. `test/print-mode.test.ts` — update `tools.get("Agent")` → `tools.get("subagent")`.
134
+ 4. `test/widget-renderer.test.ts` — update the comment text (the assertion tests general-purpose displayName, which stays `"Agent"`).
135
+
136
+ ## Test Impact Analysis
137
+
138
+ 1. No new tests are needed — this is a string-value change.
139
+ 2. Existing tests that assert the tool name `"Agent"` must be updated to assert `"subagent"`.
140
+ 3. Tests that assert the general-purpose agent type's `displayName` (`"Agent"`) are **not affected** — the `displayName` is an agent-type property, not the tool name.
141
+
142
+ ## TDD Order
143
+
144
+ 1. **Update tool definition and runner constant.**
145
+ Change `name`, `label`, `promptSnippet`, description text, and `renderCall` fallback in `agent-tool.ts`.
146
+ Change `EXCLUDED_TOOL_NAMES` in `agent-runner.ts`.
147
+ Update affected tests in `agent-tool.test.ts`, `agent-runner-extension-tools.test.ts`, `print-mode.test.ts`, and `widget-renderer.test.ts`.
148
+ Commit: `feat!: rename Agent tool to subagent (#242)`
149
+
150
+ 2. **Update documentation.**
151
+ Update `README.md` tool-name references and usage examples.
152
+ Update `docs/architecture/architecture.md` directory listing, findings table, and Step 4 status.
153
+ Commit: `docs: update tool name references after Agent → subagent rename (#242)`
154
+
155
+ ## Risks and Mitigations
156
+
157
+ | Risk | Mitigation |
158
+ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
159
+ | Breaking change for users referencing `Agent` in AGENTS.md, prompt templates, or custom configs | This is intentional and documented in the issue. Fits within the Phase 14 breaking-change window. Use `feat!:` commit prefix. |
160
+ | Forgetting a test reference to `"Agent"` | Grep all test files for `"Agent"` before committing. Distinguish tool-name references from agent-type displayName references. |
161
+ | Companion tool labels (`"Steer Agent"`, `"Get Agent Result"`) use "Agent" | These use "Agent" in the human-readable sense. They are not affected by the tool-name rename and remain consistent. |
162
+
163
+ ## Open Questions
164
+
165
+ None — the issue scope is unambiguous.
@@ -0,0 +1,37 @@
1
+ ---
2
+ issue: 239
3
+ issue_title: "Collapse filterActiveTools to recursion guard (Phase 14, Step 3)"
4
+ ---
5
+
6
+ # Retro: #239 — Collapse filterActiveTools to recursion guard
7
+
8
+ ## Stage: Planning (2026-05-27T20:00:00Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a 3-step TDD plan to flatten `SessionConfig.toolFilter` into top-level `toolNames` and `extensions` fields, simplify `filterActiveTools` to a one-liner recursion guard, remove the pre-bind filter call, and update architecture docs.
13
+ Both dependencies (#237, #238) are confirmed closed.
14
+
15
+ ### Observations
16
+
17
+ - The `builtinToolNameSet` membership check in `filterActiveTools` is fully dead code — both branches return `true` after #238 removed the `string[]` extensions path.
18
+ - `ToolFilterConfig` is only imported by `agent-runner.ts` and never referenced in test files, so deletion is clean.
19
+ - The pre-bind filter call is safe to remove because `EXCLUDED_TOOL_NAMES` tools (`subagent`, `get_subagent_result`, `steer_subagent`) are registered by this extension during `bindExtensions`, not before — they cannot appear in the pre-bind active set.
20
+ - The `agent-runner-extension-tools.test.ts` file has 4 tests; 1 becomes structurally impossible (pre-bind/post-bind ordering) and the remaining 3 need assertion index adjustments (`calls[1]` → `calls[0]`).
21
+ - `SessionConfig` is internal-only (not package-exported), so flattening has no external API impact.
22
+
23
+ ## Stage: Implementation — TDD (2026-05-27T22:00:00Z)
24
+
25
+ ### Session summary
26
+
27
+ Completed all 3 TDD cycles: (1) flattened `SessionConfig` and deleted `ToolFilterConfig`, (2) simplified `filterActiveTools` to a one-liner and removed the pre-bind filter call, (3) updated architecture docs to mark Phase 14 Step 3 complete.
28
+ Test count held at 977 (no net change — the extension-tools test file was rewritten, removing 1 test and updating 3 others while keeping the same total count).
29
+ A follow-up skill maintenance commit updated `.pi/skills/package-pi-subagents/SKILL.md` to remove stale Patch 2 references.
30
+
31
+ ### Observations
32
+
33
+ - The plan's step order (SessionConfig first → expected compile errors in `agent-runner.ts` → green after runner update) worked exactly as designed with no surprises.
34
+ - `agent-runner.test.ts` needed no changes — the default test config has `extensions: false`, so the filter call never ran in those tests.
35
+ - Pre-completion reviewer returned **WARN** for stale `package-pi-subagents` skill content: the "Patch 2 scheduled for removal" note and the `// Patch 2 (RepOne` grep instruction were both stale after #239 completion.
36
+ Fixed immediately as a follow-up `docs:` commit before writing retro notes.
37
+ - `pnpm fallow dead-code` passed with 0 issues — no orphaned exports left behind.
@@ -0,0 +1,82 @@
1
+ ---
2
+ issue: 242
3
+ issue_title: "Rename `Agent` tool to `subagent`"
4
+ ---
5
+
6
+ # Retro: #242 — Rename `Agent` tool to `subagent`
7
+
8
+ ## Stage: Planning (2026-05-27T13:45:29Z)
9
+
10
+ ### Session summary
11
+
12
+ Produced a plan for renaming the `Agent` tool to `subagent` across pi-subagents source, tests, README, and architecture docs.
13
+ Verified that pi-permission-system docs do not reference the `Agent` tool name and require no changes.
14
+ Scoped the plan to two commits: one `feat!:` for source + tests, one `docs:` for documentation.
15
+
16
+ ### Observations
17
+
18
+ - The general-purpose agent type's `displayName` (`"Agent"` in `default-agents.ts` and `agent-types.ts` fallback) is a separate concept from the tool name and stays unchanged.
19
+ Several test files assert this `displayName` — they are not affected by the rename.
20
+ - Issue #239 (Step 3, collapse `filterActiveTools`) is still open but independent — #242 only changes the string value in `EXCLUDED_TOOL_NAMES`, not its structure.
21
+ - The architecture doc already contains `(née \`Agent\`)` in the "What the core owns" section, anticipating the rename.
22
+ - The `widget-renderer.test.ts` comment references `"Agent"` as the general-purpose display name, not the tool name — only the comment text needs updating for clarity.
23
+
24
+ ## Stage: Implementation — TDD (2026-05-27T13:55:33Z)
25
+
26
+ ### Session summary
27
+
28
+ Completed 2 TDD cycles: one `feat!:` commit renaming the tool in source + tests, one `docs:` commit updating `README.md` and `docs/architecture/architecture.md`.
29
+ Baseline was 977 tests; test count unchanged at 977 after the changes.
30
+ Pre-completion reviewer returned **PASS**.
31
+
32
+ ### Observations
33
+
34
+ - All changes were pure string-literal replacements in 2 source files, 4 test files, `README.md`, and the architecture doc — no logic, type, or structural changes.
35
+ - The general-purpose agent type's `displayName: "Agent"` in `default-agents.ts` and `agent-types.ts` fallback was correctly left unchanged; `display.test.ts` still passes with `"Agent"`.
36
+ - The description body inside the `agent-tool.ts` template literal needed separate edits because the guideline lines are not tab-indented (inside a backtick template literal, tab indentation does not apply).
37
+ - Pre-completion reviewer: PASS — all deterministic checks, conventional commits, documentation, code design, tests, Mermaid diagrams, and dead-code gate all passed.
38
+
39
+ ## Stage: Final Retrospective (2026-05-27T14:07:32Z)
40
+
41
+ ### Session summary
42
+
43
+ Completed the full plan→TDD→ship→retro lifecycle for #242 in a single session.
44
+ Released as `pi-subagents-v10.0.0` (major bump from `feat!:` breaking change).
45
+ Found and fixed one stale `Agent` tool reference in `.pi/skills/pre-completion/SKILL.md`.
46
+
47
+ ### Observations
48
+
49
+ #### What went well
50
+
51
+ - Three-model pipeline (opus for planning, sonnet for TDD, deepseek-flash for shipping) matched task complexity to model capability with no quality issues.
52
+ - The plan's distinction between tool name (`"Agent"`) and agent-type `displayName` (`"Agent"`) prevented false-positive test updates — 8 test files reference `"Agent"` but only 4 needed changes.
53
+ - Pre-completion reviewer caught no issues (PASS), confirming thorough planning.
54
+
55
+ #### What caused friction (agent side)
56
+
57
+ 1. `missing-context` — Two failed `Edit` calls on `agent-tool.ts` line 175: the template literal's guideline lines have no tab indentation, but the agent initially assumed tab depth from the surrounding function.
58
+ Impact: 3 extra tool calls (grep to inspect actual indentation, then successful edit); no rework.
59
+ Self-identified.
60
+ 2. `wrong-abstraction` — Retro file edit duplicated Planning observations into the TDD stage because the `Edit` `oldText` matched from the Observations heading and the replacement included both old and new content.
61
+ Impact: 2 extra tool calls (read file, full `write` to fix); no rework.
62
+ Self-identified.
63
+ 3. `missing-context` — `.pi/skills/pre-completion/SKILL.md` line 32 references the `Agent` tool by name but was not in the plan's scope.
64
+ The plan checked pi-permission-system docs, `README.md`, and architecture docs but did not grep skill files for the old tool name.
65
+ Impact: discovered during retro; fixed as a retro change.
66
+
67
+ #### What caused friction (user side)
68
+
69
+ - None — the full pipeline ran with zero user corrections.
70
+
71
+ ### Diagnostic details
72
+
73
+ - **Model-performance correlation** — Pre-completion reviewer ran as a default-model subagent (292.7s, 36 tool uses, 63.9k tokens).
74
+ Appropriate for the judgment-heavy review task.
75
+ Ship stage on `deepseek-v4-flash` was notably efficient for purely mechanical work.
76
+ - **Feedback-loop gap analysis** — Verification was incremental: baseline check before TDD, per-file tests after Red and Green phases, full suite after implementation, then check + lint + fallow.
77
+ No gaps.
78
+
79
+ ### Changes made
80
+
81
+ 1. `.pi/skills/pre-completion/SKILL.md` — updated stale `Agent` tool reference to `subagent` on line 32.
82
+ 2. `.pi/agents/pre-completion-reviewer.md` — added rename-grep heuristic to the Skills bullet under Forward documentation checks: "When the change renames a symbol, grep `.pi/skills/` and `.pi/prompts/` for the old name."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-subagents",
3
- "version": "9.0.1",
3
+ "version": "10.0.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/service.ts"
@@ -15,37 +15,22 @@ import { registerChildSession, unregisterChildSession } from "#src/lifecycle/per
15
15
  import { extractAssistantContent } from "#src/session/content-items";
16
16
  import { extractText } from "#src/session/context";
17
17
  import type { EnvInfo } from "#src/session/env";
18
- import { type AssemblerIO, assembleSessionConfig, type ToolFilterConfig } from "#src/session/session-config";
18
+ import { type AssemblerIO, assembleSessionConfig } from "#src/session/session-config";
19
19
  import type { ShellExec, SubagentType, ThinkingLevel } from "#src/types";
20
20
 
21
21
  /** Names of tools registered by this extension that subagents must NOT inherit. */
22
- const EXCLUDED_TOOL_NAMES = ["Agent", "get_subagent_result", "steer_subagent"];
22
+ const EXCLUDED_TOOL_NAMES = ["subagent", "get_subagent_result", "steer_subagent"];
23
23
 
24
24
  /**
25
- * Filter the session's active tool names according to extension rules.
25
+ * Filter the session's active tool names: remove recursion-guard tools.
26
26
  *
27
- * Run twice - once before `bindExtensions` (filters built-in tools) and once after
28
- * (filters extension-registered tools, which only join the active set during
29
- * `bindExtensions`). Extracting this keeps the two callsites consistent and makes
30
- * the post-bind re-filter trivial.
27
+ * Run once after `bindExtensions` so extension-registered tools (added during
28
+ * `bindExtensions`) are also covered by the guard.
31
29
  *
32
30
  * @param activeTools Names currently active on the session.
33
- * @param config Tool filtering configuration from the assembled session config.
34
31
  */
35
- function filterActiveTools(
36
- activeTools: string[],
37
- config: ToolFilterConfig,
38
- ): string[] {
39
- const { toolNames, extensions } = config;
40
- if (!extensions) {
41
- return activeTools;
42
- }
43
- const builtinToolNameSet = new Set(toolNames);
44
- return activeTools.filter((t) => {
45
- if (EXCLUDED_TOOL_NAMES.includes(t)) return false;
46
- if (builtinToolNameSet.has(t)) return true;
47
- return true;
48
- });
32
+ function filterActiveTools(activeTools: string[]): string[] {
33
+ return activeTools.filter((t) => !EXCLUDED_TOOL_NAMES.includes(t));
49
34
  }
50
35
 
51
36
  /** Normalize max turns. undefined or 0 = unlimited, otherwise minimum 1. */
@@ -305,7 +290,7 @@ export async function runAgent(
305
290
  const loader = io.createResourceLoader({
306
291
  cwd: cfg.effectiveCwd,
307
292
  agentDir,
308
- noExtensions: !cfg.toolFilter.extensions,
293
+ noExtensions: !cfg.extensions,
309
294
  noSkills: cfg.noSkills,
310
295
  noPromptTemplates: true,
311
296
  noThemes: true,
@@ -329,18 +314,11 @@ export async function runAgent(
329
314
  settingsManager: io.createSettingsManager(cfg.effectiveCwd, agentDir),
330
315
  modelRegistry: snapshot.modelRegistry,
331
316
  model: cfg.model,
332
- tools: cfg.toolFilter.toolNames,
317
+ tools: cfg.toolNames,
333
318
  resourceLoader: loader,
334
319
  thinkingLevel: cfg.thinkingLevel,
335
320
  });
336
321
 
337
- // Filter active tools: remove our own tools to prevent nesting.
338
- // First pass - over built-in tools, before bindExtensions registers extension tools.
339
- if (cfg.toolFilter.extensions) {
340
- const filtered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
341
- session.setActiveToolsByName(filtered);
342
- }
343
-
344
322
  // Register with pi-permission-system's SubagentSessionRegistry before
345
323
  // bindExtensions() so isSubagentExecutionContext() hits the registry on the
346
324
  // first check during child extension initialization. Unregistered in the
@@ -356,14 +334,13 @@ export async function runAgent(
356
334
  // respect the active tool set. All ExtensionBindings fields are optional.
357
335
  await session.bindExtensions({});
358
336
 
359
- // Patch 2 (RepOne #443): re-filter active tools after bindExtensions.
360
- // Extension-registered tools (added during bindExtensions) are not in the
361
- // session's active set when the first filter pass runs above. Without this
362
- // re-filter, EXCLUDED_TOOL_NAMES would not be applied to extension-registered
363
- // tools. Run the same filter against the post-bind active set.
364
- if (cfg.toolFilter.extensions) {
365
- const refiltered = filterActiveTools(session.getActiveToolNames(), cfg.toolFilter);
366
- session.setActiveToolsByName(refiltered);
337
+ // Apply recursion guard: remove our own tools from the child's active set.
338
+ // Runs after bindExtensions so extension-registered tools are included in the
339
+ // post-bind active set. Only needed when extensions are loaded (extensions: false
340
+ // means no extension tools were registered, so the guard is a no-op).
341
+ if (cfg.extensions) {
342
+ const filtered = filterActiveTools(session.getActiveToolNames());
343
+ session.setActiveToolsByName(filtered);
367
344
  }
368
345
 
369
346
  options.onSessionCreated?.(session);
@@ -18,19 +18,6 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
18
18
 
19
19
  // ── Public interfaces ────────────────────────────────────────────────────────
20
20
 
21
- /**
22
- * Tool filtering configuration — consumed by `filterActiveTools` in `agent-runner.ts`.
23
- *
24
- * Groups the two fields that travel together through the assembly→runner boundary:
25
- * the built-in tool allowlist and the extensions setting.
26
- */
27
- export interface ToolFilterConfig {
28
- /** Built-in tool name allowlist for this agent type. */
29
- toolNames: string[];
30
- /** Resolved extensions setting: true = inherit all, false = none. */
31
- extensions: boolean;
32
- }
33
-
34
21
  /**
35
22
  * IO collaborators injected into `assembleSessionConfig`.
36
23
  *
@@ -98,8 +85,10 @@ export interface SessionConfig {
98
85
  effectiveCwd: string;
99
86
  /** Fully-assembled system prompt string (ready for `systemPromptOverride`). */
100
87
  systemPrompt: string;
101
- /** Tool filtering cluster — tool allowlist, denylist, and extensions setting. */
102
- toolFilter: ToolFilterConfig;
88
+ /** Built-in tool name allowlist for this agent type. */
89
+ toolNames: string[];
90
+ /** Resolved extensions setting: true = inherit all, false = none. */
91
+ extensions: boolean;
103
92
  /**
104
93
  * Resolved model instance (undefined → use parent model as passed to SDK).
105
94
  * Opaque handle — the assembler passes it through without inspection.
@@ -222,7 +211,8 @@ export function assembleSessionConfig(
222
211
  return {
223
212
  effectiveCwd,
224
213
  systemPrompt,
225
- toolFilter: { toolNames, extensions },
214
+ toolNames,
215
+ extensions,
226
216
  model,
227
217
  thinkingLevel,
228
218
  noSkills,
@@ -156,12 +156,12 @@ export class AgentTool {
156
156
  const registry = this.registry;
157
157
 
158
158
  return defineTool({
159
- name: "Agent" as const,
160
- label: "Agent",
161
- promptSnippet: "Agent: Launch a specialized agent for complex, multi-step tasks.",
159
+ name: "subagent" as const,
160
+ label: "Subagent",
161
+ promptSnippet: "subagent: Launch a specialized agent for complex, multi-step tasks.",
162
162
  description: `Launch a new agent to handle complex, multi-step tasks autonomously.
163
163
 
164
- The Agent tool launches specialized agents that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
164
+ The subagent tool launches specialized agents that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
165
165
 
166
166
  Available agent types:
167
167
  ${typeListText}
@@ -172,7 +172,7 @@ Guidelines:
172
172
  - Use Plan for architecture and implementation planning.
173
173
  - Use general-purpose for complex tasks that need file editing.
174
174
  - Provide clear, detailed prompts so the agent can work autonomously.
175
- - Agent results are returned as text — summarize them for the user.
175
+ - Subagent results are returned as text — summarize them for the user.
176
176
  - Use run_in_background for work you don't need immediately. You will be notified when it completes.
177
177
  - Use resume with an agent ID to continue a previous agent's work.
178
178
  - Use steer_subagent to send mid-run messages to a running background agent.
@@ -244,7 +244,7 @@ Guidelines:
244
244
  renderCall(args: Record<string, unknown>, theme: any) {
245
245
  const displayName = args.subagent_type
246
246
  ? getDisplayName(args.subagent_type as string, registry)
247
- : "Agent";
247
+ : "Subagent";
248
248
  const desc = (args.description as string | undefined) ?? "";
249
249
  return new Text(
250
250
  "▸ " +