pi-fast-subagent 0.7.0 → 0.8.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 +23 -0
- package/agents/general.md +3 -0
- package/agents/scout.md +3 -0
- package/agents.ts +13 -0
- package/index.ts +23 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,6 +69,7 @@ You are code exploration specialist. Read relevant files, trace data flow, summa
|
|
|
69
69
|
| `description` | yes | One-line description shown in `/fast-subagent:agent` |
|
|
70
70
|
| `model` | no | Model override, format `provider/model-id` (e.g. `anthropic/claude-haiku-4-5`) |
|
|
71
71
|
| `tools` | no | Tool allowlist (see below) |
|
|
72
|
+
| `maxDepth` | no | Nested subagent depth this agent may spawn. Default `0` means this agent cannot call `subagent`. |
|
|
72
73
|
|
|
73
74
|
### `tools:` field
|
|
74
75
|
|
|
@@ -125,6 +126,28 @@ tools: all
|
|
|
125
126
|
|
|
126
127
|
**YAML comments** (`# …`) are allowed inside the frontmatter — handy for documenting *why* a particular tool set was chosen. See `agents/general.md` and `agents/scout.md` for examples.
|
|
127
128
|
|
|
129
|
+
### `maxDepth:` field
|
|
130
|
+
|
|
131
|
+
Subagents cannot spawn other subagents by default, even when `tools` exposes the `subagent` tool.
|
|
132
|
+
|
|
133
|
+
```md
|
|
134
|
+
---
|
|
135
|
+
name: planner
|
|
136
|
+
description: Can delegate one level deeper
|
|
137
|
+
maxDepth: 1
|
|
138
|
+
---
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Depth counts nested generations from that agent:
|
|
142
|
+
|
|
143
|
+
| Value | Behavior |
|
|
144
|
+
|-------|----------|
|
|
145
|
+
| *(omitted)* / `0` | This agent cannot spawn subagents |
|
|
146
|
+
| `1` | This agent may spawn subagents, but those children cannot spawn again unless their own `maxDepth` allows it |
|
|
147
|
+
| `2` | Allows two nested generations, subject to each child agent's own `maxDepth` |
|
|
148
|
+
|
|
149
|
+
Aliases accepted: `max_depth`, `depth`, `subagentDepth`.
|
|
150
|
+
|
|
128
151
|
## Background Agents
|
|
129
152
|
|
|
130
153
|
Every foreground subagent can be moved to background at any time. Background jobs run concurrently while you continue chatting. When a job finishes, pi automatically posts the result as a follow-up message.
|
package/agents/general.md
CHANGED
|
@@ -11,6 +11,9 @@ model: anthropic/claude-haiku-4-5
|
|
|
11
11
|
# comma-separated list → explicit allowlist, e.g. `read, grep, web_search`
|
|
12
12
|
# General is meant to be a do-anything fallback, so it keeps everything explicit.
|
|
13
13
|
tools: all
|
|
14
|
+
|
|
15
|
+
# Subagents cannot spawn subagents by default. Set maxDepth: 1+ to opt in.
|
|
16
|
+
maxDepth: 0
|
|
14
17
|
---
|
|
15
18
|
|
|
16
19
|
You are general-purpose subagent.
|
package/agents/scout.md
CHANGED
|
@@ -11,6 +11,9 @@ model: anthropic/claude-haiku-4-5
|
|
|
11
11
|
# comma-separated list → explicit allowlist
|
|
12
12
|
# Scout is read-only: no `edit`, no `write`, no extension tools. Keeps the agent from mutating the codebase.
|
|
13
13
|
tools: read, bash, grep, find, ls
|
|
14
|
+
|
|
15
|
+
# Subagents cannot spawn subagents by default. Keep scout focused on exploration only.
|
|
16
|
+
maxDepth: 0
|
|
14
17
|
---
|
|
15
18
|
|
|
16
19
|
You are code exploration specialist.
|
package/agents.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface AgentConfig {
|
|
|
31
31
|
description: string;
|
|
32
32
|
model?: string;
|
|
33
33
|
tools: AgentTools;
|
|
34
|
+
/** Number of nested subagent generations this agent may spawn. Default: 0. */
|
|
35
|
+
maxDepth: number;
|
|
34
36
|
systemPrompt: string;
|
|
35
37
|
source: "user" | "project";
|
|
36
38
|
filePath: string;
|
|
@@ -58,6 +60,13 @@ function parseToolsField(raw: unknown): AgentTools {
|
|
|
58
60
|
return list.length ? list : "all";
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
function parseMaxDepthField(raw: unknown): number {
|
|
64
|
+
if (raw === undefined || raw === null || raw === "") return 0;
|
|
65
|
+
const n = Number(raw);
|
|
66
|
+
if (!Number.isFinite(n) || n < 0) return 0;
|
|
67
|
+
return Math.floor(n);
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
|
|
62
71
|
if (!fs.existsSync(dir)) return [];
|
|
63
72
|
let entries: fs.Dirent[];
|
|
@@ -77,11 +86,15 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
|
|
|
77
86
|
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
|
|
78
87
|
if (!frontmatter?.name || !frontmatter?.description) continue;
|
|
79
88
|
const tools = parseToolsField(frontmatter.tools);
|
|
89
|
+
const maxDepth = parseMaxDepthField(
|
|
90
|
+
frontmatter.maxDepth ?? frontmatter.max_depth ?? frontmatter.depth ?? frontmatter.subagentDepth,
|
|
91
|
+
);
|
|
80
92
|
agents.push({
|
|
81
93
|
name: frontmatter.name,
|
|
82
94
|
description: frontmatter.description,
|
|
83
95
|
model: frontmatter.model,
|
|
84
96
|
tools,
|
|
97
|
+
maxDepth,
|
|
85
98
|
systemPrompt: body.trim(),
|
|
86
99
|
source,
|
|
87
100
|
filePath,
|
package/index.ts
CHANGED
|
@@ -257,8 +257,9 @@ const _fgJobs = new Map<string, ForegroundDetachEntry>();
|
|
|
257
257
|
|
|
258
258
|
// ─── In-process runner ───────────────────────────────────────────────────────
|
|
259
259
|
|
|
260
|
-
const
|
|
260
|
+
const DEFAULT_MAX_DEPTH = 0;
|
|
261
261
|
const DEPTH_ENV = "PI_FAST_SUBAGENT_DEPTH";
|
|
262
|
+
const MAX_DEPTH_ENV = "PI_FAST_SUBAGENT_MAX_DEPTH";
|
|
262
263
|
|
|
263
264
|
interface ToolCallEntry {
|
|
264
265
|
id: string;
|
|
@@ -330,8 +331,9 @@ function formatBgJobDetails(job: BackgroundSubagentJob, now = Date.now()): strin
|
|
|
330
331
|
return lines.join("\n");
|
|
331
332
|
}
|
|
332
333
|
|
|
333
|
-
// Module-level depth
|
|
334
|
+
// Module-level depth counters for nested in-process subagent calls.
|
|
334
335
|
let _currentDepth = 0;
|
|
336
|
+
let _currentMaxDepth = DEFAULT_MAX_DEPTH;
|
|
335
337
|
|
|
336
338
|
async function runAgent(
|
|
337
339
|
agent: AgentConfig,
|
|
@@ -343,11 +345,12 @@ async function runAgent(
|
|
|
343
345
|
parentDepth?: number,
|
|
344
346
|
): Promise<RunResult> {
|
|
345
347
|
const depth = parentDepth ?? _currentDepth;
|
|
346
|
-
|
|
348
|
+
const isNestedCall = depth > 0;
|
|
349
|
+
if (isNestedCall && depth > _currentMaxDepth) {
|
|
347
350
|
return {
|
|
348
351
|
output: "",
|
|
349
352
|
exitCode: 1,
|
|
350
|
-
error: `
|
|
353
|
+
error: `Nested subagents are disabled by default. Set maxDepth: ${depth} (or higher) in the parent agent frontmatter to allow this call.`,
|
|
351
354
|
toolCalls: [],
|
|
352
355
|
usage: { input: 0, output: 0, cost: 0, turns: 0 },
|
|
353
356
|
};
|
|
@@ -543,10 +546,17 @@ async function runAgent(
|
|
|
543
546
|
});
|
|
544
547
|
});
|
|
545
548
|
|
|
546
|
-
// Propagate depth to nested calls
|
|
549
|
+
// Propagate depth to nested calls. `maxDepth` is per-agent and defaults to 0,
|
|
550
|
+
// so subagents cannot spawn subagents unless their frontmatter opts in.
|
|
547
551
|
const prevEnvDepth = process.env[DEPTH_ENV];
|
|
548
|
-
process.env[
|
|
552
|
+
const prevEnvMaxDepth = process.env[MAX_DEPTH_ENV];
|
|
553
|
+
const prevDepth = _currentDepth;
|
|
554
|
+
const prevMaxDepth = _currentMaxDepth;
|
|
555
|
+
const maxDepth = Math.max(DEFAULT_MAX_DEPTH, agent.maxDepth ?? DEFAULT_MAX_DEPTH);
|
|
549
556
|
_currentDepth = depth + 1;
|
|
557
|
+
_currentMaxDepth = depth + maxDepth;
|
|
558
|
+
process.env[DEPTH_ENV] = String(_currentDepth);
|
|
559
|
+
process.env[MAX_DEPTH_ENV] = String(_currentMaxDepth);
|
|
550
560
|
|
|
551
561
|
let exitCode = 0;
|
|
552
562
|
let error: string | undefined;
|
|
@@ -572,7 +582,10 @@ async function runAgent(
|
|
|
572
582
|
loaderLease.release();
|
|
573
583
|
if (prevEnvDepth === undefined) delete process.env[DEPTH_ENV];
|
|
574
584
|
else process.env[DEPTH_ENV] = prevEnvDepth;
|
|
575
|
-
|
|
585
|
+
if (prevEnvMaxDepth === undefined) delete process.env[MAX_DEPTH_ENV];
|
|
586
|
+
else process.env[MAX_DEPTH_ENV] = prevEnvMaxDepth;
|
|
587
|
+
_currentDepth = prevDepth;
|
|
588
|
+
_currentMaxDepth = prevMaxDepth;
|
|
576
589
|
}
|
|
577
590
|
|
|
578
591
|
return { output: lastOutput, exitCode, error, model: detectedModel, toolCalls, usage };
|
|
@@ -769,6 +782,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
769
782
|
`Description: ${agent.description}`,
|
|
770
783
|
agent.model ? `Model: ${agent.model}` : "",
|
|
771
784
|
`Tools: ${formatTools(agent.tools)}`,
|
|
785
|
+
`Max subagent depth: ${agent.maxDepth}`,
|
|
772
786
|
agent.systemPrompt ? `\nSystem prompt:\n${agent.systemPrompt}` : "",
|
|
773
787
|
].filter(Boolean).join("\n");
|
|
774
788
|
ctx.ui.notify(lines, "info");
|
|
@@ -781,7 +795,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
781
795
|
"Add .md files to:\n" +
|
|
782
796
|
" ~/.pi/agent/agents/ (user-level)\n" +
|
|
783
797
|
" .pi/agents/ (project-level)\n" +
|
|
784
|
-
"\nFrontmatter required: name, description. Optional: model, tools.",
|
|
798
|
+
"\nFrontmatter required: name, description. Optional: model, tools, maxDepth.",
|
|
785
799
|
"info"
|
|
786
800
|
);
|
|
787
801
|
return;
|
|
@@ -1181,6 +1195,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1181
1195
|
`**Description:** ${agent.description}`,
|
|
1182
1196
|
agent.model ? `**Model:** ${agent.model}` : null,
|
|
1183
1197
|
`**Tools:** ${formatTools(agent.tools)}`,
|
|
1198
|
+
`**Max subagent depth:** ${agent.maxDepth}`,
|
|
1184
1199
|
agent.systemPrompt ? `\n**System prompt:**\n${agent.systemPrompt}` : null,
|
|
1185
1200
|
].filter(Boolean).join("\n");
|
|
1186
1201
|
return { content: [{ type: "text", text: info }] };
|