- Compare the shapes - the whole trade in one table
- The symptom decides the shape - start here if something is already wrong
- Task or handoff - the choice people get wrong most
- Choosing shapes for a booking flow - one brief, worked through
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 flow 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 flow. The server owns its dynamic order.
Let the compiler hold the state
- Do not create current-step, happy-path, completion, or routing variables. Tasks, flows, 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 flow 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 flow
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 flow 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 ownwhen::
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 areexamples/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 an outcome with then: done, and saves
customer_phone with save:. 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 flow below.
Does identifying the caller have to come before booking, every time? That
is the flow 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 flow
book runs verify_customer and then manage_booking. A flow’s defaults fit
here: its steps share one context, so the booking step hears what verification
heard, and it returns the call to the concierge:
- The second booking on a call skips verification.
skip_when_confirmed: customer_phoneskips the first step when the number is already confirmed, which is every booking after the first. - The concierge makes one call into the flow instead of choosing between two tasks. Which step runs, and in what order, is the flow’s decision.
manage_bookinghas nowhen:of its own, because the flow decides when it runs. A task an agent runs on its own needs awhen:. A step inside a flow does not.
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 name verify_customer, as a bare
- verify_customer: the definition stays on the concierge, because a task name
is one name across the package. It needs the step in reach because
record_complaint takes customer_phone through with: and is refused while the caller has
not agreed to it, and a caller who opens with a complaint has never been near
the booking flow.
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.