@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 +31 -0
- package/README.md +8 -8
- package/docs/architecture/architecture.md +42 -21
- package/docs/plans/0239-collapse-filter-active-tools.md +217 -0
- package/docs/plans/0242-rename-agent-tool-to-subagent.md +165 -0
- package/docs/retro/0239-collapse-filter-active-tools.md +37 -0
- package/docs/retro/0242-rename-agent-tool-to-subagent.md +82 -0
- package/package.json +1 -1
- package/src/lifecycle/agent-runner.ts +16 -39
- package/src/session/session-config.ts +6 -16
- package/src/tools/agent-tool.ts +6 -6
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 (`
|
|
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 `
|
|
52
|
+
The parent agent spawns sub-agents using the `subagent` tool:
|
|
53
53
|
|
|
54
54
|
```text
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
193
|
+
`subagent` tool parameters only fill fields the agent config leaves unspecified.
|
|
194
194
|
|
|
195
195
|
## Tools
|
|
196
196
|
|
|
197
|
-
### `
|
|
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
|
-
|
|
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 `
|
|
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["
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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,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`
|
|
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
|
+
| `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`
|
|
718
|
-
|
|
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.
|
|
721
|
-
2.
|
|
722
|
-
3.
|
|
723
|
-
4.
|
|
724
|
-
5.
|
|
725
|
-
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.
|
|
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
|
|
744
|
-
Step 3 depends on
|
|
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
|
@@ -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
|
-
const EXCLUDED_TOOL_NAMES = ["
|
|
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,
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -156,12 +156,12 @@ export class AgentTool {
|
|
|
156
156
|
const registry = this.registry;
|
|
157
157
|
|
|
158
158
|
return defineTool({
|
|
159
|
-
name: "
|
|
160
|
-
label: "
|
|
161
|
-
promptSnippet: "
|
|
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
|
|
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
|
-
-
|
|
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
|
-
: "
|
|
247
|
+
: "Subagent";
|
|
248
248
|
const desc = (args.description as string | undefined) ?? "";
|
|
249
249
|
return new Text(
|
|
250
250
|
"▸ " +
|