pi-subagents 0.17.3 → 0.17.5

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.
@@ -0,0 +1,466 @@
1
+ ---
2
+ name: pi-subagents
3
+ description: |
4
+ Delegate work to builtin or custom subagents with single-agent, chain,
5
+ parallel, async, forked-context, and intercom-coordinated workflows. Use
6
+ for advisory review, implementation handoffs, and multi-step tasks where a
7
+ single agent should stay in control while other agents contribute context,
8
+ planning, or execution.
9
+ ---
10
+
11
+ # Pi Subagents
12
+
13
+ Use this skill when you need to launch a specialized subagent, compose multiple
14
+ agents into a workflow, or create/edit agents and chains on demand.
15
+
16
+ ## When to Use
17
+
18
+ - **Advisory review**: fork to `oracle` or `reviewer` for a branched review thread
19
+ - **Implementation handoff**: have `oracle` advise, then `oracle-executor` or `worker` implement
20
+ - **Recon and planning**: use `scout` or `context-builder`, then `planner`
21
+ - **Parallel exploration**: run multiple non-conflicting tasks concurrently
22
+ - **Long-running work**: launch async/background runs and inspect them later
23
+ - **Subagent control**: watch clear stalled/paused signals and soft-interrupt only when a delegated run is genuinely blocked
24
+ - **Agent authoring**: create, update, or override agents and chains for a project
25
+
26
+ ## Tool vs Slash Commands
27
+
28
+ Agents can use the `subagent(...)` and `subagent_status(...)` tools directly.
29
+ Humans often use the slash-command layer instead:
30
+
31
+ - `/run` — launch a single agent
32
+ - `/chain` — launch a chain of steps
33
+ - `/parallel` — launch top-level parallel tasks
34
+ - `/agents` — open the agents manager TUI
35
+ - `/subagents-status` — inspect active/recent async runs
36
+
37
+ Prefer the tool when you are writing agent logic. Prefer the slash commands when
38
+ you are guiding a human through an interactive flow.
39
+
40
+ ## Builtin Agents
41
+
42
+ Builtin agents load at the lowest priority. Project agents override user agents,
43
+ and user/project agents override builtins with the same name.
44
+
45
+ | Agent | Purpose | Model | Typical output / role |
46
+ |-------|---------|-------|------------------------|
47
+ | `scout` | Fast codebase recon | `openai-codex/gpt-5.4-mini` | Writes `context.md` handoff material |
48
+ | `planner` | Creates implementation plans | `openai-codex/gpt-5.4` | Writes `plan.md` |
49
+ | `worker` | General implementation | `openai-codex/gpt-5.4` | Edits code directly |
50
+ | `reviewer` | Review-and-fix specialist | `openai-codex/gpt-5.3-codex:high` | Can edit/fix reviewed code |
51
+ | `context-builder` | Requirements/codebase handoff builder | `openai-codex/gpt-5.4` | Writes structured context files |
52
+ | `researcher` | Web research brief generator | `openai-codex/gpt-5.4` | Writes `research.md` |
53
+ | `delegate` | Lightweight generic delegate | inherits parent model | No fixed output; generic delegated work |
54
+ | `oracle` | Decision-consistency advisory review | `openai-codex/gpt-5.4:high` | Advisory review, intercom coordination |
55
+ | `oracle-executor` | Implementation after approval | `openai-codex/gpt-5.3-codex:high` | Single-writer implementation after approval |
56
+
57
+ Override builtin defaults via settings before copying full agent files when a
58
+ small tweak is enough.
59
+
60
+ Settings locations:
61
+ - User scope: `~/.pi/agent/settings.json`
62
+ - Project scope: `.pi/settings.json`
63
+
64
+ Useful override fields: `model`, `fallbackModels`, `thinking`,
65
+ `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `disabled`,
66
+ `skills`, `tools`, and `systemPrompt`.
67
+
68
+ ## Discovery and Scope Rules
69
+
70
+ Agent files can live in:
71
+ - `~/.pi/agent/agents/*.md` — user scope
72
+ - `.pi/agents/*.md` — canonical project scope
73
+ - legacy `.agents/*.md` — still read for compatibility, but `.pi/agents/` wins on conflicts
74
+
75
+ Chains live in:
76
+ - `~/.pi/agent/agents/*.chain.md`
77
+ - `.pi/agents/*.chain.md`
78
+ - legacy `.agents/*.chain.md`
79
+
80
+ Precedence is:
81
+ 1. project scope
82
+ 2. user scope
83
+ 3. builtin agents
84
+
85
+ ## Running Subagents
86
+
87
+ ### Single agent
88
+
89
+ ```typescript
90
+ subagent({
91
+ agent: "oracle",
92
+ task: "Review my current direction and challenge assumptions."
93
+ })
94
+ ```
95
+
96
+ ### Forked context
97
+
98
+ ```typescript
99
+ subagent({
100
+ agent: "oracle",
101
+ task: "Review my current direction and challenge assumptions.",
102
+ context: "fork"
103
+ })
104
+ ```
105
+
106
+ `context: "fork"` creates a branched child session from the current persisted
107
+ parent session. It does **not** create a fresh minimal review context or filter
108
+ history down to only the relevant parts. Use it when you want a separate review
109
+ or execution thread that can still reference the parent session history.
110
+
111
+ ### Parallel execution
112
+
113
+ ```typescript
114
+ subagent({
115
+ tasks: [
116
+ { agent: "scout", task: "Explore the auth module" },
117
+ { agent: "reviewer", task: "Review the API client" }
118
+ ]
119
+ })
120
+ ```
121
+
122
+ ### Chain execution
123
+
124
+ ```typescript
125
+ subagent({
126
+ chain: [
127
+ { agent: "scout", task: "Map the auth flow and summarize key files" },
128
+ { agent: "planner", task: "Create an implementation plan from {previous}" },
129
+ { agent: "worker", task: "Implement the approved plan based on {previous}" }
130
+ ]
131
+ })
132
+ ```
133
+
134
+ Chain steps can use templated variables such as `{task}`, `{previous}`, and
135
+ `{chain_dir}`. This is the main way to pass structured summaries between steps
136
+ without forcing each step to rediscover everything.
137
+
138
+ ### Async/background
139
+
140
+ ```typescript
141
+ subagent({
142
+ agent: "worker",
143
+ task: "Run the full test suite",
144
+ async: true
145
+ })
146
+ ```
147
+
148
+ Inspect async runs with the `subagent_status(...)` tool or the
149
+ `/subagents-status` slash command.
150
+
151
+ ### Subagent control
152
+
153
+ Subagent control is the runtime visibility and intervention layer for delegated runs. It is separate from lifecycle status. Lifecycle status says whether a child is `running`, `completed`, `failed`, or detached. Activity state says what the child appears to be doing right now: `starting`, `active`, `quiet`, `stalled`, or `paused`.
154
+
155
+ Default behavior is intentionally conservative. Routine `active` and `quiet` updates are mostly UI/status information. Clear transitions such as `stalled`, `recovered`, and `paused` are the signals worth acting on.
156
+
157
+ Use soft interrupt when a child is clearly blocked or drifting and the parent needs to regain control:
158
+
159
+ ```typescript
160
+ subagent({ action: "interrupt" })
161
+ ```
162
+
163
+ Pass `runId` when targeting a specific controllable run:
164
+
165
+ ```typescript
166
+ subagent({ action: "interrupt", runId: "abc123" })
167
+ ```
168
+
169
+ A soft interrupt cancels the current child turn and leaves the run paused. It does not mean the delegated task succeeded or failed. After an interrupt, decide the next explicit action: resume with clearer instructions, replace the task, ask the user, or stop the workflow.
170
+
171
+ Per-run control thresholds can be overridden when a task legitimately runs quiet for longer than usual:
172
+
173
+ ```typescript
174
+ subagent({
175
+ agent: "worker",
176
+ task: "Run the slow migration test suite",
177
+ control: {
178
+ quietAfterMs: 60000,
179
+ stalledAfterMs: 300000,
180
+ parentMode: "transitions"
181
+ }
182
+ })
183
+ ```
184
+
185
+ ## Clarify TUI
186
+
187
+ Single and parallel runs support a clarification TUI when you want to preview or
188
+ edit parameters before launch:
189
+
190
+ ```typescript
191
+ subagent({
192
+ agent: "worker",
193
+ task: "Implement feature X",
194
+ clarify: true
195
+ })
196
+ ```
197
+
198
+ Chains default to clarify mode unless you explicitly set `clarify: false`.
199
+ For programmatic background launches, use `clarify: false, async: true`.
200
+
201
+ ## Worktree Isolation
202
+
203
+ When multiple agents might write concurrently, use worktrees instead of letting
204
+ them share one filesystem view.
205
+
206
+ ```typescript
207
+ subagent({
208
+ tasks: [
209
+ { agent: "worker", task: "Implement feature A" },
210
+ { agent: "worker", task: "Implement feature B" }
211
+ ],
212
+ worktree: true
213
+ })
214
+ ```
215
+
216
+ `worktree: true` gives each parallel task its own git worktree branched from
217
+ HEAD. This requires a clean git state and is mainly for intentionally parallel
218
+ write workflows. If you want one writer thread and several advisory agents,
219
+ prefer a single-writer pattern instead.
220
+
221
+ ## The Oracle Workflow
222
+
223
+ The intended oracle loop is:
224
+ 1. the main agent forks to `oracle`
225
+ 2. `oracle` reviews direction, drift, assumptions, and risks
226
+ 3. `oracle` can coordinate back to the orchestrator via `intercom`
227
+ 4. the main agent decides what direction to approve
228
+ 5. only then should `oracle-executor` implement
229
+
230
+ ```typescript
231
+ // Advisory review in a branched thread
232
+ subagent({
233
+ agent: "oracle",
234
+ task: "Review my current direction, challenge assumptions, and propose the best next move.",
235
+ context: "fork"
236
+ })
237
+
238
+ // Implementation only after explicit approval
239
+ subagent({
240
+ agent: "oracle-executor",
241
+ task: "Implement the approved approach: ...",
242
+ context: "fork"
243
+ })
244
+ ```
245
+
246
+ `oracle` is not a fresh-context reviewer in the Cognition article sense. It is
247
+ a forked advisory thread that inherits the parent session history and uses that
248
+ history as a baseline contract.
249
+
250
+ ## Subagent + Intercom Coordination
251
+
252
+ When `pi-intercom` is installed and enabled, delegated runs can coordinate with
253
+ the orchestrator through the intercom bridge.
254
+
255
+ ### Subagent asks the orchestrator
256
+
257
+ ```typescript
258
+ intercom({
259
+ action: "ask",
260
+ to: "orchestrator",
261
+ message: "Should I optimize for readability or performance here?"
262
+ })
263
+ ```
264
+
265
+ ### Orchestrator replies
266
+
267
+ ```typescript
268
+ intercom({ action: "reply", message: "Optimize for readability." })
269
+ ```
270
+
271
+ Or inspect unresolved asks first:
272
+
273
+ ```typescript
274
+ intercom({ action: "pending" })
275
+ ```
276
+
277
+ Use `intercom` when:
278
+ - a subagent is blocked on a decision
279
+ - an advisory agent wants to send a concise handoff mid-flight
280
+ - a detached or async child needs to coordinate without waiting for normal tool return flow
281
+
282
+ ## Management Mode
283
+
284
+ The `subagent(...)` tool also supports management actions.
285
+
286
+ ### List available agents and chains
287
+
288
+ ```typescript
289
+ subagent({ action: "list" })
290
+ ```
291
+
292
+ ### Create an agent
293
+
294
+ ```typescript
295
+ subagent({
296
+ action: "create",
297
+ config: {
298
+ name: "my-agent",
299
+ description: "Project-specific implementation helper",
300
+ systemPrompt: "Your system prompt here.",
301
+ systemPromptMode: "replace",
302
+ model: "openai-codex/gpt-5.4",
303
+ tools: "read,grep,find,ls,bash"
304
+ }
305
+ })
306
+ ```
307
+
308
+ ### Update an agent
309
+
310
+ ```typescript
311
+ subagent({
312
+ action: "update",
313
+ agent: "my-agent",
314
+ config: {
315
+ thinking: "high"
316
+ }
317
+ })
318
+ ```
319
+
320
+ ### Delete an agent
321
+
322
+ ```typescript
323
+ subagent({ action: "delete", agent: "my-agent" })
324
+ ```
325
+
326
+ Use management actions when the system needs to create or edit subagents on
327
+ demand without dropping into raw file editing.
328
+
329
+ ## Creating and Editing Agents by File
330
+
331
+ A minimal agent file looks like this:
332
+
333
+ ```markdown
334
+ ---
335
+ name: my-agent
336
+ description: What this agent does
337
+ model: openai-codex/gpt-5.4
338
+ thinking: high
339
+ tools: read, grep, find, ls, bash
340
+ systemPromptMode: replace
341
+ inheritProjectContext: true
342
+ inheritSkills: false
343
+ ---
344
+
345
+ Your system prompt here.
346
+ ```
347
+
348
+ That is only a starting point. Common optional fields include:
349
+ - `defaultProgress`
350
+ - `defaultReads`
351
+ - `output`
352
+ - `fallbackModels`
353
+ - `maxSubagentDepth`
354
+
355
+ For many customizations, builtin overrides in settings are lower-friction than
356
+ copying a full builtin file.
357
+
358
+ ## Prompt Template Integration
359
+
360
+ If `pi-prompt-template-model` is installed, prompt templates can delegate into
361
+ `pi-subagents`. This is useful when a slash command should always run through a
362
+ particular agent or with forked context.
363
+
364
+ ## Important Constraints
365
+
366
+ - **Forking requires a persisted parent session.** If the current session does not
367
+ have a persisted session file, forked runs fail.
368
+ - **Forked runs inherit parent history.** They are branched threads, not fresh
369
+ filtered contexts.
370
+ - **Default subagent nesting depth is 2.** Deeper recursive delegation is blocked
371
+ unless configured otherwise.
372
+ - **Control state is not lifecycle state.** `paused` means the child turn was intentionally interrupted or is awaiting direction; it is not the same as `failed`.
373
+ - **Intercom asks are blocking.** A session can only maintain one pending outbound
374
+ ask wait state at a time.
375
+ - **Keep conversational authority clear.** Advisory subagents should not silently
376
+ become second decision-makers.
377
+
378
+ ## Best Practices
379
+
380
+ ### Keep writes single-threaded by default
381
+
382
+ A strong pattern is one main decision-maker plus advisory/research/review
383
+ subagents around it. Use `oracle` for advice and `oracle-executor` or `worker`
384
+ for the actual write path.
385
+
386
+ ### Use fork for branched advisory or execution threads
387
+
388
+ Forked runs are useful when the child should reason in a separate thread while
389
+ still inheriting the parent’s accumulated context.
390
+
391
+ ### Prefer narrow tasks
392
+
393
+ Give subagents specific tasks rather than vague mandates.
394
+ `Review auth.ts for null-check gaps` works better than `Review everything`.
395
+
396
+ ### Escalate decisions upward
397
+
398
+ If a subagent encounters an unapproved product, architecture, or scope choice,
399
+ it should coordinate back via `intercom` instead of deciding alone.
400
+
401
+ ### Intervene only on clear control signals
402
+
403
+ Use subagent control proactively when a delegated run is explicitly `stalled` or `paused`, or when a human asks you to regain control. Do not interrupt just because a child is briefly `quiet`. Quiet can be normal during long tool calls, test runs, or model reasoning.
404
+
405
+ ### Name sessions meaningfully
406
+
407
+ Use `/name` so intercom targeting stays stable.
408
+
409
+ ## Common Workflows
410
+
411
+ ### Recon → Plan → Implement
412
+
413
+ ```typescript
414
+ subagent({
415
+ chain: [
416
+ { agent: "scout", task: "Map the auth flow and summarize relevant files" },
417
+ { agent: "planner", task: "Plan the migration from {previous}" },
418
+ { agent: "worker", task: "Implement the approved plan from {previous}" }
419
+ ]
420
+ })
421
+ ```
422
+
423
+ ### Review loop
424
+
425
+ ```typescript
426
+ subagent({ agent: "worker", task: "Add retry logic to the API client." })
427
+ subagent({
428
+ agent: "reviewer",
429
+ task: "Review the retry logic implementation. Look for edge cases and race conditions.",
430
+ context: "fork"
431
+ })
432
+ ```
433
+
434
+ ### Parallel non-conflicting analysis
435
+
436
+ ```typescript
437
+ subagent({
438
+ tasks: [
439
+ { agent: "scout", task: "Audit frontend auth flow" },
440
+ { agent: "researcher", task: "Research current retry/backoff best practices" }
441
+ ]
442
+ })
443
+ ```
444
+
445
+ ## Error Handling
446
+
447
+ **"Unknown agent"**
448
+ ```typescript
449
+ subagent({ action: "list" })
450
+ // Check available agents and chains, then confirm scope/precedence.
451
+ ```
452
+
453
+ **"Max subagent depth exceeded"**
454
+ ```typescript
455
+ // Flatten the workflow or raise maxSubagentDepth in config.
456
+ ```
457
+
458
+ **"Session manager did not return a session file"**
459
+ ```typescript
460
+ // Persist the current session before using context: "fork".
461
+ ```
462
+
463
+ **Intercom "Already waiting for a reply"**
464
+ ```typescript
465
+ // Resolve the current outbound ask before starting another one.
466
+ ```
@@ -63,6 +63,7 @@ function createPlaceholderResult(
63
63
  ...(index !== undefined ? { index } : {}),
64
64
  agent,
65
65
  status,
66
+ activityState: status === "running" ? "starting" : undefined,
66
67
  task,
67
68
  recentTools: [],
68
69
  recentOutput: [],
@@ -85,6 +86,7 @@ function buildParallelInitialResult(params: SubagentParamsLike): AgentToolResult
85
86
  index,
86
87
  agent: task.agent,
87
88
  status: "running" as const,
89
+ activityState: "starting" as const,
88
90
  task: task.task,
89
91
  recentTools: [],
90
92
  recentOutput: [],
@@ -140,6 +142,7 @@ function buildChainInitialResult(params: SubagentParamsLike): AgentToolResult<De
140
142
  index,
141
143
  agent: result.agent,
142
144
  status: index === 0 ? "running" as const : "pending" as const,
145
+ activityState: index === 0 ? "starting" as const : undefined,
143
146
  task: result.task,
144
147
  recentTools: [],
145
148
  recentOutput: [],
@@ -166,6 +169,7 @@ function buildSingleInitialResult(params: SubagentParamsLike): AgentToolResult<D
166
169
  progress: [{
167
170
  agent,
168
171
  status: "running",
172
+ activityState: "starting",
169
173
  task,
170
174
  recentTools: [],
171
175
  recentOutput: [],
@@ -0,0 +1,106 @@
1
+ import {
2
+ type ActivityState,
3
+ type ControlConfig,
4
+ type ControlEvent,
5
+ type ControlEventType,
6
+ type ResolvedControlConfig,
7
+ } from "./types.ts";
8
+
9
+ export const DEFAULT_CONTROL_CONFIG: ResolvedControlConfig = {
10
+ enabled: true,
11
+ quietAfterMs: 15_000,
12
+ stalledAfterMs: 60_000,
13
+ parentMode: "transitions",
14
+ };
15
+
16
+ function parsePositiveInt(value: unknown): number | undefined {
17
+ if (typeof value !== "number") return undefined;
18
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) return undefined;
19
+ return value;
20
+ }
21
+
22
+ export function resolveControlConfig(
23
+ globalConfig?: ControlConfig,
24
+ override?: ControlConfig,
25
+ ): ResolvedControlConfig {
26
+ const enabled = override?.enabled ?? globalConfig?.enabled ?? DEFAULT_CONTROL_CONFIG.enabled;
27
+ const quietAfterMs = parsePositiveInt(override?.quietAfterMs)
28
+ ?? parsePositiveInt(globalConfig?.quietAfterMs)
29
+ ?? DEFAULT_CONTROL_CONFIG.quietAfterMs;
30
+ const stalledAfterRaw = parsePositiveInt(override?.stalledAfterMs)
31
+ ?? parsePositiveInt(globalConfig?.stalledAfterMs)
32
+ ?? DEFAULT_CONTROL_CONFIG.stalledAfterMs;
33
+ const parentMode = override?.parentMode ?? globalConfig?.parentMode ?? DEFAULT_CONTROL_CONFIG.parentMode;
34
+ const stalledAfterMs = Math.max(stalledAfterRaw, quietAfterMs + 1);
35
+ return {
36
+ enabled,
37
+ quietAfterMs,
38
+ stalledAfterMs,
39
+ parentMode: parentMode === "verbose" ? "verbose" : "transitions",
40
+ };
41
+ }
42
+
43
+ export function deriveActivityState(input: {
44
+ config: ResolvedControlConfig;
45
+ startedAt: number;
46
+ lastActivityAt?: number;
47
+ hasSeenActivity: boolean;
48
+ paused: boolean;
49
+ now?: number;
50
+ }): ActivityState | undefined {
51
+ if (!input.config.enabled) return undefined;
52
+ if (input.paused) return "paused";
53
+ if (!input.hasSeenActivity) return "starting";
54
+ const now = input.now ?? Date.now();
55
+ const lastActivity = input.lastActivityAt ?? input.startedAt;
56
+ const ageMs = Math.max(0, now - lastActivity);
57
+ if (ageMs <= input.config.quietAfterMs) return "active";
58
+ if (ageMs <= input.config.stalledAfterMs) return "quiet";
59
+ return "stalled";
60
+ }
61
+
62
+ function controlEventType(from: ActivityState | undefined, to: ActivityState): ControlEventType {
63
+ if (to === "stalled") return "stalled";
64
+ if (to === "paused") return "paused";
65
+ if (from === "stalled" && to !== "stalled") return "recovered";
66
+ if (from === "paused" && to !== "paused") return "resumed";
67
+ return "activity";
68
+ }
69
+
70
+ export function shouldEmitControlEvent(
71
+ config: ResolvedControlConfig,
72
+ from: ActivityState | undefined,
73
+ to: ActivityState,
74
+ ): boolean {
75
+ if (!config.enabled || from === to) return false;
76
+ if (config.parentMode === "verbose") return true;
77
+ if (to === "stalled" || to === "paused") return true;
78
+ if (from === "stalled" && to !== "stalled") return true;
79
+ if (from === "paused" && to !== "paused") return true;
80
+ return false;
81
+ }
82
+
83
+ export function buildControlEvent(input: {
84
+ from: ActivityState | undefined;
85
+ to: ActivityState;
86
+ runId: string;
87
+ agent: string;
88
+ index?: number;
89
+ ts?: number;
90
+ }): ControlEvent {
91
+ const ts = input.ts ?? Date.now();
92
+ const type = controlEventType(input.from, input.to);
93
+ const message = input.from
94
+ ? `${input.agent} ${type} (${input.from} -> ${input.to})`
95
+ : `${input.agent} ${type} (${input.to})`;
96
+ return {
97
+ type,
98
+ from: input.from,
99
+ to: input.to,
100
+ ts,
101
+ runId: input.runId,
102
+ agent: input.agent,
103
+ index: input.index,
104
+ message,
105
+ };
106
+ }