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
آخر نشاط في المصدر
١ يونيو ٢٠٢٦ في ٠٠:٢٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢١
التفرعات
٣

خيارات التثبيت

يُحدَّد 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