@mystilleef/pi-subagent 0.9.0 → 0.10.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 CHANGED
@@ -1,16 +1,12 @@
1
- # Subagent
1
+ # Subagents for pi
2
2
 
3
- `pi-subagent` adds isolated subagent orchestration to
4
- [Pi](https://github.com/earendil-works/pi). It provides a `subagent`
5
- tool and `/run` command for delegating work to specialized agents in
6
- separate child Pi processes. Designed especially for the `SPAE`
7
- framework, but doesn't require it.
8
-
9
- ## Action
10
-
11
- Agents in
3
+ Designed to orchestrate agents for the
4
+ [SPAE framework](https://github.com/mystilleef/spae-framework). Agents
5
+ in
12
6
  [action](https://raw.githubusercontent.com/mystilleef/pi-subagent/main/assets/parallel-agents-demo.mp4).
13
7
 
8
+ ---
9
+
14
10
  ## Installation
15
11
 
16
12
  **Install from `npm`:**
@@ -25,13 +21,19 @@ pi install npm:@mystilleef/pi-subagent
25
21
  pi -e npm:@mystilleef/pi-subagent
26
22
  ```
27
23
 
24
+ ---
25
+
28
26
  ## Features
29
27
 
30
28
  - **Asynchronous:** Agents run in the background.
31
29
  - **Parallel:** Run many agents simultaneously.
32
30
  - **Isolated:** Each delegated task receives a separate context window.
33
- - **Simple:** No complex orchestration workflow required.
34
- - **Bloat-free:** No bundled agents.
31
+ - **Nested:** `Subagents` can spawn other `subagents`.
32
+ - **Simple:** No complex orchestration syntax.
33
+ - **Bloat-free:** No bundled agents. Write your own. Or use those from
34
+ the [SPAE Framework](https://github.com/mystilleef/spae-framework).
35
+
36
+ ---
35
37
 
36
38
  ## Usage
37
39
 
@@ -70,12 +72,16 @@ commits; use the query agent to summarize the project.
70
72
  /cancel-subagent
71
73
  ```
72
74
 
75
+ ---
76
+
73
77
  ## _SPAE_ Workflow
74
78
 
75
- `pi-subagent` supports the
76
- [`SPAE` Framework](https://github.com/mystilleef/spae-framework), but
77
- doesn't require it. `SPAE` provides pre-built agents and skills for a
78
- structured workflow.
79
+ The official extension for the
80
+ [SPAE Framework](https://github.com/mystilleef/spae-framework). The
81
+ framework provides pre-built agents and skills for a structured, or
82
+ orchestrated, workflow.
83
+
84
+ ### Structured
79
85
 
80
86
  | Phase | Agent | Purpose |
81
87
  | ----- | ------------------------- | --------------------------------------------- |
@@ -85,32 +91,44 @@ structured workflow.
85
91
  | 4 | `/run build` | Carry out tasks from `PLAN.md` |
86
92
  | 5 | `/run verify` | Verify implementation against `SPEC.md` |
87
93
 
94
+ ### Orchestrated
95
+
96
+ | Agent | Purpose |
97
+ | -------------------------------- | --------------------------------------------- |
98
+ | `/run orchestrate <requirement>` | Run all phases of the `SPAE` workflow |
99
+ | `/run prepare <requirement>` | Run preparatory phases of the `SPAE` workflow |
100
+ | `/run spawn` | Spawn build agents for each task in a plan |
101
+
102
+ ---
103
+
88
104
  ## Agent definitions
89
105
 
90
- This package ships no agents. Define agents as Markdown files with YAML
91
- `frontmatter` and a Markdown system prompt body.
106
+ This extension ships no agents. Define agents as Markdown files with
107
+ YAML `frontmatter` and a Markdown system prompt body.
92
108
 
93
- **Discovery locations:**
109
+ ### Discovery locations
94
110
 
95
111
  - User-global agents: `~/.pi/agents/*.md`
96
112
  - Project-local agents: nearest `.pi/agents/*.md`
97
113
 
98
- **Required `frontmatter`:**
114
+ ### Required front matter
99
115
 
100
116
  ```yaml
101
117
  name: review
102
118
  description: Review code for correctness and maintainability.
103
119
  ```
104
120
 
105
- **Optional `frontmatter`:**
121
+ ### Optional front matter
106
122
 
107
123
  ```yaml
108
124
  tools: read, bash, edit
109
125
  skills: code-review
110
126
  thinking: medium
127
+ provider: deepseek
128
+ model: deepseek-v4-flash
111
129
  ```
112
130
 
113
- **Accepted `thinking` values:**
131
+ ### Accepted thinking values
114
132
 
115
133
  - `off`
116
134
  - `minimal`
@@ -119,6 +137,8 @@ thinking: medium
119
137
  - `high`
120
138
  - `xhigh`
121
139
 
140
+ ---
141
+
122
142
  ## Tool
123
143
 
124
144
  The extension also registers a `subagent` tool for model-driven
@@ -130,9 +150,11 @@ delegation.
130
150
  - `task`: task prompt for the child agent.
131
151
  - `agentScope`: optional lookup scope, one of `user`, `project`, or
132
152
  `both`.
133
- - `debug`: optional flag that requests child diagnostic details. Full child
134
- messages and raw internals require `PI_SUBAGENT_DEBUG_ENABLED=1` in the
135
- host environment.
153
+ - `debug`: optional flag that requests child diagnostic details. Full
154
+ child messages and raw internals require `PI_SUBAGENT_DEBUG_ENABLED=1`
155
+ in the host environment.
156
+
157
+ ---
136
158
 
137
159
  ## Security
138
160
 
@@ -144,39 +166,43 @@ executable automation.
144
166
 
145
167
  - Review project-local agents before running them.
146
168
  - Avoid delegating secrets unless the agent and tools need them.
147
- - Treat child-agent prompts, tool arguments, stderr, and debug transcripts as
148
- potentially sensitive.
169
+ - Treat child-agent prompts, tool arguments, `stderr`, and debug
170
+ transcripts as potentially sensitive.
149
171
  - Enable debug details only for trusted investigations. `debug: true` or
150
172
  `/run --debug` can expose child conversation transcripts, termination
151
- internals, and stderr only when the host explicitly sets
173
+ internals, and `stderr` only when the host explicitly sets
152
174
  `PI_SUBAGENT_DEBUG_ENABLED=1`.
153
175
  - Prefer trusted repositories for shared agent definitions.
154
176
  - Remember that child agents can call their configured tools.
155
177
 
178
+ ---
179
+
156
180
  ## Configuration and limits
157
181
 
158
182
  **Environment variables:**
159
183
 
160
- - `PI_SUBAGENT_DEPTH`: current nested subagent depth counter set internally
161
- for child processes.
162
- - `PI_SUBAGENT_MAX_DEPTH`: max nested subagent depth. Default: `3`. Values
163
- above `10` clamp to the internal ceiling `10`; deeper nesting increases
164
- cost, latency, and runaway delegation risk.
184
+ - `PI_SUBAGENT_DEPTH`: current nested subagent depth counter set
185
+ internally for child processes.
186
+ - `PI_SUBAGENT_MAX_DEPTH`: max nested subagent depth. Default: `3`.
187
+ Values above `10` clamp to the internal ceiling `10`; deeper nesting
188
+ increases cost, latency, and runaway delegation risk.
165
189
  - `PI_SUBAGENT_AGENT_END_GRACE_MS`: child process grace period after
166
190
  `agent_end` before forced termination. Default: `250`.
167
- - `PI_SUBAGENT_MAX_STDERR_BYTES`: max captured child stderr bytes. Default:
168
- `10000`.
191
+ - `PI_SUBAGENT_MAX_STDERR_BYTES`: max captured child `stderr` bytes.
192
+ Default: `10000`.
169
193
  - `PI_SUBAGENT_MAX_OUTPUT_BYTES`: max returned output bytes. Default:
170
194
  `50000`.
171
195
  - `PI_SUBAGENT_MAX_OUTPUT_LINES`: max returned output lines. Default:
172
196
  `500`.
173
197
  - `PI_SUBAGENT_DEBUG_ENABLED`: debug detail authorization. Set to `1` to
174
- allow `debug: true` or `/run --debug` to include sanitized child messages,
175
- termination internals, and stderr; unset values keep non-debug detail
176
- behavior.
198
+ allow `debug: true` or `/run --debug` to include sanitized child
199
+ messages, termination internals, and `stderr`; unset values keep
200
+ non-debug detail behavior.
201
+
202
+ Limit variables parse as positive integers. Empty, zero, negative,
203
+ decimal, `Infinity`, and non-numeric values fall back to defaults.
177
204
 
178
- Limit variables parse as positive integers. Empty, zero, negative, decimal,
179
- `Infinity`, and non-numeric values fall back to defaults.
205
+ ---
180
206
 
181
207
  ## Troubleshooting
182
208
 
@@ -203,6 +229,8 @@ Limit variables parse as positive integers. Empty, zero, negative, decimal,
203
229
  `PI_SUBAGENT_MAX_OUTPUT_LINES`.
204
230
  - Ask the child agent for a shorter summary.
205
231
 
232
+ ---
233
+
206
234
  ## Development
207
235
 
208
236
  **Install dependencies:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mystilleef/pi-subagent",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Pi subagent for the SPAE Framework",
5
5
  "author": "Lateef Alabi-Oki <mystilleef@gmail.com>",
6
6
  "license": "MIT",
@@ -64,14 +64,14 @@
64
64
  "typebox": "*"
65
65
  },
66
66
  "devDependencies": {
67
- "@biomejs/biome": "^2.4.16",
68
- "@earendil-works/pi-agent-core": "^0.78.1",
69
- "@earendil-works/pi-ai": "^0.78.1",
70
- "@earendil-works/pi-coding-agent": "^0.78.1",
71
- "@earendil-works/pi-tui": "^0.78.1",
67
+ "@biomejs/biome": "^2.5.0",
68
+ "@earendil-works/pi-agent-core": "^0.79.3",
69
+ "@earendil-works/pi-ai": "^0.79.3",
70
+ "@earendil-works/pi-coding-agent": "^0.79.3",
71
+ "@earendil-works/pi-tui": "^0.79.3",
72
72
  "@types/bun": "^1.3.14",
73
- "@types/node": "^25.9.2",
74
- "typebox": "^1.2.2",
73
+ "@types/node": "^25.9.3",
74
+ "typebox": "^1.2.9",
75
75
  "typescript": "^6.0.3"
76
76
  }
77
77
  }
@@ -123,6 +123,7 @@ async function hashMarkdownFileAsync(
123
123
  .update(await fsPromises.readFile(path.join(dir, fileName)))
124
124
  .digest("hex");
125
125
  } catch {
126
+ /* missing or unreadable files return null to skip caching */
126
127
  return null;
127
128
  }
128
129
  }
@@ -23,6 +23,8 @@ export interface AgentConfig {
23
23
  tools?: string[] | undefined;
24
24
  skills?: string[] | undefined;
25
25
  thinking?: ThinkingLevel | undefined;
26
+ model?: string | undefined;
27
+ provider?: string | undefined;
26
28
  systemPrompt: string;
27
29
  source: AgentSource;
28
30
  filePath: string;
@@ -74,6 +76,12 @@ function parseThinkingLevel(raw: unknown): ThinkingLevel | undefined {
74
76
  : undefined;
75
77
  }
76
78
 
79
+ function parseOptionalString(raw: unknown): string | undefined {
80
+ if (typeof raw !== "string") return undefined;
81
+ const normalized = raw.trim();
82
+ return normalized.length > 0 ? normalized : undefined;
83
+ }
84
+
77
85
  function parseAgentConfig(
78
86
  content: string,
79
87
  source: AgentSource,
@@ -83,6 +91,7 @@ function parseAgentConfig(
83
91
  try {
84
92
  parsed = parseFrontmatter<Record<string, unknown>>(content);
85
93
  } catch {
94
+ /* malformed frontmatter returns null to skip invalid agent files */
86
95
  return null;
87
96
  }
88
97
  const { frontmatter, body } = parsed;
@@ -98,22 +107,31 @@ function parseAgentConfig(
98
107
  tools: rawTools,
99
108
  skills: rawSkills,
100
109
  thinking: rawThinking,
110
+ model: rawModel,
111
+ provider: rawProvider,
101
112
  } = frontmatter;
102
113
  if (typeof name !== "string" || typeof description !== "string") return null;
103
114
  if (rawTools != null && typeof rawTools !== "string") return null;
104
115
  if (rawSkills != null && typeof rawSkills !== "string") return null;
105
116
  if (rawThinking != null && typeof rawThinking !== "string") return null;
117
+ if (rawModel != null && typeof rawModel !== "string") return null;
118
+ if (rawProvider != null && typeof rawProvider !== "string") return null;
106
119
  const tools = parseCommaList(rawTools);
107
120
  const skills = Object.hasOwn(frontmatter, "skills")
108
121
  ? (parseCommaList(rawSkills) ?? [])
109
122
  : undefined;
110
123
  const thinking = parseThinkingLevel(rawThinking);
124
+ const model = parseOptionalString(rawModel);
125
+ const provider = parseOptionalString(rawProvider);
126
+ if (provider !== undefined && model === undefined) return null;
111
127
  return {
112
128
  name,
113
129
  description,
114
130
  tools,
115
131
  skills,
116
132
  thinking,
133
+ model,
134
+ provider,
117
135
  systemPrompt: body,
118
136
  source,
119
137
  filePath,
@@ -130,6 +148,7 @@ async function loadAgentEntryAsync(
130
148
  try {
131
149
  content = await fsPromises.readFile(filePath, "utf-8");
132
150
  } catch {
151
+ /* unreadable files are skipped silently during agent discovery */
133
152
  return null;
134
153
  }
135
154
  return parseAgentConfig(content, source, filePath);
@@ -156,7 +175,7 @@ async function loadAgentsFromDirAsync(
156
175
  return { agents, markdownFiles };
157
176
  }
158
177
 
159
- export function isMarkdownDirent(entry: Dirent): boolean {
178
+ function isMarkdownDirent(entry: Dirent): boolean {
160
179
  return (
161
180
  entry.name.endsWith(".md") && (entry.isFile() || entry.isSymbolicLink())
162
181
  );
@@ -175,6 +194,7 @@ export async function readMarkdownDirWithStatusAsync(
175
194
  const entries = await fsPromises.readdir(dir, { withFileTypes: true });
176
195
  return { entries: entries.filter(isMarkdownDirent), ok: true };
177
196
  } catch {
197
+ /* missing or inaccessible directories return empty listing */
178
198
  return { entries: [], ok: false };
179
199
  }
180
200
  }
@@ -187,6 +207,7 @@ export async function isDirectoryAsync(p: string): Promise<boolean> {
187
207
  try {
188
208
  return (await fsPromises.stat(p)).isDirectory();
189
209
  } catch {
210
+ /* stat failures indicate non-existent or inaccessible paths */
190
211
  return false;
191
212
  }
192
213
  }
@@ -10,6 +10,7 @@ function tryFirstResult(details: unknown): Record<string, unknown> | null {
10
10
  if (typeof nested !== "object" || nested === null) return null;
11
11
  return nested as Record<string, unknown>;
12
12
  } catch {
13
+ /* malformed details structure returns null to use fallback preview */
13
14
  return null;
14
15
  }
15
16
  }
@@ -90,6 +91,7 @@ export function parseChildEventLine(line: string): ChildEventParseResult {
90
91
  try {
91
92
  event = JSON.parse(line);
92
93
  } catch {
94
+ /* malformed JSON lines are marked invalid for diagnostic reporting */
93
95
  return { kind: "invalid", line };
94
96
  }
95
97
  if (