Skip to main content
You have four ways to organize an agent: one agent with tools, a task, a task group, or a second agent. Each one owns a different boundary. Choose from the brief, before you write files. On this page:
In a hurry? Two rules carry most of it. A task returns the caller and a handoff does not. And if one agent with good tools can do the job, use one agent with good tools.

Compare the shapes

Every shape trades something for control. This table is the whole trade, side by side. These are not exclusive. One agent can run a task in one phase and hand off in another.

Choose the native shape

If none of these boundaries exists, keep one agent with a clear prompt and its real tools. You describe the job; the coding agent should choose the shape and tell you what it chose. You do not need to ask for a task or task group by name. A server-directed sequence is dynamic, even when its response calls the field nextStep. Put that loop in one task that asks the returned question and calls the real domain tools. If fixed stages surround the loop, those stages can be tasks in a task group. The server owns its dynamic order.

Let the compiler hold the state

  • Do not create current-step, happy-path, completion, or routing variables. Tasks, groups, handoffs, and an external server already hold that state.
  • Do not create advance, proceed, dispatcher, or transition tools. A tool does real external or local work; a task, a task group or a handoff moves the conversation.
  • Keep a variable only when its value really crosses a boundary or feeds a later tool call.

The symptom decides the shape

A prompt that says “always identify the caller first” is a request. A task group is a guarantee. A when: clause naming the dependency is not a guarantee the same way, but it is cheap: no extra task, no extra prompt site, and nothing the caller has to be spoken through. See Order steps with the prompt for the full pattern, including the tool-level check that catches a task run out of order anyway. Reach for the task group when the order is what must hold. Reach for a when: clause when one value should usually be there before one task runs, and a stray extra turn asking for it is an acceptable cost.

Task or handoff

The difference is whether control comes back. That is why they are two kinds of list rather than one with a kind field: which list a thing is written in is what it is.

Order the task, not the route to it

Say the dependency on the task itself, in its own when::
The model reads that clause every time it considers the task. Reference the saved value in the owner’s prompt when the model needs to check it. See Order steps with the prompt for what still catches a task run out of order anyway, and what the caller hears when it does. Do not add an agent whose only job is to hold callers up in front of a task. That agent has to be spoken through, which costs the caller a turn and buys nothing a clause on the task does not already give. And never hold up the route to a person: someone who asks for a manager should not be interviewed first.

Choosing shapes for a booking flow

A salon booking agent runs into all four decisions in one package. Each lands on a different shape, and together they are examples/salon-concierge. Look up services and answer questions. One job, one set of rules: a single agent with tools handles it. concierge calls look_up_salon_info directly, with no task in between. Confirm who is calling before anything personal. One job with a definite, typed answer: a task. verify_customer holds the one tool that looks the caller up, ends on that tool through finish:, and saves customer_phone with assign:. The customer_phone variable carries confirm: verify_customer, so until this task has heard the caller agree, the number reaches no other prompt and every tool that needs it refuses. The task keeps a when: of its own for one case only: the caller correcting their phone number. Every other route into verification goes through the group below. Does identifying the caller have to come before booking, every time? That is the task-group question, and here the answer is yes. A booking made against a number nobody agreed to is a booking for a stranger, so the order is not a request to the model. It is something the package has to hold. The group book runs verify_customer and then manage_booking, with context_scope: shared so the booking step hears what verification heard, and then: return so the concierge gets the call back:
Three things follow from the group.
  • The second booking on a call skips verification. skip_when_confirmed: customer_phone skips the first step when the number is already confirmed, which is every booking after the first.
  • The concierge makes one call into the group instead of choosing between two tasks. Which step runs, and in what order, is the group’s decision.
  • manage_booking has no when: of its own, because the group decides when it runs. A task an agent runs on its own needs a when:. A step inside a group does not.
Hand a complaint to someone who needs a different tool list and a refund policy the booking agent should never see. A different role with different permissions, not another task in the same role: a handoff. to_complaints moves the caller to complaint_specialist, who alone holds record_complaint and alone reads the refund knowledge base through look_up_refund_policy. Recording a complaint is one action, not an ordered step, so it sits directly on the specialist rather than behind a task. to_concierge carries the reverse direction. The specialist does not list verify_customer: only the concierge verifies, and every tool that needs the number refuses while it is unconfirmed. What is left over goes to a person. A caller who asks for a manager reaches to_manager, a cold transfer, not any of the four shapes above. Both agents hold it, so asking for a person is never gated on identifying yourself first.

Where to go next

Making a task actually run

Why a task you declared never runs, and how to fix it.

Designing declared state

Choosing what a task saves, and the type each value gets.

Run it locally

The dev loop: ports, logs, and picking a target.

Optimize the build

The settings that cut round trips and speed up a call.