# Subagents

Let an agent spawn child agents with their own model, effort, and typed output contract, and stream updates from the whole agent tree.

## Overview

Every agent that a runtime spawns has delegation tools. A parent starts a child with `spawn_agent`, waits for it with `wait_agent`, and the child returns its result with `submit_result`. The parent receives a result only after it matches the child's `output_contract`.

In Rust, `runtime.take_subagent_updates()` returns one stream of `ScopedAgentUpdate` values for every agent tree in the runtime. nanocode-wasm logs the same updates as session events with `source: "subagent"`, beside the root agent's events.

```mermaid
flowchart TD
  root[Root agent: Sol] -->|spawn_agent| risks[Child: risks]
  root -->|spawn_agent| fixes[Child: mitigations]
  risks -->|accepted result| wait{wait_agent}
  fixes -->|accepted result| wait
  wait --> answer[/Merged table/]
```

## Walkthrough

The steps below run a Sol coordinator that splits a task across two children and logs each child to stderr.

### Build the Runtime

`for_backend::<Codex>().model(Model::Sol)` sets the runtime's default harness and model, so `spawn().build()` needs no backend selection.

A TypeScript session needs no extra setup: the `open` call from [Getting Started](/docs/introduction/getting-started) starts it with subagents on and no concurrency limit. nanocode-wasm exposes no `SubagentControl`, so the concurrency, off switch, and child control recipes are Rust only.

```rust title="src/main.rs"
use nanocode::{AgentUpdate, Codex, Model, Nanocode, OpenAi, Tools};

type Result<T> =
    std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;

#[tokio::main]
async fn main() -> Result<()> {
    // Subagents are on by default, with no concurrency limit
    let runtime = Nanocode::builder()
        .openai(OpenAi::default())
        .tools(Tools::builder().without_defaults().build()?)
        .without_cua()
        .runner_management(false)
        .for_backend::<Codex>()
        .model(Model::Sol)
        .build_async()
        .await?;
```

### Log the Tree

`take_subagent_updates` returns the stream once. A second call returns `None`. In TypeScript, the `published` callback receives each subagent update as a logged event.

#### Rust

```rust title="src/main.rs"
// Print each child when it starts and when it completes
let mut updates = runtime
    .take_subagent_updates()
    .ok_or("subagent updates were already taken")?;
tokio::spawn(async move {
    while let Some(scoped) = updates.recv().await {
        match scoped.update {
            AgentUpdate::Added(agent) => {
                eprintln!("+ {} {}", agent.role, agent.task)
            }
            AgentUpdate::Completion { id, status, .. } => {
                eprintln!("= {id:?} {status:?}")
            }
            _ => {}
        }
    }
});
```

#### TypeScript

```ts
type Update = {
  agent?: { role: string; task: string }
  id?: unknown
  status?: unknown
  type: string
}

// Print each child when it starts and when it completes
const runtime = await AgentRuntime.instantiate(module, sql, {
  published(events) {
    for (const entry of events) {
      const logged = entry.event as { source?: string; update?: Update }
      if (logged.source !== 'subagent' || !logged.update) continue
      const update = logged.update
      if (update.type === 'added')
        console.error('+', update.agent?.role, update.agent?.task)
      if (update.type === 'completion')
        console.error('=', update.id, update.status)
    }
  },
})
```

The other update types are `event`, `status`, and `message`.

### Prompt the Coordinator

The prompt asks for typed contracts. The model writes each `output_contract` as part of its `spawn_agent` call.

```rust title="src/main.rs"
    // The root agent spawns, waits, and merges inside one turn
    let (agent, _events) = runtime.spawn().build().await?;
    let turn = agent
.prompt(
    "Use Code Mode to spawn two subagents in parallel: one lists the risks of a Friday \
     deploy, the other lists mitigations. Give each a typed output_contract with an \
     items array of strings, wait for both accepted results, then merge them into one table.",
)
.await?;
    println!("{}", turn.result().await?.final_message());

    agent.flush().await?;
    runtime.shutdown().await?;
    Ok(())
}
```

## Recipes

### Limit Concurrency

`max_subagents` caps how many children run at once. `SubagentControl::set_max_concurrency` changes the cap while the runtime runs, and applies to new spawns.

```rust
// At most four children run at the same time
let runtime = Nanocode::builder()
    .openai(OpenAi::default())
    .max_subagents(4)
    .build_async()
    .await?;

// Raise the cap later through the runtime's control handle
if let Some(control) = runtime.subagents() {
    control.set_max_concurrency(8);
}
```

### Turn Them Off

`without_subagents` removes the delegation tools from every agent. `runtime.subagents()` and `take_subagent_updates()` then return `None`. In the CLI, `--no-subagents` has the same effect.

```rust
// Agents from this runtime cannot delegate
let runtime = Nanocode::builder()
    .openai(OpenAi::default())
    .without_subagents()
    .build_async()
    .await?;
```

### Write a Contract

An `output_contract` is a tree of nodes, each with a `kind`. Objects list `fields`, arrays have `items`, and `string_enum` lists `values`. The scalar kinds are `string`, `integer`, `number`, `boolean`, `null`, and `any`.

```json
{
  "kind": "object",
  "fields": [
    { "name": "checklist", "required": true, "schema": { "kind": "array", "items": { "kind": "string" } } },
    { "name": "risk", "required": true, "schema": { "kind": "string_enum", "values": ["low", "medium", "high"] } }
  ]
}
```

`spawn_agent` also takes a role, a task, and optional harness, model, thinking, and reasoning mode. A null value inherits the parent's setting.

### Control a Child

`SubagentControl` acts on the children of one root session. `send_user_message` sends a message to a child, and `interrupt`, `close`, and `cancel_all` stop children.

```rust
use nanocode::subagents::MessagePriority;

// List the children of this root, then message the first one
let control = runtime.subagents().ok_or("subagents are off")?;
let children = control.agents(agent.session_id()).await?;
if let Some(child) = children.first() {
    control
        .send_user_message(
            agent.session_id(),
            child.agent_id,
            MessagePriority::Urgent,
            "Stop after three items.",
        )
        .await?;
}
```

### Know the Rules

These rules apply to every spawn:

* **Effort:** By default, a child needs at least `Thinking::Low`.
* **Ranking:** Codex children rank on Luna, then Sol, then Astra, and cannot outrank their parent. Claude and custom models are unranked.
* **Durability:** With durability on, the tree survives restarts. Interrupted children resume from their latest checkpoint, and accepted results stay accepted.

## More

- [Code Mode](/docs/guides/code-mode): Spawn and wait for children from JavaScript cells.

- [Events](/docs/guides/events): Merge subagent updates with agent events.

- [Durability](/docs/guides/durability): Keep the agent tree across restarts.

- [Nanocode](/docs/api/rust/Nanocode): Reference for max_subagents and without_subagents.
