Agent Script Syntax Reference
Complete syntax guide for the Agent Script DSL. Your entire agent in one
.agentfile.
Design Principles
| Principle | Description |
|---|---|
| Declarative Over Imperative | Describe WHAT the agent should do, not HOW step-by-step |
| Human-Readable by Design | Syntax resembles structured English - non-engineers can read it |
| Single File Portability | Entire agent definition in one .agent file - copy/paste ready |
| Version Control Friendly | Plain text works with Git - diff, review, rollback |
Block Structure
Required Block Order
config → variables → system → connection → knowledge → language → start_agent → topic
| Block | Required | Purpose |
|---|---|---|
config: |
✅ Yes | Agent metadata and identification |
variables: |
Optional | State management (mutable/linked) |
system: |
✅ Yes | Global messages and instructions |
connection: |
Optional | Escalation routing (connection messaging: — singular, NOT connections:) |
knowledge: |
Optional | Knowledge base configuration |
language: |
Optional | Supported languages |
start_agent: |
✅ Yes | Entry point (exactly one) |
topic: |
✅ Yes | Conversation topics (one or more) |
✅ Validated Finding: Documentation implies strict ordering, but both config-first and system-first orderings compile. Pick one convention and be consistent.
Block Internal Ordering
Within start_agent and topic blocks, sub-blocks follow this order:
description → system → actions → reasoning → after_reasoning
Within a reasoning block:
instructions → actions
Block Definitions
1. system: Block (Required)
system:
messages:
welcome: "Hello! How can I help?"
error: "Sorry, something went wrong."
instructions: "You are a helpful assistant."
| Field | Purpose |
|---|---|
messages.welcome |
Initial greeting message |
messages.error |
Fallback error message |
instructions |
Global system prompt for the agent |
2. config: Block (Required)
config:
developer_name: "refund_agent"
agent_description: "Handles refund requests"
agent_type: "AgentforceServiceAgent"
default_agent_user: "admin@yourorg.com"
| Field | Required | Purpose |
|---|---|---|
developer_name |
✅ Yes | Internal identifier (must match folder name, case-sensitive) |
agent_description |
✅ Yes | Agent's purpose description |
agent_type |
✅ Yes | AgentforceServiceAgent or AgentforceEmployeeAgent |
default_agent_user |
⚠️ REQUIRED | Must be valid Einstein Agent User |
⚠️ Critical:
default_agent_usermust exist in the org with the "Einstein Agent User" profile. Query:SELECT Username FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true
3. variables: Block (Optional)
variables:
# Mutable: State we track and modify
failed_attempts: mutable number = 0
customer_verified: mutable boolean = False
label: "Customer Verified"
order_ids: mutable list[string] = []
order_notes: mutable string = ""
description: |
Notes collected during the order process.
May contain multiple lines of customer input.
# Linked: Read-only from external sources
session_id: linked string
source: @session.sessionID
description: "Current session identifier"
customer_id: linked string
source: @context.customerId
description: "Customer ID from context"
💡 Variables support a
label:property for UI display names and multilinedescription: |using pipe syntax.
Variable Types
| Type | Description | Example |
|---|---|---|
string |
Text values | name: mutable string = "" |
number |
Numeric values | count: mutable number = 0 |
boolean |
True/false flags | verified: mutable boolean = False |
object |
Structured data | data: mutable object = {} |
date |
Calendar dates | created: mutable date |
id |
Unique identifiers | record_id: mutable id |
list[T] |
Arrays of type T | items: mutable list[string] = [] |
Action-Only Types (valid for action I/O, NOT for mutable/linked variables):
| Type | Description |
|---|---|
datetime |
Date and time (action I/O only) |
time |
Time of day (action I/O only) |
integer |
Whole numbers (action I/O only) |
long |
Large whole numbers (action I/O only) |
timestamp |
Date and time (action I/O only) |
currency |
Money values (action I/O only) |
⚠️ Undocumented variable types:
timestampandcurrencycompile as variable types but are absent from official GA documentation. Preferdatefor date/time variables andnumberfor currency values. These types are reliable for action I/O.
Variable Modifiers
| Modifier | Behavior | Use Case |
|---|---|---|
mutable |
Read/write - can be changed during conversation | Counters, flags, accumulated state |
linked |
Read-only - populated from external source | Session IDs, user profiles, context data |
⚠️ Booleans are capitalized: Use
True/False, nottrue/false
4. language: Block (Optional)
language:
default: "en_US"
supported: ["en_US", "es_ES", "fr_FR"]
5. knowledge: Block (Optional)
knowledge:
knowledge_base: "My_Knowledge_Base"
| Field | Purpose |
|---|---|
knowledge_base |
Name of the knowledge base to attach to the agent |
💡 Knowledge bases are configured in the Salesforce org and referenced by name. The knowledge block enables RAG (Retrieval Augmented Generation) capabilities for the agent.
6. connection: Block (Optional)
# Minimal form (no routing):
connection messaging:
adaptive_response_allowed: True
# Full form with escalation routing:
connection messaging:
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "flow://Route_from_Agent"
escalation_message: "Connecting you with a specialist."
adaptive_response_allowed: False
⚠️ Use
connection messaging:(singular, NOTconnections:). The pluralconnections:wrapper is invalid — see known-issues.md Issue 16.
Supported Channels
| Channel | Purpose |
|---|---|
messaging |
Chat/messaging channels (Enhanced Chat, Web Chat, In-App) |
voice |
Voice/phone channels (Service Cloud Voice) |
web |
Web-based channels |
7. topic: Block (Required - one or more)
topic main:
description: "Main conversation handler"
reasoning:
instructions: |
Help the user with their request.
actions:
do_something: @actions.my_action
description: "Action description"
| Field | Purpose |
|---|---|
description |
Helps LLM understand topic purpose |
reasoning.instructions |
Instructions for this topic |
reasoning.actions |
Available actions in this topic |
Topic-Level System Overrides
Topics can override the agent-level system: instructions with their own system: block:
topic specialized_support:
description: "Handles technical support"
system: "You are a technical support specialist. Be precise and methodical."
reasoning:
instructions: |
Help with technical issues.
The topic-level
system:replaces (not appends to) the agent-level system instructions for that topic's reasoning context.
8. start_agent: Block (Required - exactly one)
start_agent entry:
description: "Entry point for conversations"
reasoning:
instructions: |
Greet the user and route appropriately.
actions:
go_main: @utils.transition to @topic.main
description: "Navigate to main topic"
💡 The name can be anything - "main", "entry", "topic_selector" - just be consistent.
Instruction Syntax
Pipe vs Arrow Syntax
| Syntax | Use When | Example |
|---|---|---|
instructions: | |
Simple multi-line text (no expressions) | instructions: | Help the user. |
instructions: -> |
Complex logic with conditionals/actions | instructions: -> if @variables.x: |
Arrow Syntax (->) Patterns
reasoning:
instructions: ->
# Conditional (resolves BEFORE LLM)
if @variables.customer_verified == True:
| Welcome back, verified customer!
else:
| Please verify your identity first.
# Inline action execution
run @actions.load_customer
with customer_id = @variables.customer_id
set @variables.customer_data = @outputs.data
# Variable injection in text
| Customer name: {!@variables.customer_name}
# Deterministic transition
if @variables.failed_attempts >= 3:
transition to @topic.escalation
Instruction Syntax Elements
| Element | Syntax | Purpose |
|---|---|---|
| Literal text | | text |
Text that becomes part of LLM prompt |
| Conditional | if @variables.x: |
Resolves before LLM sees instructions |
| Else clause | else: |
Alternative path |
| Inline action | run @actions.x |
Execute action during resolution |
| Set variable | set @var = @outputs.y |
Capture action output |
| Template injection | Curly-bang syntax: {!@variables.x} | Insert variable value into text |
| Deterministic transition | transition to @topic.x |
Change topic without LLM |
⚠️
else ifis NOT supported: Agent Script does not have anelse ifkeyword. Nestedifinsideelse:is also invalid. Use compound conditions (if A and B:) or flatten to sequentialifstatements.
Multiline String Continuation
Long literal text can span multiple lines using indented continuation:
instructions: |
| This is a long instruction that
continues on the next line without a pipe.
The continued line is indented further than the | line and does NOT start with a new |.
Action Configuration
Action Declaration
actions:
action_name: @actions.my_action
description: "What this action does"
with input_param = @variables.some_value
set @variables.result = @outputs.output_field
available when @variables.is_authorized == True
Action Metadata Properties
Action definitions with target: support the following metadata properties. These are NOT valid on @utils.transition utility actions.
Action-Level:
| Property | Type | Context | Notes |
|---|---|---|---|
label |
String | Action def, topic, I/O | Display name in UI |
description |
String | Action def, I/O | LLM decision-making context |
require_user_confirmation |
Boolean | Action def | Compiles; runtime no-op (Issue 6) |
include_in_progress_indicator |
Boolean | Action def | Shows spinner during execution |
progress_indicator_message |
String | Action def | Custom spinner text |
Input-Level:
| Property | Type | Notes |
|---|---|---|
is_required |
Boolean | Marks input as mandatory |
is_user_input |
Boolean | LLM extracts from conversation |
label |
String | Display name |
description |
String | LLM context |
complex_data_type_name |
String | Lightning type mapping |
Output-Level:
| Property | Type | Notes |
|---|---|---|
filter_from_agent |
Boolean | True = hide from user display (GA standard name) |
is_displayable |
Boolean | False = hide from user (compile-valid alias for filter_from_agent) |
is_used_by_planner |
Boolean | True = LLM can reason about value |
developer_name |
String | Overrides the parameter's developer name |
label |
String | Display name |
description |
String | LLM context |
complex_data_type_name |
String | Lightning type mapping |
Two-Level Action System
Agent Script uses a two-level system for actions. Understanding this distinction is critical:
Level 1: ACTION DEFINITION (in topic's `actions:` block)
→ Has `target:`, `inputs:`, `outputs:`, `description:`
→ Specifies WHAT to call (e.g., "flow://GetOrderStatus")
Level 2: ACTION INVOCATION (in `reasoning.actions:` block)
→ References Level 1 via `@actions.name`
→ Specifies HOW to call it (`with`, `set` clauses)
→ Does NOT use `inputs:`/`outputs:` (use `with`/`set` instead)
Complete Example:
topic order_lookup:
description: "Look up order details"
# Level 1: DEFINE the action (with target + I/O schemas)
actions:
get_order:
description: "Retrieves order information by ID"
inputs:
order_id: string
description: "Customer's order number"
outputs:
status: string
description: "Current order status"
target: "flow://Get_Order_Details"
reasoning:
instructions: |
Help the customer check their order status.
# Level 2: INVOKE the action (with/set, NOT inputs/outputs)
actions:
lookup: @actions.get_order
with order_id = ...
set @variables.order_status = @outputs.status
⚠️ I/O schemas are REQUIRED for publish: Action definitions with only
description:andtarget:(noinputs:/outputs:) will PASS LSP and CLI validation but FAIL server-side compilation with "Internal Error." Always include complete I/O schemas in Level 1 definitions.
Lifecycle Hooks: before_reasoning: and after_reasoning:
Lifecycle hooks enable deterministic pre/post-processing around LLM reasoning. They are FREE (no credit cost).
topic main:
description: "Topic with lifecycle hooks"
# BEFORE: Runs deterministically BEFORE LLM sees instructions
before_reasoning:
# Content goes DIRECTLY here (NO instructions: wrapper!)
set @variables.turn_count = @variables.turn_count + 1
if @variables.needs_redirect == True:
transition to @topic.redirect
# LLM reasoning phase
reasoning:
instructions: ->
| Turn {!@variables.turn_count}: How can I help?
# AFTER: Runs deterministically AFTER LLM finishes reasoning
after_reasoning:
# Content goes DIRECTLY here (NO instructions: wrapper!)
set @variables.interaction_logged = True
Key Rules:
- Content goes directly under the block (NO
instructions:wrapper) - Reliable primitives:
set,if/else,transition to runhas inconsistent runtime behavior in lifecycle blocks across bundle types — use it inreasoning.actions:orinstructions: ->insteadtransition toworks inafter_reasoning:blocks- If a topic transitions mid-execution, the original topic's
after_reasoning:does NOT run - Both hooks are FREE (no credit cost) — use for data prep, logging, cleanup
💡 Official GA docs show
after_reasoning:->(arrow syntax). Our TDD validated the direct-content form. Both forms may work — we recommend the tested direct-content form.
Action Target Protocols
Core Targets (Validated)
| Protocol | Use When | Status |
|---|---|---|
flow:// |
Data operations, business logic | ✅ Validated |
apex:// |
Custom calculations, validation | ✅ Validated |
prompt:// / generatePromptResponse:// |
Grounded LLM responses | ✅ Validated |
api:// |
REST API callouts | ✅ Validated |
retriever:// |
RAG knowledge search | ✅ Validated |
externalService:// |
Third-party APIs via Named Credential | ✅ Validated |
standardInvocableAction:// |
Built-in SF actions | ✅ Validated |
Additional Targets (From agent-script-recipes)
| Protocol | Use When | Status |
|---|---|---|
datacloudDataGraphAction:// |
Data Cloud graph queries | ⚠️ Untested |
datacloudSegmentAction:// |
Data Cloud segment operations | ⚠️ Untested |
triggerByKnowledgeSource:// |
Knowledge-triggered actions | ⚠️ Untested |
contextGrounding:// |
Context grounding operations | ⚠️ Untested |
predictiveAI:// |
Einstein predictions | ⚠️ Untested |
runAction:// |
Sub-action execution | ⚠️ Untested |
external:// |
External services | ⚠️ Untested |
copilotAction:// |
Copilot actions | ⚠️ Untested |
@topic.X |
Topic delegation (supervision) | ✅ Validated |
Note: Untested targets are documented in the official AGENT_SCRIPT.md rules. They may require specific licenses, org configurations, or future API versions.
💡
prompt://is the official shorthand forgeneratePromptResponse://. Both forms resolve to the same target. Example:target: "prompt://Email_Draft_Template"
Utility Actions
| Action | Purpose | Example |
|---|---|---|
@utils.transition to @topic.x |
LLM-chosen topic navigation | go_main: @utils.transition to @topic.main |
@utils.escalate |
Hand off to human agent | escalate: @utils.escalate |
@utils.setVariables |
Set multiple variables | set_vars: @utils.setVariables |
Resource References
| Syntax | Purpose | Example |
|---|---|---|
@variables.x |
Reference a variable | @variables.customer_id |
@actions.x |
Reference an action | @actions.process_refund |
@topic.x |
Reference a topic | @topic.escalation |
@outputs.x |
Reference action output | @outputs.status |
@session.x |
Reference session data | @session.sessionID |
@context.x |
Reference context data | @context.userProfile |
@inputs.x |
Reference procedure input | @inputs.account_number ⚠️ Procedure context only — see Common Pitfalls |
@system_variables.user_input |
Most recent user utterance | @system_variables.user_input |
💡
@system_variablesis a separate namespace from@variables. Theuser_inputsystem variable contains the customer's most recent utterance.
Whitespace Rules
Indentation
| ✅ CORRECT | ❌ INCORRECT |
|---|---|
| 2-space consistent | Mixed tabs and spaces |
| 3-space consistent | Inconsistent spacing |
| Tabs consistent | Tab in one block, spaces in another |
CRITICAL: Never mix tabs and spaces in the same file. This causes compilation errors.
Boolean Values
| ✅ CORRECT | ❌ INCORRECT |
|---|---|
True |
true |
False |
false |
Complete Example
system:
messages:
welcome: "Welcome to Pronto Support!"
error: "Sorry, something went wrong. Let me connect you with a human."
instructions: "You are a helpful customer service agent for Pronto Delivery."
config:
developer_name: "pronto_refund_agent"
agent_description: "Handles customer refund requests with churn risk assessment"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent_user@myorg.com"
variables:
# Mutable state
customer_verified: mutable boolean = False
failed_attempts: mutable number = 0
churn_risk_score: mutable number = 0
refund_status: mutable string = ""
# Linked from session
customer_id: linked string
source: @session.customerId
description: "Customer ID from messaging session"
topic identity_verification:
description: "Verify customer identity before refund processing"
reasoning:
instructions: ->
if @variables.failed_attempts >= 3:
| Too many failed attempts. Escalating to human agent.
transition to @topic.escalation
if @variables.customer_verified == True:
| Identity verified. Proceeding to refund assessment.
transition to @topic.refund_processor
| Please verify your identity by providing your email address.
actions:
verify: @actions.verify_customer
description: "Verify customer by email"
set @variables.customer_verified = @outputs.verified
topic refund_processor:
description: "Process refund based on churn risk assessment"
reasoning:
instructions: ->
# Post-action check (triggers on loop after refund)
if @variables.refund_status == "Approved":
run @actions.create_crm_case
with customer_id = @variables.customer_id
transition to @topic.success
# Pre-LLM: Load churn data
run @actions.get_churn_score
with customer_id = @variables.customer_id
set @variables.churn_risk_score = @outputs.score
# Dynamic instructions based on score
| Customer churn risk: {!@variables.churn_risk_score}%
if @variables.churn_risk_score >= 80:
| HIGH RISK - Offer full cash refund to retain customer.
else:
| LOW RISK - Offer $10 store credit as goodwill.
actions:
process_refund: @actions.process_refund
description: "Issue the refund"
available when @variables.customer_verified == True
set @variables.refund_status = @outputs.status
topic escalation:
description: "Escalate to human agent"
reasoning:
instructions: |
Apologize for the inconvenience and transfer to a human agent.
actions:
handoff: @utils.escalate
description: "Transfer to live support"
topic success:
description: "Successful refund confirmation"
reasoning:
instructions: |
Thank the customer and confirm their refund has been processed.
start_agent topic_selector:
description: "Entry point - route to identity verification"
reasoning:
instructions: |
Greet the customer and begin identity verification.
actions:
start: @utils.transition to @topic.identity_verification
description: "Begin refund process"
Expression Operators
Comparison Operators
| Operator | Description | Example |
|---|---|---|
== |
Equal to | if @variables.status == "active": |
!= |
Not equal to | if @variables.status != "closed": |
< |
Less than | if @variables.count < 10: |
<= |
Less than or equal | if @variables.count <= 5: |
> |
Greater than | if @variables.risk > 80: |
>= |
Greater than or equal | if @variables.attempts >= 3: |
is |
Identity check | if @variables.data is None: |
is not |
Negated identity check | if @variables.data is not None: |
Note: Use
!=for not-equal comparisons. The<>operator does NOT compile.
Logical Operators
| Operator | Description | Example |
|---|---|---|
and |
Logical AND | if @variables.verified == True and @variables.active == True: |
or |
Logical OR | if @variables.status == "open" or @variables.status == "pending": |
not |
Logical NOT | if not @variables.blocked: |
Arithmetic Operators
| Operator | Description | Example |
|---|---|---|
+ |
Addition | set @variables.count = @variables.count + 1 |
- |
Subtraction | set @variables.remaining = @variables.total - @variables.used |
⚠️ NOT supported:
*(multiplication),/(division),%(modulo). For complex arithmetic, use a Flow or Apex action.
Access Operators
| Operator | Description | Example |
|---|---|---|
. |
Property access | @outputs.result.status |
[] |
Index access | @variables.items[0] |
Conditional Expression (Ternary-like)
| Status: {!@variables.status if @variables.status else "pending"}
Expression Limitations (Sandboxed Python AST Subset)
Agent Script expressions use a sandboxed subset of Python. Not all Python operations are available.
Supported:
| Category | Operations |
|---|---|
| Arithmetic | +, - |
| Comparison | ==, !=, <, <=, >, >=, is, is not |
| Logical | and, or, not |
| Ternary | x if condition else y |
| Built-in functions | len(), max(), min() |
| Attribute access | @outputs.result.field |
| Index access | @variables.items[0] |
| String methods | contains, startswith, endswith |
NOT Supported:
| Operation | Workaround |
|---|---|
Multiplication (*) |
Use Flow/Apex action |
Division (/) |
Use Flow/Apex action |
Modulo (%) |
Use Flow/Apex action |
String concatenation (+ on strings) |
Use {!var1}{!var2} template injection |
List slicing (items[1:3]) |
Use Flow to extract sublist |
List comprehensions ([x for x in ...]) |
Use Flow/Apex for list transformation |
| Lambda expressions | Use Flow/Apex action |
for/while loops |
Use topic loop pattern (re-entry) |
import statements |
Not available (security sandbox) |
Apex Complex Type Notation
When action inputs or outputs reference Apex inner classes, use the @apexClassType notation:
@apexClassType/c__OuterClass$InnerClass
| Component | Description | Example |
|---|---|---|
@apexClassType/ |
Required prefix | — |
c__ |
Default namespace (or your package namespace) | c__, myns__ |
OuterClass |
The containing Apex class | OrderService |
$ |
Inner class separator | — |
InnerClass |
The inner class name | LineItem |
Example:
actions:
process_order:
inputs:
line_items: list[object]
complex_data_type_name: "@apexClassType/c__OrderService$LineItem"
target: "apex://OrderService"
Note: This notation is used in the
complex_data_type_namefield of action input/output definitions in Agentforce Assets, not in the.agentfile directly.
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
| Mixed tabs/spaces | SyntaxError: cannot mix |
Use consistent indentation |
| Invalid boolean | Type mismatch | Use True/False (capitalized) |
| Spaces in variable names | Parse error | Use snake_case |
| Mutable + linked | Conflicting modifiers | Choose one modifier |
Missing source: for linked |
Variable empty | Add source: @session.X |
Missing default_agent_user |
Internal error on deploy | Add valid Einstein Agent User |
@inputs in set directive |
Unknown deploy error | Use @utils.setVariables to capture inputs separately, then reference via @variables |
| Bare action name (no prefix) | Action not found / ignored | Always use @actions.action_name in run, templates, and instruction text |
run @actions.X for utility |
Action not found | run @actions.X resolves against topic-level actions: with target: — use @utils.setVariables directly, not via run |
| Null check vs empty string | Wrong comparison for null | Use is None for null checks, == "" for empty strings — they are different |
@inputs in set — Deploy-Breaking Anti-Pattern
# ❌ WRONG — @inputs in set causes unknown error at deploy time
verify: @actions.verify_customer
with account_number=...
set @variables.account_number = @inputs.account_number
# ✅ CORRECT — use @utils.setVariables to capture input separately
collect_input: @utils.setVariables
with account_number=...
verify: @actions.verify_customer
with account_number=@variables.account_number
set @variables.customer_name = @outputs.customer_name
Bare Action Names — Always Use @actions. Prefix
# ❌ WRONG — bare names in run, templates, and instructions
run set_user_name
| Use add_to_cart to add items.
# ✅ CORRECT — always prefix with @actions.
run @actions.set_user_name
| Use {!@actions.add_to_cart} to add items.
is None vs == "" — Different Checks
# ❌ WRONG — checks for empty string, not null
if @variables.data == "":
| Data is missing.
# ✅ CORRECT — checks for null/undefined
if @variables.data is None:
| Data has not been set.
# ✅ CORRECT — checks for empty string
if @variables.data == "":
| Data is set but empty.
Use
is Nonewhen a variable may not have been set at all. Use== ""when checking for an explicitly empty string value.
run @actions.X vs Reasoning-Level Utilities
run @actions.X resolves against the topic-level actions: block (definitions with target:). It does NOT work for reasoning-level utilities like @utils.setVariables.
# ❌ WRONG — set_user_name is defined as @utils.setVariables, not a topic-level action
run @actions.set_user_name # "Action not found" error
# ✅ CORRECT — use @utils.setVariables directly in reasoning.actions:
reasoning:
actions:
set_user_name: @utils.setVariables
with user_name=...