@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 +11 -0
- package/docs/architecture/architecture.md +13 -13
- package/docs/plans/0239-collapse-filter-active-tools.md +217 -0
- package/docs/retro/0239-collapse-filter-active-tools.md +37 -0
- package/docs/retro/0242-rename-agent-tool-to-subagent.md +45 -0
- package/package.json +1 -1
- package/src/lifecycle/agent-runner.ts +15 -38
- package/src/session/session-config.ts +6 -16
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
|
|
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
|
|
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`
|
|
681
|
-
| `ToolFilterConfig`
|
|
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`
|
|
719
|
-
|
|
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.
|
|
722
|
-
2.
|
|
723
|
-
3.
|
|
724
|
-
4.
|
|
725
|
-
5.
|
|
726
|
-
6.
|
|
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
|
@@ -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
|
|
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
|
|
25
|
+
* Filter the session's active tool names: remove recursion-guard tools.
|
|
26
26
|
*
|
|
27
|
-
* Run
|
|
28
|
-
*
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
/**
|
|
102
|
-
|
|
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
|
-
|
|
214
|
+
toolNames,
|
|
215
|
+
extensions,
|
|
226
216
|
model,
|
|
227
217
|
thinkingLevel,
|
|
228
218
|
noSkills,
|