@wolido/async-subagent-isolation 1.0.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/ADVANCED.en.md +305 -0
- package/ADVANCED.md +305 -0
- package/LICENSE +21 -0
- package/README.en.md +294 -0
- package/README.md +294 -0
- package/examples/README.en.md +100 -0
- package/examples/README.md +100 -0
- package/examples/pi/agent/agents/coder.md +36 -0
- package/examples/pi/agent/agents/reviewer.md +39 -0
- package/examples/pi/agent/agents/writer.md +36 -0
- package/examples/pi/agent/master.md +63 -0
- package/examples/pi/agent/skills/brainstorming/SKILL.md +54 -0
- package/examples/pi/agent/skills/systematic-debugging/SKILL.md +319 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/SKILL.md +88 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/02-elementary-rules-of-usage.md +214 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/03-elementary-principles-of-composition.md +394 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/04-a-few-matters-of-form.md +90 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/05-words-and-expressions-commonly-misused.md +346 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/common-issues.md +22 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/full-example.md +19 -0
- package/examples/pi/agent/skills/writing-clearly-and-concisely/references/signs-of-ai-writing.md +345 -0
- package/logo.svg +33 -0
- package/package.json +72 -0
- package/src/index.ts +2300 -0
package/ADVANCED.en.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<div align="right"><a href="ADVANCED.md">中文</a></div>
|
|
2
|
+
|
|
3
|
+
# async-subagent-isolation advanced reference
|
|
4
|
+
|
|
5
|
+
This document covers low-level invocation, configuration fields, and environment variables for `async-subagent-isolation`. Most users can follow the natural-language Quick Start in the main README; refer to this file only when you need to construct `subagent` calls manually, reuse an isolated session, or tune runtime parameters.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Agent definition format
|
|
10
|
+
|
|
11
|
+
Agents are Markdown files (`.md`) in an agents directory. Frontmatter describes metadata; the body becomes the system prompt.
|
|
12
|
+
|
|
13
|
+
```markdown
|
|
14
|
+
---
|
|
15
|
+
name: coder
|
|
16
|
+
description: Writes clean TypeScript and handles refactors.
|
|
17
|
+
tools: read, edit, write, bash
|
|
18
|
+
model: claude-3-7-sonnet
|
|
19
|
+
skills: /path/to/skill1,/path/to/skill2
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
You are a senior TypeScript engineer. Prefer async/await and avoid callbacks.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Frontmatter fields
|
|
26
|
+
|
|
27
|
+
| Field | Type | Description |
|
|
28
|
+
|-------|------|-------------|
|
|
29
|
+
| `name` | `string` | **Required.** Unique identifier used in tool calls. |
|
|
30
|
+
| `description` | `string` | **Required.** Short summary shown in discovery / error messages. |
|
|
31
|
+
| `tools` | `string[]` (comma-separated) | Optional tool whitelist for the subagent. |
|
|
32
|
+
| `model` | `string` | Optional model override, e.g. `claude-3-7-sonnet`. |
|
|
33
|
+
| `thinking` | `string` | Optional thinking level. One of `off \| minimal \| low \| medium \| high \| xhigh \| max`. |
|
|
34
|
+
| `skills` | `string[]` (comma-separated) | Optional skill path list. If present, global skills are disabled and only these are loaded. Paths can be absolute or relative to the working directory. |
|
|
35
|
+
|
|
36
|
+
## Per-subagent model & thinking level config (subagent-isolation.json)
|
|
37
|
+
|
|
38
|
+
Use `subagent-isolation.json` to assign a model and thinking level to each subagent. The file name is retained from the sync original, so both projects can share one config.
|
|
39
|
+
|
|
40
|
+
### Config file locations
|
|
41
|
+
|
|
42
|
+
| Scope | Path |
|
|
43
|
+
|-------|------|
|
|
44
|
+
| User-level | `~/.pi/agent/subagent-isolation.json` |
|
|
45
|
+
| Project-level | `.pi/subagent-isolation.json` (the nearest `.pi/` directory found by walking up from the working directory) |
|
|
46
|
+
|
|
47
|
+
The project-level file overrides the user-level file **per key**; keys not overridden keep their user-level value.
|
|
48
|
+
|
|
49
|
+
### Format
|
|
50
|
+
|
|
51
|
+
Each key is an agent name; the value can be either:
|
|
52
|
+
|
|
53
|
+
- **Plain string (legacy format)**: model only, equivalent to `{ "model": "..." }`.
|
|
54
|
+
- **Object**: `{ "model": ..., "thinking": ... }` — both fields optional, but at least one must be present.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"coder": { "model": "deepseek/deepseek-v4-pro", "thinking": "high" },
|
|
59
|
+
"writer": "deepseek/deepseek-v4-flash"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`model` must be a non-empty string; `thinking` must be a valid level from the table below (case-sensitive). Invalid values are ignored.
|
|
64
|
+
|
|
65
|
+
### Valid thinking levels
|
|
66
|
+
|
|
67
|
+
| Value | Meaning |
|
|
68
|
+
|-------|---------|
|
|
69
|
+
| `off` | Thinking off |
|
|
70
|
+
| `minimal` | Minimal thinking |
|
|
71
|
+
| `low` | Low thinking |
|
|
72
|
+
| `medium` | Medium thinking |
|
|
73
|
+
| `high` | High thinking |
|
|
74
|
+
| `xhigh` | Extra high thinking |
|
|
75
|
+
| `max` | Maximum thinking |
|
|
76
|
+
|
|
77
|
+
### Priority rules
|
|
78
|
+
|
|
79
|
+
For a subagent such as `coder`, the model and thinking level each resolve to the first non-empty value:
|
|
80
|
+
|
|
81
|
+
**Model**:
|
|
82
|
+
|
|
83
|
+
1. Config file (`model` for this agent in `subagent-isolation.json`)
|
|
84
|
+
2. Agent frontmatter (`model:` in `coder.md`)
|
|
85
|
+
3. Inherit the main agent's current model
|
|
86
|
+
|
|
87
|
+
**Thinking level**:
|
|
88
|
+
|
|
89
|
+
1. Config file (`thinking` for this agent in `subagent-isolation.json`)
|
|
90
|
+
2. Agent frontmatter (`thinking:` in `coder.md`)
|
|
91
|
+
|
|
92
|
+
The thinking level is not inherited from the main agent.
|
|
93
|
+
|
|
94
|
+
### Merge rules
|
|
95
|
+
|
|
96
|
+
Project-level and user-level configs merge **per key**: a project-level key overrides the same key in the user-level file; all other keys are kept. In other words, the nearest `.pi/subagent-isolation.json` overrides matching keys in `~/.pi/agent/subagent-isolation.json`.
|
|
97
|
+
|
|
98
|
+
> **Note**: when the selected model's provider does not support reasoning, pi automatically clamps the thinking level to `off`.
|
|
99
|
+
|
|
100
|
+
## Async mode (TUI)
|
|
101
|
+
|
|
102
|
+
In TUI mode, the `subagent` tool is **asynchronous**: it returns a dispatch receipt immediately, the subagent runs in the background, and its result arrives later as a `[subagent-result]` system notification. Non-TUI modes (print/json, including `mode` `undefined`) fall back to synchronous — they wait for the subagent to finish and return the full result directly, with no notification.
|
|
103
|
+
|
|
104
|
+
### Dispatch receipt
|
|
105
|
+
|
|
106
|
+
In TUI mode, `subagent` returns this receipt immediately (it is NOT the result!):
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
已派出 coder. taskId: 01912345-6789-7abc-8def-0123456789ab
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Key points:
|
|
113
|
+
|
|
114
|
+
- **The receipt is a single line.** The async-semantics guidance (don't fabricate results, don't poll, results arrive as a `[subagent-result]` notification) is embedded in the `subagent` tool's `description` / `promptGuidelines`; the receipt itself stays a single line.
|
|
115
|
+
- **The receipt is not the result.** Do not fabricate results.
|
|
116
|
+
- **taskId = sessionId.** The `taskId` in the receipt is the session ID; reuse it directly.
|
|
117
|
+
- **Do not poll.** Results arrive automatically as `[subagent-result]` notifications; to confirm which tasks are still in flight (e.g. after a `/tree` rewind), use the `subagent_status` tool.
|
|
118
|
+
|
|
119
|
+
### [subagent-result] envelope format
|
|
120
|
+
|
|
121
|
+
Once the subagent finishes, its result is pushed into the conversation:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
## [subagent-result] coder 成功 (taskId: 01912345-6789-7abc-8def-0123456789ab)
|
|
125
|
+
|
|
126
|
+
- 状态: 成功
|
|
127
|
+
- 任务: 将认证中间件重构为使用 async/await。
|
|
128
|
+
- 耗时: 02:34 · 用量: 5 turns/↑12.5k/↓3.2k/$0.0042
|
|
129
|
+
- 会话: 01912345-6789-7abc-8def-0123456789ab
|
|
130
|
+
|
|
131
|
+
在途任务: 1
|
|
132
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): 更新 README。
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
<full subagent output>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Status enumeration: **成功** (success, exit=0) / **失败** (failure, exit≠0 or stopReason=error) / **超时** (timeout, activity_timeout or hard_timeout) / **已取消** (cancelled, aborted or killed_on_shutdown).
|
|
139
|
+
|
|
140
|
+
"Cancelled" has three sub-cases with different envelope bodies:
|
|
141
|
+
- User cancelled via `/subagent-cancel` (cancelledBy: user) → body states this is a deliberate user action; the main agent must NOT auto-retry and must ask the user before re-dispatching.
|
|
142
|
+
- Main agent cancelled via `subagent_cancel` tool (cancelledBy: agent) → body states the task was cancelled by the main agent via the subagent_cancel tool.
|
|
143
|
+
- Session shutdown killed the task (cancelledBy: none) → body states the task was terminated by session_shutdown.
|
|
144
|
+
|
|
145
|
+
When the main agent receives a "已取消" notification, it should distinguish the origin: a user cancel must never be auto-retried (ask the user first); an agent cancel is its own decision — do not re-dispatch without new information; a session-shutdown cancel can be re-dispatched after the session resumes, at the agent's discretion.
|
|
146
|
+
|
|
147
|
+
**In-flight block**: the "在途任务" list in the envelope's metadata section lists the **other** background tasks still running (this task is removed from the registry before the envelope is built, so it never appears in its own list). Its format matches the `subagent_status` tool — `在途任务: N` followed by one `- taskId (agent): task description` line per task, or `当前无在途任务。` when none remain. It deliberately carries **no elapsed time** (it answers "what is still running", not "how long has it run"). The main agent uses it to know how many tasks are still outstanding — while the count is non-zero, do not report "all done" to the user.
|
|
148
|
+
|
|
149
|
+
The full output enters the LLM context (not truncated). The `details` carries structured data (taskId, agent, status, exitCode, stopReason, usage, sessionId, full output) for programmatic consumption; it does not enter the LLM context.
|
|
150
|
+
|
|
151
|
+
### Notification delivery
|
|
152
|
+
|
|
153
|
+
Notifications are sent via `pi.sendMessage` with `deliverAs: "followUp"` + `triggerTurn: true`:
|
|
154
|
+
- When the main agent is idle, it triggers a new conversation turn immediately.
|
|
155
|
+
- When the main agent is busy, the notification is queued and triggers a turn after the current one finishes.
|
|
156
|
+
|
|
157
|
+
The main agent is trained (via `promptGuidelines`) to recognize the `[subagent-result]` prefix as a system notification, not a user request.
|
|
158
|
+
|
|
159
|
+
### Progress widget
|
|
160
|
+
|
|
161
|
+
While subagents run, a progress widget appears above the TUI editor, listing all in-flight tasks. Each row shows the taskId, agent name, current phase, and elapsed time:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
● 01912345-abcd... coder ⚡ read... 01:23
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The taskId in the widget row can be copied for `/subagent-result` (view full result) or `/subagent-cancel` (cancel the task).
|
|
168
|
+
|
|
169
|
+
### subagent_status (in-flight query)
|
|
170
|
+
|
|
171
|
+
The main agent can actively query still-running background tasks via the `subagent_status` tool (no parameters), which returns:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
在途任务: 2
|
|
175
|
+
- 01912345-6789-7abc-8def-0123456789ab (coder): 将认证中间件重构为使用 async/await。
|
|
176
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): 更新 README。
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
With no in-flight tasks it returns `当前无在途任务。`. Each line carries the taskId, agent name, and task description, with **no elapsed time**.
|
|
180
|
+
|
|
181
|
+
Use cases:
|
|
182
|
+
|
|
183
|
+
- After a `/tree` rewind loses the receipts, confirm which tasks are still in flight.
|
|
184
|
+
- When unsure how many tasks remain, or to pick a taskId for `subagent_cancel`.
|
|
185
|
+
|
|
186
|
+
**Do not use it to poll for completion**: results arrive automatically as `[subagent-result]` notifications. This tool only confirms "what is still running" — it should not be called frequently.
|
|
187
|
+
|
|
188
|
+
### Cancelling background tasks
|
|
189
|
+
|
|
190
|
+
Cancelling a running background subagent task has two paths, both sharing the same underlying cancel flow (SIGTERM → 5s → SIGKILL cascade, followed by a `[subagent-result]` notification).
|
|
191
|
+
|
|
192
|
+
**Path 1: User commands**
|
|
193
|
+
|
|
194
|
+
From the TUI, the user enters `/subagent-cancel <taskId>` to cancel a single running task:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
/subagent-cancel <taskId>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Without arguments, lists currently running tasks. The cancel source is recorded as `cancelledBy: "user"`.
|
|
201
|
+
|
|
202
|
+
To cancel all running tasks at once:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
/subagent-cancel-all
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Takes no arguments. Unlike `/subagent-cancel`, which cancels a single task by taskId, this cancels every running task. Each cancelled task still emits its own "已取消" `[subagent-result]` notification (the main agent receives N cancelled envelopes). On success it notifies "已取消全部 N 个运行中任务"; with no running tasks it notifies "无运行中任务可取消". The cancel source is likewise recorded as `cancelledBy: "user"`.
|
|
209
|
+
|
|
210
|
+
**Path 2: Main agent `subagent_cancel` tool**
|
|
211
|
+
|
|
212
|
+
The main agent can call the `subagent_cancel` tool (parameter `taskId`) to cancel a dispatched background task. The cancel source is recorded as `cancelledBy: "agent"`. On success, the tool returns the remaining in-flight task list (same format as `subagent_status`); the cancelled task's final result arrives later as a `[subagent-result]` notification.
|
|
213
|
+
|
|
214
|
+
**Usage discipline:** The main agent should only use `subagent_cancel` when:
|
|
215
|
+
- The task is clearly wrong (wrong agent, incorrect task description, etc.).
|
|
216
|
+
- The task is no longer needed (requirement change, later discovery that this step is unnecessary).
|
|
217
|
+
|
|
218
|
+
**Do NOT** cancel merely because the task is taking a long time — background subagents are expected to run long. The criterion for cancellation is "this task should not continue", not "it's been a while".
|
|
219
|
+
|
|
220
|
+
### /subagent-result
|
|
221
|
+
|
|
222
|
+
View the full final result of a background task. User-only, from the TUI:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
/subagent-result <taskId>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- Without arguments, prints usage.
|
|
229
|
+
- Task still running → "任务仍在运行,完成后才能查看".
|
|
230
|
+
- No record found → "无此任务记录: `<taskId>`".
|
|
231
|
+
- Task exists but produced no final output (likely killed) → "任务无最终输出(未产生 assistant 文本,可能已被终止)" with the session file path.
|
|
232
|
+
- When output exists, displays the full Markdown result in a full-screen viewer; press Enter or Esc to close.
|
|
233
|
+
|
|
234
|
+
### session_shutdown
|
|
235
|
+
|
|
236
|
+
On quit, session switch, or reload, all in-flight subprocesses are killed and marked `killed_on_shutdown`. The corresponding `[subagent-result]` notification body says the task was terminated by session_shutdown — distinct from a user cancel. Note: on extension reload or process crash, in-flight tasks are not persisted or re-delivered. If the extension is dead when a task completes, the notification is lost (session log can still be inspected).
|
|
237
|
+
|
|
238
|
+
### TUI vs non-TUI summary
|
|
239
|
+
|
|
240
|
+
| Behavior | TUI | Non-TUI (print/json) |
|
|
241
|
+
|----------|-----|----------------------|
|
|
242
|
+
| execute returns | Dispatch receipt immediately | Full result after subagent finishes |
|
|
243
|
+
| Result delivery | `[subagent-result]` system notification | Inline in the return value |
|
|
244
|
+
| /subagent-cancel | Available | Not available (no TUI command system) |
|
|
245
|
+
| /subagent-cancel-all | Available | Not available (no TUI command system) |
|
|
246
|
+
| Parallel dispatch | Supported (independent tasks can be dispatched together) | Not supported (each call blocks) |
|
|
247
|
+
|
|
248
|
+
## Manual invocation
|
|
249
|
+
|
|
250
|
+
To make a manual call, use JSON like this:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"agent": "coder",
|
|
255
|
+
"task": "Refactor the auth middleware to use async/await."
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> **Note**: The `task` field must be non-empty, and it is recommended to follow the standard task format in `master.md`: **background, input, requirements, output format, acceptance criteria**. A `task` that is empty or contains only whitespace will be rejected.
|
|
260
|
+
|
|
261
|
+
## Reusing a sessionId
|
|
262
|
+
|
|
263
|
+
### Non-TUI mode
|
|
264
|
+
|
|
265
|
+
When the subagent finishes, its output ends with a session ID:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
<subagent output>
|
|
269
|
+
|
|
270
|
+
[subagent session: 01912345-6789-7abc-8def-0123456789ab]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
To continue the same isolated session, pass the `sessionId`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"agent": "coder",
|
|
278
|
+
"task": "Add unit tests for the refactored auth middleware.",
|
|
279
|
+
"sessionId": "01912345-6789-7abc-8def-0123456789ab"
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> ⚠️ **Concurrency note**: reusing the same `sessionId` from multiple concurrent `subagent` calls can corrupt the session file. Use it sequentially, or make sure the subagent process has fully exited before reuse.
|
|
284
|
+
|
|
285
|
+
### TUI mode
|
|
286
|
+
|
|
287
|
+
The dispatch receipt contains the `taskId` (which is the session ID). The `[subagent-result]` envelope also carries the sessionId on the `- 会话:` line — just reuse it. No need to wait for the subagent to finish; you already have the session ID from the receipt.
|
|
288
|
+
|
|
289
|
+
## Environment variables
|
|
290
|
+
|
|
291
|
+
These variables are propagated into every subagent process automatically:
|
|
292
|
+
|
|
293
|
+
| Variable | Default | Description |
|
|
294
|
+
|----------|---------|-------------|
|
|
295
|
+
| `PI_SUBAGENT_DEPTH` | `0` | Current recursion depth. Auto-incremented per nested call. **Depth limit is 1** — a subagent (depth ≥ 1) cannot dispatch `subagent`; recursive delegation is blocked entirely. |
|
|
296
|
+
| `PI_CURRENT_AGENT_NAME` | — | Name of the current agent, injected into every subagent process. |
|
|
297
|
+
| `PI_SUBAGENT_ACTIVITY_TIMEOUT_MS` | `600000` (10 min) | Max idle time with no output on either stdout or stderr before the subagent is killed. |
|
|
298
|
+
| `PI_SUBAGENT_HARD_TIMEOUT_MS` | `0` (disabled) | Absolute maximum runtime for a single call. Set a positive value (ms) to enable. |
|
|
299
|
+
|
|
300
|
+
## Timeouts and termination
|
|
301
|
+
|
|
302
|
+
- Activity timeout: 10 minutes — subagent is killed if neither stdout nor stderr produces output (no activity). The timer starts as soon as the child process spawns and resets whenever either stream receives data.
|
|
303
|
+
- Hard timeout: disabled by default (`PI_SUBAGENT_HARD_TIMEOUT_MS=0`), no absolute maximum runtime. Set a positive value in milliseconds to enable.
|
|
304
|
+
- When a timeout kills the subagent, the result's `stopReason` is `"activity_timeout"` (activity timeout) or `"hard_timeout"` (hard timeout). It appears in the diagnostic output (`Stop reason: ...`) and as a UI badge, distinguishing "killed by timeout" from "subagent failed on its own".
|
|
305
|
+
- On `AbortSignal`, `SIGTERM` is sent; `SIGKILL` follows after 5 seconds if still running. In async mode, users can trigger cancellation with `/subagent-cancel <taskId>` or `/subagent-cancel-all`.
|
package/ADVANCED.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<div align="right"><a href="ADVANCED.en.md">English</a></div>
|
|
2
|
+
|
|
3
|
+
# async-subagent-isolation 进阶参考
|
|
4
|
+
|
|
5
|
+
这里收录 `async-subagent-isolation` 的底层调用方式、配置字段和环境变量。普通用户按照主 README 的 Quick Start 用自然语言即可;只有当你需要手动构造 `subagent` 调用、复用隔离会话或调整运行参数时才需要查看本文档。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Agent 定义格式
|
|
10
|
+
|
|
11
|
+
Agent 是 agents 目录中的 Markdown 文件(`.md`)。frontmatter 描述元数据,正文成为系统提示。
|
|
12
|
+
|
|
13
|
+
```markdown
|
|
14
|
+
---
|
|
15
|
+
name: coder
|
|
16
|
+
description: 编写整洁的 TypeScript 并处理重构。
|
|
17
|
+
tools: read, edit, write, bash
|
|
18
|
+
model: claude-3-7-sonnet
|
|
19
|
+
skills: /path/to/skill1,/path/to/skill2
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
你是一名资深 TypeScript 工程师。优先使用 async/await,避免回调。
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Frontmatter 字段
|
|
26
|
+
|
|
27
|
+
| 字段 | 类型 | 说明 |
|
|
28
|
+
|------|------|------|
|
|
29
|
+
| `name` | `string` | **必填。** 工具调用时使用的唯一标识符。 |
|
|
30
|
+
| `description` | `string` | **必填。** 在 agent 列表和错误信息中显示的简短描述。 |
|
|
31
|
+
| `tools` | `string[]`(逗号分隔) | 可选的子 agent 工具白名单。 |
|
|
32
|
+
| `model` | `string` | 可选的模型覆盖,例如 `claude-3-7-sonnet`。 |
|
|
33
|
+
| `thinking` | `string` | 可选,思考等级。值为 `off \| minimal \| low \| medium \| high \| xhigh \| max`。 |
|
|
34
|
+
| `skills` | `string[]`(逗号分隔) | 可选的 skill 路径列表。若存在,则禁用全局 skills,仅加载列出的 skill。路径可绝对或相对于工作目录。 |
|
|
35
|
+
|
|
36
|
+
## 子 agent 模型与思考等级配置(subagent-isolation.json)
|
|
37
|
+
|
|
38
|
+
可用 `subagent-isolation.json` 为每个子 agent 单独指定模型与思考等级(thinking level)。配置文件名沿用同步版,两个项目可共享同一份配置。
|
|
39
|
+
|
|
40
|
+
### 配置文件位置
|
|
41
|
+
|
|
42
|
+
| 层级 | 路径 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| 用户级 | `~/.pi/agent/subagent-isolation.json` |
|
|
45
|
+
| 项目级 | `.pi/subagent-isolation.json`(从当前工作目录向上查找最近的 `.pi/` 目录) |
|
|
46
|
+
|
|
47
|
+
项目级覆盖用户级**同名 key**;未覆盖的 key 继续沿用用户级配置。
|
|
48
|
+
|
|
49
|
+
### 配置格式
|
|
50
|
+
|
|
51
|
+
每个 key 是 agent 名,value 支持两种写法:
|
|
52
|
+
|
|
53
|
+
- **纯字符串(旧格式)**:只指定模型,等价于 `{ "model": "..." }`。
|
|
54
|
+
- **对象**:`{ "model": ..., "thinking": ... }`,两个字段均可选,但须至少提供一个。
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"coder": { "model": "deepseek/deepseek-v4-pro", "thinking": "high" },
|
|
59
|
+
"writer": "deepseek/deepseek-v4-flash"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`model` 须为非空字符串;`thinking` 须为下表中的合法等级(大小写敏感),非法值会被忽略。
|
|
64
|
+
|
|
65
|
+
### thinking 可选值
|
|
66
|
+
|
|
67
|
+
| 值 | 含义 |
|
|
68
|
+
|----|------|
|
|
69
|
+
| `off` | 关闭思考 |
|
|
70
|
+
| `minimal` | 最低思考量 |
|
|
71
|
+
| `low` | 低 |
|
|
72
|
+
| `medium` | 中 |
|
|
73
|
+
| `high` | 高 |
|
|
74
|
+
| `xhigh` | 很高 |
|
|
75
|
+
| `max` | 最高 |
|
|
76
|
+
|
|
77
|
+
### 优先级规则
|
|
78
|
+
|
|
79
|
+
以 agent `coder` 为例,模型与思考等级分别取第一个非空值:
|
|
80
|
+
|
|
81
|
+
**模型**:
|
|
82
|
+
|
|
83
|
+
1. 配置文件(`subagent-isolation.json` 中该 agent 的 `model`)
|
|
84
|
+
2. Agent frontmatter(`coder.md` 的 `model:` 字段)
|
|
85
|
+
3. 继承主 agent 当前使用的模型
|
|
86
|
+
|
|
87
|
+
**思考等级**:
|
|
88
|
+
|
|
89
|
+
1. 配置文件(`subagent-isolation.json` 中该 agent 的 `thinking`)
|
|
90
|
+
2. Agent frontmatter(`coder.md` 的 `thinking:` 字段)
|
|
91
|
+
|
|
92
|
+
思考等级不继承主 agent。
|
|
93
|
+
|
|
94
|
+
### 合并规则
|
|
95
|
+
|
|
96
|
+
项目级配置与用户级配置按 **key 合并**:项目级 key 覆盖用户级同名 key,其余 key 保留。即最近的 `.pi/subagent-isolation.json` 覆盖 `~/.pi/agent/subagent-isolation.json` 中的同名项。
|
|
97
|
+
|
|
98
|
+
> **注意**:当指定模型的 provider 不支持 reasoning 时,pi 会自动把 thinking 钳制为 `off`。
|
|
99
|
+
|
|
100
|
+
## 异步模式(TUI)
|
|
101
|
+
|
|
102
|
+
在 TUI 交互模式下,`subagent` 工具是**异步**的:调用后立即返回派发回执,子 agent 在后台运行,完成后结果以 `[subagent-result]` 系统通知推送到对话中。非 TUI 模式(print/json,包括 `mode` 为 `undefined`)则降级为同步——等待子 agent 完成后直接返回完整结果,无通知。
|
|
103
|
+
|
|
104
|
+
### 派发回执
|
|
105
|
+
|
|
106
|
+
TUI 模式下 `subagent` 立即返回如下回执(不是结果!):
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
已派出 coder. taskId: 01912345-6789-7abc-8def-0123456789ab
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
关键点:
|
|
113
|
+
|
|
114
|
+
- **回执为单行。** 异步语义引导(不臆造结果、不轮询、结果以 `[subagent-result]` 通知到达)已内嵌于 `subagent` 工具的 `description` / `promptGuidelines`,回执本身保持单行。
|
|
115
|
+
- **回执 ≠ 结果。** 不要臆造结果。
|
|
116
|
+
- **taskId = sessionId。** 回执中的 `taskId` 就是 session ID,可直接复用。
|
|
117
|
+
- **不要轮询。** 结果自动以 `[subagent-result]` 通知到达;如需确认还有哪些任务在途(如 `/tree` 回退后),用 `subagent_status` 工具查询。
|
|
118
|
+
|
|
119
|
+
### [subagent-result] 信封格式
|
|
120
|
+
|
|
121
|
+
子 agent 完成后,结果以如下格式推送到对话:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
## [subagent-result] coder 成功 (taskId: 01912345-6789-7abc-8def-0123456789ab)
|
|
125
|
+
|
|
126
|
+
- 状态: 成功
|
|
127
|
+
- 任务: 将认证中间件重构为使用 async/await。
|
|
128
|
+
- 耗时: 02:34 · 用量: 5 turns/↑12.5k/↓3.2k/$0.0042
|
|
129
|
+
- 会话: 01912345-6789-7abc-8def-0123456789ab
|
|
130
|
+
|
|
131
|
+
在途任务: 1
|
|
132
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): 更新 README。
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
<子 agent 完整结果文本>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
状态枚举:**成功**(exit=0)/ **失败**(exit≠0 或 stopReason=error)/ **超时**(activity_timeout 或 hard_timeout)/ **已取消**(aborted 或 killed_on_shutdown)。
|
|
139
|
+
|
|
140
|
+
"已取消"分三种情况,信封正文不同:
|
|
141
|
+
- 用户通过 `/subagent-cancel` 取消(cancelledBy: user)→ 正文注明"属用户主动操作。请勿自动重新派发;如需重新派发,先询问用户。"
|
|
142
|
+
- 主 agent 通过 `subagent_cancel` 工具取消(cancelledBy: agent)→ 正文注明"该任务已由主 agent 通过 subagent_cancel 工具取消。"
|
|
143
|
+
- 会话关闭(session_shutdown)终止(cancelledBy: 无)→ 正文注明"任务因会话关闭被终止(session_shutdown)。"
|
|
144
|
+
|
|
145
|
+
主 agent 收到状态为"已取消"的通知时,应区分来源:用户主动取消**不得自动重试**,必须先询问用户;agent 取消是自身决策,不应在无新信息时重新派发;会话关闭终止可在会话恢复后视情况重新派发。
|
|
146
|
+
|
|
147
|
+
**在途任务块**:信封元信息区的"在途任务"列表列出**其余**仍在运行的后台任务(本任务在构建信封前已从注册表移除,故不包含自身),格式与 `subagent_status` 工具一致——`在途任务: N` 加每行 `- taskId (agent名): 任务描述`,无在途任务时为"当前无在途任务。"。列表**不含耗时**(回答"还有什么在跑",而非"跑了多久")。主 agent 据此知道还有几个任务没回来:剩余不为 0 时,不要向用户汇报"全部完成"。
|
|
148
|
+
|
|
149
|
+
结果全量进入 LLM 上下文(不截断)。`details` 携带结构化数据(taskId、agent、status、exitCode、stopReason、usage、sessionId、完整输出),不参与 LLM 上下文,供程序消费。
|
|
150
|
+
|
|
151
|
+
### 通知投递
|
|
152
|
+
|
|
153
|
+
通知通过 `pi.sendMessage` 发送,`deliverAs: "followUp"` + `triggerTurn: true`:
|
|
154
|
+
- 主 agent 空闲时直接触发新的对话回合。
|
|
155
|
+
- 主 agent 忙碌时进入消息队列,待当前回合结束后触发。
|
|
156
|
+
|
|
157
|
+
主 agent 通过 promptGuidelines 被训练识别 `[subagent-result]` 前缀为系统通知(非用户请求)。
|
|
158
|
+
|
|
159
|
+
### 进度 widget
|
|
160
|
+
|
|
161
|
+
子 agent 运行时,TUI 编辑器上方会显示进度 widget,列出所有在飞任务。每行包含 taskId、agent 名称、当前阶段和耗时,例如:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
● 01912345-abcd... coder ⚡ read... 01:23
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
widget 行中的 taskId 可直接复制,用于 `/subagent-result` 查看结果或 `/subagent-cancel` 取消任务。
|
|
168
|
+
|
|
169
|
+
### subagent_status(在途任务查询)
|
|
170
|
+
|
|
171
|
+
主 agent 可主动查询仍在运行的后台任务,调用 `subagent_status` 工具(无参数),返回:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
在途任务: 2
|
|
175
|
+
- 01912345-6789-7abc-8def-0123456789ab (coder): 将认证中间件重构为使用 async/await。
|
|
176
|
+
- 01912345-aaaa-7bbb-8ccc-0123456789ab (writer): 更新 README。
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
无在途任务时返回 `当前无在途任务。`。每行含 taskId、agent 名和任务描述,**不含耗时**。
|
|
180
|
+
|
|
181
|
+
用途场景:
|
|
182
|
+
|
|
183
|
+
- `/tree` 回退后回执丢失,确认还有哪些任务在途。
|
|
184
|
+
- 不确定剩余任务时快速核对,或选取 taskId 用于 `subagent_cancel`。
|
|
185
|
+
|
|
186
|
+
**不要用它轮询完成状态**:结果会自动以 `[subagent-result]` 通知到达,本工具只用于确认"还有什么在跑",不应频繁调用。
|
|
187
|
+
|
|
188
|
+
### 取消后台任务
|
|
189
|
+
|
|
190
|
+
取消运行中的后台子 agent 任务有两条路径,底层共享同一套取消流程(SIGTERM → 5s → SIGKILL 级联,最终推送 `[subagent-result]` 通知)。
|
|
191
|
+
|
|
192
|
+
**路径一:用户命令**
|
|
193
|
+
|
|
194
|
+
用户在 TUI 中输入 `/subagent-cancel <taskId>` 取消单个运行中的任务:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
/subagent-cancel <taskId>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
不带参数时列出当前运行中的任务。取消来源标记为 `cancelledBy: "user"`。
|
|
201
|
+
|
|
202
|
+
一键取消所有运行中的任务:
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
/subagent-cancel-all
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
无参数。与 `/subagent-cancel` 按 taskId 取消单个任务不同,`/subagent-cancel-all` 取消全部运行中的任务。每个被取消任务照常推送各自的"已取消" `[subagent-result]` 通知(主 agent 会收到 N 个已取消信封)。成功时提示"已取消全部 N 个运行中任务",无运行中任务时提示"无运行中任务可取消"。取消来源同样标记为 `cancelledBy: "user"`。
|
|
209
|
+
|
|
210
|
+
**路径二:主 agent `subagent_cancel` 工具**
|
|
211
|
+
|
|
212
|
+
主 agent 可调用 `subagent_cancel` 工具(参数 `taskId`)取消已派出的后台任务。取消来源标记为 `cancelledBy: "agent"`。取消成功后返回剩余在途任务列表(格式与 `subagent_status` 一致),被取消任务的最终结果稍后以 `[subagent-result]` 通知返回。
|
|
213
|
+
|
|
214
|
+
**使用纪律:** 主 agent 仅在以下情况使用 `subagent_cancel`:
|
|
215
|
+
- 任务明显错误(委派了错误的 agent、任务描述有误等)。
|
|
216
|
+
- 任务不再需要(用户需求变更、后续发现无需此步骤)。
|
|
217
|
+
|
|
218
|
+
**禁止**因等待时间长而取消——后台子 agent 本就预期长时间运行。取消的依据是"这个任务不该继续",不是"等太久了"。
|
|
219
|
+
|
|
220
|
+
### /subagent-result
|
|
221
|
+
|
|
222
|
+
查看某后台任务的完整返回。仅限用户在 TUI 中使用:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
/subagent-result <taskId>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- 不带参数时提示用法。
|
|
229
|
+
- 任务仍在运行 → 提示"任务仍在运行,完成后才能查看"。
|
|
230
|
+
- 无此任务记录 → 提示"无此任务记录: `<taskId>`"。
|
|
231
|
+
- 任务存在但无最终输出(可能已被终止) → 提示"任务无最终输出(未产生 assistant 文本,可能已被终止)"并附会话文件路径。
|
|
232
|
+
- 有输出时在全屏查看器中展示完整 Markdown 结果,按 Enter 或 Esc 关闭。
|
|
233
|
+
|
|
234
|
+
### session_shutdown
|
|
235
|
+
|
|
236
|
+
退出、切会话或 reload 时,自动 kill 所有在飞子进程并标记 `killed_on_shutdown`。对应的 `[subagent-result]` 通知正文为"任务因会话关闭被终止(session_shutdown)。"与用户主动取消的正文不同。注意:扩展 reload 或进程崩溃时,在飞任务不落盘、不补投;任务完成后若扩展已死,通知丢失(可查 session 记录)。
|
|
237
|
+
|
|
238
|
+
### TUI / 非 TUI 差异总结
|
|
239
|
+
|
|
240
|
+
| 行为 | TUI | 非 TUI(print/json) |
|
|
241
|
+
|------|-----|----------------------|
|
|
242
|
+
| execute 返回 | 立即返回派发回执 | 等待子 agent 完成后返回完整结果 |
|
|
243
|
+
| 结果投递 | `[subagent-result]` 系统通知 | 直接内联在返回值中 |
|
|
244
|
+
| /subagent-cancel | 可用 | 不可用(无 TUI 命令系统) |
|
|
245
|
+
| /subagent-cancel-all | 可用 | 不可用(无 TUI 命令系统) |
|
|
246
|
+
| 并行派发 | 支持(无依赖任务可同时派出) | 不支持(每次调用阻塞) |
|
|
247
|
+
|
|
248
|
+
## 手动调用
|
|
249
|
+
|
|
250
|
+
如果需要手动发起调用,JSON 格式如下:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"agent": "coder",
|
|
255
|
+
"task": "将认证中间件重构为使用 async/await。"
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> **注意**:`task` 字段必须非空,且建议按照 `master.md` 中的标准任务格式书写,包含:**背景、输入、要求、输出格式、验收标准**。空字符串或仅包含空白的 `task` 会被拒绝执行。
|
|
260
|
+
|
|
261
|
+
## sessionId 复用
|
|
262
|
+
|
|
263
|
+
### 非 TUI 模式
|
|
264
|
+
|
|
265
|
+
子 agent 完成后,返回结果末尾会附带 session ID:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
<子 agent 的输出>
|
|
269
|
+
|
|
270
|
+
[subagent session: 01912345-6789-7abc-8def-0123456789ab]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
下次继续同一任务时,传入 `sessionId` 即可复用隔离会话:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"agent": "coder",
|
|
278
|
+
"task": "为重构后的认证中间件添加单元测试。",
|
|
279
|
+
"sessionId": "01912345-6789-7abc-8def-0123456789ab"
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> ⚠️ **并发提醒**:同一个 `sessionId` 不要同时用于多个并发的 `subagent` 调用,否则可能损坏 session 文件。请顺序复用,或确认子 agent 已完全退出。
|
|
284
|
+
|
|
285
|
+
### TUI 模式
|
|
286
|
+
|
|
287
|
+
派发回执中直接包含 `taskId`(即 session ID)。`[subagent-result]` 通知信封的 `- 会话:` 行也携带 sessionId——复用即可。无需等待子 agent 完成就已经拿到了。
|
|
288
|
+
|
|
289
|
+
## 环境变量
|
|
290
|
+
|
|
291
|
+
以下变量会自动传播到每个子 agent 进程:
|
|
292
|
+
|
|
293
|
+
| 变量 | 默认值 | 说明 |
|
|
294
|
+
|------|--------|------|
|
|
295
|
+
| `PI_SUBAGENT_DEPTH` | `0` | 当前递归深度。每次嵌套调用自动递增。**深度限制为 1**——子 agent(depth ≥ 1)不可再派发 `subagent`,递归委派已被完全禁止。 |
|
|
296
|
+
| `PI_CURRENT_AGENT_NAME` | — | 当前 agent 名称,注入每个子 agent 进程。 |
|
|
297
|
+
| `PI_SUBAGENT_ACTIVITY_TIMEOUT_MS` | `600000`(10 分钟) | stdout 和 stderr 均无输出(无活动)时的最大允许时间。 |
|
|
298
|
+
| `PI_SUBAGENT_HARD_TIMEOUT_MS` | `0`(禁用) | 单次调用的绝对最大运行时长。设为正数(毫秒)启用。 |
|
|
299
|
+
|
|
300
|
+
## 超时与终止
|
|
301
|
+
|
|
302
|
+
- 活动超时 10 分钟:子 agent 的 stdout 和 stderr 长时间均无输出(无活动)会被终止。计时器自子进程启动即开始计时,stdout 或 stderr 任一有数据即重置。
|
|
303
|
+
- 硬超时默认禁用(`PI_SUBAGENT_HARD_TIMEOUT_MS=0`),不设绝对最大运行时长。如需启用,显式设置为正数毫秒值。
|
|
304
|
+
- 超时 kill 时结果中的 `stopReason` 为 `"activity_timeout"`(活动超时)或 `"hard_timeout"`(硬超时),会出现在诊断输出(`Stop reason: ...`)和 UI 徽标中,用于区分"超时被杀"与"子 agent 主动失败"。
|
|
305
|
+
- 收到 `AbortSignal` 时先发送 `SIGTERM`,5 秒后未退出则发送 `SIGKILL`。异步模式下,用户可通过 `/subagent-cancel <taskId>` 或 `/subagent-cancel-all` 触发取消。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 async-subagent-isolation authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|