The definitive guide to Salesforce’s domain-specific language for creating predictable, enterprise-grade AI agents.
Building AI agents for the enterprise has always involved a fundamental tension: flexibility vs. predictability. Large Language Models are brilliant at interpreting natural language and handling ambiguous requests, but when it comes to critical business workflows — processing refunds, verifying identities, or handling loan applications — you need deterministic, step-by-step precision. One wrong interpretation can mean a compliance violation or a frustrated customer.
Salesforce’s answer to this challenge is Agent Script — a high-level, declarative domain-specific language (DSL) purpose-built for the Agentforce platform. Agent Script introduces the concept of hybrid reasoning: the ability to let an LLM handle what it’s good at (conversation, interpretation, flexibility) while enforcing strict programmatic control where business rules demand it.
In this guide, we’ll walk through everything you need to know to build Agentforce agents using Agent Script — from the language fundamentals to production-ready architectural patterns.
Agent Script is a compiled language that gets converted into an Agent Graph — a structured specification consumed by Salesforce’s Atlas Reasoning Engine. You don’t write traditional code that runs line-by-line at runtime. Instead, Agent Script defines the blueprint of your agent: what it knows, how it reasons, what tools it can use, and when it should follow rules versus when it should let the LLM think freely.
| Capability | Description |
|---|---|
| Reasoning boundaries | Define exactly where the LLM reasons vs. where logic is deterministic |
| State management | Variables reliably store information across turns — no relying on LLM memory |
| Conditional logic | if/else expressions control execution paths based on variable states |
| Deterministic actions | Chain actions in precise sequences with guaranteed execution order |
| Topic transitions | Route conversations programmatically or let the LLM decide |
Salesforce provides multiple authoring methods, each suited to a different skill level:
Describe your desired agent behavior conversationally:
“If the order total is over $100, then offer free shipping.”
Agentforce automatically converts your request into the appropriate topics, actions, instructions, and expressions. This is the lowest-barrier entry point.
A visual, block-based editor where Agent Script appears as summarized, expandable blocks. Use shortcuts:
/ for common expression patterns (conditionals, transitions)@ to reference resources (topics, actions, variables)For developers who want full control. Write and edit Agent Script directly with:
For a professional developer workflow, use Agentforce DX to pull script files into a local Salesforce DX project and edit them in VS Code with the Agentforce DX Extension, which provides syntax highlighting, error indicators, and a symbol tree in the Outline view.
You can also use Agentforce Vibes — an AI-assisted coding panel where you describe changes in natural language and the system generates the corresponding Agent Script.
Property Structure: Everything is key: value pairs, with nesting via indentation.
Indentation: Use either spaces (minimum 2) or tabs (1 per level) — but never mix them in a single script. Mixing causes parsing errors.
Comments: Use # for single-line comments.
# This is a comment
config:
agent_label: "Customer Service Bot" # Inline comment
Multiline Strings: Use the pipe symbol |:
instructions: |
You are a helpful customer service agent.
Always be polite and professional.
Never share internal pricing data.
Procedural Instructions: Use the arrow -> followed by indented instructions:
reasoning:
instructions: ->
| Welcome the customer and ask how you can help.
if @variables.is_returning_customer:
| Welcome back! I see you've been with us before.
else:
| Nice to meet you! Let me help you get started.
Access agent resources with the @ symbol:
| Reference | Purpose |
|---|---|
@actions.action_name | Reference an action |
@topic.topic_name | Reference a topic |
@variables.variable_name | Access a variable |
@outputs.output_name | Access action output |
@utils.transition | Utility for topic transitions |
@utils.escalate | Utility for escalation |
Embed dynamic values in natural language instructions using {!expression}:
| Hello, {[email protected]_name}! Your order #{[email protected]_id}
| has a current status of: {[email protected]_status}.
Agent Script supports standard operators for building expressions:
==, !=, >, <+, -is None, is not Noneif, else, elifAn Agent Script file is organized into distinct blocks, each serving a specific role. Here’s the anatomy of a complete agent:
Contains global instructions and required messages for the agent:
system:
instructions: |
You are a professional customer service agent for Acme Corp.
Always maintain a helpful, concise tone.
Never reveal internal policies or pricing formulas.
messages:
welcome: "Hello! How can I help you today?"
error: "I'm sorry, something went wrong. Let me connect you with a human agent."
welcome and error messages are mandatory in every agent script.
Defines basic agent metadata:
config:
developer_name: "Acme_Service_Agent"
agent_label: "Acme Customer Service"
default_agent_user: "[email protected]"
Each agent must have a unique developer_name.
Declares global variables that persist across the agent’s conversation:
variables:
customer_name: mutable string = ""
customer_email: mutable string = ""
is_verified: mutable boolean = False
order_total: mutable number = 0
num_turns: mutable number = 0
Variables are referenced as @variables.customer_name in expressions and {[email protected]_name} inside instruction text.
Specifies supported languages/locales:
language:
- en_US
- es_MX
- fr_FR
Describes how the agent interacts with external channels (e.g., Enhanced Chat). Works with @utils.escalate for handing off to human agents.
start_agent)The entry point for every conversation turn. With each customer utterance, the agent begins execution here. This block handles topic classification, filtering, and routing:
start_agent topic_selector:
description: "Routes conversations to the appropriate topic"
reasoning:
instructions: ->
set @variables.num_turns = @variables.num_turns + 1
| You are an intelligent router. Analyze the customer's message
| and determine which topic best handles their request.
actions:
go_to_orders: @utils.transition to @topic.order_management
description: "Route to order management for order-related queries"
go_to_identity: @utils.transition to @topic.identity_verification
description: "Route to identity verification when authentication is needed"
The workhorses of your agent. Each topic encapsulates the logic for a specific task:
topic order_management:
description: "Handles order lookups, status checks, and modifications"
reasoning:
instructions: ->
if @variables.is_verified == False:
transition to @topic.identity_verification
| Help the customer with their order inquiry.
| You can look up orders by order number or email address.
if @variables.order_status == "delivered":
| The order has been delivered. Ask if they need anything else.
elif @variables.order_status == "pending":
| The order is still being processed. Provide estimated delivery.
else:
| Look up the order details first.
actions:
lookup_order: @actions.get_order_details
description: "Look up order details by order number"
inputs:
order_number: string
description: "The customer's order number"
is_required: True
Understanding execution flow is critical for building reliable agents.
start_agentAgentforce processes reasoning instructions top-to-bottom, building a prompt that will be sent to the LLM. Here’s the critical insight: the LLM doesn’t reason during parsing — it only receives the fully resolved prompt after all instructions have been evaluated.
Consider this script:
topic delivery_check:
description: "Check delivery status"
reasoning:
instructions: ->
set @variables.num_turns = @variables.num_turns + 1
run @actions.get_delivery_date
with order_id = @variables.current_order_id
set @variables.delivery_date = @outputs.estimated_date
| The customer's delivery is expected on {[email protected]_date}.
if @variables.delivery_date is None:
| We could not find delivery info. Apologize and offer alternatives.
else:
| Inform the customer about their delivery date.
Here’s what happens when this topic runs:
num_turns increases by 1get_delivery_date with the stored order ID@variables.delivery_datedelivery_date is NoneWhen the engine encounters a transition command, it immediately jumps to the target topic, discarding any already-built prompt. This is a one-way operation — control never returns to the previous topic.
# This text will NOT be in the final prompt if the transition fires
if @variables.needs_verification:
transition to @topic.identity_verification
# Execution continues here only if the condition was False
| Proceed with the order lookup...
start_agent for potential reclassification.
Let’s build a full customer service agent that handles identity verification and order management:
# ============================================
# SYSTEM CONFIGURATION
# ============================================
system:
instructions: |
You are a customer service agent for Coral Cloud Resort.
Be friendly, professional, and concise.
Never share internal system details with customers.
messages:
welcome: "Welcome to Coral Cloud Resort! How can I assist you today?"
error: "I apologize for the inconvenience. Let me transfer you to a team member."
config:
developer_name: "Coral_Cloud_Service_Agent"
agent_label: "Coral Cloud Concierge"
# ============================================
# GLOBAL VARIABLES
# ============================================
variables:
customer_name: mutable string = ""
customer_email: mutable string = ""
is_verified: mutable boolean = False
verification_code: mutable string = ""
order_id: mutable string = ""
order_status: mutable string = ""
num_turns: mutable number = 0
# ============================================
# ENTRY POINT - TOPIC ROUTER
# ============================================
start_agent topic_selector:
description: "Routes incoming messages to the appropriate topic"
reasoning:
instructions: ->
set @variables.num_turns = @variables.num_turns + 1
| Analyze the customer's message and determine the best topic.
| If the customer wants to check an order, verify identity first.
actions:
verify_identity: @utils.transition to @topic.identity
description: "Verify customer identity before accessing account data"
check_order: @utils.transition to @topic.order_management
description: "Help with order status, tracking, and modifications"
# ============================================
# TOPIC: IDENTITY VERIFICATION
# ============================================
topic identity:
description: "Verifies user identity via email and verification code"
reasoning:
instructions: ->
if @variables.is_verified:
| Customer is already verified. Ask how you can help.
transition to @topic.order_management
if @variables.customer_email == "":
| Ask the customer for their email address.
else:
if @variables.verification_code == "":
run @actions.send_verification_code
with email = @variables.customer_email
set @variables.verification_code = @outputs.code
| A code has been sent to {[email protected]_email}.
| Ask the customer to provide the code.
else:
| Verify the code the customer provides matches
| {[email protected]_code}.
actions:
send_code: @actions.send_verification_code
description: "Send a verification code to the customer's email"
confirm_identity: @actions.confirm_verification
description: "Mark the customer as verified"
after_reasoning: ->
if @variables.is_verified:
transition to @topic.order_management
# ============================================
# TOPIC: ORDER MANAGEMENT
# ============================================
topic order_management:
description: "Enables users to look up order details and check status"
reasoning:
instructions: ->
if @variables.is_verified == False:
transition to @topic.identity
| Help the customer with their order. You can look up orders
| and provide status information.
actions:
get_order: @actions.get_order_details
description: "Look up order information"
order_management topic immediately redirects to identity if the customer isn’t verified — this is deterministic, not left to LLM interpretationis_verified and verification_code persist across turns, so the agent never “forgets”after_reasoning block automatically transitions after verification completesActions are how your agent connects to external systems. Here are the key patterns:
actions:
get_customer: @actions.lookup_customer
description: "Find a customer record by email"
target: "apex://CustomerService.lookupByEmail"
inputs:
email: string
description: "Customer email address"
is_required: True
outputs:
customer_id: string
customer_name: string
account_status: string
Actions can target:
apex://ClassName.methodNameflow://FlowNameRun actions in sequence, passing outputs from one to the next:
run @actions.lookup_customer
with email = @variables.customer_email
set @variables.customer_id = @outputs.customer_id
set @variables.customer_name = @outputs.customer_name
run @actions.get_orders
with customer_id = @variables.customer_id
set @variables.recent_orders = @outputs.orders
Control when actions are available to the LLM:
actions:
process_refund: @actions.issue_refund
description: "Process a refund for the customer"
available when @variables.is_verified
available when clause ensures the refund action is only presented to the LLM as a tool when the customer has been verified — no prompt engineering needed.
For straightforward knowledge-based agents:
start_agent qa_router:
description: "Answers product questions"
reasoning:
instructions: |
Answer the customer's question about our products using
the knowledge base. Be concise and accurate.
actions:
search_kb: @actions.knowledge_search
description: "Search the knowledge base for relevant articles"
For complex agents that handle multiple domains:
start_agent router:
description: "Multi-domain router"
reasoning:
instructions: ->
| Determine what the customer needs and route accordingly.
actions:
to_billing: @utils.transition to @topic.billing
description: "For billing and payment questions"
to_support: @utils.transition to @topic.technical_support
description: "For technical issues and troubleshooting"
to_sales: @utils.transition to @topic.sales
description: "For new purchases and upgrades"
Topics can transition to specialist topics and back:
topic primary_support:
description: "Main support flow"
reasoning:
instructions: ->
| Handle the support request. If billing questions arise,
| consult the billing specialist.
actions:
consult_billing: @utils.transition to @topic.billing_specialist
description: "Get billing expertise for payment sub-questions"
topic billing_specialist:
description: "Billing expertise"
reasoning:
instructions: ->
| Answer the billing question, then return to primary support.
actions:
return: @utils.transition to @topic.primary_support
description: "Return to the main support flow after answering"
Build safety into your agents:
topic safe_operation:
description: "Operation with error handling"
reasoning:
instructions: ->
if @variables.retry_count > 3:
| We've encountered persistent issues. Escalating to a human.
run @utils.escalate
run @actions.process_request
with data = @variables.request_data
set @variables.result = @outputs.result
set @variables.error = @outputs.error_message
if @variables.error is not None:
set @variables.retry_count = @variables.retry_count + 1
| Something went wrong: {[email protected]}.
| Apologize and ask the customer to try again.
else:
| Operation successful. Share the result with the customer.
For teams that prefer a code-first workflow:
Script files live in:
force-app/main/default/aiAuthoringBundles/<bundle-API-name>/
The Agentforce DX extension provides:
.ascript filesUse the Agentforce Vibes panel to describe changes in natural language:
“Add a new topic for handling returns and refunds”
“Add an action to look up customers by phone number”
Validate your script compiles successfully, then deploy to your org for preview and testing.
start_agentif/else and transition for compliance-critical logic. Don’t ask the LLM to “remember to check verification” — make it a programmatic guard clause.
| text) for conversational guidance. The LLM excels at tone, empathy, and adapting to customer phrasing.
after_reasoning for Side Effectsdeveloper_name. This is easy to overlook when copying examples.
system, config, variables, and start_agent blocksAgent Script represents a paradigm shift in how enterprise AI agents are built. By introducing hybrid reasoning — the ability to blend deterministic business logic with LLM-powered conversation — Salesforce has solved one of the hardest problems in enterprise AI: making agents that are both flexible and reliable.
The language is intentionally accessible. If you can read YAML and write basic if/else logic, you can build an Agentforce agent. And with the multiple authoring methods (chat, canvas, script, and DX), there’s an on-ramp for every skill level.
The key insight to take away: don’t fight the LLM, and don’t blindly trust it. Use Agent Script to draw clear boundaries — let the LLM shine at conversation and interpretation, but enforce your business rules with code. That’s the promise of hybrid reasoning, and Agent Script is the tool that delivers it.