@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 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.8",
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.1"
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 = 8;
46
- /** Max concurrent subagent processes. */
47
- export const MAX_CONCURRENCY = 4;
48
- /** Per-task output byte cap for parallel results. */
49
- export const PER_TASK_OUTPUT_CAP = 50 * 1024;
50
- /** Max chars of final output kept in the stored record (TUI summary). */
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
- /** Max task rows rendered in the expanded TUI widget. */
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, Theme } from "@earendil-works/pi-coding-agent";
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
  }
@@ -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.