@dreki-gg/pi-subagent 0.3.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @dreki-gg/pi-subagent
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`52d744e`](https://github.com/dreki-gg/pi-extensions/commit/52d744e34f593a7bd6b907d67d5e245ef63b6079) Thanks [@jalbarrang](https://github.com/jalbarrang)! - Improve bundled agent resolution and `/delegate` workflow control in `@dreki-gg/pi-subagent`.
8
+
9
+ - Add explicit agent source tracking for bundled, user, and project agents.
10
+ - Resolve agents with layered precedence: bundled → user → project.
11
+ - Add `agentScope` support to delegated execution so workflows can opt into user, project, or both agent layers.
12
+ - Add `/delegate` argument parsing for `--scope`, `--workflow`, and `--yes-project-agents`.
13
+ - Add a confirmation step before running project-local agents from `/delegate` or the `subagent` tool when UI is available.
14
+ - Replace the old `subagent-workflows` skill with `spawn-subagents`, which steers the assistant toward conversational `subagent` usage and keeps `/delegate` as an explicit gated workflow option.
15
+
16
+ ## 0.3.1
17
+
18
+ ### Patch Changes
19
+
20
+ - [`144498f`](https://github.com/dreki-gg/pi-extensions/commit/144498fd9c23cb5060fb5171e56ff722ebf0c4f2) Thanks [@jalbarrang](https://github.com/jalbarrang)! - Fix two bugs in the `subagent` tool surfaced when spawning long-running reviewer agents:
21
+
22
+ - **Bun standalone binary spawn failure**: `getPiInvocation` now detects Bun's virtual filesystem paths (`/$bunfs/...`) in `process.argv[1]` and falls back to invoking the compiled binary directly. Previously, spawned subagents would fail with errors like `/$bunfs/root/pi doesn't exist in this environment` because the virtual path was passed verbatim to `spawn`.
23
+ - **Parallel summary truncation**: `parallel` mode no longer truncates each child agent's final output to 100 characters in the tool result summary. Long reviews from editorial/scout agents are now returned in full so callers don't need to scrape temp files or re-run agents to see their work.
24
+
3
25
  ## 0.3.0
4
26
 
5
27
  ### Minor Changes
@@ -11,7 +33,7 @@
11
33
  - `/delegate` command — synthesize conversation into a task, pick a workflow, execute with scouts/planner/worker/reviewer
12
34
  - `/delegate-agents` command — list, customize, or reset bundled agents
13
35
  - 6 bundled agents: scout, docs-scout, planner, worker, reviewer, ux-designer
14
- - `subagent-workflows` skill for guided orchestration
36
+ - `spawn-subagents` skill for conversational subagent orchestration
15
37
  - 3 prompt templates: implement, scout-and-plan, implement-and-review
16
38
 
17
39
  ### Bundled agent discovery
package/README.md CHANGED
@@ -18,9 +18,21 @@ The `subagent` tool supports three modes:
18
18
  | Parallel | `{ tasks: [...] }` — multiple agents concurrently |
19
19
  | Chain | `{ chain: [...] }` — sequential with `{previous}` placeholder |
20
20
 
21
- ## Delegate Command
21
+ ## Recommended Usage
22
22
 
23
- After a design/grill session:
23
+ In day-to-day use, the main agent should usually call the `subagent` tool from normal conversation.
24
+
25
+ Examples:
26
+ - "spawn a scout for the auth code"
27
+ - "run scout and docs-scout in parallel"
28
+ - "have planner make a plan, then worker implement it"
29
+ - "send this to reviewer"
30
+
31
+ `/delegate` is optional. It exists for rigid gated workflows, plan approval, and explicit workflow control.
32
+
33
+ ## Optional Delegate Command
34
+
35
+ After a design/grill session, or when you explicitly want a guided workflow:
24
36
 
25
37
  ```
26
38
  /delegate
@@ -32,6 +44,18 @@ Or with an explicit task:
32
44
  /delegate implement the auth flow we designed
33
45
  ```
34
46
 
47
+ Use project-local repo agents:
48
+
49
+ ```
50
+ /delegate --scope project implement the subagent refactor
51
+ ```
52
+
53
+ Pin a workflow and skip the project-agent confirmation prompt:
54
+
55
+ ```
56
+ /delegate --scope project --workflow implement --yes-project-agents improve delegate execution
57
+ ```
58
+
35
59
  ### How it works
36
60
 
37
61
  1. **Synthesize** — extracts goal, decisions, constraints, architecture, intent from conversation
@@ -41,6 +65,14 @@ Or with an explicit task:
41
65
  5. **Plan gate** — shows planner output for approval before worker runs
42
66
  6. **Summary** — full phase-by-phase report with usage totals
43
67
 
68
+ ### Delegate Flags
69
+
70
+ | Flag | Meaning |
71
+ |------|---------|
72
+ | `--scope user|project|both` | Which agent layers to use. Default: `user` |
73
+ | `--workflow <id>` | Skip the picker and force a workflow |
74
+ | `--yes-project-agents` | Disable the confirmation prompt for project-local agents |
75
+
44
76
  ### Workflows
45
77
 
46
78
  | Workflow | Phases |
@@ -54,7 +86,7 @@ Or with an explicit task:
54
86
 
55
87
  ## Agent Definitions
56
88
 
57
- Create agent files in `~/.pi/agent/agents/` as markdown with YAML frontmatter:
89
+ Create agent files in `~/.pi/agent/agents/` or `.pi/agents/` as markdown with YAML frontmatter:
58
90
 
59
91
  ```markdown
60
92
  ---
@@ -78,7 +110,9 @@ The package ships with these agents out of the box:
78
110
  - `reviewer` — code review
79
111
  - `ux-designer` — frontend UI design
80
112
 
81
- User agents in `~/.pi/agent/agents/` override bundled agents by name.
113
+ Resolution order is: bundled → user (`~/.pi/agent/agents/`) → project (`.pi/agents/`). Project agents override user and bundled agents by name.
114
+
115
+ > Note: pi packages do not currently expose a first-class `agents` manifest. If you mirror package agents into `~/.pi/agent/agents/`, treat those files as managed package artifacts rather than hand-authored overrides.
82
116
 
83
117
  ### Managing Agents
84
118
 
@@ -92,7 +126,7 @@ User agents in `~/.pi/agent/agents/` override bundled agents by name.
92
126
  ## Bundled Resources
93
127
 
94
128
  ### Skill
95
- - `subagent-workflows` — guides the model on when/how to use subagent orchestration
129
+ - `spawn-subagents` — guides the model on when/how to spawn specialized subagents conversationally
96
130
 
97
131
  ### Prompt Templates
98
132
  - `/implement` — scout → planner → worker
@@ -7,6 +7,7 @@ import * as path from 'node:path';
7
7
  import { getAgentDir, parseFrontmatter } from '@mariozechner/pi-coding-agent';
8
8
 
9
9
  export type AgentScope = 'user' | 'project' | 'both';
10
+ export type AgentSource = 'bundled' | 'user' | 'project';
10
11
 
11
12
  export interface AgentConfig {
12
13
  name: string;
@@ -15,7 +16,7 @@ export interface AgentConfig {
15
16
  model?: string;
16
17
  thinking?: string;
17
18
  systemPrompt: string;
18
- source: 'user' | 'project';
19
+ source: AgentSource;
19
20
  filePath: string;
20
21
  }
21
22
 
@@ -27,7 +28,7 @@ export interface AgentDiscoveryResult {
27
28
  /** Bundled agents ship with the package (../../agents relative to extensions/subagent/) */
28
29
  const bundledAgentsDir = path.resolve(import.meta.dirname, '..', '..', 'agents');
29
30
 
30
- function loadAgentsFromDir(dir: string, source: 'user' | 'project'): AgentConfig[] {
31
+ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
31
32
  const agents: AgentConfig[] = [];
32
33
 
33
34
  if (!fs.existsSync(dir)) {
@@ -104,7 +105,7 @@ export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryRe
104
105
  const projectAgentsDir = findNearestProjectAgentsDir(cwd);
105
106
 
106
107
  // Bundled agents from the package itself (lowest priority)
107
- const bundledAgents = loadAgentsFromDir(bundledAgentsDir, 'project');
108
+ const bundledAgents = loadAgentsFromDir(bundledAgentsDir, 'bundled');
108
109
 
109
110
  const userAgents = scope === 'project' ? [] : loadAgentsFromDir(userDir, 'user');
110
111
  const projectAgents =
@@ -0,0 +1,84 @@
1
+ import type { AgentScope } from './agents';
2
+ import type { WorkflowId } from './delegate-types';
3
+
4
+ export interface DelegateCommandOptions {
5
+ agentScope: AgentScope;
6
+ workflowId?: WorkflowId;
7
+ confirmProjectAgents: boolean;
8
+ explicitTask?: string;
9
+ }
10
+
11
+ const WORKFLOW_IDS = new Set<WorkflowId>([
12
+ 'scout-only',
13
+ 'scout-and-plan',
14
+ 'implement',
15
+ 'implement-and-review',
16
+ 'quick-fix',
17
+ 'review',
18
+ ]);
19
+
20
+ function tokenize(input: string): string[] {
21
+ return input.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, '')) ?? [];
22
+ }
23
+
24
+ export function formatDelegateUsage(): string {
25
+ return [
26
+ 'Usage: /delegate [--scope user|project|both] [--workflow <id>] [--yes-project-agents] [task]',
27
+ '',
28
+ 'Workflows: scout-only, scout-and-plan, implement, implement-and-review, quick-fix, review',
29
+ ].join('\n');
30
+ }
31
+
32
+ export function parseDelegateArgs(rawArgs?: string):
33
+ | { ok: true; options: DelegateCommandOptions }
34
+ | { ok: false; error: string } {
35
+ const tokens = tokenize(rawArgs?.trim() ?? '');
36
+ const taskTokens: string[] = [];
37
+
38
+ let agentScope: AgentScope = 'user';
39
+ let workflowId: WorkflowId | undefined;
40
+ let confirmProjectAgents = true;
41
+
42
+ for (let i = 0; i < tokens.length; i++) {
43
+ const token = tokens[i];
44
+
45
+ if (token === '--scope') {
46
+ const value = tokens[++i];
47
+ if (!value || !['user', 'project', 'both'].includes(value)) {
48
+ return { ok: false, error: `Invalid --scope value.\n\n${formatDelegateUsage()}` };
49
+ }
50
+ agentScope = value as AgentScope;
51
+ continue;
52
+ }
53
+
54
+ if (token === '--workflow') {
55
+ const value = tokens[++i] as WorkflowId | undefined;
56
+ if (!value || !WORKFLOW_IDS.has(value)) {
57
+ return { ok: false, error: `Invalid --workflow value.\n\n${formatDelegateUsage()}` };
58
+ }
59
+ workflowId = value;
60
+ continue;
61
+ }
62
+
63
+ if (token === '--yes-project-agents') {
64
+ confirmProjectAgents = false;
65
+ continue;
66
+ }
67
+
68
+ if (token.startsWith('--')) {
69
+ return { ok: false, error: `Unknown option: ${token}\n\n${formatDelegateUsage()}` };
70
+ }
71
+
72
+ taskTokens.push(token);
73
+ }
74
+
75
+ return {
76
+ ok: true,
77
+ options: {
78
+ agentScope,
79
+ workflowId,
80
+ confirmProjectAgents,
81
+ explicitTask: taskTokens.join(' ').trim() || undefined,
82
+ },
83
+ };
84
+ }
@@ -4,7 +4,7 @@ import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
5
  import { withFileMutationQueue } from '@mariozechner/pi-coding-agent';
6
6
  import type { Message } from '@mariozechner/pi-ai';
7
- import { discoverAgents } from './agents';
7
+ import { discoverAgents, type AgentScope } from './agents';
8
8
  import type { AgentResult, UsageStats } from './delegate-types';
9
9
 
10
10
  function emptyUsage(): UsageStats {
@@ -39,14 +39,21 @@ async function writePromptToTempFile(
39
39
 
40
40
  export type OnPhaseUpdate = (phaseName: string, agentName: string, result: AgentResult) => void;
41
41
 
42
+ export interface RunAgentOptions {
43
+ agentScope?: AgentScope;
44
+ cwd?: string;
45
+ onUpdate?: OnPhaseUpdate;
46
+ phaseName?: string;
47
+ signal?: AbortSignal;
48
+ }
49
+
42
50
  export async function runAgent(
43
51
  cwd: string,
44
52
  agentName: string,
45
53
  task: string,
46
- onUpdate?: OnPhaseUpdate,
47
- phaseName?: string,
54
+ options: RunAgentOptions = {},
48
55
  ): Promise<AgentResult> {
49
- const { agents } = discoverAgents(cwd, 'user');
56
+ const { agents } = discoverAgents(cwd, options.agentScope ?? 'user');
50
57
  const agent = agents.find((a) => a.name === agentName);
51
58
 
52
59
  if (!agent) {
@@ -92,7 +99,7 @@ export async function runAgent(
92
99
  const exitCode = await new Promise<number>((resolve) => {
93
100
  const invocation = getPiInvocation(args);
94
101
  const proc = spawn(invocation.command, invocation.args, {
95
- cwd,
102
+ cwd: options.cwd ?? cwd,
96
103
  shell: false,
97
104
  stdio: ['ignore', 'pipe', 'pipe'],
98
105
  });
@@ -128,12 +135,14 @@ export async function runAgent(
128
135
  if (msg.errorMessage) result.errorMessage = msg.errorMessage;
129
136
  }
130
137
 
131
- if (onUpdate) onUpdate(phaseName ?? 'unknown', agentName, { ...result });
138
+ if (options.onUpdate)
139
+ options.onUpdate(options.phaseName ?? 'unknown', agentName, { ...result });
132
140
  }
133
141
 
134
142
  if (event.type === 'tool_result_end' && event.message) {
135
143
  result.messages.push(event.message as Message);
136
- if (onUpdate) onUpdate(phaseName ?? 'unknown', agentName, { ...result });
144
+ if (options.onUpdate)
145
+ options.onUpdate(options.phaseName ?? 'unknown', agentName, { ...result });
137
146
  }
138
147
  };
139
148
 
@@ -154,6 +163,17 @@ export async function runAgent(
154
163
  });
155
164
 
156
165
  proc.on('error', () => resolve(1));
166
+
167
+ if (options.signal) {
168
+ const killProc = () => {
169
+ proc.kill('SIGTERM');
170
+ setTimeout(() => {
171
+ if (!proc.killed) proc.kill('SIGKILL');
172
+ }, 5000);
173
+ };
174
+ if (options.signal.aborted) killProc();
175
+ else options.signal.addEventListener('abort', killProc, { once: true });
176
+ }
157
177
  });
158
178
 
159
179
  result.exitCode = exitCode;
@@ -178,8 +198,7 @@ export async function runParallel(
178
198
  cwd: string,
179
199
  agentNames: string[],
180
200
  task: string,
181
- onUpdate?: OnPhaseUpdate,
182
- phaseName?: string,
201
+ options: RunAgentOptions = {},
183
202
  ): Promise<AgentResult[]> {
184
203
  const MAX_CONCURRENCY = 4;
185
204
  const results: AgentResult[] = new Array(agentNames.length);
@@ -191,7 +210,7 @@ export async function runParallel(
191
210
  while (true) {
192
211
  const current = nextIndex++;
193
212
  if (current >= agentNames.length) return;
194
- results[current] = await runAgent(cwd, agentNames[current], task, onUpdate, phaseName);
213
+ results[current] = await runAgent(cwd, agentNames[current], task, options);
195
214
  }
196
215
  });
197
216
 
@@ -31,7 +31,14 @@ import {
31
31
  } from '@mariozechner/pi-coding-agent';
32
32
  import { Container, Markdown, Spacer, Text } from '@mariozechner/pi-tui';
33
33
  import { Type } from '@sinclair/typebox';
34
- import { type AgentConfig, type AgentScope, bundledAgentsDir, discoverAgents } from './agents.js';
34
+ import {
35
+ type AgentConfig,
36
+ type AgentScope,
37
+ type AgentSource,
38
+ bundledAgentsDir,
39
+ discoverAgents,
40
+ } from './agents.js';
41
+ import { formatDelegateUsage, parseDelegateArgs } from './delegate-args.js';
35
42
  import { runAgent, runParallel } from './delegate-executor.js';
36
43
  import { buildSynthesisPrompt, extractRecentConversation } from './synthesis.js';
37
44
  import type {
@@ -50,6 +57,7 @@ import {
50
57
  getFinalText,
51
58
  pickWorkflow,
52
59
  } from './delegate-ui.js';
60
+ import { getWorkflow, suggestWorkflow } from './workflows.js';
53
61
 
54
62
  const MAX_PARALLEL_TASKS = 8;
55
63
  const MAX_CONCURRENCY = 4;
@@ -172,7 +180,7 @@ interface UsageStats {
172
180
 
173
181
  interface SingleResult {
174
182
  agent: string;
175
- agentSource: 'user' | 'project' | 'unknown';
183
+ agentSource: AgentSource | 'unknown';
176
184
  task: string;
177
185
  exitCode: number;
178
186
  messages: Message[];
@@ -256,13 +264,19 @@ async function writePromptToTempFile(
256
264
 
257
265
  function getPiInvocation(args: string[]): { command: string; args: string[] } {
258
266
  const currentScript = process.argv[1];
259
- if (currentScript && fs.existsSync(currentScript)) {
267
+
268
+ // Bun standalone binaries set argv[1] to a virtual FS path (e.g. /$bunfs/root/pi)
269
+ // that only resolves inside the running Bun process. Skip it — the binary IS the entry point.
270
+ const isBunVirtualPath = currentScript?.startsWith('/$bunfs/');
271
+
272
+ if (currentScript && !isBunVirtualPath && fs.existsSync(currentScript)) {
260
273
  return { command: process.execPath, args: [currentScript, ...args] };
261
274
  }
262
275
 
263
276
  const execName = path.basename(process.execPath).toLowerCase();
264
277
  const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
265
278
  if (!isGenericRuntime) {
279
+ // Standalone binary (e.g. pi compiled with Bun) — invoke directly
266
280
  return { command: process.execPath, args };
267
281
  }
268
282
 
@@ -719,8 +733,7 @@ export default function (pi: ExtensionAPI) {
719
733
  const successCount = results.filter((r) => r.exitCode === 0).length;
720
734
  const summaries = results.map((r) => {
721
735
  const output = getFinalOutput(r.messages);
722
- const preview = output.slice(0, 100) + (output.length > 100 ? '...' : '');
723
- return `[${r.agent}] ${r.exitCode === 0 ? 'completed' : 'failed'}: ${preview || '(no output)'}`;
736
+ return `[${r.agent}] ${r.exitCode === 0 ? 'completed' : 'failed'}: ${output || '(no output)'}`;
724
737
  });
725
738
  return {
726
739
  content: [
@@ -1128,6 +1141,7 @@ export default function (pi: ExtensionAPI) {
1128
1141
  synthesis: string,
1129
1142
  previousOutput: string,
1130
1143
  ctx: ExtensionCommandContext,
1144
+ agentScope: AgentScope,
1131
1145
  ): Promise<{ results: AgentResult[]; output: string; aborted: boolean }> {
1132
1146
  const task = phase.taskTemplate
1133
1147
  .replace(/\{synthesis\}/g, synthesis)
@@ -1140,17 +1154,18 @@ export default function (pi: ExtensionAPI) {
1140
1154
 
1141
1155
  let results: AgentResult[];
1142
1156
  if (phase.kind === 'parallel') {
1143
- results = await runParallel(
1144
- cwd,
1145
- phase.agents,
1146
- task,
1147
- (_phaseName, _agentName, _result) => {
1157
+ results = await runParallel(cwd, phase.agents, task, {
1158
+ agentScope,
1159
+ onUpdate: (_phaseName, _agentName, _result) => {
1148
1160
  // Streaming updates happen per-message
1149
1161
  },
1150
- phase.name,
1151
- );
1162
+ phaseName: phase.name,
1163
+ });
1152
1164
  } else {
1153
- const result = await runAgent(cwd, phase.agents[0], task, undefined, phase.name);
1165
+ const result = await runAgent(cwd, phase.agents[0], task, {
1166
+ agentScope,
1167
+ phaseName: phase.name,
1168
+ });
1154
1169
  results = [result];
1155
1170
  }
1156
1171
 
@@ -1168,7 +1183,7 @@ export default function (pi: ExtensionAPI) {
1168
1183
  const output = combineParallelOutputs(results);
1169
1184
  ctx.ui.notify(formatPhaseHeader(phase.name, 'done'), 'info');
1170
1185
 
1171
- if (phase.requiresConfirmation) {
1186
+ if (phase.requiresConfirmation && ctx.hasUI) {
1172
1187
  const planText = output;
1173
1188
  const proceed = await confirmPlan(ctx, planText);
1174
1189
  if (!proceed) {
@@ -1286,24 +1301,28 @@ export default function (pi: ExtensionAPI) {
1286
1301
  });
1287
1302
 
1288
1303
  pi.registerCommand('delegate', {
1289
- description: 'Orchestrate subagent workflows — scouts, planners, workers, reviewers',
1304
+ description:
1305
+ 'Orchestrate subagent workflows — supports --scope, --workflow, and project-local agents',
1290
1306
  handler: async (args, ctx) => {
1291
- const explicitTask = args?.trim() || undefined;
1307
+ const parsed = parseDelegateArgs(args);
1308
+ if (!parsed.ok) {
1309
+ ctx.ui.notify(parsed.error, 'warning');
1310
+ return;
1311
+ }
1312
+
1313
+ const { explicitTask, agentScope, workflowId, confirmProjectAgents } = parsed.options;
1292
1314
 
1293
1315
  // Step 1: Synthesize
1294
1316
  const conversation = extractRecentConversation(ctx);
1295
1317
  if (!conversation.trim() && !explicitTask) {
1296
- ctx.ui.notify(
1297
- 'No conversation context and no task specified. Usage: /delegate [task]',
1298
- 'warning',
1299
- );
1318
+ ctx.ui.notify(`No conversation context and no task specified.\n\n${formatDelegateUsage()}`, 'warning');
1300
1319
  return;
1301
1320
  }
1302
1321
 
1303
1322
  const prompt = buildSynthesisPrompt(conversation, explicitTask);
1304
1323
  ctx.ui.notify('⏳ Synthesizing task from conversation...', 'info');
1305
1324
 
1306
- const synthResult = await runAgent(ctx.cwd, 'planner', prompt);
1325
+ const synthResult = await runAgent(ctx.cwd, 'planner', prompt, { agentScope });
1307
1326
  const synthesis = getFinalText(synthResult);
1308
1327
 
1309
1328
  if (!synthesis.trim()) {
@@ -1312,19 +1331,46 @@ export default function (pi: ExtensionAPI) {
1312
1331
  }
1313
1332
 
1314
1333
  // Step 2: Confirm synthesis
1315
- const synthOk = await confirmSynthesis(ctx, synthesis);
1316
- if (!synthOk) {
1317
- ctx.ui.notify('Delegate cancelled.', 'info');
1318
- return;
1334
+ if (ctx.hasUI) {
1335
+ const synthOk = await confirmSynthesis(ctx, synthesis);
1336
+ if (!synthOk) {
1337
+ ctx.ui.notify('Delegate cancelled.', 'info');
1338
+ return;
1339
+ }
1319
1340
  }
1320
1341
 
1321
1342
  // Step 3: Pick workflow
1322
- const workflow = await pickWorkflow(ctx, synthesis);
1343
+ const workflow = workflowId
1344
+ ? getWorkflow(workflowId)
1345
+ : ctx.hasUI
1346
+ ? await pickWorkflow(ctx, synthesis)
1347
+ : getWorkflow(suggestWorkflow(synthesis));
1323
1348
  if (!workflow) {
1324
1349
  ctx.ui.notify('Delegate cancelled — no workflow selected.', 'info');
1325
1350
  return;
1326
1351
  }
1327
1352
 
1353
+ if ((agentScope === 'project' || agentScope === 'both') && confirmProjectAgents && ctx.hasUI) {
1354
+ const discovery = discoverAgents(ctx.cwd, agentScope);
1355
+ const requestedProjectAgents = workflow.phases
1356
+ .flatMap((phase) => phase.agents)
1357
+ .map((name) => discovery.agents.find((agent) => agent.name === name))
1358
+ .filter((agent): agent is AgentConfig => agent?.source === 'project');
1359
+
1360
+ if (requestedProjectAgents.length > 0) {
1361
+ const names = Array.from(new Set(requestedProjectAgents.map((agent) => agent.name))).join(', ');
1362
+ const dir = discovery.projectAgentsDir ?? '(unknown)';
1363
+ const ok = await ctx.ui.confirm(
1364
+ 'Run project-local agents?',
1365
+ `Agents: ${names}\nSource: ${dir}\n\nProject agents are repo-controlled. Only continue for trusted repositories.`,
1366
+ );
1367
+ if (!ok) {
1368
+ ctx.ui.notify('Delegate cancelled — project-local agents not approved.', 'info');
1369
+ return;
1370
+ }
1371
+ }
1372
+ }
1373
+
1328
1374
  // Step 4: Execute phases
1329
1375
  const state: DelegateState = {
1330
1376
  synthesis,
@@ -1335,10 +1381,17 @@ export default function (pi: ExtensionAPI) {
1335
1381
 
1336
1382
  let previousOutput = '';
1337
1383
 
1338
- ctx.ui.notify(`Starting workflow: ${workflow.label}`, 'info');
1384
+ ctx.ui.notify(`Starting workflow: ${workflow.label} [${agentScope}]`, 'info');
1339
1385
 
1340
1386
  for (const phaseDef of workflow.phases) {
1341
- const phaseResult = await executePhase(ctx.cwd, phaseDef, synthesis, previousOutput, ctx);
1387
+ const phaseResult = await executePhase(
1388
+ ctx.cwd,
1389
+ phaseDef,
1390
+ synthesis,
1391
+ previousOutput,
1392
+ ctx,
1393
+ agentScope,
1394
+ );
1342
1395
 
1343
1396
  const phase: PhaseResult = {
1344
1397
  name: phaseDef.name,
@@ -1370,6 +1423,7 @@ export default function (pi: ExtensionAPI) {
1370
1423
  display: true,
1371
1424
  details: {
1372
1425
  workflow: workflow.id,
1426
+ agentScope,
1373
1427
  phaseCount: state.phases.length,
1374
1428
  totalUsage: state.totalUsage,
1375
1429
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreki-gg/pi-subagent",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Subagent tool and delegate orchestration for pi — isolated agents, parallel scouts, planning gates, and workflow presets",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: spawn-subagents
3
+ description: Use conversational subagent orchestration with the `subagent` tool. Trigger when the user says spawn, spin up, fan out, parallelize, send to reviewer, use scout/planner/worker, or wants specialized agents to investigate, plan, implement, or review.
4
+ ---
5
+
6
+ Use this skill when the user wants agents spawned conversationally.
7
+
8
+ Default behavior:
9
+ - Prefer the `subagent` tool directly.
10
+ - Do **not** default to `/delegate`.
11
+ - Use `/delegate` only when the user explicitly wants a gated workflow, workflow picker, or plan approval step.
12
+
13
+ Strong triggers:
14
+ - "spawn a scout"
15
+ - "fan this out"
16
+ - "run reviewer on this"
17
+ - "have planner make a plan first"
18
+ - "send docs-scout to check the docs"
19
+ - "parallelize discovery"
20
+
21
+ Patterns:
22
+
23
+ ## 1. Single specialist
24
+ Use one agent when the task is narrow.
25
+ - `scout` for repo reconnaissance
26
+ - `docs-scout` for docs lookup
27
+ - `planner` for a concrete plan
28
+ - `worker` for implementation
29
+ - `reviewer` for quality/security review
30
+
31
+ ## 2. Parallel recon
32
+ Use parallel mode for noisy discovery.
33
+ - Run `scout` + `docs-scout` in parallel when both code and docs matter.
34
+ - Keep parallel tasks non-overlapping.
35
+ - Return a compact synthesis to the main thread.
36
+
37
+ ## 3. Plan then implement
38
+ Use chain mode when the user wants safe sequencing.
39
+ 1. `scout` and optionally `docs-scout`
40
+ 2. `planner`
41
+ 3. `worker`
42
+ 4. optional `reviewer`
43
+
44
+ ## 4. Review loop
45
+ Use after implementation or when the user asks for a second opinion.
46
+ 1. `reviewer`
47
+ 2. main agent applies fixes directly or sends focused follow-up to `worker`
48
+
49
+ Execution rules:
50
+ - Parallelize reconnaissance, not conflicting edits.
51
+ - Prefer one `worker` unless file ownership is clearly partitioned.
52
+ - Use `docs-scout` when external library/framework details matter.
53
+ - Pass previous outputs verbatim or as a tight structured summary.
54
+
55
+ When reporting back:
56
+ - say which subagents ran
57
+ - summarize what each contributed
58
+ - keep the main thread focused on conclusions, not raw logs
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: write-an-agent
3
+ description: Writes or refines concise pi subagent definitions for this repo, keeping each agent under 100 lines with a sharp role, tool policy, and output contract. Use when creating, rewriting, or tightening agent prompts in `.pi/agents/` or `packages/subagent/agents/`.
4
+ ---
5
+
6
+ # Write an Agent
7
+
8
+ Goal: produce a high-signal agent file in fewer than 100 lines.
9
+
10
+ ## Use this for
11
+ - new repo-local agents in `.pi/agents/`
12
+ - bundled package agents in `packages/subagent/agents/`
13
+ - tightening bloated prompts into a crisp reusable worker
14
+
15
+ ## Design rules
16
+ 1. One job only. If the agent does two unrelated things, split it.
17
+ 2. One tool policy. Give the minimum tools needed.
18
+ 3. One output contract. Tell the next agent exactly what comes back.
19
+ 4. Keep the whole file under 100 lines.
20
+ 5. If the prompt needs more than 100 lines, write a skill instead.
21
+
22
+ ## File shape
23
+ ```md
24
+ ---
25
+ name: agent-name
26
+ description: What it does. Use when ...
27
+ tools: read, grep, find, ls
28
+ model: openai/gpt-5.4-mini
29
+ thinking: medium
30
+ ---
31
+
32
+ You are the <agent-name>.
33
+
34
+ Mission:
35
+ - outcome
36
+
37
+ Rules:
38
+ 1. boundary
39
+ 2. boundary
40
+
41
+ Output:
42
+ ## Section
43
+ - exact shape
44
+ ```
45
+
46
+ ## Authoring workflow
47
+ 1. Name the role.
48
+ 2. Write one-sentence description with trigger language: "Use when ...".
49
+ 3. Pick the smallest tool list.
50
+ 4. Pick a cheap model for scouts, stronger model for planner/reviewer/worker.
51
+ 5. Write 3-6 rules max.
52
+ 6. Define output sections for handoff.
53
+ 7. Count lines. Trim anything decorative.
54
+
55
+ ## Repo conventions
56
+ - Repo-local overrides belong in `.pi/agents/`.
57
+ - Bundled reusable agents belong in `packages/subagent/agents/`.
58
+ - For this repo, default roles are `scout`, `docs-scout`, `planner`, `worker`, `reviewer`.
59
+ - Prefer local pi docs before external docs when writing `docs-scout`.
60
+ - Mention validation commands in planner/worker/reviewer when package behavior changes.
61
+
62
+ ## Review checklist
63
+ - Is the role narrower than a general engineer?
64
+ - Would another agent know exactly when to use it?
65
+ - Are the tools minimal?
66
+ - Is the handoff structured?
67
+ - Is the file under 100 lines?
@@ -1,52 +0,0 @@
1
- ---
2
- name: subagent-workflows
3
- description: Use explicit subagent workflows for planning or implementation after a grill/design session. Runs parallel scouts, then planner, then optional worker/reviewer.
4
- ---
5
-
6
- Use this skill when the user wants to execute a plan with subagents, parallelize discovery intelligently, or asks to hand work off after a design/grill session.
7
-
8
- Principles:
9
- - Use the `subagent` tool explicitly; do not improvise ad hoc parallelism.
10
- - Parallelize reconnaissance, not conflicting edits.
11
- - Prefer one planner synthesis step after parallel scouts.
12
- - Prefer one worker unless file ownership can be partitioned safely.
13
- - Use `docs-scout` whenever library/framework docs matter.
14
-
15
- Recommended workflows:
16
-
17
- ## 1. Scout and plan
18
- Use when the user wants a concrete implementation plan before coding.
19
-
20
- Run `subagent` in parallel mode with two tasks:
21
- - `scout` for codebase reconnaissance
22
- - `docs-scout` for library/framework docs if docs are relevant
23
-
24
- Then run `subagent` in chain or single mode with `planner`, feeding it the scout outputs.
25
-
26
- ## 2. Implement from a grilled plan
27
- Use when the design is already clarified and the user wants execution.
28
-
29
- Recommended sequence:
30
- 1. Parallel scouts (`scout` + optionally `docs-scout`)
31
- 2. `planner` synthesis
32
- 3. `worker` implementation
33
- 4. Optional `reviewer`
34
-
35
- ## 3. Review-oriented loop
36
- Use when the user wants implementation with a final quality pass.
37
-
38
- Recommended sequence:
39
- 1. `worker`
40
- 2. `reviewer`
41
- 3. Main agent decides whether to apply fixes directly or run another `worker`
42
-
43
- Execution guidance:
44
- - Keep parallel scouts focused on different concerns.
45
- - Do not run multiple workers against the same file set unless boundaries are explicit.
46
- - If docs are required, use `docs-scout` instead of relying on memory.
47
- - When handing off between subagents, pass the previous output verbatim or clearly summarized.
48
-
49
- When reporting back to the user:
50
- - Say which subagents ran
51
- - Summarize what each contributed
52
- - Present the synthesized plan or implementation result clearly