@d3ara1n/pi-subagent 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -22
- package/package.json +1 -1
- package/src/config.ts +10 -3
- package/src/index.ts +469 -284
- package/src/spawn.ts +158 -53
- package/src/types.ts +42 -2
- package/src/utils.test.ts +278 -0
- package/src/utils.ts +286 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ This means:
|
|
|
17
17
|
- **Multiple subagents can run in parallel** — emit multiple `delegate` calls in one turn; pi executes them concurrently
|
|
18
18
|
- **Subagents can nest subagents** — a `worker` can delegate exploration to `explorer` without returning to the main model
|
|
19
19
|
|
|
20
|
-
> This design
|
|
20
|
+
> This design currently focuses on single-task delegation rather than chain pipelines or context-forking — those patterns fit better when subagents act as advisors (planner, oracle) rather than executors.
|
|
21
21
|
|
|
22
22
|
## How it works
|
|
23
23
|
|
|
@@ -47,11 +47,9 @@ This means:
|
|
|
47
47
|
- **Collapsed result**: `✓ explorer · Found login, registration, and token logic` + recent tool calls + usage stats
|
|
48
48
|
- **Expanded result** (Ctrl+O): Full task text, all tool calls, final output as rendered Markdown, and usage details
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Dependencies
|
|
51
51
|
|
|
52
|
-
-
|
|
53
|
-
- **@earendil-works/pi-tui** — bundled with pi, no separate install needed
|
|
54
|
-
- Role definitions must exist in `modelRoles` settings
|
|
52
|
+
- [`@d3ara1n/pi-model-roles`](../pi-model-roles) — model role resolution
|
|
55
53
|
|
|
56
54
|
## Installation
|
|
57
55
|
|
|
@@ -63,43 +61,44 @@ pi install @d3ara1n/pi-subagent
|
|
|
63
61
|
|
|
64
62
|
Edit `~/.pi/agent/settings.json`:
|
|
65
63
|
|
|
66
|
-
```
|
|
64
|
+
```json
|
|
67
65
|
{
|
|
68
66
|
"subagent": {
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
"timeout": 600,
|
|
68
|
+
"maxConcurrency": 4,
|
|
69
|
+
"maxDepth": 3,
|
|
70
|
+
"maxTurns": 0,
|
|
71
|
+
"maxCost": 0,
|
|
72
|
+
"history": {
|
|
73
|
+
"enabled": true
|
|
74
|
+
},
|
|
74
75
|
"summary": {
|
|
75
|
-
"role": "utility",
|
|
76
|
-
"enabled": true
|
|
76
|
+
"role": "utility",
|
|
77
|
+
"enabled": true
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
All fields are optional. Defaults: `
|
|
83
|
+
All fields are optional. Defaults: `timeout: 600` (seconds; 10 min; roles that can `delegate` get 2× automatically when no per-role timeout is set), `maxConcurrency: 4`, `maxDepth: 3`, `maxTurns: 0` (unlimited), `maxCost: 0` (unlimited), `history.enabled: true`, `summary.role: "utility"`, `summary.enabled: true`.
|
|
83
84
|
|
|
84
85
|
### Agent Overrides
|
|
85
86
|
|
|
86
87
|
Override, disable, or add subagent roles via `agentOverrides`. Built-in and custom roles are treated equally — all descriptions, examples, and decision triggers feed into the LLM's prompt dynamically.
|
|
87
88
|
|
|
88
|
-
```
|
|
89
|
+
```json
|
|
89
90
|
{
|
|
90
91
|
"subagent": {
|
|
91
92
|
"agentOverrides": {
|
|
92
|
-
// ── Override a built-in role (only specify changed fields) ──
|
|
93
93
|
"worker": {
|
|
94
|
-
"role": "heavy"
|
|
94
|
+
"role": "heavy",
|
|
95
|
+
"timeout": 600,
|
|
96
|
+
"maxTurns": 50,
|
|
97
|
+
"maxCost": 1.0
|
|
95
98
|
},
|
|
96
|
-
|
|
97
|
-
// ── Disable a built-in role ──
|
|
98
99
|
"reviewer": {
|
|
99
100
|
"disabled": true
|
|
100
101
|
},
|
|
101
|
-
|
|
102
|
-
// ── Add a custom role (all required fields must be provided) ──
|
|
103
102
|
"tester": {
|
|
104
103
|
"role": "default",
|
|
105
104
|
"description": "Test automation & QA — write and run tests, validate fixes. Tools: read, bash, edit, write, grep. Can delegate to explorer.",
|
|
@@ -118,7 +117,7 @@ Override, disable, or add subagent roles via `agentOverrides`. Built-in and cust
|
|
|
118
117
|
|
|
119
118
|
**Required fields for custom roles:** `role`, `description`, `examples`, `decisionTrigger`, `tools`, `systemPrompt`.
|
|
120
119
|
|
|
121
|
-
**Optional fields:** `subagentRoles` (roles this role can spawn via delegate), `fallbackRole` (backup pi-model-roles role on provider errors).
|
|
120
|
+
**Optional fields:** `subagentRoles` (roles this role can spawn via delegate), `timeout` (per-role timeout override in seconds; when unset, delegate-capable roles get 2× the global default automatically), `maxTurns` / `maxCost` (per-role budget overrides; 0 = unlimited), `fallbackRole` (backup pi-model-roles role on provider errors).
|
|
122
121
|
|
|
123
122
|
Invalid custom roles (missing required fields) are silently skipped with an error notification at session start.
|
|
124
123
|
|
|
@@ -151,6 +150,48 @@ Delegate tasks that would generate many tool calls or verbose output to keep you
|
|
|
151
150
|
]
|
|
152
151
|
```
|
|
153
152
|
|
|
153
|
+
### Passing context and reference files
|
|
154
|
+
|
|
155
|
+
pi-subagent delivers context to the child as **independent channels**, never fused into the task string. This keeps the task an unambiguous directive and lets each channel be sized independently.
|
|
156
|
+
|
|
157
|
+
#### `context` (inline text)
|
|
158
|
+
|
|
159
|
+
Hand the subagent precise context — selected code, a prior delegate's result, a file list, a git diff — without inflating the `task` string. It's delivered as a separate channel:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"role": "worker",
|
|
164
|
+
"task": "Add input validation to the login function",
|
|
165
|
+
"context": "Current implementation (src/auth.ts:42-70):\n```ts\nasync function login(email, pw) { ... }\n```\nValidation must reject empty/invalid emails and enforce a min 8-char password."
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The stored/displayed task stays as the original `task`. When small, `context` inlines as a `<context>` block; when large (over 8,000 chars) it spills to a temp file injected via `@file`, so a large context never drags a short task into a spill.
|
|
170
|
+
|
|
171
|
+
#### `files` (reference paths)
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"role": "explorer",
|
|
176
|
+
"task": "Report the public API of the auth module",
|
|
177
|
+
"files": ["src/auth.ts", "src/auth.types.ts"]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each path is injected as an independent `@file` attachment the subagent reads directly. **File contents stay out of your context window** — you pass only the paths. Prefer this over pasting file contents into `context`, since the child receives the content on its first turn without spending a tool call to read it.
|
|
182
|
+
|
|
183
|
+
### Budget enforcement
|
|
184
|
+
|
|
185
|
+
`maxTurns` / `maxCost` cap a run. When exceeded, the child is killed and the last completed output is returned with `stopReason: "budget_exceeded"` (shown in the expanded TUI). Defaults are unlimited (0); set global defaults in config or per-role via `agentOverrides`.
|
|
186
|
+
|
|
187
|
+
### Oversized outputs
|
|
188
|
+
|
|
189
|
+
When a run's output exceeds the size limit (50,000 chars), pi-subagent first tries to **compress** it with the summary model (same role configured under `summary.role`) into a compact form that preserves conclusions, code, file paths, and errors. If compression fails or doesn't shrink enough, it falls back to mechanical head+tail truncation. The prepared text is what the main model receives and what the expanded TUI renders; a hint line notes which method was used. The **full raw output is always kept in the history file** for auditing.
|
|
190
|
+
|
|
191
|
+
### Run history
|
|
192
|
+
|
|
193
|
+
Every completed delegate run is written (best-effort) to `~/.pi/subagent/history/{sessionId}/{toolCallId}.json`, recording role, task, usage, activity log, and the **full raw output** (even when the main model saw a compressed/truncated version). Useful for auditing what subagents did and how much they cost. Disable with `history.enabled: false`.
|
|
194
|
+
|
|
154
195
|
## License
|
|
155
196
|
|
|
156
197
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d3ara1n/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Role-based subagent orchestration for pi — delegates tasks to specialized pi child processes with configurable model roles",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/config.ts
CHANGED
|
@@ -21,8 +21,7 @@ function readSettingsFile(filePath: string): any {
|
|
|
21
21
|
try {
|
|
22
22
|
if (!fs.existsSync(filePath)) return {};
|
|
23
23
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
24
|
-
|
|
25
|
-
return JSON.parse(stripped);
|
|
24
|
+
return JSON.parse(content);
|
|
26
25
|
} catch {
|
|
27
26
|
return {};
|
|
28
27
|
}
|
|
@@ -53,8 +52,16 @@ export function loadSubagentConfig(cwd?: string): SubagentConfig {
|
|
|
53
52
|
if (!raw) return DEFAULT_CONFIG;
|
|
54
53
|
|
|
55
54
|
const rawSummary = raw?.summary;
|
|
55
|
+
const rawHistory = raw?.history;
|
|
56
56
|
return {
|
|
57
|
-
|
|
57
|
+
timeout: raw.timeout ?? DEFAULT_CONFIG.timeout,
|
|
58
|
+
maxConcurrency: raw.maxConcurrency ?? DEFAULT_CONFIG.maxConcurrency,
|
|
59
|
+
maxDepth: raw.maxDepth ?? DEFAULT_CONFIG.maxDepth,
|
|
60
|
+
maxTurns: raw.maxTurns ?? DEFAULT_CONFIG.maxTurns,
|
|
61
|
+
maxCost: raw.maxCost ?? DEFAULT_CONFIG.maxCost,
|
|
62
|
+
history: {
|
|
63
|
+
enabled: rawHistory?.enabled ?? DEFAULT_CONFIG.history.enabled,
|
|
64
|
+
},
|
|
58
65
|
summary: {
|
|
59
66
|
role: rawSummary?.role ?? DEFAULT_CONFIG.summary.role,
|
|
60
67
|
enabled: rawSummary?.enabled ?? DEFAULT_CONFIG.summary.enabled,
|