Examples
Real-world AgentScript agents demonstrating key patterns — from a minimal hello world to a multi-subagent order tracking system with actions, callbacks, guards, and slot-filling.
Hello World
The simplest possible agent — a single start_agent block with reasoning instructions. Demonstrates the minimal required structure.
- system
- config
- language
- start_agent
- reasoning.instructions
- | template
system:
instructions: "You are a friendly and empathetic Salesforce Employee bot that helps employees with their day to day questions."
messages:
error: "Sorry, something went wrong."
welcome: "Hello! I'm Greeting Bot. How are you feeling today?"
config:
agent_name: "HelloWorldBot"
default_agent_user: "hello@world.com"
language:
default_locale: "en_US"
start_agent hello_world:
description: "you do things"
reasoning:
instructions: ->
| respond to whatever the user says! Make sure to speak in iambic pentameter
What to notice
system.instructionssets global behavior inherited by all subagents.system.messagesdefines standard messages shown at runtime events (welcome,error).start_agentis the entry point — exactly one required per script.reasoning.instructionsuses->+|for a procedure that builds the LLM's prompt.
Lead Qualification Agent
A conversational lead qualification agent for Salesforce Agentforce. Collects prospect details through natural slot-filling, scores the lead via an action, then deterministically routes to a deep-discovery subagent for qualified leads or a nurture track for prospects not yet ready to buy.
- slot filling
- @utils.setVariables
- lead scoring action
- after_reasoning auto-transition
- score-driven routing
- dynamic instructions
- {! } interpolation
- available when
- multi-subagent flow
Variables
All lead fields are declared as mutable with rich description fields — these descriptions guide the LLM on exactly what to extract from conversation during slot-filling:
variables:
# Prospect identity
lead_name: mutable string = ""
description: "Full name of the prospect"
lead_email: mutable string = ""
description: "Business email address"
# Company profile
company_name: mutable string = ""
description: "Company or organisation name"
company_size: mutable string = ""
description: "Number of employees — e.g. '50-200' or '1000+'"
# Buying signals
budget_range: mutable string = ""
description: "Estimated annual budget for the solution"
timeline: mutable string = ""
description: "Purchasing timeline — e.g. 'Q3 2025' or 'within 6 months'"
use_case: mutable string = ""
description: "Primary use case or pain point the prospect wants to solve"
# Qualification result
lead_score: mutable integer = 0
description: "Calculated lead score 0–100"
lead_qualified: mutable boolean = False
description: "Whether the lead meets minimum qualification criteria"
Subagent 1: lead_intake (start_agent)
The entry point gathers lead details conversationally via @utils.setVariables (slot-filling), then in after_reasoning automatically scores the lead and routes based on the result:
start_agent lead_intake:
description: "Gathers prospect information through natural conversation"
actions:
Score_Lead:
description: "Calculates a lead score from company profile and buying signals"
require_user_confirmation: False
include_in_progress_indicator: True
inputs:
company_size: string
is_required: True
budget_range: string
is_required: True
timeline: string
is_required: True
outputs:
score: integer
qualified: boolean
target: "flow://ScoreLead"
reasoning:
instructions: ->
| Welcome! I'm here to learn about your needs and see how we can help.
| Through natural conversation, gather:
| - Prospect name and business email
| - Company name and approximate size
| - Budget range and purchasing timeline
| - Primary use case or pain point
| Do not ask all questions at once — keep the conversation natural.
actions:
capture_lead: @utils.setVariables
description: "Capture lead details extracted from the conversation"
with lead_name=@variables.lead_name
with lead_email=@variables.lead_email
with company_name=@variables.company_name
with company_size=@variables.company_size
with budget_range=@variables.budget_range
with timeline=@variables.timeline
with use_case=@variables.use_case
after_reasoning:
# Score once all three buying-signal fields are collected
if @variables.company_size != "" and @variables.budget_range != "" and @variables.timeline != "":
run @actions.Score_Lead
with company_size=@variables.company_size
with budget_range=@variables.budget_range
with timeline=@variables.timeline
set @variables.lead_score = @outputs.score
set @variables.lead_qualified = @outputs.qualified
# Route based on qualification result
if @variables.lead_qualified:
transition to @topic.qualified_discovery
else if @variables.lead_score > 0 and not @variables.lead_qualified:
transition to @topic.nurture_track
Subagent 2: qualified_discovery
Deep-dive discovery for qualified leads. Dynamic instructions show score and company context; the LLM can book a demo directly from this subagent:
topic qualified_discovery:
description: "Deep-dive discovery conversation for qualified leads"
actions:
Schedule_Demo:
description: "Books a product demo for the prospect"
inputs:
email: string
is_required: True
company: string
is_required: True
outputs:
meeting_link: string
meeting_time: string
target: "flow://ScheduleDemo"
reasoning:
instructions: ->
| You are speaking with {!@variables.lead_name} from {!@variables.company_name}.
| Lead score: {!@variables.lead_score}/100 — this is a qualified opportunity.
| Conduct deeper discovery to understand:
| - Current tools and workflows
| - Specific pain points and success criteria
| - Decision-making process and key stakeholders
| When the prospect expresses interest in seeing the product, book a demo.
actions:
book_demo: @actions.Schedule_Demo
with email=@variables.lead_email
with company=@variables.company_name
description: "Book a product demo when prospect expresses interest"
Subagent 3: nurture_track
Handles prospects not yet ready to buy. Sends educational resources and uses available when to unlock the re-qualify transition only once the score crosses a threshold:
topic nurture_track:
description: "Educates and nurtures leads that are not yet ready to purchase"
actions:
Send_Nurture_Email:
description: "Sends educational content and case studies to the prospect"
inputs:
email: string
is_required: True
use_case: string
is_required: False
target: "flow://SendNurtureEmail"
reasoning:
instructions: ->
| {!@variables.lead_name} from {!@variables.company_name} is not yet ready to buy.
| Lead score: {!@variables.lead_score}/100.
| Be helpful and educational. Offer relevant resources tailored to their use case.
| Do not pressure. Check back on their timeline and buying criteria.
actions:
send_resources: @actions.Send_Nurture_Email
with email=@variables.lead_email
with use_case=@variables.use_case
description: "Send educational content relevant to the prospect's use case"
move_to_discovery: @utils.transition to @topic.qualified_discovery
description: "Move to deep discovery if prospect signals readiness to buy"
available when @variables.lead_score >= 60
What to notice
- Slot-filling:
@utils.setVariablesinreasoning.actionslets the LLM extract all seven lead fields from conversation — variabledescriptionfields guide extraction without explicit form prompts. - Score gate:
after_reasoningonly firesScore_Leadonce all three buying-signal fields are non-empty, avoiding unnecessary action calls on every turn. - Deterministic routing:
transition toinafter_reasoningmeans the LLM never decides which track to use — that logic is fully deterministic and audit-able. - available when: The
move_to_discoverytransition is hidden from the LLM untillead_score >= 60, preventing premature re-qualification. - Dynamic instructions: Both downstream subagents inject
lead_score,lead_name, andcompany_namedirectly into the LLM prompt via{! }interpolation.
Order Tracking Assistant
A production-style e-commerce support agent with three subagents: order locator, order details viewer, and issue resolver. Demonstrates guards, pre-fetching, slot-filling, callbacks, and multi-subagent transitions.
- variables
- actions with inputs/outputs
- before_reasoning guard
- after_reasoning transitions
- callbacks + @outputs
- @utils.setVariables
- @utils.transition
- available when
- multi-subagent flow
Variables
All state is declared up-front with descriptions that help the LLM understand what each variable holds:
variables:
# Customer identification
customer_email: mutable string = ""
description: "Customer's email address"
customer_verified: mutable boolean = False
description: "Whether customer identity has been verified"
customer_id: mutable string = ""
description: "Internal customer ID"
# Order information
order_number: mutable string = ""
description: "Order number provided by customer"
order_found: mutable boolean = False
description: "Whether order was successfully found"
order_status: mutable string = ""
description: "Current order status"
# Shipping details
tracking_number: mutable string = ""
description: "Shipping tracking number"
return_eligible: mutable boolean = False
description: "Whether order is eligible for return"
Subagent 1: order_locator (start_agent)
The entry point. Uses before_reasoning to reset state on fresh sessions, and after_reasoning to automatically transition once an order is found:
start_agent order_locator:
description: "Locates and validates customer order information"
actions:
Get_Customer_Info:
description: "Retrieves customer information using email address"
require_user_confirmation: False
include_in_progress_indicator: True
inputs:
email: string
is_required: True
outputs:
customer_found: boolean
customer_name: string
customer_id: string
target: "flow://GetCustomerInfo"
Find_Order_By_Number:
description: "Locates order using order number"
inputs:
order_number: string
is_required: True
outputs:
order_found: boolean
target: "flow://FindOrderByNumber"
before_reasoning:
# Reset flags if no search info yet
if @variables.order_number == "" and @variables.customer_email == "":
set @variables.order_found = False
set @variables.customer_verified = False
reasoning:
instructions: ->
| Welcome! I'm here to help you with your order.
| To locate your order, I can search by order number or email.
actions:
lookup_customer: @actions.Get_Customer_Info
with email=@variables.customer_email
set @variables.customer_verified = @outputs.customer_found
set @variables.customer_name = @outputs.customer_name
set @variables.customer_id = @outputs.customer_id
find_order: @actions.Find_Order_By_Number
with order_number=@variables.order_number
set @variables.order_found = @outputs.order_found
capture_order_info: @utils.setVariables
description: "Capture order search information from customer"
with order_number=@variables.order_number
with customer_email=@variables.customer_email
show_order_details: @utils.transition to @topic.order_details
description: "Show detailed order information"
after_reasoning:
# Deterministic post-turn processing
if @variables.customer_email != "":
run @actions.Get_Customer_Info
with email=@variables.customer_email
set @variables.customer_verified = @outputs.customer_found
set @variables.customer_id = @outputs.customer_id
# Auto-advance when order or customer is located
if @variables.order_found or @variables.customer_verified:
transition to @topic.order_details
Subagent 2: order_details
Pre-fetches order data in before_reasoning before the LLM reasons, then presents rich dynamic instructions using interpolation:
topic order_details:
description: "Shows detailed order information including status and tracking"
before_reasoning:
# Pre-fetch details only if not yet loaded
if @variables.order_found and @variables.order_status == "":
run @actions.Get_Order_Details
with order_number=@variables.order_number
with customer_id=@variables.customer_id
set @variables.order_status = @outputs.order_status
set @variables.tracking_number = @outputs.tracking_number
set @variables.delivery_date = @outputs.delivery_date
set @variables.return_eligible = @outputs.return_eligible
reasoning:
instructions: ->
| Great! I found your order. Here are the current details:
| **Order Information:**
| - Customer: {!@variables.customer_name}
| - Order Number: {!@variables.order_number}
| - Status: {!@variables.order_status}
| **Shipping Details:**
| - Tracking Number: {!@variables.tracking_number}
| - Expected Delivery: {!@variables.delivery_date}
actions:
get_live_tracking: @actions.Get_Tracking_Updates
with tracking_number=@variables.tracking_number
set @variables.delivery_date = @outputs.updated_delivery_date
handle_issue: @utils.transition to @topic.issue_resolver
description: "Handle shipping issues or returns"
after_reasoning:
if @variables.issue_type != "":
transition to @topic.issue_resolver
What to notice
- Pre-fetch pattern:
before_reasoningruns theGet_Order_Detailsaction only iforder_statusis still empty — avoiding redundant calls on subsequent turns. - Rich dynamic instructions:
{! @variables.x }interpolation renders current state directly into the LLM's prompt. - Dual-path transitions: The LLM can trigger
handle_issueduring reasoning;after_reasoningalso checks state and transitions deterministically.
Weather Assistant
A multi-subagent weather service with a routing entry point, dedicated subagents for current conditions, forecasts, severe weather alerts, and user preferences. Demonstrates per-subagent system.instructions, auto-transition on alert detection, and complex state management.
- service router pattern
- per-subagent system.instructions
- before_reasoning auto-fetch
- after_reasoning auto-transition
- list[object] variables
- object variables
- multi-target URIs (flow:// + apex://)
- progress_indicator_message
- is_user_input flag
Router entry point
The start_agent acts as a pure router — no actions of its own, just @utils.transition bindings that the LLM selects based on user intent:
start_agent weather_service_router:
description: "Welcome users and route to the appropriate weather service"
system:
instructions: "Analyze user requests and route them to the appropriate weather service. Always prioritize safety by directing users with severe weather concerns to emergency alerts first."
reasoning:
instructions: ->
| Analyze user input to determine weather service intent and route appropriately:
| - For current weather requests, call {!@actions.current_weather}
| - For forecast requests, call {!@actions.weather_forecast}
| - For alerts or safety concerns, call {!@actions.emergency_alerts}
| - For preferences, call {!@actions.user_settings}
actions:
current_weather: @utils.transition to @topic.current_weather_service
description: "Route when users ask for current conditions or temperature."
weather_forecast: @utils.transition to @topic.forecast_service
description: "Route when users ask for future weather predictions."
emergency_alerts: @utils.transition to @topic.severe_weather_alerts
description: "Route when users mention storms, warnings, or safety concerns."
user_settings: @utils.transition to @topic.weather_preferences
description: "Route when users want to set preferences."
Auto-transition on alert detection
The current_weather_service subagent automatically transitions to the alerts subagent if severe weather is detected — a pattern combining pre-fetch and post-turn auto-routing:
topic current_weather_service:
description: "Provides current weather conditions for any location worldwide"
before_reasoning:
# Auto-fetch if location is known
if @variables.user_city != "" and @variables.user_country != "":
run @actions.Get_Current_Weather_Data
with city=@variables.user_city
with country=@variables.user_country
set @variables.temperature = @outputs.temperature_celsius
set @variables.conditions = @outputs.conditions
set @variables.humidity = @outputs.humidity
set @variables.wind_speed = @outputs.wind_speed
after_reasoning:
# Safety-first: auto-route to alerts if severe weather detected
if @variables.severe_weather_alert:
transition to @topic.severe_weather_alerts
Severe weather alerts subagent
Dedicated subagent with its own scoped actions and per-subagent system.instructions emphasizing safety:
topic severe_weather_alerts:
description: "Provides critical severe weather alerts and safety guidance"
system:
instructions: "You are an emergency weather alert specialist. Prioritize user safety by providing clear, actionable severe weather information. Always emphasize following official emergency guidance."
before_reasoning:
# Auto-check for alerts when location is known
if @variables.user_city != "" and @variables.user_country != "":
run @actions.Get_Weather_Alerts_Data
with city=@variables.user_city
with country=@variables.user_country
set @variables.severe_weather_alert = @outputs.alert_count > 0
set @variables.alert_severity = @outputs.highest_severity
set @variables.alert_type = @outputs.active_alerts[0].type
Pattern: Guard + Pre-fetch in before_reasoning
Use before_reasoning to enforce preconditions and pre-load data before the LLM ever sees the turn. This keeps reasoning focused on what to do, not on housekeeping:
before_reasoning:
# Guard: redirect if not verified
if @variables.verified is not True:
transition to @subagent.Identity
# Pre-fetch: load data only when needed and not yet loaded
if @variables.order_id != "" and @variables.order_status == "":
run @actions.GetOrder
with id=@variables.order_id
set @variables.order_status = @outputs.status
Pattern: Slot Filling with @utils.setVariables
Expose @utils.setVariables in reasoning.actions to let the LLM extract values from conversation and set them into variables. Variable description fields guide the LLM on what to extract:
variables:
customer_name: mutable string = ""
description: "Full name of the customer — extract from conversation"
order_number: mutable string = ""
description: "Order number — looks like ORD-12345"
reasoning:
actions:
capture_info: @utils.setVariables
description: "Capture customer name and order number from conversation"
with customer_name=...
with order_number=...
Pattern: Dynamic Instructions with Interpolation
Build context-aware LLM prompts by combining | append semantics, if/else branching, and {! } interpolation. Instructions are re-evaluated each turn so they always reflect current state:
reasoning:
instructions: ->
| Help the customer with their request.
if @variables.verified:
| You are speaking with {!@variables.customer_name} (ID: {!@variables.customer_id}).
if @variables.order_status != "":
| Current order status: {!@variables.order_status}.
| Return eligible: {!"yes" if @variables.return_eligible else "no"}.
else:
| The customer has not been verified yet. Ask for their email or order number.
| Always be concise and professional.
Pattern: Multi-Subagent Flow
Compose agents from multiple subagents, each responsible for a distinct conversation area. Transitions can be triggered by the LLM (via @utils.transition in reasoning), deterministically (via transition to in before_reasoning/after_reasoning), or from action callbacks:
start_agent router:
description: "Routes users to the right subagent"
reasoning:
actions:
go_orders: @utils.transition to @subagent.Orders
description: "Handle order inquiries"
go_billing: @utils.transition to @subagent.Billing
description: "Handle billing questions"
available when @variables.verified is True
subagent Orders:
description: "Handles order lookups and status"
before_reasoning:
# Guard: must be verified to access orders
if @variables.verified is not True:
transition to @subagent.Identity
reasoning:
actions:
go_back: @utils.transition to @subagent.router
description: "Return to main menu"
subagent Identity:
description: "Verifies customer identity before granting access"
reasoning:
actions:
verify: @actions.VerifyCustomer
with email=@variables.customer_email
set @variables.verified = @outputs.verified
transition to @subagent.router