Skip to main content
Reference for your agent. The precise shape of things — signatures, fields, rules — written so an agent can read it and act. You do not need to: ask your agent for what you want in words, and if it needs this page, hand it the URL or use Copy page. What to ask for, and how to check it, is in Ask; how to see what changed is in Look inside.
A sub-agent is an agent your program hands a job to. It gets its own run, its own conversation and its own context window, and its answer comes back where you asked for it — in the caller’s code, or as files on disk. There are two ways to start one, and they are the same mechanism seen from two sides.

The model decides: agents=[...]

Name a declaration in another agent’s agents= list and it becomes a tool of that agent:
What travels is the name. The calling model sees a tool called executor — the name sanitized into an identifier — and description is the only thing it knows before it decides to use it. Write that description as an instruction to the caller rather than a summary of the callee: the two things a caller gets wrong are that the sub-agent does not see the conversation, and that what should come back is the result rather than a description of it. Two things are refused: an agent cannot spawn itself, and a sub-agent cannot spawn at all. Both are recursion with no floor, and every level costs a run and its tokens before the next one starts.
A spawn is an ordinary run in a chat of its own, parented to the caller’s, and it belongs to the same program — so it runs on the same hooks, and t.spawned is true for it.

Your code decides: calling the declaration

A declaration is callable, and calling it starts a run:
Inside a program’s own main.py the variable is enough (executor(...)); from a script that is not that program, program("<name>").<agent> is how you reach it, because a bare name says nothing about whose agent it is. program() with no name is the program this run belongs to.
A keyword the call does not know is refused where it is written. attempts= needs wait=True or schema= — an async handle has not failed yet, so there is nothing to replace — and each retry states what went wrong, so the fresh agent does not repeat it.

AgentRun

Every call returns an AgentRun, never text.
Without wait=True the call returns as soon as the agent is spawned, and your script exits in a second with a run id and nothing else. If you want the answer, block for it.

schema= holds the run to a shape

schema= blocks and returns the parsed object. The contract travels with the spawn, so nothing in your prompt has to instruct the model about JSON, and a violation is shown back to the same agent inside the run that produced it — a constraint costs one more turn instead of a lost phase. schema() builds it: str/int/float/bool for scalars, [T] for an array of T, a nested schema() for an object, every field required unless wrapped in opt(). Besides type, required, properties, items, enum, minimum and maximum, the validator understands minItems, maxItems, minLength and const. A run that never met its contract raises SchemaError; one that ended in any other non-succeeded state raises AgentError. Both are FlowError.

Words come back in .output(); files come back on disk

This is the part worth getting right, because it is where the shape of the work is decided. You and every sub-agent share one sandbox and one filesystem. There is no fetch, no artifact download and no serialization step. /home/daytona/workspace is the caller’s git checkout, and workspace="landing-ui" gives that agent its own git worktree at /home/daytona/workspace-landing-ui, on branch landing-ui.
workspace= prepends a contract to the agent’s message, before the task, because it decides where the agent writes before it reads what to do: the worktree it owns, the command that creates it, and an instruction to commit before answering. The commit is for durability and merging, not for transport — the files are visible at that path the moment the agent finishes.
Any agent whose result is files gets a workspace of its own, and its own paths, so no two ever write the same tree. Omit workspace= only for an agent that answers in words. And never ask an agent to return a file’s contents in a schema: its typed reply carries facts about the work, the worktree carries the work.
A directory that has to leave the machine — a checkout somewhere else, a human clone — is publish_workspace(path), which pushes it to the chat’s own git remote and returns the clone URL.

Many at once

parallel(items, fn) maps fn over items with no closure gotcha; parallel(thunks) runs a list of zero-arg callables. It returns when the slowest finishes, in input order. A unit that raises becomes None in its slot, with the exception at .errors[i] — a failure never shifts the results after it. What comes back is a Results list: .complete() exists because partial coverage is a real answer for some work — 47 of 50 sources read — and a broken deliverable for other work — one unimplemented module. The caller states which.

Three at a time

One process-wide governor caps live agents at 3 by default, across every spawn path: raw and typed, sync and async, nested fan-outs included. Pool(n) changes it for a block:
Lowering the cap below the current count does not cancel anything; it queues new spawns until enough live agents become terminal.

The rest of tools.agents

gather(runs) waits for a list of handles and returns [{run_id, status, output, chat_id}]. capture() records the runs spawned inside a block so a relaunched script reattaches instead of paying twice. tasks(board, rows) puts rows on the task panel a person watches in the chat. get_cap() and set_cap(n) read and move the live-agent cap outside a Pool. tools.agents holds machinery and no agents at all — importing an agent from there is an ImportError. Agents come from their program.

Which shape a job wants

One call, an answer in words

A question, an explanation, one local edit, a piece of research. executor("...", wait=True).output(). No workspace.

One call, an answer with a shape

Anything a caller has to branch on. schema=, and let the runtime enforce it.

Several agents, files back

workspace= each, then read the worktrees and merge the ones you want.

A make-review-repair cycle

That is a program, not a turn. programs/compiler/README.md in your checkout is the worked example — read it before writing one.
The last one is worth saying plainly: work that fans out over several agents and comes back as files belongs in programs/, as a script with a main(), not in the middle of a chat turn. See Programs and Patterns.