Skip to main content

subagent-background

Run subagents asynchronously with `run_in_background: true` and manage them with `subagent_send` (status / wait / steer / abort). Use when you want to fan out work, hide latency behind other steps, or keep the parent responsive while a long child runs. Pairs with `subagent-delegation`, which covers when and how to delegate at all.

跳到安装

来源信息

仓库
weikinhuang/dotfiles
最近来源活动
2026年6月1日 00:22
检测到的 SKILL.md 语言
英语
星标
21
分支
3

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
subagent-background
description
Run subagents asynchronously with `run_in_background: true` and manage them with `subagent_send` (status / wait / steer / abort). Use when you want to fan out work, hide latency behind other steps, or keep the parent responsive while a long child runs. Pairs with `subagent-delegation`, which covers when and how to delegate at all.
# Subagent Background Lifecycle `subagent({ run_in_background: true })` returns a handle immediately instead of blocking. The child keeps running in the background - even across the parent turn boundary - and you retrieve its answer later with `subagent_send({ to, action: "wait" })`. This skill is the lifecycle and the policy for when async is worth the extra bookkeeping. If you haven't decided whether to delegate at all, read `subagent-delegation` first. ## When background beats synchronous Sync (`run_in_background: false`, the default) blocks the current assistant turn until the child finishes. The child can't outlast the turn. That's fine for: - A single quick exploration (<30s of child work). - Anything where you literally cannot proceed without the result. Go background when **any** of these is true: - **Fan-out with work to do.** You want to spawn two or more children and use the parent's turn to make progress on something else while they run. - **Latency-hiding.** The child is going to take a while; meanwhile you could be drafting code, running tests, or reading other files. - **Cross-turn work.** The investigation is long enough that it would blow your turn budget or trigger a stall - let it run across multiple parent turns. - **Exploratory "maybe I'll use this".** You want a second opinion or an alternative plan in parallel; finish your own attempt first, then compare. - **Steerable tasks.** You expect to nudge the child mid-run (new constraint, narrower scope) via `subagent_send({ to, text: "..." })`. Stay sync when: - The child answer is the _immediate next input_ to your reasoning. - You have nothing else to do while it runs. - The task is short enough that polling overhead costs more than it saves. ## Fan-out pattern Multiple `subagent` calls in a **single assistant turn** run concurrently. This is the cheapest way to parallelize: ```text Turn N (parent): subagent({ agent: "explore", task: "find callers of A", run_in_background: true }) subagent({ agent: "explore", task: "find callers of B", run_in_background: true }) subagent({ agent: "explore", task: "find callers of C", run_in_background: true }) → returns three handles immediately Turn N (parent, same turn): ... do other work inline ... Turn N+1 (parent): subagent_send({ to: h1, action: "wait" }) subagent_send({ to: h2, action: "wait" }) subagent_send({ to: h3, action: "wait" }) → merge results ``` If the tasks truly are independent and you have no inline work to do, you can also call them synchronously in the same turn - the harness still runs them concurrently. Reach for `run_in_background` specifically when you want the parent free to do something else, or when the children might outlast the turn. ## The handle Every `subagent({ run_in_background: true })` call returns a short handle string (something like `c1`, `c2`, …). **Record it the moment you get it.** Good places: - `scratchpad` - "spawned `c1` to find callers of X; waiting." - The `note` field on a `todo` review item - "parked pending `c1`". Do NOT try to reconstruct handles from memory across compactions. If the scratchpad doesn't have it, list active children with the `/agents running` surface or treat the work as lost and respawn. ## `subagent_send` actions | Action | Blocking? | Purpose | | ------------------- | --------- | --------------------------------------------------------------------- | | `status` (default) | No | Cheap snapshot: running / finished / aborted, plus turn count. | | `wait` | Yes | Block up to `timeoutMs` for the child to finish; return final answer. | | `abort` | No | Cancel a running child. Final answer classified as `aborted`. | | (no action, `text`) | No | Inject a user-role message into a running child to steer it. | Rules the harness enforces: - `text` is **not combinable** with `action: "abort"`. Pick one. - `text` on a **finished** child is rejected - steer only while running. - `wait` on a finished child returns the stored answer instantly; safe to call repeatedly. - Only the parent can call `subagent_send`. Subagents never see this tool. ## Polling loop pattern For a single long-running child: ```text spawn → handle h ... do inline work ... subagent_send({ to: h, action: "status" }) # cheap check → still running, do more inline work subagent_send({ to: h, action: "wait", timeoutMs: 30000 }) → final answer ``` For fan-out: ```text spawn h1, h2, h3 in one turn ... do inline work ... for h in [h1, h2, h3]: subagent_send({ to: h, action: "wait" }) merge and act ``` Prefer `wait` with a generous `timeoutMs` over tight `status` polling - each `status` call burns a tool slot. ## Steering a running child Use `subagent_send({ to, text })` to inject new guidance into a child that's still working. Good reasons: - You discovered a constraint the child needs to honor ("ignore anything under external/"). - You realized the output shape you asked for was wrong ("return JSON, not prose"). - The child is about to waste turns on the wrong sub-problem ("stop reading search.ts; look at validate.ts instead"). The injected text appears to the child as a new user-role message. Write it the same way you'd write a clarifying reply in chat - short, imperative, paste any paths literally. Do NOT use steering as a replacement for a better initial `task`. If you're steering on every spawn, tighten the prompt (see `subagent-delegation` → Writing the `task`). ## Abort criteria Abort a background child when: - The task is obsolete. The user changed direction, or the parent already solved it inline. - The child is clearly stuck (repeated reads of the same file, no progress across multiple `status` checks, turn count climbing with no output growth). - You spawned the wrong agent type and steering won't fix it (e.g., you need write access but spawned `explore`). `subagent_send({ to, action: "abort" })` is the clean exit. Don't just stop calling `wait` and hope it dies - background children survive turn boundaries and keep consuming until they abort, finish, or time out. ## The "don't orphan children" rule Every background handle you receive is a commitment. Before ending your assistant turn, for every live handle you spawned: - `wait` on it (if you need the answer), OR - `status` it and note in `scratchpad` that you'll check next turn, OR - `abort` it (if obsolete). Silently moving on and never touching a handle again is the worst outcome: - The child keeps burning tokens until it hits its own turn cap. - Its final answer ends up in a tempfile the parent never reads. - Future turns get cluttered with "running" entries in the injected status block. A quick audit at the end of each turn - "any live handles I haven't resolved?" - catches this. ## Combining with `todo` and `scratchpad` Background work and planning tools compose naturally: - Move the corresponding todo to `review` with a note like "parked on `c1`" when you spawn. - `complete` the todo only after `wait` returns and you've verified the answer. - Keep handle → task mapping in `scratchpad` so you can re-attach after compaction: `c1 = explore callers of searchHandler (src/api/search.ts:412)`. ## Anti-patterns - **Don't spawn background children you have nothing to do with.** If you'll just `wait` immediately, spawn sync. Background is for latency-hiding, not syntax sugar. - **Don't tight-poll with `status`.** One `status` between substantial inline work is fine; a `status` every line of reasoning is pure waste. Use `wait` with a timeout instead. - **Don't steer on every turn.** If you're injecting text more than once or twice, the original `task` was wrong - abort and respawn with a better prompt. - **Don't forget handles exist across turns.** Unlike sync children, background children outlive the turn that spawned them. Treat them like open file descriptors. - **Don't race children against the user.** If the user is typing a follow-up, a background explorer can deliver its answer mid-conversation and derail the thread. Abort or `wait` before pivoting direction. - **Don't spawn a child per file.** If you're about to loop `for each file: subagent(...)`, write one `task` that handles the batch, or fan out with a handful of children covering ranges - not dozens. ## Quick reference | Goal | Move | | ------------------------------------------ | ------------------------------------------------------------------------ | | Spawn and keep working | `subagent({ run_in_background: true, ... })`, record handle | | Cheap progress check | `subagent_send({ to, action: "status" })` | | Block for the final answer | `subagent_send({ to, action: "wait", timeoutMs: 30000 })` | | Push a new constraint into a running child | `subagent_send({ to, text: "also ignore external/" })` | | Cancel a running child | `subagent_send({ to, action: "abort" })` | | Fan out 3 independent explorations | 3× `subagent({ run_in_background: true })` in one turn, `wait` next turn | | Never again for this handle | `abort` or `wait` - don't orphan |
在 GitHub 查看