Manage State Across a Multi-Turn Conversation
Learning Objectives
After completing this unit, you'll be able to:
- Use a step variable to enforce a required sequence of subagents across many turns.
- Explain how action chaining runs a follow-up action automatically and deterministically.
- Describe how after reasoning blocks run deterministic logic after each LLM response.
- Apply a subagent-level system block to override the agent's persona for one subagent.
- Describe how model config assigns different models to different subagents.
When One Variable Isn’t Enough
So far, your variables have held single facts—a lifetime value, a yes-or-no flag. That's plenty for the loyalty logic you built. But imagine extending the CC Service Agent to book an experience. Booking is a conversation, not a lookup—you need the date, the party size, any dietary needs, and then a confirmation. Each answer might change the next question. Customers might give vague answers and need a follow-up. And they absolutely can’t skip to "confirm" before you've collected the essentials.
This is the hardest thing to get right with an LLM, because LLMs are happy to wander. One solution in Agent Script is a step variable. It’s a single variable that records where the customer is in the sequence, with the router using it to pick the right subagent every turn.
The Step Variable Pattern
The idea has two halves that work together turn after turn.
First, the router reads the step variable. Instead of letting the LLM choose freely, the router uses deterministic transitions keyed to the current step. Remember the deterministic transitions from Unit 2? Here's where they earn their keep.
variables:
bookingStep: mutable string = "ChooseDate"
description: "Tracks the customer's progress through booking: ChooseDate,
PartySize, Confirm, or Done."
start_agent agent_router:
description: "Route to the current booking step."
reasoning:
instructions: ->
if @variables.bookingStep == "ChooseDate":
transition to @subagent.choose_date
if @variables.bookingStep == "PartySize":
transition to @subagent.party_size
if @variables.bookingStep == "Confirm":
transition to @subagent.confirm_bookingThen each subagent owns one step and advances the variable when its work is done. The subagent decides whether the customer's answer was good enough. If not, it stays put and asks again. If so, it sets bookingStep to the next step—and on the next turn, the router routes accordingly.
subagent choose_date:
description: "Collect the date the customer wants to book."
reasoning:
instructions: ->
| Ask the customer which date they'd like to book their experience.
If the date is in the past or unclear, ask again until you have a
valid future date.
Once you have a valid date, call {!@actions.advanceToPartySize}.
actions:
advanceToPartySize: @utils.setVariables
description: "Advance the booking to the party size step."
with bookingStep = "PartySize"Think through what this guarantees. The router never sends the customer to confirm_booking until some earlier subagent sets bookingStep to "Confirm"—and a subagent only does that once it's satisfied with the answer it collected. The customer can plead, change the subject, or try to skip ahead, but the sequence holds because routing is determined by a variable you control, not by LLM goodwill.
This is the same instinct behind the loyalty work in the last badge. Let deterministic logic, not the LLM, enforce the rules that matter—scaled up to a whole multi-turn workflow.
Chain Actions Together
Booking often means doing two things back to back. In the last badge, you set the isCreditIssued variable as a follow-up right after IssueResortCredit ran—that was a chained action. Agent Script lets you nest a run inside a reasoning action so the follow-up fires automatically whenever the LLM calls the first one.
subagent confirm_booking:
reasoning:
actions:
book_experience: @actions.book_experience
with date=@variables.bookingDate
set @variables.confirmation_id=@outputs.confirmation_id
run @actions.send_confirmation_email
with confirmation_id=@outputs.confirmation_idWhen the LLM calls book_experience, send_confirmation_email runs immediately after—deterministically, no second decision is required. Chaining keeps your prompts clean (you don't have to tell the LLM to remember step two) and your workflow reliable (step two can't be forgotten).
Run Logic After the LLM Responds
Everything so far runs before the LLM responds. But sometimes you need a deterministic check on every turn afterward. That's the after_reasoning block.
Imagine you want to transfer the conversation to a human when booking conversations drag on too long.
subagent choose_date:
reasoning:
instructions: ->
| Ask the customer which date they'd like to book.
after_reasoning: ->
set @variables.turn_count = @variables.turn_count + 1
if @variables.turn_count >= 5:
transition to @subagent.human_handoffBecause after_reasoning runs after the response is sent, the customer reads the agent's current reply, and the transition to human_handoff takes effect in the next message. It's the right tool for counters, logging, and "if we're still here after N turns" escalations.
Give Subagents a Voice
The system block sets your agent's overall persona—for Coral Cloud Resorts, that’s warm and welcoming. But a single tone doesn't always fit. A billing-dispute subagent might need to be more formal and precise. You can override the persona for just that subagent by adding a system block inside it.
system:
instructions: "You are a warm, friendly Coral Cloud Resorts assistant."
subagent billing_dispute:
description: "Handles billing disputes and account adjustments."
system:
instructions: "You are a precise billing specialist. Use exact language,
cite policy when explaining decisions, and never speculate
about account status."
reasoning:
instructions: ->
| Help the customer resolve their billing dispute.When a subagent has a discrete system block, those instructions replace the global ones for that subagent only—they don't stack. The rest of the agent keeps the friendly Coral Cloud Resorts voice. This also prevents a subtle bug—if your global persona contradicts what a subagent needs to do, the agent can stall trying to reconcile them. An override resolves the conflict cleanly.
Configure Models per Subagent
By default, every subagent uses the model the agent is configured with. But a router that just classifies intent doesn't need the same horsepower as a subagent that writes detailed responses. Use model_config to assign a model at the agent or subagent level.
config:
developer_name: "cc_service_agent"
model_config:
model: "sfdc_ai__DefaultGPT4Omni"
start_agent agent_router:
description: "Classify intent and route to the right subagent."
model_config:
model: "EinsteinHyperClassifier"
reasoning:
instructions: ->
| Determine the best subagent for the customer's request.EinsteinHyperClassifier is a Salesforce-owned model tuned for fast intent classification—a smart default for a router, where speed matters more than prose. Sophisticated models handle the subagents that actually reason and write. A subagent-level model_config overrides the global one for that subagent only—the same override pattern you just learned about for system instructions.
You Can Read and Write Agent Script
You started this journey going through the Canvas to reward loyal Coral Cloud Resorts customers. Now you can open Script view and read exactly what that work produced—and write the parts the Canvas doesn't reach. You know the blocks, the reasoning loop, and the variables that carry state. You can route deterministically, gate tools behind conditions, and enforce a required flow.
And you can hold a multi-turn conversation on the rails with a step variable, chain actions, run after-response logic, reshape a subagent's persona, and tune its model. That's the Agent Script language—and it's the same language whether you write it, the Canvas writes it, or an AI coding tool writes it for your review.
