@leo-alvarenga/pi-mini-subagents 0.2.8 → 0.2.11
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 +0 -8
- package/package.json +2 -2
- package/src/config.ts +129 -0
- package/src/constants.ts +17 -27
- package/src/index.ts +4 -112
- package/skills/subagent/SKILL.md +0 -52
package/README.md
CHANGED
|
@@ -70,14 +70,6 @@ Tool parameters:
|
|
|
70
70
|
`Alt+S` toggles the panel. If your terminal intercepts that chord, pick a free
|
|
71
71
|
one and change `PANEL_TOGGLE_CHORD` in `src/constants.ts`.
|
|
72
72
|
|
|
73
|
-
## Skill
|
|
74
|
-
|
|
75
|
-
The package ships a sample `subagent` skill (`skills/subagent/SKILL.md`),
|
|
76
|
-
auto-loaded by pi. It teaches the agent when to delegate to `mini_subagent`,
|
|
77
|
-
how to write self-contained tasks, and when to keep work in the main context.
|
|
78
|
-
To install it manually (e.g. without the extension), copy that `SKILL.md` into
|
|
79
|
-
`~/.pi/agent/skills/subagent/`.
|
|
80
|
-
|
|
81
73
|
## License
|
|
82
74
|
|
|
83
75
|
MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Leonardo A. Alvarenga.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leo-alvarenga/pi-mini-subagents",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Transient subagents for Pi: mini_subagent tool (single + parallel), /subagents command, and a live TUI panel",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "github.com/leo-alvarenga/pi-mono",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"typebox": "*"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@leo-alvarenga/pi-ext-core": "^0.4.
|
|
30
|
+
"@leo-alvarenga/pi-ext-core": "^0.4.3"
|
|
31
31
|
},
|
|
32
32
|
"pi": {
|
|
33
33
|
"extensions": [
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
capitalize,
|
|
4
|
+
formatTokens,
|
|
5
|
+
type SubagentRecord,
|
|
6
|
+
type SubagentSpec,
|
|
7
|
+
} from "@leo-alvarenga/pi-ext-core";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
MAX_CONCURRENCY,
|
|
11
|
+
MAX_PANEL_ROWS,
|
|
12
|
+
MAX_PARALLEL_TASKS,
|
|
13
|
+
MAX_STORED_OUTPUT,
|
|
14
|
+
MAX_WRITES_PER_SUBAGENT,
|
|
15
|
+
NEEDS_INPUT_MARKER,
|
|
16
|
+
NEEDS_INPUT_SUFFIX,
|
|
17
|
+
PANEL_TOGGLE_CHORD,
|
|
18
|
+
PER_TASK_OUTPUT_CAP,
|
|
19
|
+
REPORT_ENTRY,
|
|
20
|
+
STATE_ENTRY,
|
|
21
|
+
STATUS_STYLES,
|
|
22
|
+
WIDGET_KEY,
|
|
23
|
+
} from "./constants";
|
|
24
|
+
|
|
25
|
+
function getStyledSubagent(r: SubagentRecord, th: Theme): string {
|
|
26
|
+
const task = r.task.replace(/\s*[\r\n]+\s*/g, " ");
|
|
27
|
+
|
|
28
|
+
const style = STATUS_STYLES[r.status] ?? STATUS_STYLES.completed;
|
|
29
|
+
let line = `${th.fg(style.fg, style.icon)} ${th.fg("accent", `#${r.id}`)} ${th.fg(style.fg, task)}`;
|
|
30
|
+
|
|
31
|
+
if (r.status === "completed" && r.tokens) {
|
|
32
|
+
line += th.fg("dim", ` · ${formatTokens(r.tokens)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (r.status === "failed") line += th.fg("dim", " · failed");
|
|
36
|
+
if (r.status === "needs_input") line += th.fg("dim", " · needs input");
|
|
37
|
+
if (r.allowWrite) line += th.fg("warning", " ✎");
|
|
38
|
+
|
|
39
|
+
return line;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const SUBAGENTS_SPEC: SubagentSpec = {
|
|
43
|
+
tool: {
|
|
44
|
+
name: "mini_subagents",
|
|
45
|
+
label: "Mini Subagents",
|
|
46
|
+
description:
|
|
47
|
+
"Delegate a task to a transient headless subagent (a separate pi process) and get its findings back. " +
|
|
48
|
+
"Modes: single (task) or parallel (tasks array, max 8). Subagents are read-only by default; set allowWrite to let one edit files. " +
|
|
49
|
+
"If a subagent reports it needs input (NEEDS_INPUT), answer the questions and call again with `answers`.",
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
spawn: {
|
|
53
|
+
flagEnv: "PI_SUBAGENT",
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
prompt: {
|
|
57
|
+
snippet:
|
|
58
|
+
"Delegate a task to a transient headless subagent (single or parallel) and get its results back",
|
|
59
|
+
guidelines: [
|
|
60
|
+
"Subagents are read-only unless you set allowWrite: true.",
|
|
61
|
+
"If a result asks for input, answer the questions (ask the user if needed) and re-call with `answers` — do not guess.",
|
|
62
|
+
],
|
|
63
|
+
instructions: {
|
|
64
|
+
always: "You are a transient subagent. Complete the task, then stop",
|
|
65
|
+
readOnly:
|
|
66
|
+
"You may only READ and EXPLORE. Do not modify files or run mutating commands.",
|
|
67
|
+
writeAllowed:
|
|
68
|
+
"You may edit files ONLY if strictly necessary, preferring hash-anchored operations (replace/insert) over rewriting.",
|
|
69
|
+
},
|
|
70
|
+
needsInput: {
|
|
71
|
+
marker: NEEDS_INPUT_MARKER,
|
|
72
|
+
suffix: NEEDS_INPUT_SUFFIX,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
endorsement: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
orchestratorMode: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
limits: {
|
|
85
|
+
maxPanelRows: MAX_PANEL_ROWS,
|
|
86
|
+
maxConcurrency: MAX_CONCURRENCY,
|
|
87
|
+
maxStoredOutput: MAX_STORED_OUTPUT,
|
|
88
|
+
maxParallelTasks: MAX_PARALLEL_TASKS,
|
|
89
|
+
perTaskOutputCap: PER_TASK_OUTPUT_CAP,
|
|
90
|
+
maxWritesPerSubagent: MAX_WRITES_PER_SUBAGENT,
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
entries: {
|
|
94
|
+
state: STATE_ENTRY,
|
|
95
|
+
report: REPORT_ENTRY,
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
panel: {
|
|
99
|
+
title: " Subagents",
|
|
100
|
+
widgetKey: WIDGET_KEY,
|
|
101
|
+
toggleChord: PANEL_TOGGLE_CHORD,
|
|
102
|
+
emptyText: "No subagents yet. Ask the agent to delegate a task!",
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
rowLine: (r, theme) => getStyledSubagent(r, theme),
|
|
106
|
+
|
|
107
|
+
reportSections: (s) => {
|
|
108
|
+
const byStatus = s.records.reduce<Record<string, SubagentRecord[]>>(
|
|
109
|
+
(acc, curr) => {
|
|
110
|
+
const label = capitalize(curr.status.toLowerCase()).replaceAll(
|
|
111
|
+
"_",
|
|
112
|
+
" ",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!acc[label]?.length) acc[label] = [];
|
|
116
|
+
|
|
117
|
+
acc[label].push(curr);
|
|
118
|
+
|
|
119
|
+
return acc;
|
|
120
|
+
},
|
|
121
|
+
{},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return Object.entries(byStatus).map(([label, records]) => ({
|
|
125
|
+
label,
|
|
126
|
+
records,
|
|
127
|
+
}));
|
|
128
|
+
},
|
|
129
|
+
};
|
package/src/constants.ts
CHANGED
|
@@ -15,21 +15,6 @@ export const STATUS_STYLES: Record<SubagentStatus, StatusUi> = {
|
|
|
15
15
|
needs_input: { icon: "? ", fg: "warning", bold: true },
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
/** Tool allowlist for a read-only subagent. */
|
|
19
|
-
export const READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
20
|
-
|
|
21
|
-
/** Tool allowlist for a write-capable subagent (hash-anchored ops first). */
|
|
22
|
-
export const WRITE_TOOLS = [
|
|
23
|
-
"read",
|
|
24
|
-
"grep",
|
|
25
|
-
"find",
|
|
26
|
-
"ls",
|
|
27
|
-
"replace",
|
|
28
|
-
"insert",
|
|
29
|
-
"edit",
|
|
30
|
-
"write",
|
|
31
|
-
];
|
|
32
|
-
|
|
33
18
|
/** Marker the subagent emits when it cannot proceed without outside input. */
|
|
34
19
|
export const NEEDS_INPUT_MARKER = "NEEDS_INPUT:";
|
|
35
20
|
|
|
@@ -41,26 +26,31 @@ ${NEEDS_INPUT_MARKER}
|
|
|
41
26
|
|
|
42
27
|
Report your findings clearly and concisely.`;
|
|
43
28
|
|
|
44
|
-
/** Max parallel tasks per call
|
|
45
|
-
export const MAX_PARALLEL_TASKS =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
29
|
+
/** Max parallel tasks per call */
|
|
30
|
+
export const MAX_PARALLEL_TASKS = 12;
|
|
31
|
+
|
|
32
|
+
/** Max concurrent subagent processes */
|
|
33
|
+
export const MAX_CONCURRENCY = 8;
|
|
34
|
+
|
|
35
|
+
/** Per-task output byte cap for parallel results */
|
|
36
|
+
export const PER_TASK_OUTPUT_CAP = 80 * 1024;
|
|
37
|
+
|
|
38
|
+
/** Max chars of final output kept in the stored record (TUI summary) */
|
|
51
39
|
export const MAX_STORED_OUTPUT = 2000;
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
export const MAX_WRITES_PER_SUBAGENT = 4;
|
|
42
|
+
|
|
43
|
+
/** Max task rows rendered in the expanded TUI widget */
|
|
54
44
|
export const MAX_PANEL_ROWS = 8;
|
|
55
45
|
|
|
56
|
-
/** Widget key for the panel above the editor
|
|
46
|
+
/** Widget key for the panel above the editor */
|
|
57
47
|
export const WIDGET_KEY = "subagents";
|
|
58
48
|
|
|
59
|
-
/** Custom entry type carrying the durable state snapshot
|
|
49
|
+
/** Custom entry type carrying the durable state snapshot */
|
|
60
50
|
export const STATE_ENTRY = "subagents.state";
|
|
61
51
|
|
|
62
|
-
/** Custom entry type rendered by the /subagents command
|
|
52
|
+
/** Custom entry type rendered by the /subagents command */
|
|
63
53
|
export const REPORT_ENTRY = "subagents.report";
|
|
64
54
|
|
|
65
|
-
/** Chord that toggles the panel
|
|
55
|
+
/** Chord that toggles the panel */
|
|
66
56
|
export const PANEL_TOGGLE_CHORD: KeyId = "alt+s";
|
package/src/index.ts
CHANGED
|
@@ -1,120 +1,12 @@
|
|
|
1
|
-
import type { ExtensionAPI
|
|
2
|
-
import {
|
|
3
|
-
capitalize,
|
|
4
|
-
createSubagentRuntime,
|
|
5
|
-
formatTokens,
|
|
6
|
-
type SubagentRecord,
|
|
7
|
-
} from "@leo-alvarenga/pi-ext-core";
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { createSubagentRuntime } from "@leo-alvarenga/pi-ext-core";
|
|
8
3
|
|
|
9
|
-
import {
|
|
10
|
-
MAX_CONCURRENCY,
|
|
11
|
-
MAX_PANEL_ROWS,
|
|
12
|
-
MAX_PARALLEL_TASKS,
|
|
13
|
-
MAX_STORED_OUTPUT,
|
|
14
|
-
NEEDS_INPUT_MARKER,
|
|
15
|
-
NEEDS_INPUT_SUFFIX,
|
|
16
|
-
PANEL_TOGGLE_CHORD,
|
|
17
|
-
PER_TASK_OUTPUT_CAP,
|
|
18
|
-
READ_ONLY_TOOLS,
|
|
19
|
-
REPORT_ENTRY,
|
|
20
|
-
STATE_ENTRY,
|
|
21
|
-
STATUS_STYLES,
|
|
22
|
-
WIDGET_KEY,
|
|
23
|
-
WRITE_TOOLS,
|
|
24
|
-
} from "./constants";
|
|
25
|
-
|
|
26
|
-
function getStyledSubagent(r: SubagentRecord, th: Theme): string {
|
|
27
|
-
const task = r.task.replace(/\s*[\r\n]+\s*/g, " ");
|
|
28
|
-
|
|
29
|
-
const style = STATUS_STYLES[r.status] ?? STATUS_STYLES.completed;
|
|
30
|
-
let line = `${th.fg(style.fg, style.icon)} ${th.fg("accent", `#${r.id}`)} ${th.fg(style.fg, task)}`;
|
|
31
|
-
|
|
32
|
-
if (r.status === "completed" && r.tokens) {
|
|
33
|
-
line += th.fg("dim", ` · ${formatTokens(r.tokens)}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (r.status === "failed") line += th.fg("dim", " · failed");
|
|
37
|
-
if (r.status === "needs_input") line += th.fg("dim", " · needs input");
|
|
38
|
-
if (r.allowWrite) line += th.fg("warning", " ✎");
|
|
39
|
-
|
|
40
|
-
return line;
|
|
41
|
-
}
|
|
4
|
+
import { SUBAGENTS_SPEC } from "./config";
|
|
42
5
|
|
|
43
6
|
export default function (pi: ExtensionAPI): void {
|
|
44
7
|
// Child subagent processes inherit PI_SUBAGENT=1; they must not be able to
|
|
45
8
|
// spawn sub-subagents, so the tool/widget/command never register there
|
|
46
9
|
if (process.env.PI_SUBAGENT) return;
|
|
47
10
|
|
|
48
|
-
createSubagentRuntime(pi,
|
|
49
|
-
toolName: "mini_subagent",
|
|
50
|
-
toolLabel: "Mini Subagent",
|
|
51
|
-
spawnFlagEnv: "PI_SUBAGENT",
|
|
52
|
-
toolDescription:
|
|
53
|
-
"Delegate a task to a transient headless subagent (a separate pi process) and get its findings back. " +
|
|
54
|
-
"Modes: single (task) or parallel (tasks array, max 8). Subagents are read-only by default; set allowWrite to let one edit files. " +
|
|
55
|
-
"If a subagent reports it needs input (NEEDS_INPUT), answer the questions and call again with `answers`.",
|
|
56
|
-
promptSnippet:
|
|
57
|
-
"Delegate a task to a transient read-only subagent (single or parallel)",
|
|
58
|
-
promptGuidelines: [
|
|
59
|
-
"Subagents are read-only unless you set allowWrite: true.",
|
|
60
|
-
"If a result asks for input, answer the questions (ask the user if needed) and re-call with `answers` — do not guess.",
|
|
61
|
-
],
|
|
62
|
-
promptInstructions: {
|
|
63
|
-
always: "You are a transient subagent. Complete the task, then stop",
|
|
64
|
-
readOnly:
|
|
65
|
-
"You may only READ and EXPLORE. Do not modify files or run mutating commands.",
|
|
66
|
-
writeAllowed:
|
|
67
|
-
"You may edit files ONLY if strictly necessary, preferring hash-anchored operations (replace/insert) over rewriting.",
|
|
68
|
-
},
|
|
69
|
-
needsInput: {
|
|
70
|
-
marker: NEEDS_INPUT_MARKER,
|
|
71
|
-
suffix: NEEDS_INPUT_SUFFIX,
|
|
72
|
-
},
|
|
73
|
-
allowlists: {
|
|
74
|
-
readOnly: READ_ONLY_TOOLS,
|
|
75
|
-
writeable: WRITE_TOOLS,
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
limits: {
|
|
79
|
-
maxPanelRows: MAX_PANEL_ROWS,
|
|
80
|
-
maxConcurrency: MAX_CONCURRENCY,
|
|
81
|
-
maxStoredOutput: MAX_STORED_OUTPUT,
|
|
82
|
-
maxParallelTasks: MAX_PARALLEL_TASKS,
|
|
83
|
-
perTaskOutputCap: PER_TASK_OUTPUT_CAP,
|
|
84
|
-
},
|
|
85
|
-
|
|
86
|
-
state: { entryType: STATE_ENTRY },
|
|
87
|
-
report: { entryType: REPORT_ENTRY },
|
|
88
|
-
|
|
89
|
-
panel: {
|
|
90
|
-
title: " Subagents",
|
|
91
|
-
widgetKey: WIDGET_KEY,
|
|
92
|
-
toggleChord: PANEL_TOGGLE_CHORD,
|
|
93
|
-
emptyText: "No subagents yet. Ask the agent to delegate a task!",
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
rowLine: (r, theme) => getStyledSubagent(r, theme),
|
|
97
|
-
reportSections: (s) => {
|
|
98
|
-
const byStatus = s.records.reduce<Record<string, SubagentRecord[]>>(
|
|
99
|
-
(acc, curr) => {
|
|
100
|
-
const label = capitalize(curr.status.toLowerCase()).replaceAll(
|
|
101
|
-
"_",
|
|
102
|
-
" ",
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
if (!acc[label]?.length) acc[label] = [];
|
|
106
|
-
|
|
107
|
-
acc[label].push(curr);
|
|
108
|
-
|
|
109
|
-
return acc;
|
|
110
|
-
},
|
|
111
|
-
{},
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
return Object.entries(byStatus).map(([label, records]) => ({
|
|
115
|
-
label,
|
|
116
|
-
records,
|
|
117
|
-
}));
|
|
118
|
-
},
|
|
119
|
-
});
|
|
11
|
+
createSubagentRuntime(pi, SUBAGENTS_SPEC);
|
|
120
12
|
}
|
package/skills/subagent/SKILL.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: subagent
|
|
3
|
-
description: Delegate independent or high-output work to transient subagents via the mini_subagent tool. Use when a task can be parallelized or would flood the main context with raw output (repo-wide scans, large files, multi-part research); subagents report back only their findings.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Using Subagents
|
|
7
|
-
|
|
8
|
-
The `mini_subagent` tool runs a transient headless `pi` process with its own
|
|
9
|
-
isolated context window and returns only its findings. Use it to protect your
|
|
10
|
-
main context and to parallelize independent work.
|
|
11
|
-
|
|
12
|
-
## When to delegate
|
|
13
|
-
|
|
14
|
-
- The task produces a lot of output you only need a summary of (large logs,
|
|
15
|
-
repo-wide greps, multi-file analysis).
|
|
16
|
-
- Several independent questions that can run in parallel — one subagent per
|
|
17
|
-
module, provider, route, or issue.
|
|
18
|
-
- Reading or analyzing big files whose raw bytes should not enter the main
|
|
19
|
-
context.
|
|
20
|
-
|
|
21
|
-
## When NOT to delegate
|
|
22
|
-
|
|
23
|
-
- A one-line answer you can get from a single `read`/`grep` yourself.
|
|
24
|
-
- Work that depends on the main session's in-flight edits or reasoning — a
|
|
25
|
-
subagent starts fresh with no session memory.
|
|
26
|
-
- Edits you must review carefully yourself: keep the subagent read-only and
|
|
27
|
-
make the changes in the main context.
|
|
28
|
-
|
|
29
|
-
## Writing a good task
|
|
30
|
-
|
|
31
|
-
- Make it self-contained: state the goal, the files or directories, and the
|
|
32
|
-
exact shape of the answer you want back.
|
|
33
|
-
- Use absolute or unambiguous paths — the subagent shares only a `cwd`, not
|
|
34
|
-
your current reasoning.
|
|
35
|
-
- Ask for a summary, not a raw dump. That is the whole point.
|
|
36
|
-
|
|
37
|
-
## Read-only by default
|
|
38
|
-
|
|
39
|
-
Subagents can only `read`/`grep`/`find`/`ls` unless you set `allowWrite: true`.
|
|
40
|
-
Prefer read-only: have the subagent report findings, then edit in the main
|
|
41
|
-
context yourself.
|
|
42
|
-
|
|
43
|
-
## Parallel mode
|
|
44
|
-
|
|
45
|
-
Use `tasks` (array, max 8) when the work is independent. Run sequentially when
|
|
46
|
-
tasks share state or depend on each other's results.
|
|
47
|
-
|
|
48
|
-
## NEEDS_INPUT loop
|
|
49
|
-
|
|
50
|
-
If a subagent returns `needs_input`, it cannot proceed. Answer its questions
|
|
51
|
-
(ask the user if you don't know), then re-call with the same `task` and your
|
|
52
|
-
answers in `answers`. Never guess.
|