@gotgenes/pi-subagents 10.0.0 → 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,17 @@ 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
+
8
19
  ## [10.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v9.0.1...pi-subagents-v10.0.0) (2026-05-27)
9
20
 
10
21
 
@@ -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,8 @@ 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
682
  | `Agent` tool name is PascalCase — misaligns with Pi's lowercase built-in convention | D: Convention | 2 | 1 | 3 |
683
683
 
684
684
  ### Step 1: Remove `disallowed_tools` — [#237] ✅ Complete
@@ -713,17 +713,17 @@ The `extensions: false` case (used by `isolated`) is retained in this step and r
713
713
  - Smell: A (tool filtering disguised as extension lifecycle control)
714
714
  - Outcome: `filterActiveTools` reduces to two concerns: recursion guard and `extensions: false` passthrough
715
715
 
716
- ### Step 3: Collapse `filterActiveTools` to recursion guard — [#239]
716
+ ### Step 3: Collapse `filterActiveTools` to recursion guard — [#239] ✅ Complete
717
717
 
718
- With Steps 1–2 complete, `filterActiveTools` has only two remaining branches: the `EXCLUDED_TOOL_NAMES` recursion guard and the `extensions === false` passthrough.
719
- 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.
720
720
 
721
- 1. Simplify `filterActiveTools` to filter only `EXCLUDED_TOOL_NAMES`.
722
- 2. Remove `ToolFilterConfig` — the function no longer needs a config bag.
723
- 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.
724
- 4. Keep a single post-bind filter call for the recursion guard.
725
- 5. Simplify `SessionConfig` — remove the `toolFilter` field, keep `toolNames` as a flat field.
726
- 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.
727
727
 
728
728
  - Target: `agent-runner.ts`, `session-config.ts`
729
729
  - Smell: B (accidental complexity), C (two-pass filter dance)
@@ -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,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.
@@ -35,3 +35,48 @@ Pre-completion reviewer returned **PASS**.
35
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
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
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": "10.0.0",
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
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,