oira666_pi-subagent 0.1.15 → 0.2.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/README.md +7 -13
- package/agents/code-architect.md +0 -2
- package/agents/code-reviwer.md +0 -2
- package/agents/code-writer.md +0 -2
- package/index.ts +28 -121
- package/package.json +1 -1
- package/render.ts +7 -16
- package/runner-sdk.ts +1 -72
- package/runner.ts +1 -67
- package/shared.ts +1 -1
- package/types.ts +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Pi Subagent
|
|
2
2
|
|
|
3
|
-
Delegate tasks to specialized subagents
|
|
3
|
+
Delegate tasks to specialized subagents.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -39,21 +39,17 @@ Processes are spawned via the operating system and communicate through JSON-line
|
|
|
39
39
|
Each subagent runs as an **in-process `AgentSession`** created via the pi SDK — no new process is spawned.
|
|
40
40
|
|
|
41
41
|
- No spawn overhead — sessions start in milliseconds
|
|
42
|
-
- No temp files for system prompts
|
|
42
|
+
- No temp files for system prompts
|
|
43
43
|
- Concurrency through the Node.js event loop (fine for I/O-bound LLM work)
|
|
44
44
|
- All sessions share the same memory and event loop
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**`spawn` (default)** — Child receives only the task string. Best for isolated work, lower cost.
|
|
49
|
-
**`fork`** — Child receives a snapshot of the current session context + task. Best for follow-up work.
|
|
50
|
-
|
|
51
|
-
The main agent receives only the **final text output** from subagents (no tool calls, no reasoning).
|
|
46
|
+
Each subagent receives only the task string. The main agent in turn receives
|
|
47
|
+
only the **final text output** from subagents (no tool calls, no reasoning).
|
|
52
48
|
|
|
53
49
|
## Tool Call Shape
|
|
54
50
|
|
|
55
51
|
```json
|
|
56
|
-
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }]
|
|
52
|
+
{ "tasks": [{ "agent": "code-writer", "task": "Implement the API" }] }
|
|
57
53
|
```
|
|
58
54
|
|
|
59
55
|
Multiple tasks run in parallel:
|
|
@@ -63,8 +59,7 @@ Multiple tasks run in parallel:
|
|
|
63
59
|
"tasks": [
|
|
64
60
|
{ "agent": "code-writer", "task": "Draft the implementation" },
|
|
65
61
|
{ "agent": "code-reviwer", "task": "Review the plan" }
|
|
66
|
-
]
|
|
67
|
-
"mode": "fork"
|
|
62
|
+
]
|
|
68
63
|
}
|
|
69
64
|
```
|
|
70
65
|
|
|
@@ -136,7 +131,6 @@ PI_SUBAGENTS_MODE=subprocess pi
|
|
|
136
131
|
| Session isolation | Full OS-level | Shared memory/event loop |
|
|
137
132
|
| Startup overhead | ~200–500 ms per agent | ~10–50 ms per agent |
|
|
138
133
|
| Parallelism | True OS parallelism | Event-loop concurrency |
|
|
139
|
-
| Fork mode context | Temp JSONL file | Temp JSONL file (same format) |
|
|
140
134
|
| Depth tracking | Env vars in child process | Closure parameters |
|
|
141
135
|
| `pi` binary required | Yes | No |
|
|
142
136
|
| Crash isolation | Yes — subprocess crash is contained | No — exception bubbles up |
|
|
@@ -236,7 +230,7 @@ tool_result_end
|
|
|
236
230
|
interface SubagentDetails {
|
|
237
231
|
// Execution metadata
|
|
238
232
|
mode: "single" | "parallel"; // one task vs multiple parallel tasks
|
|
239
|
-
delegationMode: "spawn"
|
|
233
|
+
delegationMode: "spawn"; // always "spawn" (kept for backward-compatible serialization)
|
|
240
234
|
projectAgentsDir: string | null; // path to .pi/agents/ dir if used
|
|
241
235
|
|
|
242
236
|
// Individual agent results (one per task)
|
package/agents/code-architect.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-architect
|
|
3
3
|
description: Technical design agent for shaping implementations, APIs, module boundaries, and tradeoffs before coding. Use this agent for plans and architecture decisions.
|
|
4
|
-
thinking: high
|
|
5
|
-
tools: read,bash,grep,find,ls
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a senior software architect focused on practical design.
|
package/agents/code-reviwer.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-reviwer
|
|
3
3
|
description: Code review specialist for finding bugs, regressions, edge cases, and maintainability issues. Use this agent to review code, plans, or patches.
|
|
4
|
-
thinking: high
|
|
5
|
-
tools: read,bash,grep,find,ls
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a skeptical, detail-oriented code reviewer.
|
package/agents/code-writer.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-writer
|
|
3
3
|
description: Focused implementation agent for writing and refactoring code with small, reliable diffs. Use this agent when you want code changes made directly.
|
|
4
|
-
thinking: medium
|
|
5
|
-
tools: read,bash,edit,write
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
You are a pragmatic software engineer focused on implementation.
|
package/index.ts
CHANGED
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
* The tool always accepts a `tasks` array:
|
|
14
14
|
* - One task: treated as a single-agent delegation.
|
|
15
15
|
* - Multiple tasks: treated as a parallel delegation.
|
|
16
|
-
*
|
|
17
|
-
* And two context modes:
|
|
18
|
-
* - spawn (default): child gets only the task prompt.
|
|
19
|
-
* - fork: child gets a forked snapshot of current session context + task prompt.
|
|
20
16
|
*/
|
|
21
17
|
|
|
22
18
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -25,7 +21,12 @@ import { type AgentConfig, discoverAgents } from "./agents.js";
|
|
|
25
21
|
import { renderCall, renderResult } from "./render.js";
|
|
26
22
|
import { runAgentSubprocess, executeParallelSubprocess } from "./runner.js";
|
|
27
23
|
import { runAgentSameProcess, executeParallelSameProcess } from "./runner-sdk.js";
|
|
28
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
DEFAULT_MAX_PARALLEL_TASKS,
|
|
26
|
+
SUBAGENT_MAX_PARALLEL_TASKS_ENV,
|
|
27
|
+
parseNonNegativeInt,
|
|
28
|
+
subagentContext,
|
|
29
|
+
} from "./shared.js";
|
|
29
30
|
|
|
30
31
|
import {
|
|
31
32
|
type DelegationMode,
|
|
@@ -33,7 +34,6 @@ import {
|
|
|
33
34
|
type SubagentDetails,
|
|
34
35
|
DEFAULT_DELEGATION_MODE,
|
|
35
36
|
buildSubagentDetails,
|
|
36
|
-
emptyUsage,
|
|
37
37
|
getFinalOutput,
|
|
38
38
|
isResultError,
|
|
39
39
|
} from "./types.js";
|
|
@@ -65,7 +65,7 @@ const TaskItem = Type.Object({
|
|
|
65
65
|
}),
|
|
66
66
|
task: Type.String({
|
|
67
67
|
description:
|
|
68
|
-
"Task description for this delegated run.
|
|
68
|
+
"Task description for this delegated run. Include all required context; the subagent receives only this prompt.",
|
|
69
69
|
}),
|
|
70
70
|
cwd: Type.Optional(
|
|
71
71
|
Type.String({ description: "Working directory for this agent's process" }),
|
|
@@ -78,13 +78,6 @@ const SubagentParams = Type.Object({
|
|
|
78
78
|
description:
|
|
79
79
|
"Array of {agent, task} objects. One task behaves like a single-agent delegation; multiple tasks run concurrently.",
|
|
80
80
|
}),
|
|
81
|
-
mode: Type.Optional(
|
|
82
|
-
Type.String({
|
|
83
|
-
description:
|
|
84
|
-
"Context mode for delegated runs. 'spawn' (default) sends only the task prompt (best for isolated, reproducible runs with lower token/cost and less context leakage). 'fork' adds a snapshot of current session context plus task prompt (best for follow-up work, but usually higher token/cost and may include sensitive context).",
|
|
85
|
-
default: DEFAULT_DELEGATION_MODE,
|
|
86
|
-
}),
|
|
87
|
-
),
|
|
88
81
|
});
|
|
89
82
|
|
|
90
83
|
// ---------------------------------------------------------------------------
|
|
@@ -99,34 +92,6 @@ interface DelegationDepthConfig {
|
|
|
99
92
|
preventCycles: boolean;
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
interface SessionSnapshotSource {
|
|
103
|
-
getHeader: () => unknown;
|
|
104
|
-
getBranch: () => unknown[];
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function parseDelegationMode(raw: unknown): DelegationMode | null {
|
|
108
|
-
if (raw === undefined) return DEFAULT_DELEGATION_MODE;
|
|
109
|
-
if (typeof raw !== "string") return null;
|
|
110
|
-
const normalized = raw.trim().toLowerCase();
|
|
111
|
-
if (normalized === "spawn" || normalized === "fork") {
|
|
112
|
-
return normalized;
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function buildForkSessionSnapshotJsonl(
|
|
118
|
-
sessionManager: SessionSnapshotSource,
|
|
119
|
-
): string | null {
|
|
120
|
-
const header = sessionManager.getHeader();
|
|
121
|
-
if (!header || typeof header !== "object") return null;
|
|
122
|
-
|
|
123
|
-
const branchEntries = sessionManager.getBranch();
|
|
124
|
-
const lines = [JSON.stringify(header)];
|
|
125
|
-
for (const entry of branchEntries) lines.push(JSON.stringify(entry));
|
|
126
|
-
return `${lines.join("\n")}\n`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
95
|
function parseBoolean(raw: unknown): boolean | null {
|
|
131
96
|
if (typeof raw === "boolean") return raw;
|
|
132
97
|
if (typeof raw !== "string") return null;
|
|
@@ -449,6 +414,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
449
414
|
const { currentDepth, maxDepth, canDelegate, ancestorAgentStack, preventCycles } =
|
|
450
415
|
depthConfig;
|
|
451
416
|
const subagentMode = getSubagentMode();
|
|
417
|
+
const maxParallelTasks =
|
|
418
|
+
parseNonNegativeInt(process.env[SUBAGENT_MAX_PARALLEL_TASKS_ENV]) ??
|
|
419
|
+
DEFAULT_MAX_PARALLEL_TASKS;
|
|
452
420
|
|
|
453
421
|
let discoveredAgents: AgentConfig[] = [];
|
|
454
422
|
const approvedProjectAgentDirsForSession = new Set<string>();
|
|
@@ -496,30 +464,25 @@ ${agentList}
|
|
|
496
464
|
|
|
497
465
|
Each subagent runs in an **isolated process**.
|
|
498
466
|
|
|
499
|
-
|
|
500
|
-
-
|
|
501
|
-
-
|
|
467
|
+
Pass a \`tasks\` array. **Every task in the same call runs in parallel.**
|
|
468
|
+
- 1 task -> single delegation
|
|
469
|
+
- N tasks -> all N run concurrently in one call
|
|
502
470
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
- multiple items = parallel delegation
|
|
471
|
+
For **sequential** work (task B needs task A's output), make separate tool
|
|
472
|
+
calls one after another. Do NOT put dependent tasks in the same array.
|
|
506
473
|
|
|
507
|
-
**Single
|
|
474
|
+
**Single (1 agent)**:
|
|
508
475
|
\`\`\`json
|
|
509
|
-
{ "tasks": [{ "agent": "agent-name", "task": "Detailed task..." }]
|
|
476
|
+
{ "tasks": [{ "agent": "agent-name", "task": "Detailed task..." }] }
|
|
510
477
|
\`\`\`
|
|
511
478
|
|
|
512
|
-
**
|
|
479
|
+
**Parallel (N agents at once)**:
|
|
513
480
|
\`\`\`json
|
|
514
|
-
{ "tasks": [{ "agent": "agent-
|
|
481
|
+
{ "tasks": [{ "agent": "agent-a", "task": "..." }, { "agent": "agent-b", "task": "..." }] }
|
|
515
482
|
\`\`\`
|
|
516
483
|
|
|
517
|
-
### Runtime delegation guards
|
|
518
|
-
|
|
519
484
|
- Max depth: current depth ${currentDepth}, max depth ${maxDepth}
|
|
520
|
-
-
|
|
521
|
-
- Current delegation stack: ${ancestorAgentStack.length > 0 ? ancestorAgentStack.join(" -> ") : "(root)"}
|
|
522
|
-
- Execution mode: ${subagentMode === "sdk" ? "in-process SDK" : "subprocess"}
|
|
485
|
+
- Max subagents per tool call: ${maxParallelTasks}
|
|
523
486
|
`,
|
|
524
487
|
};
|
|
525
488
|
} catch (err) {
|
|
@@ -537,18 +500,15 @@ The tool always accepts a \`tasks\` array:
|
|
|
537
500
|
subagentMode === "sdk" ? "in-process SDK sessions" : "isolated pi processes"
|
|
538
501
|
}.`,
|
|
539
502
|
"",
|
|
540
|
-
"
|
|
541
|
-
" -
|
|
542
|
-
" -
|
|
503
|
+
"Pass a `tasks` array. Every task in the same call runs IN PARALLEL.",
|
|
504
|
+
" - 1 task -> single delegation",
|
|
505
|
+
" - N tasks -> all N run concurrently in one call",
|
|
543
506
|
"",
|
|
544
|
-
"
|
|
545
|
-
"
|
|
546
|
-
" Best for isolated/reproducible work; lower token/cost and less context leakage.",
|
|
547
|
-
" mode: \"fork\" -> child gets current session context + your task prompt.",
|
|
548
|
-
" Best for follow-up work that depends on prior context; higher token/cost and may include sensitive context.",
|
|
507
|
+
"For sequential work (task B depends on task A's output), make separate",
|
|
508
|
+
"tool calls one after another. Do NOT put dependent tasks in the same array.",
|
|
549
509
|
"",
|
|
550
|
-
'
|
|
551
|
-
'
|
|
510
|
+
'Single: { tasks: [{ agent: "writer", task: "Rewrite README.md" }] }',
|
|
511
|
+
'Parallel: { tasks: [{ agent: "writer", task: "..." }, { agent: "tester", task: "..." }] }',
|
|
552
512
|
].join("\n"),
|
|
553
513
|
parameters: SubagentParams,
|
|
554
514
|
|
|
@@ -557,48 +517,11 @@ The tool always accepts a \`tasks\` array:
|
|
|
557
517
|
const discovery = discoverAgents(ctx.cwd, "both");
|
|
558
518
|
const { agents } = discovery;
|
|
559
519
|
|
|
560
|
-
const delegationMode = parseDelegationMode(params.mode);
|
|
561
|
-
if (!delegationMode) {
|
|
562
|
-
const fallbackDetails = makeDetailsFactory(
|
|
563
|
-
discovery.projectAgentsDir,
|
|
564
|
-
DEFAULT_DELEGATION_MODE,
|
|
565
|
-
);
|
|
566
|
-
return {
|
|
567
|
-
content: [
|
|
568
|
-
{
|
|
569
|
-
type: "text",
|
|
570
|
-
text: `Invalid mode \"${String(params.mode)}\". Expected \"spawn\" or \"fork\".\nAvailable agents: ${formatAgentNames(agents)}`,
|
|
571
|
-
},
|
|
572
|
-
],
|
|
573
|
-
details: fallbackDetails("single")([]),
|
|
574
|
-
isError: true,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
|
|
578
520
|
const makeDetails = makeDetailsFactory(
|
|
579
521
|
discovery.projectAgentsDir,
|
|
580
|
-
|
|
522
|
+
DEFAULT_DELEGATION_MODE,
|
|
581
523
|
);
|
|
582
524
|
|
|
583
|
-
let forkSessionSnapshotJsonl: string | undefined;
|
|
584
|
-
if (delegationMode === "fork") {
|
|
585
|
-
forkSessionSnapshotJsonl = buildForkSessionSnapshotJsonl(
|
|
586
|
-
ctx.sessionManager,
|
|
587
|
-
);
|
|
588
|
-
if (!forkSessionSnapshotJsonl) {
|
|
589
|
-
return {
|
|
590
|
-
content: [
|
|
591
|
-
{
|
|
592
|
-
type: "text",
|
|
593
|
-
text: "Cannot use mode=\"fork\": failed to snapshot current session context.",
|
|
594
|
-
},
|
|
595
|
-
],
|
|
596
|
-
details: makeDetails("single")([]),
|
|
597
|
-
isError: true,
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
525
|
const tasks = params.tasks ?? [];
|
|
603
526
|
if (tasks.length === 0) {
|
|
604
527
|
return {
|
|
@@ -700,8 +623,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
700
623
|
task.agent,
|
|
701
624
|
task.task,
|
|
702
625
|
task.cwd,
|
|
703
|
-
delegationMode,
|
|
704
|
-
forkSessionSnapshotJsonl,
|
|
705
626
|
agents,
|
|
706
627
|
ctx.cwd,
|
|
707
628
|
signal,
|
|
@@ -714,8 +635,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
714
635
|
|
|
715
636
|
return await executeParallel(
|
|
716
637
|
tasks,
|
|
717
|
-
delegationMode,
|
|
718
|
-
forkSessionSnapshotJsonl,
|
|
719
638
|
agents,
|
|
720
639
|
ctx.cwd,
|
|
721
640
|
signal,
|
|
@@ -750,8 +669,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
750
669
|
agentName: string,
|
|
751
670
|
task: string,
|
|
752
671
|
cwd: string | undefined,
|
|
753
|
-
delegationMode: DelegationMode,
|
|
754
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
755
672
|
agents: AgentConfig[],
|
|
756
673
|
defaultCwd: string,
|
|
757
674
|
signal: AbortSignal | undefined,
|
|
@@ -768,8 +685,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
768
685
|
agentName,
|
|
769
686
|
task,
|
|
770
687
|
taskCwd: cwd,
|
|
771
|
-
delegationMode,
|
|
772
|
-
forkSessionSnapshotJsonl,
|
|
773
688
|
parentDepth: currentDepth,
|
|
774
689
|
parentAgentStack: ancestorAgentStack,
|
|
775
690
|
maxDepth,
|
|
@@ -786,8 +701,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
786
701
|
agentName,
|
|
787
702
|
task,
|
|
788
703
|
taskCwd: cwd,
|
|
789
|
-
delegationMode,
|
|
790
|
-
forkSessionSnapshotJsonl,
|
|
791
704
|
parentDepth: currentDepth,
|
|
792
705
|
parentAgentStack: ancestorAgentStack,
|
|
793
706
|
maxDepth,
|
|
@@ -827,8 +740,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
827
740
|
|
|
828
741
|
async function executeParallel(
|
|
829
742
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
830
|
-
delegationMode: DelegationMode,
|
|
831
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
832
743
|
agents: AgentConfig[],
|
|
833
744
|
defaultCwd: string,
|
|
834
745
|
signal: AbortSignal | undefined,
|
|
@@ -840,8 +751,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
840
751
|
if (subagentMode === "sdk") {
|
|
841
752
|
return executeParallelSameProcess(
|
|
842
753
|
tasks,
|
|
843
|
-
delegationMode,
|
|
844
|
-
forkSessionSnapshotJsonl,
|
|
845
754
|
agents,
|
|
846
755
|
defaultCwd,
|
|
847
756
|
currentDepth,
|
|
@@ -857,8 +766,6 @@ This guard prevents self-recursion and cyclic handoffs (for example A -> B -> A)
|
|
|
857
766
|
}
|
|
858
767
|
return executeParallelSubprocess(
|
|
859
768
|
tasks,
|
|
860
|
-
delegationMode,
|
|
861
|
-
forkSessionSnapshotJsonl,
|
|
862
769
|
agents,
|
|
863
770
|
defaultCwd,
|
|
864
771
|
currentDepth,
|
package/package.json
CHANGED
package/render.ts
CHANGED
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import { Container, Spacer, Text } from "@mariozechner/pi-tui";
|
|
7
7
|
import {
|
|
8
|
-
type DelegationMode,
|
|
9
8
|
type LiveLogEntry,
|
|
10
9
|
type NestedSubagentResult,
|
|
11
10
|
type SingleResult,
|
|
12
11
|
type SubagentDetails,
|
|
13
12
|
type UsageStats,
|
|
14
|
-
DEFAULT_DELEGATION_MODE,
|
|
15
13
|
aggregateUsage,
|
|
16
14
|
getDisplayItems,
|
|
17
15
|
getFinalOutput,
|
|
@@ -35,6 +33,7 @@ interface TreeNode {
|
|
|
35
33
|
children: TreeNode[];
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
|
|
38
37
|
interface TreeCounts {
|
|
39
38
|
total: number;
|
|
40
39
|
running: number;
|
|
@@ -45,7 +44,6 @@ interface TreeCounts {
|
|
|
45
44
|
|
|
46
45
|
interface PendingSubagentCall {
|
|
47
46
|
toolCallId: string;
|
|
48
|
-
mode: DelegationMode;
|
|
49
47
|
tasks: Array<{ agent: string; task?: string }>;
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -83,10 +81,6 @@ function truncate(text: string, maxLen: number): string {
|
|
|
83
81
|
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text;
|
|
84
82
|
}
|
|
85
83
|
|
|
86
|
-
function normalizeDelegationMode(raw: unknown): DelegationMode {
|
|
87
|
-
return raw === "fork" ? "fork" : DEFAULT_DELEGATION_MODE;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
84
|
function splitOutputLines(text: string): string[] {
|
|
91
85
|
const lines = text.replace(/\r\n?/g, "\n").split("\n");
|
|
92
86
|
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
@@ -166,7 +160,6 @@ function extractPendingSubagentCalls(messages: SingleResult["messages"]): Pendin
|
|
|
166
160
|
: typeof part.id === "string"
|
|
167
161
|
? part.id
|
|
168
162
|
: `${messageIndex}:${partIndex}`,
|
|
169
|
-
mode: normalizeDelegationMode((args as any).mode),
|
|
170
163
|
tasks,
|
|
171
164
|
});
|
|
172
165
|
}
|
|
@@ -178,14 +171,13 @@ function buildPendingNodes(call: PendingSubagentCall): TreeNode[] {
|
|
|
178
171
|
return call.tasks.map((task) => ({
|
|
179
172
|
label: task.agent,
|
|
180
173
|
status: "running",
|
|
181
|
-
meta: call.mode === "fork" ? "fork" : "spawn",
|
|
182
174
|
task: task.task,
|
|
183
175
|
children: [],
|
|
184
176
|
}));
|
|
185
177
|
}
|
|
186
178
|
|
|
187
179
|
function buildNodesFromNestedResult(nested: NestedSubagentResult): TreeNode[] {
|
|
188
|
-
return nested.details.results.map((result) => buildResultNode(result
|
|
180
|
+
return nested.details.results.map((result) => buildResultNode(result));
|
|
189
181
|
}
|
|
190
182
|
|
|
191
183
|
function buildNestedChildren(result: SingleResult): TreeNode[] {
|
|
@@ -292,10 +284,10 @@ function buildLeafPreview(result: SingleResult): string[] | undefined {
|
|
|
292
284
|
return unique.length > 0 ? unique.slice(-OUTPUT_PREVIEW_LINE_COUNT) : undefined;
|
|
293
285
|
}
|
|
294
286
|
|
|
295
|
-
function buildResultNode(result: SingleResult
|
|
287
|
+
function buildResultNode(result: SingleResult): TreeNode {
|
|
296
288
|
const status = statusFromResult(result);
|
|
297
289
|
const usage = formatUsage(result.usage, result.model);
|
|
298
|
-
const metaParts: string[] = [result.agentSource
|
|
290
|
+
const metaParts: string[] = [result.agentSource];
|
|
299
291
|
if (usage) metaParts.push(usage);
|
|
300
292
|
if (status === "error") {
|
|
301
293
|
const errorText = result.errorMessage || result.stderr || result.stopReason;
|
|
@@ -316,7 +308,7 @@ function buildResultNode(result: SingleResult, delegationMode: DelegationMode):
|
|
|
316
308
|
}
|
|
317
309
|
|
|
318
310
|
function buildTopLevelNodes(details: SubagentDetails): TreeNode[] {
|
|
319
|
-
return details.results.map((result) => buildResultNode(result
|
|
311
|
+
return details.results.map((result) => buildResultNode(result));
|
|
320
312
|
}
|
|
321
313
|
|
|
322
314
|
function renderTreeLines(
|
|
@@ -374,10 +366,9 @@ function topLevelSummary(details: SubagentDetails, counts: TreeCounts): string {
|
|
|
374
366
|
// ---------------------------------------------------------------------------
|
|
375
367
|
|
|
376
368
|
export function renderCall(args: Record<string, any>, theme: { fg: ThemeFg; bold: (s: string) => string }): Text {
|
|
377
|
-
const delegationMode = normalizeDelegationMode(args.mode);
|
|
378
369
|
const tasks = Array.isArray(args.tasks) ? args.tasks : [];
|
|
379
370
|
const count = tasks.length;
|
|
380
|
-
let text = `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("
|
|
371
|
+
let text = `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", `${count} task${count === 1 ? "" : "s"}`)}`;
|
|
381
372
|
for (const task of tasks.slice(0, 6)) {
|
|
382
373
|
const agent = typeof task?.agent === "string" ? task.agent : "...";
|
|
383
374
|
const preview = typeof task?.task === "string" ? ` ${truncate(task.task, 56)}` : "";
|
|
@@ -414,7 +405,7 @@ export function renderResult(
|
|
|
414
405
|
const container = new Container();
|
|
415
406
|
container.addChild(
|
|
416
407
|
new Text(
|
|
417
|
-
`${icon} ${theme.fg("toolTitle", theme.bold("subagent tree "))}${theme.fg("
|
|
408
|
+
`${icon} ${theme.fg("toolTitle", theme.bold("subagent tree "))}${theme.fg("dim", topLevelSummary(details, counts))}`,
|
|
418
409
|
0,
|
|
419
410
|
0,
|
|
420
411
|
),
|
package/runner-sdk.ts
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* read at the root level exactly as before — they're just propagated to nested
|
|
9
9
|
* sessions via closure parameters rather than via subprocess env inheritance.
|
|
10
10
|
*
|
|
11
|
-
* Fork mode still writes a JSONL snapshot to a temp file; the child session
|
|
12
|
-
* opens it with SessionManager.open() instead of receiving it via --session.
|
|
13
11
|
*/
|
|
14
12
|
|
|
15
13
|
import {
|
|
@@ -26,12 +24,8 @@ import {
|
|
|
26
24
|
createLsTool,
|
|
27
25
|
type ToolDefinition,
|
|
28
26
|
} from "@mariozechner/pi-coding-agent";
|
|
29
|
-
import * as fs from "node:fs";
|
|
30
|
-
import * as os from "node:os";
|
|
31
|
-
import * as path from "node:path";
|
|
32
27
|
import type { AgentConfig } from "./agents.js";
|
|
33
28
|
import {
|
|
34
|
-
type DelegationMode,
|
|
35
29
|
type LiveLogEntry,
|
|
36
30
|
type SingleResult,
|
|
37
31
|
type SubagentDetails,
|
|
@@ -60,26 +54,6 @@ import {
|
|
|
60
54
|
// ---------------------------------------------------------------------------
|
|
61
55
|
|
|
62
56
|
|
|
63
|
-
function writeForkSessionToTempFile(
|
|
64
|
-
agentName: string,
|
|
65
|
-
sessionJsonl: string,
|
|
66
|
-
): { dir: string; filePath: string } {
|
|
67
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
68
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
69
|
-
const filePath = path.join(tmpDir, `fork-${safeName}.jsonl`);
|
|
70
|
-
fs.writeFileSync(filePath, sessionJsonl, { encoding: "utf-8", mode: 0o600 });
|
|
71
|
-
return { dir: tmpDir, filePath };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function cleanupTempDir(dir: string | null): void {
|
|
75
|
-
if (!dir) return;
|
|
76
|
-
try {
|
|
77
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
78
|
-
} catch {
|
|
79
|
-
/* ignore */
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
57
|
/**
|
|
84
58
|
* Build the tool list for a child agent.
|
|
85
59
|
* When the agent config restricts tools, only those are included.
|
|
@@ -127,9 +101,6 @@ export interface RunAgentSameProcessOptions {
|
|
|
127
101
|
agentName: string;
|
|
128
102
|
task: string;
|
|
129
103
|
taskCwd?: string;
|
|
130
|
-
delegationMode: DelegationMode;
|
|
131
|
-
/** JSONL session snapshot for fork mode — same format as subprocess runner */
|
|
132
|
-
forkSessionSnapshotJsonl?: string;
|
|
133
104
|
parentDepth: number;
|
|
134
105
|
parentAgentStack: string[];
|
|
135
106
|
maxDepth: number;
|
|
@@ -163,8 +134,6 @@ export async function runAgentSameProcess(
|
|
|
163
134
|
agentName,
|
|
164
135
|
task,
|
|
165
136
|
taskCwd,
|
|
166
|
-
delegationMode,
|
|
167
|
-
forkSessionSnapshotJsonl,
|
|
168
137
|
parentDepth,
|
|
169
138
|
parentAgentStack,
|
|
170
139
|
maxDepth,
|
|
@@ -194,28 +163,6 @@ export async function runAgentSameProcess(
|
|
|
194
163
|
};
|
|
195
164
|
}
|
|
196
165
|
|
|
197
|
-
if (
|
|
198
|
-
delegationMode === "fork" &&
|
|
199
|
-
(!forkSessionSnapshotJsonl || !forkSessionSnapshotJsonl.trim())
|
|
200
|
-
) {
|
|
201
|
-
return {
|
|
202
|
-
agent: agentName,
|
|
203
|
-
agentSource: agent.source,
|
|
204
|
-
task,
|
|
205
|
-
exitCode: 1,
|
|
206
|
-
messages: [],
|
|
207
|
-
stderr: "Cannot run in fork mode: missing parent session snapshot context.",
|
|
208
|
-
usage: emptyUsage(),
|
|
209
|
-
toolCalls: {},
|
|
210
|
-
model: agent.model,
|
|
211
|
-
stopReason: "error",
|
|
212
|
-
errorMessage: "Cannot run in fork mode: missing parent session snapshot context.",
|
|
213
|
-
completedTurns: 0,
|
|
214
|
-
turnInProgress: false,
|
|
215
|
-
liveLog: [],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
166
|
const childDepth = Math.max(0, Math.floor(parentDepth)) + 1;
|
|
220
167
|
const childStack = [...parentAgentStack, agentName];
|
|
221
168
|
const effectiveCwd = taskCwd ?? cwd;
|
|
@@ -243,16 +190,6 @@ export async function runAgentSameProcess(
|
|
|
243
190
|
|
|
244
191
|
emitUpdate();
|
|
245
192
|
|
|
246
|
-
// Fork mode: write JSONL to a temp file; opened via SessionManager.open()
|
|
247
|
-
// (same serialisation format as the subprocess runner — backward-compatible)
|
|
248
|
-
let forkTmpDir: string | null = null;
|
|
249
|
-
let forkTmpPath: string | null = null;
|
|
250
|
-
if (delegationMode === "fork" && forkSessionSnapshotJsonl) {
|
|
251
|
-
const tmp = writeForkSessionToTempFile(agentName, forkSessionSnapshotJsonl);
|
|
252
|
-
forkTmpDir = tmp.dir;
|
|
253
|
-
forkTmpPath = tmp.filePath;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
193
|
const childCtx: SubagentSessionContext = {
|
|
257
194
|
depth: childDepth,
|
|
258
195
|
maxDepth,
|
|
@@ -284,9 +221,7 @@ export async function runAgentSameProcess(
|
|
|
284
221
|
// Thinking level from agent config (same string values as --thinking flag)
|
|
285
222
|
const thinkingLevel = agent.thinking as any | undefined;
|
|
286
223
|
|
|
287
|
-
const sessionManager =
|
|
288
|
-
? SessionManager.open(forkTmpPath)
|
|
289
|
-
: SessionManager.inMemory();
|
|
224
|
+
const sessionManager = SessionManager.inMemory();
|
|
290
225
|
|
|
291
226
|
const { session } = await subagentContext.run(childCtx, () =>
|
|
292
227
|
createAgentSession({
|
|
@@ -407,8 +342,6 @@ export async function runAgentSameProcess(
|
|
|
407
342
|
result.errorMessage = result.errorMessage ?? msg;
|
|
408
343
|
if (!result.stderr.trim()) result.stderr = msg;
|
|
409
344
|
return result;
|
|
410
|
-
} finally {
|
|
411
|
-
cleanupTempDir(forkTmpDir);
|
|
412
345
|
}
|
|
413
346
|
}
|
|
414
347
|
|
|
@@ -418,8 +351,6 @@ export async function runAgentSameProcess(
|
|
|
418
351
|
|
|
419
352
|
export async function executeParallelSameProcess(
|
|
420
353
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
421
|
-
delegationMode: DelegationMode,
|
|
422
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
423
354
|
agents: AgentConfig[],
|
|
424
355
|
defaultCwd: string,
|
|
425
356
|
depth: number,
|
|
@@ -509,8 +440,6 @@ export async function executeParallelSameProcess(
|
|
|
509
440
|
agentName: t.agent,
|
|
510
441
|
task: t.task,
|
|
511
442
|
taskCwd: t.cwd,
|
|
512
|
-
delegationMode,
|
|
513
|
-
forkSessionSnapshotJsonl,
|
|
514
443
|
parentDepth: depth,
|
|
515
444
|
parentAgentStack: stack,
|
|
516
445
|
maxDepth,
|
package/runner.ts
CHANGED
|
@@ -12,12 +12,10 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
|
12
12
|
import type { Message } from "@mariozechner/pi-ai";
|
|
13
13
|
import type { AgentConfig } from "./agents.js";
|
|
14
14
|
import {
|
|
15
|
-
type DelegationMode,
|
|
16
15
|
type LiveLogEntry,
|
|
17
16
|
type SingleResult,
|
|
18
17
|
type SubagentDetails,
|
|
19
18
|
MAX_LIVE_LOG_ENTRIES,
|
|
20
|
-
buildSubagentDetails,
|
|
21
19
|
emptyUsage,
|
|
22
20
|
extractToolCalls,
|
|
23
21
|
getFinalOutput,
|
|
@@ -74,17 +72,6 @@ function writePromptToTempFile(
|
|
|
74
72
|
return { dir: tmpDir, filePath };
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
function writeForkSessionToTempFile(
|
|
78
|
-
agentName: string,
|
|
79
|
-
sessionJsonl: string,
|
|
80
|
-
): { dir: string; filePath: string } {
|
|
81
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-"));
|
|
82
|
-
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
83
|
-
const filePath = path.join(tmpDir, `fork-${safeName}.jsonl`);
|
|
84
|
-
fs.writeFileSync(filePath, sessionJsonl, { encoding: "utf-8", mode: 0o600 });
|
|
85
|
-
return { dir: tmpDir, filePath };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
75
|
function cleanupTempDir(dir: string | null): void {
|
|
89
76
|
if (!dir) return;
|
|
90
77
|
try {
|
|
@@ -363,8 +350,6 @@ function buildPiArgs(
|
|
|
363
350
|
agent: AgentConfig,
|
|
364
351
|
systemPromptPath: string | null,
|
|
365
352
|
task: string,
|
|
366
|
-
delegationMode: DelegationMode,
|
|
367
|
-
forkSessionPath: string | null,
|
|
368
353
|
): string[] {
|
|
369
354
|
const args: string[] = [
|
|
370
355
|
"--mode",
|
|
@@ -372,14 +357,9 @@ function buildPiArgs(
|
|
|
372
357
|
..._inheritedCliArgs.extensionArgs,
|
|
373
358
|
..._inheritedCliArgs.alwaysProxy,
|
|
374
359
|
"-p",
|
|
360
|
+
"--no-session",
|
|
375
361
|
];
|
|
376
362
|
|
|
377
|
-
if (delegationMode === "spawn") {
|
|
378
|
-
args.push("--no-session");
|
|
379
|
-
} else if (forkSessionPath) {
|
|
380
|
-
args.push("--session", forkSessionPath);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
363
|
// Agent config takes priority; fall back to parent CLI value
|
|
384
364
|
const model = agent.model ?? _inheritedCliArgs.fallbackModel;
|
|
385
365
|
if (model) args.push("--model", model);
|
|
@@ -425,10 +405,6 @@ export interface RunAgentOptions {
|
|
|
425
405
|
task: string;
|
|
426
406
|
/** Optional override working directory. */
|
|
427
407
|
taskCwd?: string;
|
|
428
|
-
/** Context mode: spawn (fresh) or fork (session snapshot + task). */
|
|
429
|
-
delegationMode: DelegationMode;
|
|
430
|
-
/** Serialized parent session snapshot used when delegationMode is "fork". */
|
|
431
|
-
forkSessionSnapshotJsonl?: string;
|
|
432
408
|
/** Current delegation depth of the caller process. */
|
|
433
409
|
parentDepth: number;
|
|
434
410
|
/** Delegation stack from the caller process (ancestor agent names). */
|
|
@@ -457,8 +433,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
457
433
|
agentName,
|
|
458
434
|
task,
|
|
459
435
|
taskCwd,
|
|
460
|
-
delegationMode,
|
|
461
|
-
forkSessionSnapshotJsonl,
|
|
462
436
|
parentDepth,
|
|
463
437
|
parentAgentStack,
|
|
464
438
|
maxDepth,
|
|
@@ -486,30 +460,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
486
460
|
};
|
|
487
461
|
}
|
|
488
462
|
|
|
489
|
-
if (
|
|
490
|
-
delegationMode === "fork" &&
|
|
491
|
-
(!forkSessionSnapshotJsonl || !forkSessionSnapshotJsonl.trim())
|
|
492
|
-
) {
|
|
493
|
-
return {
|
|
494
|
-
agent: agentName,
|
|
495
|
-
agentSource: agent.source,
|
|
496
|
-
task,
|
|
497
|
-
exitCode: 1,
|
|
498
|
-
messages: [],
|
|
499
|
-
stderr:
|
|
500
|
-
"Cannot run in fork mode: missing parent session snapshot context.",
|
|
501
|
-
usage: emptyUsage(),
|
|
502
|
-
toolCalls: {},
|
|
503
|
-
model: agent.model,
|
|
504
|
-
stopReason: "error",
|
|
505
|
-
errorMessage:
|
|
506
|
-
"Cannot run in fork mode: missing parent session snapshot context.",
|
|
507
|
-
completedTurns: 0,
|
|
508
|
-
turnInProgress: false,
|
|
509
|
-
liveLog: [],
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
463
|
const result: SingleResult = {
|
|
514
464
|
agent: agentName,
|
|
515
465
|
agentSource: agent.source,
|
|
@@ -548,22 +498,11 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
548
498
|
promptTmpPath = tmp.filePath;
|
|
549
499
|
}
|
|
550
500
|
|
|
551
|
-
// Write forked session snapshot if needed
|
|
552
|
-
let forkSessionTmpDir: string | null = null;
|
|
553
|
-
let forkSessionTmpPath: string | null = null;
|
|
554
|
-
if (delegationMode === "fork" && forkSessionSnapshotJsonl) {
|
|
555
|
-
const tmp = writeForkSessionToTempFile(agent.name, forkSessionSnapshotJsonl);
|
|
556
|
-
forkSessionTmpDir = tmp.dir;
|
|
557
|
-
forkSessionTmpPath = tmp.filePath;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
501
|
try {
|
|
561
502
|
const piArgs = buildPiArgs(
|
|
562
503
|
agent,
|
|
563
504
|
promptTmpPath,
|
|
564
505
|
task,
|
|
565
|
-
delegationMode,
|
|
566
|
-
forkSessionTmpPath,
|
|
567
506
|
);
|
|
568
507
|
let wasAborted = false;
|
|
569
508
|
|
|
@@ -761,7 +700,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
761
700
|
return result;
|
|
762
701
|
} finally {
|
|
763
702
|
cleanupTempDir(promptTmpDir);
|
|
764
|
-
cleanupTempDir(forkSessionTmpDir);
|
|
765
703
|
}
|
|
766
704
|
}
|
|
767
705
|
|
|
@@ -774,8 +712,6 @@ export async function runAgentSubprocess(opts: RunAgentOptions): Promise<SingleR
|
|
|
774
712
|
|
|
775
713
|
export async function executeParallelSubprocess(
|
|
776
714
|
tasks: Array<{ agent: string; task: string; cwd?: string }>,
|
|
777
|
-
delegationMode: DelegationMode,
|
|
778
|
-
forkSessionSnapshotJsonl: string | undefined,
|
|
779
715
|
agents: AgentConfig[],
|
|
780
716
|
defaultCwd: string,
|
|
781
717
|
parentDepth: number,
|
|
@@ -865,8 +801,6 @@ export async function executeParallelSubprocess(
|
|
|
865
801
|
agentName: t.agent,
|
|
866
802
|
task: t.task,
|
|
867
803
|
taskCwd: t.cwd,
|
|
868
|
-
delegationMode,
|
|
869
|
-
forkSessionSnapshotJsonl,
|
|
870
804
|
parentDepth,
|
|
871
805
|
parentAgentStack,
|
|
872
806
|
maxDepth,
|
package/shared.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const subagentContext = new AsyncLocalStorage<SubagentSessionContext>();
|
|
|
31
31
|
// Parallel execution limits
|
|
32
32
|
// ---------------------------------------------------------------------------
|
|
33
33
|
|
|
34
|
-
export const DEFAULT_MAX_PARALLEL_TASKS =
|
|
34
|
+
export const DEFAULT_MAX_PARALLEL_TASKS = 30;
|
|
35
35
|
export const DEFAULT_MAX_CONCURRENCY = 8;
|
|
36
36
|
export const PARALLEL_HEARTBEAT_MS = 1000;
|
|
37
37
|
export const SUBAGENT_MAX_PARALLEL_TASKS_ENV = "PI_SUBAGENT_MAX_PARALLEL_TASKS";
|
package/types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import type { Message } from "@mariozechner/pi-ai";
|
|
6
6
|
|
|
7
7
|
/** Context mode for delegated runs. */
|
|
8
|
-
export type DelegationMode = "spawn"
|
|
8
|
+
export type DelegationMode = "spawn";
|
|
9
9
|
|
|
10
10
|
/** Default context mode for delegated runs. */
|
|
11
11
|
export const DEFAULT_DELEGATION_MODE: DelegationMode = "spawn";
|
|
@@ -224,7 +224,7 @@ export function isSubagentDetails(value: unknown): value is SubagentDetails {
|
|
|
224
224
|
if (!value || typeof value !== "object") return false;
|
|
225
225
|
const maybe = value as Partial<SubagentDetails>;
|
|
226
226
|
return (maybe.mode === "single" || maybe.mode === "parallel") &&
|
|
227
|
-
|
|
227
|
+
maybe.delegationMode === "spawn" &&
|
|
228
228
|
Array.isArray(maybe.results);
|
|
229
229
|
}
|
|
230
230
|
|