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.
The model decides: agents=[...]
Name a declaration in another agent’s agents= list and it becomes a tool of that
agent:
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: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.
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 anAgentRun, never text.
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.
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:
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.programs/, as a script with a main(), not in
the middle of a chat turn. See Programs and
Patterns.
