Teams & sub-agents

Three ways to fan work out: spawn isolated sub-agents with the task tool, run them in the background and poll them, or stand up a full teammate/coordinator system with a shared mailbox and task board.

Sub-agents — the task tool

When sub-agents are enabled, the agent gains a task tool. Each call runs a fresh, isolated child agent to completion and returns only its final text — the child's intermediate tool calls never enter the parent conversation. Use it to parallelize or isolate self-contained work (research, broad searches, multi-file changes).

Enabling sub-agents

Pass an agents map (even an empty one enables the general-purpose sub-agent):

import { query } from 'anyclaude-sdk'

for await (const m of query({
  prompt: 'Audit every route file for missing auth checks.',
  workspace, llm,
  agents: {},                  // enables the `task` tool (general-purpose sub-agent)
  maxSubagentDepth: 2,         // how deep sub-agents may nest (default 2)
})) { /* … */ }

The task tool parameters

description

Short (3–5 word) label for the task.

prompt

The detailed instructions for the sub-agent.

subagent_type

Optional — name of a configured agent type (see below). Defaults to general-purpose.

run_in_background

Run detached and return a task id immediately. Poll with task_output / task_list.

Custom agent types (AgentDefinition)

Give named agents their own system prompt, a restricted tool set, and even a different model. The model selects one via subagent_type.

await query({
  prompt: 'Research the auth flow, then write a summary.',
  workspace, llm,
  agents: {
    researcher: {
      prompt: 'You are a read-only research agent. Find facts; do not edit files.',
      tools: ['read_file', 'glob', 'grep', 'web_search'], // subset of tool names
      model: 'claude-sonnet-4-6',                          // optional per-agent model
    },
  },
})
// The model can now call: task({ subagent_type: 'researcher', description: '…', prompt: '…' })

Background tasks

Background tasks run detached from the main loop so a long sub-agent (or shell command) doesn't block the conversation. Enable them with background: true, or inject a shared BackgroundTaskManager so tasks survive across turns (start one in one message, read it in a later one).

import { query, BackgroundTaskManager } from 'anyclaude-sdk'

const background = new BackgroundTaskManager() // shared across turns
await query({ prompt, workspace, llm, agents: {}, backgroundManager: background })
// (or simply: query({ ..., background: true }) for a per-call manager)

This adds three tools the agent uses to manage detached work:

task + run_in_background

Starts a sub-agent in the background, returns bg_N immediately.

task_list

List background tasks and their status (running/completed/failed/stopped).

task_output

Read a task's accumulated output by id (streams progress as it runs).

task_stop

Abort a running background task (cancels its sub-agent).

You can also drive the manager from your own code: background.list(), background.output(id), await background.wait(id), background.stop(id) — handy for rendering a live task bar in your UI.

Teammates & coordinator

Set team: true to turn the agent into a coordinator: it gets a coordinator system-prompt addendum plus tools to message teammates and manage a shared task board. Inject a Mailbox and TaskBoard to share them across turns (and with sub-agents).

import { query, Mailbox, TaskBoard } from 'anyclaude-sdk'

const mailbox = new Mailbox()
const board = new TaskBoard()

await query({
  prompt: 'Build the dirsize CLI: scaffold, implement, test. Use the board + workers.',
  workspace, llm,
  agents: {},        // sub-agents (workers)
  team: true,        // coordinator tools + prompt
  mailbox, board,    // shared, persistent across turns
})

Coordinator tools

send_message

{ to, text } — message a teammate via the shared mailbox. Addressed to a running worker (worker:<taskId>), it's delivered on that worker's next step — see push delivery below.

task_create

{ subject, description?, owner?, blocked_by? } — add a board task; gate it on prerequisite task ids.

task_update

{ id, status?, owner?, subject?, description? } — status is pending|in_progress|completed|failed.

task_get

{ id } — full detail for one task (incl. blocked/blocks).

board_list

{ status?, owner? } — list board tasks, optionally filtered.

dispatch_tasks

Hand ready (unblocked) board tasks to workers in parallel and collect results. Each worker is named worker:<taskId> (and recorded as the task owner). Pass { background: true } to return immediately and supervise while they run.

Push delivery to running agents

0.11 Unread mailbox messages addressed to an agent are auto-injected into its transcript at each turn boundary — the same delivery model as the message queue, but sourced from the shared mailbox and addressed by name. So a coordinator (or any peer, the host app, or another Web Worker) can dispatch a message to a running sub-agent and have it land on the sub-agent's next tool round — no polling tool required.

// any sender — coordinator, peer, host app, or another worker
mailbox.send('coordinator', 'worker:task_1', 'while you work: also add logging')
// worker:task_1 sees "[Team messages] - from coordinator: …" on its next step.

On by default when team: true; opt out with query({ deliverTeamMessages: false }) for explicit pull instead.

Supervise & redirect while workers run

Combine background dispatch + push delivery to monitor and steer live: kick the board off detached, then poll the board and message a specific running worker mid-task.

await query({ prompt, workspace, llm, agents: {}, team: true, background: true, mailbox, board })
// The coordinator: task_create … → dispatch_tasks({ background: true }) → returns a bg id,
// then board_list / task_get to monitor, and send_message to worker:<taskId> to redirect —
// the worker picks it up on its next step. (Pace your polling; don't busy-loop.)

Teams across Web Workers / tabs

0.10 The in-memory Mailbox is single-context. BroadcastChannelMailbox is a drop-in replacement that gossips over a BroadcastChannel, so agents in different Web Workers (or browser tabs, or Node worker_threads) share one mailbox — the same team tools work unchanged across workers. Pair it with the Comlink helpers (wrapWorker / exposeBackgroundWorker, which now ship with the SDK) for main→worker control.

import { BroadcastChannelMailbox } from 'anyclaude-sdk'

// inside each Web Worker / tab / worker_thread, same channel name:
const mailbox = new BroadcastChannelMailbox({ channelName: 'team', origin: 'planner' })
query({ prompt, workspace, llm, team: true, mailbox })

// durable cross-tab (IndexedDB/localStorage fallbacks, older browsers, Node) in one call —
// backed by the bundled `broadcast-channel` package, lazy-imported:
const mb = await BroadcastChannelMailbox.crossTab({ channelName: 'team', origin: 'planner' })

Uses the global BroadcastChannel by default; inject { channel } (any ChannelLike) for a custom transport. Each instance keeps an eventually-consistent replica; message ids are origin-scoped so workers never collide.

Driving a team in code

For full control, runTeamLoop coordinates an idle-loop where workers claim ready tasks until the board drains:

import { runTeamLoop } from 'anyclaude-sdk'
// runTeamLoop({ board, mailbox, spawnWorker, ... }) → drives workers to claim
// unblocked tasks, run them, and report back until the board is complete.

Shared by design. The same Mailbox and TaskBoard are passed to spawned sub-agents, so the coordinator and workers operate on one set of tasks and messages.