@pi-unipi/subagents 2.6.1 → 2.6.2
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 +89 -78
- package/agents/delegate.md +14 -0
- package/agents/oracle.md +78 -0
- package/agents/researcher.md +52 -0
- package/agents/reviewer.md +79 -0
- package/agents/scout.md +50 -0
- package/agents/worker.md +59 -0
- package/package.json +19 -17
- package/prompts/council.md +48 -0
- package/prompts/gather-context-and-clarify.md +13 -0
- package/prompts/parallel-cleanup.md +59 -0
- package/prompts/parallel-research.md +50 -0
- package/prompts/parallel-review.md +54 -0
- package/prompts/review-loop.md +43 -0
- package/skills/council-mode/SKILL.md +230 -0
- package/skills/subagents/SKILL.md +49 -0
- package/skills/subagents/references/constraints-and-recipes.md +259 -0
- package/skills/subagents/references/execution-controls.md +454 -0
- package/skills/subagents/references/management-authoring-rpc.md +161 -0
- package/skills/subagents/references/multi-lane-orchestration.md +39 -0
- package/skills/subagents/references/prompting-and-roles.md +267 -0
- package/src/acceptance.ts +331 -0
- package/src/agent-manager.ts +405 -0
- package/src/agent-memory.ts +300 -0
- package/src/agent-overrides.ts +183 -0
- package/src/agent-runner.ts +265 -0
- package/src/async-runner.ts +417 -0
- package/src/authority-policy.ts +47 -0
- package/src/budgets.ts +285 -0
- package/src/child-safety.ts +140 -0
- package/src/config.ts +226 -0
- package/src/conversation-viewer.ts +281 -0
- package/src/core-compat.ts +94 -0
- package/src/custom-agents.ts +323 -0
- package/src/file-system-retry.ts +50 -0
- package/src/fleet-data.ts +61 -0
- package/src/fleet-view.ts +316 -0
- package/src/foreground-detach.ts +59 -0
- package/src/fork-context.ts +285 -0
- package/src/global.d.ts +7 -0
- package/src/guide.ts +129 -0
- package/src/index.ts +1149 -0
- package/src/mission-state.ts +133 -0
- package/src/mission-store.ts +434 -0
- package/src/model-resolver.ts +79 -0
- package/src/output-limits.ts +142 -0
- package/src/parity-types.ts +315 -0
- package/src/pi-args.ts +161 -0
- package/src/pi-spawn.ts +147 -0
- package/src/result-files.ts +232 -0
- package/src/result-watcher.ts +221 -0
- package/src/retained-children.ts +176 -0
- package/src/run-fanout-budget.ts +356 -0
- package/src/scheduled-runs.ts +351 -0
- package/src/schemas.ts +263 -0
- package/src/slash-commands.ts +77 -0
- package/src/supervisor-channel.ts +249 -0
- package/src/tool-handler.ts +1144 -0
- package/src/types.ts +238 -0
- package/src/widget.ts +447 -0
- package/src/workflow-script.ts +737 -0
- package/src/workflow-worker.ts +384 -0
- package/src/worktree.ts +614 -0
- package/dist/agent-manager.d.ts +0 -81
- package/dist/agent-manager.d.ts.map +0 -1
- package/dist/agent-manager.js +0 -295
- package/dist/agent-manager.js.map +0 -1
- package/dist/agent-runner.d.ts +0 -51
- package/dist/agent-runner.d.ts.map +0 -1
- package/dist/agent-runner.js +0 -262
- package/dist/agent-runner.js.map +0 -1
- package/dist/config.d.ts +0 -24
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -132
- package/dist/config.js.map +0 -1
- package/dist/conversation-viewer.d.ts +0 -40
- package/dist/conversation-viewer.d.ts.map +0 -1
- package/dist/conversation-viewer.js +0 -276
- package/dist/conversation-viewer.js.map +0 -1
- package/dist/core-compat.d.ts +0 -21
- package/dist/core-compat.d.ts.map +0 -1
- package/dist/core-compat.js +0 -86
- package/dist/core-compat.js.map +0 -1
- package/dist/custom-agents.d.ts +0 -14
- package/dist/custom-agents.d.ts.map +0 -1
- package/dist/custom-agents.js +0 -113
- package/dist/custom-agents.js.map +0 -1
- package/dist/file-lock.d.ts +0 -42
- package/dist/file-lock.d.ts.map +0 -1
- package/dist/file-lock.js +0 -91
- package/dist/file-lock.js.map +0 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -758
- package/dist/index.js.map +0 -1
- package/dist/model-resolver.d.ts +0 -19
- package/dist/model-resolver.d.ts.map +0 -1
- package/dist/model-resolver.js +0 -61
- package/dist/model-resolver.js.map +0 -1
- package/dist/types.d.ts +0 -98
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -47
- package/dist/types.js.map +0 -1
- package/dist/widget.d.ts +0 -56
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.js +0 -396
- package/dist/widget.js.map +0 -1
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Pi Subagents: Execution Controls
|
|
2
|
+
|
|
3
|
+
This file is a detailed reference loaded from `skills/subagents/SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Discovery and Scope Rules
|
|
6
|
+
|
|
7
|
+
Agent files can live in:
|
|
8
|
+
- `~/.unipi/config/agents/**/*.md` — user scope
|
|
9
|
+
- `.pi/agents/**/*.md` — canonical project scope
|
|
10
|
+
- legacy `.agents/**/*.md` — still read for compatibility, but `.pi/agents/` wins on conflicts
|
|
11
|
+
|
|
12
|
+
Saved chain files may still be discovered for management and existing durable run state, but they are not a public execution surface. Author new orchestration with `workflowScript`.
|
|
13
|
+
|
|
14
|
+
Precedence is by parsed runtime name:
|
|
15
|
+
1. project scope
|
|
16
|
+
2. user scope
|
|
17
|
+
3. builtin agents
|
|
18
|
+
|
|
19
|
+
Project settings resolve from the nearest parent directory containing `.pi` or `.agents` by default. In monorepos or git worktrees where an incidental nested `.pi` directory should not shadow the repository config, set `subagents.projectRootResolution: "git-root"` in the repository root `.pi/settings.json`; a nested project can opt back with `"nearest"` in its own settings.
|
|
20
|
+
|
|
21
|
+
## Running Subagents
|
|
22
|
+
|
|
23
|
+
### External CLI profiles
|
|
24
|
+
|
|
25
|
+
An agent may set `runner.type: external-cli` with a non-empty `command`, optional string `args`, and `promptDelivery: stdin` (the default). The command runs with `shell: false`, inherits the resolved cwd and environment, and receives the combined agent instructions and task through stdin. It must already be installed; unipi subagents adds no CLI dependency.
|
|
26
|
+
|
|
27
|
+
External CLI profiles are async-only and one-shot. They support lifecycle artifacts, stdout/stderr logs, timeout, and stop. Full stdout and stderr are retained in their log files, while the final stdout response and stderr error kept in memory are each limited to their last 64 KiB. They do not support foreground/clarify, steer/resume/interrupt-as-pause, Pi models/tools/extensions/skills, tool or turn budgets, structured output, nested subagents, fallbacks, or sessions.
|
|
28
|
+
|
|
29
|
+
### External job profiles
|
|
30
|
+
|
|
31
|
+
An agent may set `runner.type: external-job` with a non-empty `provider` and optional JSON `options`. When `surf-cli` is installed and loaded, Surf can optionally expose a `gpt-pro` package agent through provider `surf-oracle`. Surf maps `model: pro` to ChatGPT GPT-5.6 Sol Pro web mode. unipi subagents does not own that package agent or model mapping. Remove any old `agentOverrides.gpt-pro.disabled` workaround before using Surf's package agent. The provider must be registered in the host Pi process through `unipi subagents/external-job-provider`; the async runner talks to that parent-owned registry through a local operation bridge.
|
|
32
|
+
|
|
33
|
+
External job profiles are async-only. The provider owns the remote job and Pi owns the async run record. Status persists provider name, provider job id, prompt digest, provider options, handle/conversation URLs when supplied, result artifact path, last known state, and provider failure code/message. Recovery uses existing provider job metadata to call `reattach` and `result`; it refuses to redispatch a prompt when the persisted provider job does not match the prompt digest.
|
|
34
|
+
|
|
35
|
+
External job profiles do not support foreground/clarify, steer/resume, Pi models/tools/extensions/skills, tool or turn budgets, structured output, native child permissions, fallbacks, or Pi child sessions. Capacity conflicts fail closed and include the blocking provider job id when the provider supplies it.
|
|
36
|
+
|
|
37
|
+
### Single agent
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
subagent({
|
|
41
|
+
workflowScript: `return runs.run("oracle-check", { agent: "oracle", task: "Review my current direction and challenge assumptions." })`
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Forked context
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
subagent({
|
|
49
|
+
workflowScript: `return runs.run("oracle-check", { agent: "oracle", task: "Review my current direction and challenge assumptions.", context: "fork" })`
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`context: "fork"` creates a branched child session from the current persisted
|
|
54
|
+
parent session. It does **not** create a fresh minimal review context or filter
|
|
55
|
+
history down to only the relevant parts. Use it when you want a separate review
|
|
56
|
+
or execution thread that can still reference the parent session history.
|
|
57
|
+
|
|
58
|
+
Foreground results, async status, fleet, and widget surfaces label each child with
|
|
59
|
+
its resolved launch context as `[fresh]` or `[fork]`. Aggregate headers show
|
|
60
|
+
`[mixed]` when a run uses both modes.
|
|
61
|
+
|
|
62
|
+
### Scripted workflows
|
|
63
|
+
|
|
64
|
+
`workflowScript` is the sole public execution surface. Use `runs.run(key, { agent, task, ... })` for one child, `runs.all([...])` for parallel children, and ordinary JavaScript for sequence, branching, filtering, retries, and aggregation. Scripts are ordinary JavaScript statement bodies, so use an explicit return such as `workflowScript: "return runs.run('main', { agent: 'worker', task: '...' })"` for a useful one-child result. Use top-level `await`, plain helper functions, or explicit Promise chains; nested `async function` helpers, async arrows, and async methods are rejected. Prefer a single scripted workflow whenever the parent is starting a coordinated wave, such as multiple reviews, review plus gate monitor, worker then monitor setup, cross-repo prep lanes, or a fanout that the parent will consume together.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
subagent({
|
|
68
|
+
workflowScript: `
|
|
69
|
+
const scan = await runs.run("scan", { agent: "scout", task: "Map the target" });
|
|
70
|
+
const reviews = await runs.all([
|
|
71
|
+
{ key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
72
|
+
{ key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
73
|
+
]);
|
|
74
|
+
return reviews.map(result => result.output);
|
|
75
|
+
`
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Scripts run in a timed worker with only `runs.run`, `runs.all`, `runs.status`, `runs.ref/refs`, `emit`, captured `console`, and standard JavaScript. Pass explicit task text to `runs.run`. Mission-attached workflows also get `await state.get(key)` and `await state.set(key, value)` for durable JSON state shared across workflows on the same mission; `mission: false` workflows have no `state` global. Stable keys are required. Child launches follow ordinary single-agent execution controls. Give each child a distinct decision and output path when reports must outlive the workflow, then consume the aggregate workflow result before opening individual reports.
|
|
80
|
+
|
|
81
|
+
For one host-run verification command, pass `gate: "npm test"` on a `runs.run`/`runs.all` item (or at the top level as a workflow default). It is shorthand for verified acceptance with that single command: the runtime executes it on the host, records the result as evidence, and memoizes it per tracked workspace state and effective environment. `gate` cannot be combined with `acceptance`; use explicit `acceptance.verify` for multiple commands or custom criteria.
|
|
82
|
+
|
|
83
|
+
Completed workflow children from this parent session stay addressable as retained children. `subagent({ action: "children.list" })` lists up to the last 10 with run ids and reports each row as `resumable` or `not resumable` with a reason. Resume only rows reported `resumable`. For a retained-child challenge, use `resume` instead of `steer` when the child is complete. If no retained child is resumable, launch a same-role fallback challenge and label it as fallback. A later workflow continues a resumable child with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`. Inside `workflowScript`, awaiting that call waits for the revived child to finish and returns its completed output and new `runId`; top-level `{ action: "resume" }` remains detached. Pass explicit follow-up task text. Assign each returned child result back to the loop variable because every resume can return a new retained `runId`; always resume the latest returned id. `resume` and `agent` are mutually exclusive, the revived child keeps its stored agent/model/tool contract, and `gate` is rejected on retained resume items.
|
|
84
|
+
|
|
85
|
+
Terminal async workflows also persist `workflow-receipt.json` beside `status.json`. It maps each stable child key to its agent, requested and resolved context when known, latest run id, resumability, output reference, and continuation lineage. A later workflow can resume the latest retained child without copying its run id:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
return runs.run("cross-oracle", {
|
|
89
|
+
resume: { workflowRunId: "<pass-1-workflow-id>", key: "advisor-oracle", latest: true },
|
|
90
|
+
task: "Review the focused challenge packet."
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keyed resume reads that one exact receipt and revalidates the retained run at launch. It fails when the workflow or key is missing, the receipt is stale, `latest` is not `true`, or the recorded child is no longer resumable. Foreground workflow results expose the same receipt in `details.workflow.receipt`, but cross-workflow keyed lookup requires the durable receipt from an async workflow.
|
|
95
|
+
|
|
96
|
+
### Async/background
|
|
97
|
+
|
|
98
|
+
Prefer async mode for every subagent launch. Set `async: true` no matter the task unless the parent must block until completion. This applies to scouts, researchers, workers, reviewers, validators, oracle checks, one-off delegates, final review gates, backlog gates, and scripted workflows. Keep the write path single-threaded even when the run is async.
|
|
99
|
+
|
|
100
|
+
Use `async:false` only when the parent must block until completion. Async mode still shows progress. Do not use `async:false` because a task is short, because it is the last gate, because no other work is ready, because the user asked to finish the overall job, or because blocking is convenient.
|
|
101
|
+
|
|
102
|
+
Async does not mean parallel writes. Do not edit the same active worktree while an async worker is changing it. Parent-side overlap should be reading, validation prep, synthesis, command planning, or review of unaffected context unless the writer is isolated in a separate worktree.
|
|
103
|
+
|
|
104
|
+
Do not end your turn immediately after launching an async child if you promised to keep working. Continue the local inspection, synthesis, or validation prep, then check the async run when its result is needed. If no safe independent work remains, return control and let Pi wake the session; do not convert the child to foreground.
|
|
105
|
+
|
|
106
|
+
In an interactive chat, normally return control when ready to yield and let Pi wake the session on completion; do not call `subagent_wait()` merely to wait. A run-to-completion user request is not by itself a reason to use foreground children. Override the normal yield-and-wake flow only when this exact turn cannot safely end without the result, such as a headless provider flow or a skill contract that must produce a same-turn artifact. Use `subagent_wait()`, not `async:false`, for that current-turn dependency. Never substitute sleep or status-polling loops.
|
|
107
|
+
|
|
108
|
+
`subagent_wait()` returns when the next initially active async run or registered provider item finishes or a subagent needs attention. Use `subagent_wait({ all: true })` for all work active at call time, `subagent_wait({ id: "..." })` for one async or remembered detached foreground run, and `subagent_wait({ timeoutMs })` to cap the block. In a long-lived interactive parent session, use `subagent_wait({ id: "...", nonBlocking: true })` to resolve the prefix to one exact run, persist an armed subscription, return immediately, and wake later on completion, failure, attention, reconciliation failure, or timeout. Ordinary status lists armed subscriptions separately from active children. This differs from disabling `waitTool`, which returns immediately without arming a future wake. If a foreground child detaches for supervisor coordination, reply first, then wait on its id; do not resume or launch a replacement while it remains detached. Headless sessions also auto-drain exact current-session work at `agent_end` as a final safeguard.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
subagent({
|
|
112
|
+
workflowScript: `return runs.run("main", { agent: "worker", task: "Run the full test suite" })`,
|
|
113
|
+
async: true
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
File-only output mode works for workflowScript child launches. Use distinct absolute or durable output paths when later script steps need stable references. For cross-codebase waves, include the repo slug or lane key in each output path so reports from different repositories cannot collide.
|
|
118
|
+
|
|
119
|
+
For review fanout where the parent continues a local audit:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const run = subagent({
|
|
123
|
+
workflowScript: `return runs.run("correctness", { agent: "reviewer", task: "Review the current diff for correctness issues. Do not edit files." })`,
|
|
124
|
+
async: true,
|
|
125
|
+
context: "fresh"
|
|
126
|
+
})
|
|
127
|
+
// Continue local inspection, then later call status with the returned id.
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
While children run, the persistent FleetView and the collapsed foreground tool-result card show live per-child detail: resolved model and thinking level, `[fresh]`/`[fork]` context, tool/token/elapsed counters, and current activity. The collapsed running card also prints the configured expand-key hint ("Press … for live detail"); expanding it shows nested children, recent tools, and recent output. Model badges appear once the child's model resolves at first attempt start. `/subagents-fleet` opens the live fleet inspector, which also has per-child controls (`s` steer, `D` stop with confirmation). When optional Herdr 0.7.5+ is available, `H` opens a raw inspector dashboard for the selected active async child; this mirrors artifacts rather than attaching to the headless child. Use it for confusing or long-running active async work when the human wants a dedicated visual pane or FleetView is insufficient, not for routine headless runs.
|
|
131
|
+
|
|
132
|
+
Inspect async runs with `subagent({ action: "status", id: "..." })` or `subagent({ action: "status" })` for active runs. Use `subagent({ action: "status", view: "fleet" })` when supervising several active foreground/background runs and `subagent({ action: "status", id: "...", view: "transcript", index: 0 })` when you need the latest child output without digging through artifacts. If a delegated fanout child launches nested runs, the parent status view shows them as a tree and you can target a nested run directly with its nested id.
|
|
133
|
+
|
|
134
|
+
Stop a current-session top-level async run with `stop` (or `/subagents-stop`). Stopped runs finish as `stopped`/cancelled and are not resumable. For an active foreground single-subagent run, `/subagents-detach [run-id]` leaves the child running without terminating it and returns the eventual result through status/wait.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
subagent({ action: "stop", id: "run-id" })
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Use `steer` for top-level live async guidance and `resume` after a delegated run pauses or finishes. Routed nested runs retain their existing non-destructive live follow-up path:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
subagent({ action: "steer", id: "run-id", message: "Focus on the failing test." })
|
|
144
|
+
subagent({ action: "resume", id: "run-id", message: "Follow up on this point." })
|
|
145
|
+
subagent({ action: "resume", id: "run-id", index: 1, message: "Continue reviewer 2." })
|
|
146
|
+
subagent({ action: "resume", id: "nested-run-id", message: "Continue this nested reviewer." })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Resume behavior:
|
|
150
|
+
- `resume` revives paused, completed, or failed async/foreground children from persisted session files; stopped runs remain non-resumable, and it does not interrupt live top-level async children.
|
|
151
|
+
- Use `steer` for acknowledged guidance to a live top-level async child.
|
|
152
|
+
- A live nested run can still receive a non-destructive `resume` follow-up through its owner route.
|
|
153
|
+
- If an async child has completed, `resume` revives it by starting a new async child from the persisted child session file.
|
|
154
|
+
- Multi-child async runs require `index` unless only one running child is selectable.
|
|
155
|
+
- Completed foreground single, parallel, and chain runs can also be revived by `index` while their run metadata remains in extension state.
|
|
156
|
+
- Nested runs can be resumed by nested id when a live route or persisted nested session metadata is available.
|
|
157
|
+
- Revive starts a new child process from the old session context; it does not restart the same OS process.
|
|
158
|
+
- Direct revival holds an exclusive cross-process lease on the canonical child session file until the new child finishes. Concurrent attempts fail before Pi starts and identify the owning revived run; stale ownership is reclaimed only when the recorded process is demonstrably gone or reused.
|
|
159
|
+
- If the chosen child has no persisted `.jsonl` session file, resume fails and reports that directly.
|
|
160
|
+
|
|
161
|
+
Use diagnostics when setup or child startup looks wrong:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
subagent({ action: "doctor" })
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### External terminal work
|
|
168
|
+
|
|
169
|
+
Use native `subagent` runs for unattended implementation, review, and gate work that needs managed isolation, durable artifacts, and process controls. Use `interactive_shell` for visible terminal work, alternate CLIs, trust prompts, and recovery.
|
|
170
|
+
|
|
171
|
+
A cooperating terminal runtime can register read-only external records through `unipi subagents/external-runs`. Records include the source, session, state, optional report path, and completion reason. They are observations only: unipi subagents does not start, stop, steer, or otherwise own the foreign process. Run unattended raw terminal agents in an explicit isolated cwd or worktree; do not use a live project checkout as disposable review space.
|
|
172
|
+
|
|
173
|
+
### Scheduled subagent runs
|
|
174
|
+
|
|
175
|
+
Schedules are durable project records under `.pi/subagents/schedules/`. They are enabled by default; set `{ "scheduledRuns": { "enabled": false } }` in `~/.pi/agent/extensions/subagent/config.json` to disable them. Only schedule explicit work the user asked for.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// One-shot reviewer
|
|
179
|
+
subagent({ action: "schedule.create", id: "evening-review", name: "Evening review", at: "+30m", workflowScript: "return runs.run('main', { agent: 'reviewer', task: 'Review the diff.' })" })
|
|
180
|
+
|
|
181
|
+
// Fixed recurring workflow
|
|
182
|
+
subagent({ action: "schedule.create", id: "backlog", every: "6h", catchUp: "latest", workflowScript: "..." })
|
|
183
|
+
|
|
184
|
+
subagent({ action: "schedule.list" })
|
|
185
|
+
subagent({ action: "schedule.show", id: "backlog" })
|
|
186
|
+
subagent({ action: "schedule.history", id: "backlog" })
|
|
187
|
+
subagent({ action: "schedule.pause", id: "backlog" })
|
|
188
|
+
subagent({ action: "schedule.resume", id: "backlog" })
|
|
189
|
+
subagent({ action: "schedule.run", id: "backlog" })
|
|
190
|
+
subagent({ action: "schedule.run-due" })
|
|
191
|
+
subagent({ action: "schedule.delete", id: "backlog" })
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`schedule.create` accepts exactly one target, `workflowScript`, and exactly one trigger (`at`, or a fixed `every` interval using `m`, `h`, `d`, or `w`). Runs always launch async with fresh context and no automatic mission; mission attachment is deferred from this first slice. `overlap` is currently `skip`; `catchUp` supports `latest` and `none`. `schedule.run-due` is the headless external-launcher seam. Calendar recurrence, cron, and the schedule inspector are deferred from this first safe slice. Definitions, bounded history, append-only events, and per-run receipts remain project-scoped across Pi sessions.
|
|
195
|
+
|
|
196
|
+
Humans can use `/subagents-doctor` for the same read-only report. It checks runtime paths, discovery counts, async support, current session context, and intercom bridge state.
|
|
197
|
+
|
|
198
|
+
### Subagent control
|
|
199
|
+
|
|
200
|
+
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 `queued`, `running`, `paused`, `complete`, `stopped`, `failed`, or `rejected`. Activity reporting is factual: it tracks the last observed activity time and the current tool when known. It does not pretend to know that a child is truly stuck. Manual top-level async cancellation uses `stop` / `/subagents-stop`.
|
|
201
|
+
|
|
202
|
+
Default behavior is intentionally conservative. When no activity has been observed past the configured threshold, the run emits a `needs_attention` control event. Foreground runs can push this as a `subagent:control-event` event, and async runs persist it to `events.jsonl` so the parent tracker can surface it without constant manual polling. Notification-worthy control events are also inserted into the visible transcript so both the user and the parent agent can see them, with a proactive hint plus concrete `nudge`, `status`, and `interrupt` options. Visible notifications fire once per child run and attention state.
|
|
203
|
+
|
|
204
|
+
Use soft interrupt when a child is clearly blocked or drifting and the parent needs to regain control:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
subagent({ action: "interrupt" })
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Pass `id` when targeting a specific controllable run, including a nested run shown in the parent status tree:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
subagent({ action: "interrupt", id: "abc123" })
|
|
214
|
+
subagent({ action: "interrupt", id: "nested-run-id" })
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
A soft interrupt cancels the current child turn and leaves the run paused. It does not mean the delegated task succeeded or failed. Bare `interrupt` does not target hidden nested descendants; use the explicit nested id. After an interrupt, decide the next explicit action: resume with clearer instructions, replace the task, ask the user, or stop the workflow.
|
|
218
|
+
|
|
219
|
+
Per-run control thresholds can be overridden when a task legitimately runs without observable output for longer than usual:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
subagent({
|
|
223
|
+
workflowScript: `return runs.run("slow-tests", {
|
|
224
|
+
agent: "worker",
|
|
225
|
+
task: "Run the slow migration test suite",
|
|
226
|
+
control: {
|
|
227
|
+
needsAttentionAfterMs: 300000,
|
|
228
|
+
notifyOn: ["needs_attention"]
|
|
229
|
+
}
|
|
230
|
+
})`
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
If the run already has an active intercom bridge target, needs-attention notifications can also prepare a compact intercom ping for the orchestrator. When a child route is available, the ping tells the orchestrator which agent needs attention and includes the exact `intercom({ action: "send", to: "..." })` target for a nudge. Do not invent a target or ask the child to self-report when no bridge exists.
|
|
235
|
+
|
|
236
|
+
Steering is acknowledged delivery, not a send attempt or model-compliance signal:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
subagent({ action: "steer", id: "abc123", message: "Focus on the failing test." })
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The action waits up to three seconds for the child Pi session to accept the correlated user input and returns a request id with `delivered`, `scheduled`, `pending`, `partial`, `recovered`, or `failed` plus per-child states. Indexed pending children return `scheduled` immediately. Only a top-level single-child run may automatically interrupt after a missed acknowledgment and recover after confirmed pause within a further 15 seconds. Recovery preserves the original child contract and only its remaining deadline, turn, and tool budgets. If the session is missing, a budget is exhausted, the pause cannot be confirmed, or replacement launch fails, the source remains paused when pausing succeeded and the action returns the exact failure. Chain, parallel, and nested runs never auto-interrupt; inspect their per-child outcomes and handle failures explicitly. A late acknowledgment is recorded and cannot cancel committed recovery.
|
|
243
|
+
|
|
244
|
+
## Watchdog
|
|
245
|
+
|
|
246
|
+
The subagent watchdog is an **opt-in** adversarial change reviewer. It is not the
|
|
247
|
+
`reviewer` subagent and is not configured by `subagents.defaultModel` or
|
|
248
|
+
`agentOverrides.reviewer`.
|
|
249
|
+
|
|
250
|
+
When enabled, it reviews actual repo edits at safe `agent_end` boundaries only if
|
|
251
|
+
the final worktree state changed during that turn. Unchanged or reverted diffs and
|
|
252
|
+
generated `.pi/subagents/` / temp artifacts do not trigger review. Writing children
|
|
253
|
+
can review their own worktree; the parent can still review the aggregate diff after
|
|
254
|
+
child changes land. Enabled watchdogs also run changed-file TypeScript/JavaScript
|
|
255
|
+
LSP diagnostics before the model pass when `typescript-language-server` is available.
|
|
256
|
+
They keep bounded current-scope context from real user prompts (`watchdog.scope.enabled`)
|
|
257
|
+
and can optionally run non-blocking Scopey-style cadence reviews every N tool results
|
|
258
|
+
(`watchdog.cadence.everyNTools`). Cadence corrections and blocker auto-follow prompts
|
|
259
|
+
are always transcript-visible; choose the watchdog model that matches the desired
|
|
260
|
+
cheap-monitor vs strong-reviewer policy.
|
|
261
|
+
|
|
262
|
+
Prefer a strong complementary model (for example Opus 4.8 high paired against a
|
|
263
|
+
GPT 5.5 main session, or the reverse). Recommendation and configuration:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
/subagents-watchdog recommend-model
|
|
267
|
+
/subagents-watchdog session model recommended
|
|
268
|
+
/subagents-watchdog on
|
|
269
|
+
/subagents-watchdog status
|
|
270
|
+
/subagents-watchdog check
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
subagent({ action: "watchdog.status" })
|
|
275
|
+
subagent({ action: "watchdog.recommend-model" })
|
|
276
|
+
subagent({ action: "watchdog.configure", model: "recommended", scope: "session" })
|
|
277
|
+
subagent({ action: "watchdog.check" })
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`session` scope is temporary. Persistent `user`/`project` scopes write settings only
|
|
281
|
+
when the user asked. Use ordinary fresh-context `reviewer` fanout for planned review
|
|
282
|
+
waves; enable the watchdog when you want an automatic second pass on real edits.
|
|
283
|
+
|
|
284
|
+
## Missions and cross-project routing
|
|
285
|
+
|
|
286
|
+
Missions are the durable orchestration layer. Use this noun map:
|
|
287
|
+
|
|
288
|
+
- **Project/codebase** — where work happens.
|
|
289
|
+
- **Mission** — why delegated work exists and how to recover it later.
|
|
290
|
+
- **Run** — one actual subagent execution.
|
|
291
|
+
- **Receipt** — proof or a link for an external outcome, such as a PR, CI check, deployment, or release.
|
|
292
|
+
|
|
293
|
+
Ordinary launches with a task create a mission by default, so substantial delegated work has a persisted objective, status, run links, decisions, artifacts, and delivery receipts that survive compaction or a new parent chat. Automatic persistence failures leave the run intact and set `details.missionWarning`; explicit `missionId` or `mission` remains strict before launch. Human receipts end with a mission id/status line, while structured JSON text remains untouched and `details.missionId` is authoritative. Pass `missionId` to attach an existing mission, use `mission: { title, objective?, goal?, budget?, labels? }` to control the auto-created record, pass `mission: false` for intentionally ephemeral work, or set `missions.enabled: false` to opt out globally. `objective` is the intent string; `goal: true` requires `budget.tokens` and turns the open mission into a continuation driver that sends one needs-attention notice after idle parent turns until the budget is exhausted, the goal is paused with `mission.update` `{ goal: { paused: true } }`, or the mission closes.
|
|
294
|
+
|
|
295
|
+
Use `mission.update` while work runs to record decisions, artifacts, labels, summaries, or delivery receipts. A receipt records a pull request, CI, deployment, or release link with a concise status; it does not authorize or automate merge, CI polling, or deployment. Record open product, architecture, or safety decisions there and escalate them upward; do not let a child decide silently. Use `mission.attach-run` only for runs launched outside the normal mission-backed path, and use `mission.close` with a terminal status and concise summary when the mission is done.
|
|
296
|
+
|
|
297
|
+
### Mission use policy
|
|
298
|
+
|
|
299
|
+
- **Keep the default.** Every ordinary `workflowScript` launch with a task creates one enclosing mission automatically. All workflow children share it and never get their own. Do not add `mission: {...}` boilerplate. Pass it only to set the title, objective, labels, or to enable `goal` with `budget`.
|
|
300
|
+
- **Use `mission: false` for noise.** Use it for trivial one-shot lookups, scouts, disposable probes, and quick validation where a recovery record is noise. It removes the mission and the `state` global for the whole workflow, so do not use it for monitors or multi-workflow loops that coordinate through `state`. Scheduled runs already launch without automatic missions.
|
|
301
|
+
- **Use `missionId` for follow-up work.** Attach later work to an existing objective with `missionId`; attachment re-marks the mission active. `missionId` and `mission` are mutually exclusive. Explicit attachment fails before launch if the mission is missing, while automatic missions degrade to `details.missionWarning` without blocking the run.
|
|
302
|
+
- **Keep `state` small.** Mission `state` is JSON coordination across workflows on the same mission. Keys use the same format as run keys, values must be JSON, and the whole state file is capped at 256 KiB. Each `set` merges one key under a file lock. Put large content in artifact files and store paths in state. In goal missions, write `state.set("nextReadyAction", "...")` so the next idle-turn notice names the exact ready step.
|
|
303
|
+
- **Use artifacts and receipts as evidence.** Mission-backed launches already record run artifacts such as async `status.json`, `events.jsonl`, child output paths, and handoff manifests. Add `mission.update` artifacts only for extra durable outputs such as `patch`, `review`, or `note` files. Add receipts for external outcomes: `pull_request`, `ci`, `deployment`, or `release`; each receipt needs an absolute URL. Receipts are evidence, not authority to merge, deploy, or release.
|
|
304
|
+
- **Resolve decisions explicitly.** `mission.update` `decisions` can only add open decisions; `mission.update` itself cannot resolve one — use the `mission.resolve-decision` action (decision `id` plus a non-empty `summary`) to settle and close it. In a goal mission, an unresolved decision becomes the fallback next ready action in each notice. Use decisions sparingly there; record them for escalation and audit, steer goal continuation through `state.nextReadyAction`, and close the mission when the question is settled.
|
|
305
|
+
- **Close missions when done.** `mission.close` takes `missionStatus` `completed`, `failed`, or `cancelled` plus a concise `summary`, and ends any goal loop. Goal notices go only to the owning session and stop silently at `budget-exhausted` without closing or claiming success, so close explicitly. Terminal missions are pruned beyond configured retention, so store durable outputs as artifacts, receipts, and summary before closing.
|
|
306
|
+
|
|
307
|
+
After compaction, restart, or confusing history, recover from durable state first: `mission.list` in the project, `mission.list` with `missionScope: "global"` for the user-local cross-project pointer index, then `mission.show` for the relevant mission. `mission.show` refreshes linked async status when available and returns warnings instead of hiding the mission if a linked status file is temporarily unreadable. Use the linked run ids with normal `status`, `steer`, `resume`, or `stop` actions. Project mission JSON remains authoritative over chat history.
|
|
308
|
+
|
|
309
|
+
Routing rule:
|
|
310
|
+
- Same project: ordinary mission-backed subagents.
|
|
311
|
+
- Different project, small/bounded task: ordinary async subagent with explicit `cwd`, an authority boundary, and durable output.
|
|
312
|
+
- Several projects with independent work: one async `workflowScript` whose child keys include repo slugs and whose child calls set explicit `cwd`; keep publication and merge decisions serial per repo.
|
|
313
|
+
- Different project, substantial or long-running work: open a project-owned Herdr pane rooted there when a separate visible project session is useful, then give that project Pi session a narrow mission/result contract. Do not model it as ordinary child nesting, and do not expect existing headless runs to move into the pane.
|
|
314
|
+
|
|
315
|
+
Project panes run a separate Pi session from the target directory. Subagents launched inside that pane use that project's config, agents, skills, files, git state, and mission records. The pane binding lives under `<projectRoot>/.pi/subagents/project-panes/herdr.json`. For ordinary headless delegation to another repo, prefer explicit `cwd` first; reserve project panes for visible or persistent project ownership.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
subagent({ action: "mission.create", mission: { title: "Ship auth refresh", objective: "Implement and validate refresh handling" } })
|
|
319
|
+
subagent({ workflowScript: `return runs.run("main", { agent: "worker", task: "Implement the approved plan" })`, missionId: "<mission-id>" })
|
|
320
|
+
subagent({ workflowScript: `return runs.run("main", { agent: "scout", task: "Quickly answer whether this file exists" })`, mission: false })
|
|
321
|
+
subagent({ action: "mission.list", missionScope: "global" })
|
|
322
|
+
subagent({ action: "mission.resolve-decision", missionId: "<mission-id>", id: "<decision-id>", summary: "Settled: ship the v2 API; no schema freeze needed." })
|
|
323
|
+
subagent({ action: "project.open", cwd: "/path/to/other-repo", message: "Own this mission for the project and report back with receipts." })
|
|
324
|
+
subagent({ action: "project.status", cwd: "/path/to/other-repo" })
|
|
325
|
+
subagent({ action: "project.close", cwd: "/path/to/other-repo" })
|
|
326
|
+
subagent({ action: "mission.close", missionId: "<mission-id>", missionStatus: "completed", summary: "Auth refresh shipped and tests pass." })
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Worktree Isolation
|
|
330
|
+
|
|
331
|
+
When multiple agents might write concurrently, use worktrees instead of letting
|
|
332
|
+
them share one filesystem view.
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
subagent({
|
|
336
|
+
workflowScript: `
|
|
337
|
+
const results = await runs.all([
|
|
338
|
+
{ key: "feature-a", agent: "worker", task: "Implement feature A", worktree: true },
|
|
339
|
+
{ key: "feature-b", agent: "worker", task: "Implement feature B", worktree: true }
|
|
340
|
+
]);
|
|
341
|
+
return results.map(({ key, artifactPaths }) => ({ key, artifactPaths }));
|
|
342
|
+
`
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`worktree: true` on a `runs.run` / `runs.all` item gives that child its own git
|
|
347
|
+
worktree branched from HEAD. A top-level workflow `worktree: true` makes this the
|
|
348
|
+
default for every child, and a child can opt out with `worktree: false`. This
|
|
349
|
+
requires a clean git state and is mainly for intentionally parallel write
|
|
350
|
+
workflows. On completion, use each child's handoff path from its
|
|
351
|
+
`artifactPaths` instead of scraping combined text. Each manifest records child status and output references, full
|
|
352
|
+
patch paths and stats, and whether each temporary worktree and branch was
|
|
353
|
+
removed. The manifest is journaled immediately after managed worktree setup, before children run, so abrupt exits retain owned paths and branches for recovery. Dirty or divergent work without a successfully captured patch is preserved with a partial-cleanup warning. Permanently discard recorded preserved work with `subagent({ action: "worktree.discard", handoffPath: "<child handoff path>" })`; authority defaults to interactive confirmation and refuses headlessly, and partial results print manual Git recovery commands. If you want one writer thread and several advisory agents, prefer a
|
|
354
|
+
single-writer pattern instead.
|
|
355
|
+
|
|
356
|
+
Git worktrees start from tracked files, so ignored or untracked build state
|
|
357
|
+
such as `node_modules` may be absent. The clean-check ignores unipi subagents'
|
|
358
|
+
own `.pi/subagents/` runtime state, including default mission records, but still
|
|
359
|
+
rejects ordinary source/config changes. `unipi subagents` attempts to symlink the
|
|
360
|
+
root checkout's `node_modules` into each managed worktree when it exists, but
|
|
361
|
+
agents should still treat dependency setup as an explicit bootstrap step before
|
|
362
|
+
running tests, typecheck, or builds. If module resolution fails in a fresh
|
|
363
|
+
worktree, first confirm dependencies were linked, installed, or provisioned by
|
|
364
|
+
`worktreeSetupHook` before treating it as a code failure.
|
|
365
|
+
|
|
366
|
+
## The Oracle Workflow
|
|
367
|
+
|
|
368
|
+
### Oracle consultation loop
|
|
369
|
+
|
|
370
|
+
For plan, design, or architecture advice that asks to ask, consult, discuss with, or come to agreement with `oracle`, start with one forked oracle run. Read its result. If it challenges the direction or leaves a material tradeoff, resume that same completed child once with a focused follow-up, then synthesize the parent decision. `resume` returns a new run id, but continues the same oracle session and inherited context. Do not force a second round for an explicit one-shot request, a trivial question, or a fully settled first answer.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
const first = await runs.run("oracle-consult", { agent: "oracle", task: "Review this plan and identify the strongest unresolved tradeoff." });
|
|
374
|
+
const final = await runs.run("oracle-consult-follow-up", { resume: first.runId, task: "Address this focused question, then state the best recommendation: ..." });
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The parent remains the final decision-maker. Oracle advice does not approve a direction or start implementation.
|
|
378
|
+
|
|
379
|
+
The intended oracle loop is:
|
|
380
|
+
1. the main agent forks to `oracle`
|
|
381
|
+
2. `oracle` reviews direction, drift, assumptions, and risks
|
|
382
|
+
3. `oracle` can coordinate back through `contact_supervisor` when the bridge injects it
|
|
383
|
+
4. the main agent decides what direction to approve
|
|
384
|
+
5. only then should `worker` implement
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// Advisory review in a branched thread. Oracle defaults to forked context.
|
|
388
|
+
subagent({
|
|
389
|
+
workflowScript: `return runs.run("oracle-check", { agent: "oracle", task: "Review my current direction, challenge assumptions, and propose the best next move." })`
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
// Implementation only after explicit approval. Worker defaults to forked context.
|
|
393
|
+
subagent({
|
|
394
|
+
workflowScript: `return runs.run("implementation", { agent: "worker", task: "Implement the approved approach: ..." })`
|
|
395
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
`oracle` is not a fresh-context reviewer in the Cognition article sense. It is
|
|
399
|
+
a forked advisory thread that inherits the parent session history and uses that
|
|
400
|
+
history as a baseline contract.
|
|
401
|
+
|
|
402
|
+
Use `oracle` as a smart-friend escalation when the parent needs help with trajectory rather than diff inspection: architectural boundaries, model capability routing, merge conflicts, reviewer disagreement, context drift after long work, a worker about to invent a pattern, or fixes that require product/scope tradeoffs. Ask broad questions when the right concern is unclear, and let `oracle` point out missing context or files the parent should inspect before asking again. Keep `oracle` advisory unless it has been explicitly assigned the single writer role.
|
|
403
|
+
|
|
404
|
+
## Subagent + Intercom Coordination
|
|
405
|
+
|
|
406
|
+
`unipi subagents` includes native supervisor coordination. Child agents can use `contact_supervisor` to ask the exact parent session that spawned them; messages are scoped by parent session id and should not appear in other Pi sessions. Parents inspect or reply with `subagent_supervisor`. This path does not require `pi-intercom`.
|
|
407
|
+
|
|
408
|
+
This is separate from optional external completion delivery. Set `intercomBridge.resultDelivery: true` only when an external listener consumes and acknowledges `subagent:result-intercom` grouped results. It does not deliver results by itself, and it does not change native supervisor asks or progress updates.
|
|
409
|
+
|
|
410
|
+
Generic `intercom` is external or provider-supplied only. Native supervisor coordination injects `contact_supervisor`, not generic `intercom`. Use generic `intercom` only when external bridge instructions provide an explicit safe target. Do not invent a target. Prefer the tool from the injected bridge instructions.
|
|
411
|
+
|
|
412
|
+
Use `contact_supervisor` with `reason: "need_decision"` when:
|
|
413
|
+
- a subagent is blocked on a decision
|
|
414
|
+
- a child needs clarification instead of guessing
|
|
415
|
+
- an approval, product, API, or scope choice is required before continuing safely
|
|
416
|
+
|
|
417
|
+
Use `contact_supervisor` with `reason: "interview_request"` when the child needs structured supervisor input rather than a freeform answer. The request waits for a parent reply, so the child should stay alive and continue only after the reply arrives.
|
|
418
|
+
|
|
419
|
+
Do not use `contact_supervisor` just to resolve review-only/no-project-edit versus progress-writing or output-artifact instructions. The child must not modify project/source files, but returning findings through its normal response or configured output artifact is allowed unless the parent explicitly set `output: false`.
|
|
420
|
+
|
|
421
|
+
Use `contact_supervisor` with `reason: "progress_update"` when:
|
|
422
|
+
- a child is explicitly asked for progress
|
|
423
|
+
- a meaningful discovery changes the plan
|
|
424
|
+
- a long-running child needs to report a blocked/progress checkpoint without waiting for normal tool return flow
|
|
425
|
+
|
|
426
|
+
Message conventions:
|
|
427
|
+
- `reason: "need_decision"` and `reason: "interview_request"` wait for the parent reply and return it to the child.
|
|
428
|
+
- `reason: "progress_update"` is non-blocking and should stay concise.
|
|
429
|
+
- Child-side routine completion handoffs are not expected. Native supervisor messages are for decisions, structured input, and meaningful progress updates while a child is still running.
|
|
430
|
+
|
|
431
|
+
If bridge instructions provide the child-facing tool, a child can ask:
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
contact_supervisor({
|
|
435
|
+
reason: "need_decision",
|
|
436
|
+
message: "Should I optimize for readability or performance here?"
|
|
437
|
+
})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
The parent replies with the native supervisor tool:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
subagent_supervisor({ action: "reply", message: "Optimize for readability." })
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Or inspects unresolved asks first:
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
subagent_supervisor({ action: "pending" })
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Native supervisor coordination does not expose generic `intercom` as a fallback. Use `subagent_supervisor` for parent replies.
|
|
453
|
+
|
|
454
|
+
If intercom messages do not show up, run `subagent({ action: "doctor" })` or `/subagents-doctor`.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Pi Subagents: Management Authoring Rpc
|
|
2
|
+
|
|
3
|
+
This file is a detailed reference loaded from `skills/subagents/SKILL.md`.
|
|
4
|
+
|
|
5
|
+
## Management Mode
|
|
6
|
+
|
|
7
|
+
The `subagent(...)` tool also supports management actions.
|
|
8
|
+
|
|
9
|
+
### List available agents and legacy chain records
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
spawn_helper({ action: "list" })
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### List retained children
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
subagent({ action: "children.list" })
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Lists up to the last 10 retained workflow children from this parent session with explicit `resumable` or `not resumable` rows. Resume only rows reported `resumable`. Send a simple follow-up or implementation challenge with `subagent({ action: "resume", id: "<run-id>", message: "..." })`. Continue one inside a workflow with `runs.run(key, { resume: "<run-id>", task: "follow-up" })`; the revived child keeps its stored agent, model, and tool contract. If no resumable child is listed, start a same-role fallback challenge and label it as fallback. `steer` with `mode: "follow_up"` only queues text for the next `resume` when the child has already completed.
|
|
22
|
+
|
|
23
|
+
### Refinement overlays
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
subagent({ action: "refine", agent: "reviewer" })
|
|
27
|
+
subagent({ action: "refine.show", agent: "reviewer" })
|
|
28
|
+
subagent({ action: "refine.rollback", agent: "reviewer" })
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`refine` builds a bounded project-local guidance overlay for one agent from recent run evidence, using a fresh read-only proposal child; validated guidance is stored under `.pi/subagents/refinements/<agent>.md` with revision snapshots and is injected into that agent's child system prompt for this project. `refine.show` prints the current overlay and history; `refine.rollback` restores the previous revision. Guidance that tries to override safety, policy, tool, output, acceptance, developer, or system instructions is rejected. `/subagents-refine <agent>` is the slash equivalent.
|
|
32
|
+
|
|
33
|
+
### Create an agent
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
subagent({
|
|
37
|
+
action: "create",
|
|
38
|
+
config: {
|
|
39
|
+
name: "my-agent",
|
|
40
|
+
package: "code-analysis",
|
|
41
|
+
description: "Project-specific implementation helper",
|
|
42
|
+
systemPrompt: "Your system prompt here.",
|
|
43
|
+
systemPromptMode: "replace",
|
|
44
|
+
model: "openai-codex/gpt-5.4",
|
|
45
|
+
tools: "read,grep,find,ls,bash"
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Update an agent
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
subagent({
|
|
54
|
+
action: "update",
|
|
55
|
+
agent: "code-analysis.my-agent",
|
|
56
|
+
config: {
|
|
57
|
+
thinking: "high"
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Delete an agent
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
subagent({ action: "delete", agent: "code-analysis.my-agent" })
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Eject, disable, enable, and reset
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Copy a bundled builtin/package agent to user scope as an editable custom file.
|
|
72
|
+
subagent({ action: "eject", agent: "reviewer" })
|
|
73
|
+
subagent({ action: "eject", agent: "reviewer", agentScope: "project" })
|
|
74
|
+
|
|
75
|
+
// Hide an agent from runtime discovery without deleting it (reversible).
|
|
76
|
+
subagent({ action: "disable", agent: "reviewer" })
|
|
77
|
+
subagent({ action: "enable", agent: "reviewer", agentScope: "project" })
|
|
78
|
+
|
|
79
|
+
// Delete the scope's custom agent file and/or settings override, restoring the bundled default.
|
|
80
|
+
subagent({ action: "reset", agent: "reviewer" })
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`eject` copies a builtin or package agent verbatim into the user (default) or project agent dir so it can be customized without hunting package files; the copy shadows the original by runtime name. `disable` writes a reversible `agentOverrides.<name>.disabled: true` entry to the user or project settings file. `enable` removes that `disabled` field while keeping any other override fields. `reset` removes the scope's custom file and settings override to restore the bundled default, and refuses if no bundled default exists (use `delete` for purely custom agents). All four take optional `agentScope: "user" | "project"`; project overrides win over user ones, so target the project scope to undo a project-scope disable.
|
|
84
|
+
|
|
85
|
+
Use management actions when the system needs to create or edit subagents on
|
|
86
|
+
demand without dropping into raw file editing.
|
|
87
|
+
|
|
88
|
+
Management actions create or update user/project agent files. `config.name` is the local frontmatter name; optional `config.package` registers and looks up the runtime name as `{package}.{name}`. Use the dotted runtime name for `get`, `update`, `delete`, slash commands, and scripted workflow steps. For small builtin changes such as a model swap, prefer `subagents.agentOverrides` in settings. Durable `.chain.md` definitions are legacy records, not a current authoring target; use `workflowScript` or `/prompt-workflow` for repeatable orchestration.
|
|
89
|
+
|
|
90
|
+
## Creating and Editing Agents by File
|
|
91
|
+
|
|
92
|
+
A minimal agent file looks like this:
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
---
|
|
96
|
+
name: my-agent
|
|
97
|
+
package: code-analysis
|
|
98
|
+
description: What this agent does
|
|
99
|
+
aliases: developer, coder
|
|
100
|
+
model: openai-codex/gpt-5.4
|
|
101
|
+
thinking: high
|
|
102
|
+
tools: read, grep, find, ls, bash
|
|
103
|
+
systemPromptMode: replace
|
|
104
|
+
inheritProjectContext: true
|
|
105
|
+
inheritSkills: false
|
|
106
|
+
skills: safe-bash, review-checklist
|
|
107
|
+
skillPath: ./skills, ../shared-skills
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
Your system prompt here.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
That is only a starting point. Omit `package` for the traditional unqualified runtime name. Common optional fields include:
|
|
114
|
+
- `defaultProgress`
|
|
115
|
+
- `defaultReads`
|
|
116
|
+
- `output`
|
|
117
|
+
- `aliases`
|
|
118
|
+
- `fallbackModels`
|
|
119
|
+
- `subagentOnlyExtensions`
|
|
120
|
+
- `skills`
|
|
121
|
+
- `skillPath`
|
|
122
|
+
- `memory`
|
|
123
|
+
- `maxSubagentDepth`
|
|
124
|
+
- `acceptance`
|
|
125
|
+
- `acceptanceRole`
|
|
126
|
+
- `async` — single-agent default for background launch (`true`/`false`); explicit tool-call `async` wins
|
|
127
|
+
- `timeoutMs` — single-agent default run-level max runtime in ms; foreground calls use a 30-minute package default only when neither the call nor agent provides one (tool alias `maxRuntimeMs` is also accepted)
|
|
128
|
+
- `turnBudget` — single-agent default `{ maxTurns, graceTurns? }` JSON object
|
|
129
|
+
|
|
130
|
+
`aliases` is an optional comma-separated or block-list set of alternate names for selecting an agent. Aliases resolve to the canonical `name` for execution, status, persistence, and config. Exact canonical names take precedence over aliases, and alias collisions between distinct canonical agents fail as ambiguous. Management create/update accepts a comma-separated string, string array, or `false`/empty string to clear aliases.
|
|
131
|
+
|
|
132
|
+
`acceptance` is a single-agent launch default. Use a scalar level such as `checked` or an inline/block YAML map such as `{ level: "none", reason: "lightweight lookup" }`. An explicit tool-call value wins; scripted workflow child acceptance remains configured on the `runs.run` or `runs.all` item. Management create/update accepts the same policy object, and `acceptance: ""` clears the frontmatter default (`false` remains the deprecated disabled-policy shorthand).
|
|
133
|
+
|
|
134
|
+
`acceptanceRole` is `read-only` or `writer` and controls automatic acceptance inference only. Explicit task mutation or no-edit intent wins; otherwise the role replaces agent-name guessing. Omission preserves the current name heuristics. The field does not grant or revoke tools. Management accepts `false` or an empty string to clear it.
|
|
135
|
+
|
|
136
|
+
`tools` is a strict child allowlist, not an extension loader. For a named extension tool, keep its registered name in `tools` and load its provider through normal Pi discovery, `extensions`, a path-like `tools` entry, or `subagentOnlyExtensions`. For example, pair `tools: read, fixture_search` with `subagentOnlyExtensions: ./tools/fixture-search.ts` when the provider should exist only in that agent's child sessions. The child now fails with the unavailable names and provider-loading guidance instead of silently continuing when a requested tool is absent; internal `structured_output` is allowed automatically when an output schema requires it.
|
|
137
|
+
|
|
138
|
+
`skillPath` adds invocation-private skill files or discovery directories relative to the agent file; it does not select them, so list the desired names under `skills`. Local matches win, unresolved or unreadable matches use normal discovery, and local candidates never enter the parent/global catalog. Use `memory: { scope: "project" | "user", path: "<name>" }` for opt-in role-specific durable memory under the dedicated `agent-memory/` namespace; it is separate from parent/session project memory.
|
|
139
|
+
|
|
140
|
+
For many customizations, builtin overrides in settings are lower-friction than
|
|
141
|
+
copying a full builtin file.
|
|
142
|
+
|
|
143
|
+
## Prompt Template Integration
|
|
144
|
+
|
|
145
|
+
The package includes prompt shortcuts for common workflows: `/parallel-review`,
|
|
146
|
+
`/review-loop`, `/parallel-research`, `/gather-context-and-clarify`, and
|
|
147
|
+
`/parallel-cleanup`. Use them when the user wants repeatable review,
|
|
148
|
+
review/fix loops, research, context handoff, implementation handoff,
|
|
149
|
+
clarification, or cleanup-review patterns. `/parallel-review autofix` and
|
|
150
|
+
`/parallel-cleanup autofix` synthesize reviewer feedback and then apply only the
|
|
151
|
+
fixes worth doing now. Parent agents can also apply the same recipes directly
|
|
152
|
+
with `subagent(...)` when the user describes the workflow in natural language
|
|
153
|
+
instead of invoking a slash command.
|
|
154
|
+
|
|
155
|
+
Additional user prompt templates can delegate into `unipi subagents` through the native `/prompt-workflow` command. This is useful when a slash command should always run through a particular agent or with forked context. Prompt frontmatter can set `subagent`, `model`, `skill`, `cwd`, `fresh`, `fork`, or `inheritContext` for the native adapter.
|
|
156
|
+
|
|
157
|
+
## Extension RPC
|
|
158
|
+
|
|
159
|
+
Other Pi extensions can call `unipi subagents` through the in-process event bus. The RPC channels are `subagents:rpc:v1:ready`, `subagents:rpc:v1:request`, and per-request replies at `subagents:rpc:v1:reply:<requestId>`. Envelopes use `{ version: 1, requestId, method, params }`, and replies use `{ version: 1, requestId, success, data | error }`. `ping` advertises the exact process-local async completion event as `events.asyncComplete` for RPC-spawn consumers.
|
|
160
|
+
|
|
161
|
+
Methods: `ping`, `status`, `spawn`, `steer`, `interrupt`, `resume`, and `stop`. `ping` capability metadata advertises optional projections: `capabilities.fleetStatus: { version: 1 }` adds bounded current-session `data.fleet` records (opaque reconciliation `key`, resolved `agent`, optional `role`, `model`, `effort`, caller-facing `goal`, `startedAt`, split `{ input, output, total }` tokens, plus `totalActive`/`omitted` overflow counts) to successful `status` replies; `capabilities.launchResolvedExtensions` advertises parent-resolved opaque launch-extension identifiers in status details; `capabilities.runtimeAcknowledgedExtensions` advertises the best-effort child-runtime acknowledgement projection fed by cooperating extensions emitting `subagent:acknowledge-extension`. Foreground `details.results[]` rows carry a stable numeric `index`; correlate children by `(runId, index)` rather than row position. Consumers should read status/result artifacts and RPC projections instead of scraping terminal output and must ignore unknown fields. `spawn` requires `workflowScript`, is async-only, and rejects management actions, `async: false`, or `clarify: true`; it reuses the normal executor, so discovery, validation, session attribution, configured spawn caps, child-safety depth, artifacts, and async status are shared with the `spawn_helper` tool. `status`, acknowledged async `steer`, and `interrupt` map to the normal control actions. RPC steer disables pause-and-revive recovery and advertises `capabilities.nonRecoveringSteer`, preserving the caller's authority over the exact spawned child. `resume` requires a target plus non-empty message and delegates to the package-owned revival path; it may set a caller-owned `file-only` output path but cannot override the persisted child model, tools, budgets, session ownership, or exclusive session lease. For retained-child workflows, list children first and resume only rows reported `resumable`; otherwise start a same-role fallback challenge and label it as fallback. `stop` targets running async runs through the existing timeout control channel. `pi.events` is process-local, so separate Pi processes and child subagents need lifecycle artifact files or `pi-intercom` instead.
|