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
hello_world.agent
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.instructions sets global behavior inherited by all subagents.
  • system.messages defines standard messages shown at runtime events (welcome, error).
  • start_agent is the entry point — exactly one required per script.
  • reasoning.instructions uses -> + | 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:

lead_qualification.agent — variables
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:

lead_qualification.agent — start_agent lead_intake
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:

lead_qualification.agent — topic qualified_discovery
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:

lead_qualification.agent — topic nurture_track
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.setVariables in reasoning.actions lets the LLM extract all seven lead fields from conversation — variable description fields guide extraction without explicit form prompts.
  • Score gate: after_reasoning only fires Score_Lead once all three buying-signal fields are non-empty, avoiding unnecessary action calls on every turn.
  • Deterministic routing: transition to in after_reasoning means the LLM never decides which track to use — that logic is fully deterministic and audit-able.
  • available when: The move_to_discovery transition is hidden from the LLM until lead_score >= 60, preventing premature re-qualification.
  • Dynamic instructions: Both downstream subagents inject lead_score, lead_name, and company_name directly 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:

order_tracking_assistant.agent — variables
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:

order_tracking_assistant.agent — start_agent order_locator
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:

order_tracking_assistant.agent — topic order_details
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_reasoning runs the Get_Order_Details action only if order_status is 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_issue during reasoning; after_reasoning also 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:

weather.agent — start_agent weather_service_router
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:

weather.agent — topic current_weather_service (after_reasoning)
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:

weather.agent — topic severe_weather_alerts (before_reasoning)
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