Tasks & sub-agents
Delegate a bounded job to a sub-agent, run it, and resume with a typed result.
A task is an agent that finishes with a value. You hand control to it, it runs its own little conversation — asking whatever it needs — and when it's done it returns a typed result and hands control back. That "hand over, run, come back with an answer" shape is the whole idea, and it's what separates a task from a handoff, which is one-way and never returns.
Use a task to collect a shipping address, verify identity, or run a consent flow — anything that needs its own back-and-forth but should ultimately report an answer to whoever asked.
A task and its completion tool
A task needs two things: instructions (how it talks to the caller) and one
tool that finishes it by calling Complete with the result. Here is a task
that collects an address:
type Address struct {
Line1 string `json:"line1" description:"street address"`
Postcode string `json:"postcode" description:"postal code"`
}
// 1. Build the task. Its result type is Address.
collectAddress, err := agents.NewAgentTask[Address](agents.AgentTaskOptions{
AgentOptions: agents.AgentOptions{
Instructions: "Collect the caller's postal address, then read it back and confirm.",
// Speak first when the task takes over (on_enter → generate_reply):
OnEnter: func(_ context.Context, ac *agents.AgentContext) error {
_, err := ac.GenerateReply(agents.GenerateReplyOptions{
Instructions: "Ask the caller for their postal address.",
})
return err
},
},
})
if err != nil {
log.Fatal(err)
}
// 2. Build the tool that finishes the task. It calls collectAddress.Complete,
// so it has to be created AFTER the task — it closes over it.
saveAddress, err := tool.New("save_address",
func(_ context.Context, _ *tool.RunContext, in *Address) (*struct{}, error) {
return &struct{}{}, collectAddress.Complete(*in) // ends the task, sets the result
},
tool.WithDescription("Call once the caller has confirmed their address."),
)
if err != nil {
log.Fatal(err)
}
// 3. Give the task its tool.
collectAddress.UpdateTools(ctx, []tool.Entry{saveAddress})Why the task comes first, then its tools. The finishing tool's whole job is
to call task.Complete(result), so it needs a reference to the task. You build
the task, then build a tool that closes over it, then attach the tool with
UpdateTools. This is the one ordering that looks backwards at first — everywhere
else you build tools and hand them to an agent up front.
Delegating to the task
The task above just sits there until something runs it. That something is a tool
on the supervisor (the agent in control of the conversation) that calls
agents.DelegateTask:
getAddress, err := tool.New("get_shipping_address",
func(ctx context.Context, run *tool.RunContext, _ *struct{}) (*Address, error) {
addr, err := agents.DelegateTask(ctx, run, collectAddress) // blocks until Complete
if err != nil {
return nil, err
}
return &addr, nil // the task is done; carry on with the value
},
tool.WithDescription("Collect the caller's shipping address."),
)
if err != nil {
log.Fatal(err)
}So there are two tools with different jobs, and it's worth being clear which is which:
get_shipping_addressruns on the supervisor and starts the task.save_addressruns inside the task and finishes it.
A task is single-use — Complete fires once. When a supervisor may delegate
the same kind of work more than once, build a fresh task per delegation (a
newCollectAddressTask() constructor). See
examples/supervisor.
What happens during a live turn
When DelegateTask runs, control moves from the supervisor to the task and later back
again. Here is the full sequence for one delegation:
Step by step:
- The caller asks for something; the supervisor's model calls the delegation tool.
DelegateTaskparks the supervisor — its turn is suspended and its speech is locked so a stray interruption can't tear the handover down — and makes the task the active agent.- The task becomes active. If its
OnEntercallsGenerateReply/Sayit speaks first, prompting the caller immediately; the greeting plays before the caller's next turn. It then drives the conversation on its own instructions and tools, across as many caller turns as it needs. - The task's tool calls
Complete. The task's messages merge into the shared history, and control resumes on the supervisor, which now has the typed value. - The delegation tool returns that value, and the supervisor's turn finishes — it speaks its follow-up.
A task can speak first on entry. Call ac.GenerateReply(...) or
ac.Say(...) from the task's OnEnter (as above) to prompt the caller
immediately ("What's your order number?") — the speech plays before the caller's
next turn. This is the reference on_enter → generate_reply behaviour.
Alternatively, have the supervisor say the prompt in the same turn it delegates
(a model can emit speech and a tool call together).
How state moves
- One shared history. The supervisor and the task read and write the same
AgentSessionhistory (see Chat context), so the task sees what came before and its messages stay in the record after it completes. There is no separate transcript per agent. - Parked, not gone. While the task runs, the supervisor is paused — its
OnExitdoes not fire (it hasn't left, it's waiting). OnCompleteit resumes without re-runningOnEnter. - Nesting is a stack. A task can itself delegate to another task; completions unwind last-in-first-out.
- Merge policy. On resume, the task's tool-call chatter is dropped from the
merge unless you set
AgentTaskOptions.PreserveFunctionCallHistory— keeping the supervisor's view clean.
If an explicit UpdateAgent swaps the active agent while a task is running, or
the session closes, DelegateTask still returns the result (or the context error) but
the supervisor is not resumed — control stays wherever the change left it.
Delegate, or hand off?
Both move control to another agent. The difference is whether control comes back.
- Delegate with
agents.DelegateTaskwhen you need an answer back and the supervisor should keep the conversation afterward. - Hand off with
agents.RequestHandoff(graceful, after the tool batch) orsession.UpdateAgent(hard, immediate) when the other agent should own the rest of the conversation. See Agents & handoffs.
TaskGroup — a fixed sequence
TaskGroup runs an ordered set of tasks over a shared chat context, each
completing with its result, with an optional completion callback and a
return-exceptions policy. Reach for it for scripted multi-step flows (a survey, an
intake form) rather than model-driven routing.
When a task, and when not
- Tool — one deterministic step, no sub-conversation (fetch a record, send an email). See Tools.
- Task — a focused job that needs its own multi-turn conversation and returns a value. If the model has to ask clarifying questions, it's a task.
- Handoff — the other agent should own the rest of the conversation, with no return. See Agents & handoffs.
Next
- Supervisor pattern & scaling — route work to many tasks.
- Tools — the tools a task (or agent) runs.
- Full API: Reference → AgentTask & TaskGroup.