Subagents¶
A subagent is a child agent that handles one focused task in its own isolated
context. The model delegates to it through the task tool, gets a single summary
back, and keeps the noisy detail out of the main conversation.
The task tool¶
The model calls task to spawn a subagent. The subagent sees only the prompt
it's given — not the parent conversation — runs its tools to completion, and
returns a single string. Use it to parallelize independent work or to keep a
self-contained subtask from cluttering the main context. For a loop or a wide
fan-out driven by real Python control flow instead of one task per call, see
Workflows.
The task tool takes four arguments:
| Argument | Required | Notes |
|---|---|---|
prompt |
yes | The complete, self-contained task — the subagent sees nothing else |
subagent_type |
yes | Which profile to run (see below) |
model |
no | A per-call model override (e.g. a cheaper or faster model) |
run_in_background |
no | Detach the subagent — the tool returns immediately with an agent id instead of the subagent's result; the model gets a one-line completion reminder on the next user turn and inspects the child with agent_list / agent_output / agent_kill |
Spawning is non-destructive and auto-allowed, so the spawn itself never prompts.
The subagent inherits the parent's permission mode, so any destructive tool
call it makes still goes through the normal gate — see
Modes & permissions. Every subagent is dispatched
non-interactively: a shared harness preamble tells it there is no user to
ask, and the ask tool is scoped out of every spawned child (even general's
all-tools set) — the parent asks on the child's behalf after it returns. Its tool activity folds into
the task row itself — the row's title tracks the latest child call live
(⬡ task ▸ grep … · 4 calls · 12s) and expanding the row shows the full
activity log, one line per call with its verdict glyph — instead of each child
call printing its own transcript line. The returned summary is capped at
32 KB; anything longer is truncated with a note.
A background subagent is inspected with companion tools: agent_list (one
row per agent — fg/bg marker, status, profile, last output line, tool count)
and agent_output(agent_id, wait?, max_wait_ms?) (the buffered prose;
wait=true blocks up to max_wait_ms, default 10s), alongside agent_kill
(below) and agent_message (see
Continuing a parked subagent).
Agent profiles¶
A profile defines a subagent's persona and its toolset. Eight are bundled:
| Profile | Tools | For |
|---|---|---|
general |
all available tools | A general focused task in isolation (the default) |
researcher |
read + web (read_file, web_fetch, web_search, web_context, cite_check, todos, skill) |
Read-only research; no edits, no shell |
coder |
read + edit + shell (read_file, write_file, edit_file, shell family, todos, skill) |
Code changes verified with the shell; no web |
explorer |
read-only + search_workspace (read_file, glob, grep, ls, search_workspace, todos) |
Read-only codebase orientation; no web (disjoint from researcher) |
planner |
read-only + write_plan (read_file, glob, grep, ls, web_fetch, web_search, search_workspace, todos, write_plan) |
Researches and drafts a plan file; the plan lifecycle's entry point |
critic |
read-only (read_file, glob, grep, ls, web_fetch, web_search, todos) |
Adversarial plan review; the plan lifecycle's "Review plan first" path |
critic-code |
read-only (read_file, glob, grep, ls, web_fetch, web_search, todos) |
Adversarial code/output review; what the bundled /critique command dispatches |
security-auditor |
read-only (read_file, glob, grep, ls, web_fetch, web_search, todos) |
Adversarial security review (six-dimension rubric: INJECTION / INPUT_HANDLING / AUTH / SECRETS / SUPPLY_CHAIN / CONFIG); dispatched independently via task |
You can add your own, or override a bundled one, with a markdown-plus-frontmatter file. Discovery walks five roots, highest priority first; a project profile always wins, and the bundled defaults are the last-resort fallback:
<workdir>/.kin/agents/<workdir>/.claude/agents/~/.kin/agents/~/.claude/agents/- the bundled defaults shipped with kin
---
name: reviewer
description: Reviews a diff for correctness and risk.
tools:
- read_file
- shell
max-turns: 15
---
You are a code reviewer. Read the diff, then report bugs and risks.
| Field | Notes |
|---|---|
name |
Required; must match ^[a-z][a-z0-9-]*$. The filename stem is canonical |
description |
Shown to the model when it picks a subagent_type |
tools |
The allow-list. Omit it (or leave it empty) to grant all available tools |
max-turns |
The subagent's turn budget; defaults to 100, clamped to 1–1000 |
The body becomes the subagent's system prompt. The subagent_type enum the model
sees is rebuilt each turn against your project's workspace, so a profile you add
shows up without a restart. A profile using TOML-style +++ frontmatter is
skipped with a warning — kin reads only --- frontmatter.
Note
The tools allow-list is a real boundary: a researcher profile can't write
files or run a shell. Scope a profile to the least it needs.
Limits¶
The harness caps recursion and fan-out so a runaway delegation can't spiral:
| Limit | Value | Meaning |
|---|---|---|
| Max depth | 4 | How deeply subagents may nest (0 is the top-level agent). A task call that would exceed it is refused, and the model is told to do the work directly |
| Max parallel | 3 | Concurrent in-flight subagents per session, bounded by a semaphore |
| Max background agents | 3 | Concurrent background subagents (run_in_background=true) per root session. Foreground children don't count against it — they're bounded by max-parallel + depth |
| Max result size | 32 KB | The returned summary string is truncated past this |
Each subagent gets its own parallelism budget, so a parent waiting on a child never contends with that child's own children. Blocking prompts (approvals, questions) raised inside a subagent are always resolved at the root session, so they reach you in the normal modal.
When a subagent fails¶
A foreground task call can fail in a few ways, and each one produces a
structured, id-bearing result so the parent can tell what happened and
where the full transcript lives. The result is ALWAYS prefixed with
error: — that prefix is the cross-backend is_error mechanism
(loop._finalize auto-detects it; the Anthropic wire carries
is_error: true, the OpenAI wire has no error field at all, so the text
prefix is the only signal that reaches the model on both wires):
done_reason |
Result shape (registered task child) |
|---|---|
error (wire death after retries) |
error: subagent <id> failed mid-run — partial output below; transcript retained (agent_output("<id>")) + the partial prose. A mid-run wire death surfaces the partial prose with a marker — you can no longer mistake a half-answer for a whole. |
turn_cap (hit the profile's max-turns bound) |
error: subagent <id> hit its turn cap (N turns) — partial output below; transcript retained (agent_output("<id>")) + partial prose (or the head alone when no prose was produced). |
loop_detected (doom-loop guard tripped) |
error: subagent <id> stopped — doom-loop guard tripped (partial output below; transcript retained (agent_output("<id>"))) + partial prose. |
crashed (a raw harness exception in run_subagent) |
error: subagent <id> crashed: {exc} — transcript retained (agent_output("<id>")). The child parks (not closes — see the Lifecycle section below), so the transcript hint is true. |
killed (DR 0057 — agent_kill / panel x) |
error: subagent <id> was killed before finishing. The parent turn continues. |
The full transcript is always reachable via
agent_output("<id>") — or the agent panel —
Ctrl+O → select the row → o or Enter — for every failed row. The
returned prose is capped at 32 KB; if a child produced more, the result
gets a truncated at 32KB note pointing at agent_output("<id>") for the
uncapped text.
The 32 KB cap is signalled by a truncated flag on the child's emit,
not by string-sniffing — the shaper reads the flag and appends the
id-bearing hint.
Lifecycle: spawn → run → park → closed¶
A subagent isn't destroyed when its run finishes — it parks. After a
foreground task call returns (or a background agent completes), the child's
session stays live on its registry row with the full transcript retained in
messages. You can re-read it through the agent panel
(Ctrl+O → select the row → o or Enter) or the agent_output tool without
re-running anything.
Closing is deliberate, and happens in three places:
agent_kill(or the panel'sxon a running row) cancels + closes the child's session.- Dismissing a finished row (the panel's
xon a done agent) closes the child before dropping the row. - App exit (
Session.aclose— quitting kin) closes every retained-idle child, so a parked subagent's lazyrun_codekernel / headless browser / bg shells don't orphan.
Closing tears down processes, not data — a closed row's transcript stays
readable. Each subagent also writes its own journal next to the root session's;
those child journals are hidden from /resume and --continue (see
Sessions).
Continuing a parked subagent: agent_message¶
A parked child isn't a snapshot — it's a live Session between turns. The
agent_message(agent_id, message, run_in_background?) tool sends the next
instruction to an idle child: the message is appended to its transcript and
runs as a new turn, with the full prior history retained. This is the
turn-completion resume model — what to call when you want to continue a
conversation rather than spawn a fresh task.
| Arg | Required | What it does |
|---|---|---|
agent_id |
yes | The parked child's id (from task or agent_list) |
message |
yes | The next instruction (appended to the child's transcript, runs as a new turn) |
run_in_background |
no | Detach the resume — returns immediately, fresh completion reminder fires on the next user turn |
Refusals (all error:-prefixed, one line):
| Target state | Tool result |
|---|---|
| Unknown id | error: unknown agent_id: <aid> (call agent_list to see live agents) |
| Still running | error: <aid> is still running — it can only be messaged between turns |
| Closed (or killed) | error: <aid> is closed — its transcript is still readable via agent_output |
Two rules govern the model:
- A running agent cannot be messaged — wait with
agent_output(wait=true)for it to finish, thenagent_message. A running resumed child refuses further messages too — the depth-invariant: any await-chain target must be IDLE, so two children can never await each other. - Resume does not change the child's
depth— the child already exists (no new spawn), so the depth cap is the original spawn's depth. There is no spawn-rate gate onagent_message.
A fg agent_message returns ONLY the new turn's prose (the parent's
messages already carries the full transcript; you re-read via
agent_output / the panel). A bg agent_message returns
resumed agent<N> (<profile>) in background, fires a fresh
subagent_completed reminder on the next depth-0 turn, and behaves like an
initial task(run_in_background=true) from the parent's perspective (counts
against MAX_BG_SUBAGENTS = 3).
Step 4's failure strings now carry the resume hint alongside the transcript
hint — a failed child hands the model both: transcript retained
(agent_output("<id>")) — resume with agent_message("<id>", "<next instruction>").
The stop branch deliberately omits the resume hint — a clean stop is a
completed turn, not a failure to recover from.