Complete syntax reference for the Agent Script language used in Agentforce. Includes AiAuthoringBundle compatibility notes, reserved words, and error troubleshooting. **Updated**: December 2025 - Verified through systematic testing. ---
Installs into .claude/skills of the current project.
Are you the author of 005 Tier 013a934a?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-005-tier-013a934a)
<!-- TIER: 2 | PRIMARY REFERENCE -->
<!-- Read after: SKILL.md -->
<!-- Read before: Specialized guides (actions-reference.md, prompt-templates.md) -->
# Agent Script Reference
Complete syntax reference for the Agent Script language used in Agentforce.
Includes AiAuthoringBundle compatibility notes, reserved words, and error troubleshooting.
**Updated**: December 2025 - Verified through systematic testing.
---
## File Structure
Agent Script files use the `.agent` extension and contain YAML-like syntax with specific Agent Script keywords.
### ⚠️ CRITICAL: Two Deployment Methods
There are **two deployment methods** with **different capabilities**:
| Aspect | GenAiPlannerBundle | AiAuthoringBundle |
|--------|-------------------|-------------------|
| Deploy Command | `sf project deploy start` | `sf agent publish authoring-bundle` |
| **Visible in Agentforce Studio** | ❌ NO | ✅ YES |
| Flow Actions (`flow://`) | ✅ Supported | ✅ Supported (with exact name matching) |
| Apex Actions (`apex://`) | ✅ Supported | ⚠️ Limited (class must exist) |
| Escalation (`@utils.escalate with reason`) | ✅ Supported (tested Dec 2025) | ❌ NOT Supported (SyntaxError) |
| `run` keyword (action callbacks) | ✅ Supported (tested Dec 2025) | ❌ NOT Supported (SyntaxError) |
| Variables without defaults | ✅ Supported | ✅ Supported (tested Dec 2025) |
| Lifecycle blocks (`before/after_reasoning`) | ✅ Supported | ✅ Supported (tested Dec 2025) |
| Topic transitions (`@utils.transition`) | ✅ Supported | ✅ Supported |
| Basic escalation (`@utils.escalate`) | ✅ Supported | ✅ Supported |
| API Version | v65.0+ required | v65.0+ required |
**Why the difference?** These methods correspond to two authoring experiences:
- **Script View** (GenAiPlannerBundle): Full Agent Script syntax with utility actions (transition, set variables, escalate) inherent to the script
- **Canvas/Builder View** (AiAuthoringBundle): Low-code visual builder where some utility actions are not yet available
Salesforce is working on feature parity - future releases will add more actions and variable management to the Canvas view.
---
### AiAuthoringBundle (Visible in Agentforce Studio)
**Use this when**: You need agents visible in Agentforce Studio UI.
**Required Files**:
```
force-app/main/default/aiAuthoringBundles/[AgentName]/
├── [AgentName].agent # Agent definition
└── [AgentName].bundle-meta.xml # Metadata XML
```
**bundle-meta.xml content**:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<bundleType>AGENT</bundleType>
</AiAuthoringBundle>
```
---
### GenAiPlannerBundle (Full Feature Support)
**Use this when**: You need flow actions, escalation with reasons, or full Agent Script syntax.
**Required Files**:
```
force-app/main/default/genAiPlannerBundles/[AgentName]/
├── [AgentName].genAiPlannerBundle # XML manifest
└── agentScript/
└── [AgentName]_definition.agent # Agent Script file
```
**genAiPlannerBundle content**:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<GenAiPlannerBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Agent description</description>
<masterLabel>Agent Label</masterLabel>
<plannerType>Atlas__ConcurrentMultiAgentOrchestration</plannerType>
</GenAiPlannerBundle>
```
**⚠️ WARNING**: Agents deployed via GenAiPlannerBundle exist in org metadata but do **NOT appear** in Agentforce Studio UI!
### Block Order (CRITICAL)
Blocks MUST appear in this order:
1. `system:` - Instructions and messages
2. `config:` - Agent metadata
3. `variables:` - Linked and mutable variables
4. `language:` - Locale settings
5. `start_agent [name]:` - Entry point topic
6. `topic [name]:` - Additional topics
### Complete Working Example
```agentscript
system:
instructions: "You are a helpful assistant. Be professional and friendly."
messages:
welcome: "Hello! How can I help you today?"
error: "I apologize, but I encountered an issue."
config:
agent_name: "My_Agent"
default_agent_user: "user@org.salesforce.com"
agent_label: "My Agent"
description: "A helpful assistant agent"
variables:
EndUserId: linked string
source: @MessagingSession.MessagingEndUserId
description: "Messaging End User ID"
RoutableId: linked string
source: @MessagingSession.Id
description: "Messaging Session ID"
ContactId: linked string
source: @MessagingEndUser.ContactId
description: "Contact ID"
user_query: mutable string
description: "User's current question"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
start_agent topic_selector:
label: "Topic Selector"
description: "Routes users to appropriate topics"
reasoning:
instructions: ->
| Determine user intent and route.
actions:
go_help: @utils.transition to @topic.help
go_farewell: @utils.transition to @topic.farewell
topic help:
label: "Help"
description: "Provides help to users"
reasoning:
instructions: ->
| Answer the user's question helpfully.
topic farewell:
label: "Farewell"
description: "Ends conversation gracefully"
reasoning:
instructions: ->
| Thank the user and say goodbye.
```
---
## Indentation Rules
**CRITICAL**: Agent Script is whitespace-sensitive (like Python/YAML). Use **CONSISTENT indentation** throughout.
| Rule | Details |
|------|---------|
| **Tabs (Recommended)** | ✅ Use tabs for easier manual editing and consistent alignment |
| **Spaces** | 2, 3, or 4 spaces also work if used consistently |
| **Mixing** | ❌ NEVER mix tabs and spaces (causes parse errors) |
| **Consistency** | All lines at same nesting level must use same indentation |
**⚠️ RECOMMENDED: Use TAB indentation for all Agent Script files.** Tabs are easier to edit manually and provide consistent visual alignment across editors.
```agentscript
# ✅ RECOMMENDED - consistent tabs (best for manual editing)
config:
agent_name: "My_Agent"
description: "Description"
# ✅ ALSO CORRECT - consistent spaces (if you prefer)
config:
agent_name: "My_Agent"
description: "Description"
# ❌ WRONG - mixing tabs and spaces
config:
agent_name: "My_Agent" # tab
description: "My agent" # spaces - PARSE ERROR!
```
---
## Blocks
### System Block
Global agent settings and instructions. **Must be first block**.
```agentscript
system:
instructions: "You are a helpful assistant. Be professional."
messages:
welcome: "Hello! How can I help you today?"
error: "I'm sorry, something went wrong. Please try again."
```
⚠️ **NOTE**: System instructions must be a single quoted string. The `|` pipe multiline syntax does NOT work in the `system:` block (only in `reasoning: instructions: ->`).
```agentscript
# ✅ CORRECT - Single quoted string
system:
instructions: "You are a helpful customer service agent. Be professional and courteous. Never share confidential information."
messages:
welcome: "Hello!"
error: "Sorry, an error occurred."
```
**✅ Template Expressions Work in System Instructions:**
While pipe syntax doesn't work, template expressions `{!expression}` ARE supported in system.instructions:
```agentscript
# ✅ Template expressions work in system.instructions
system:
instructions: "Welcome {!@variables.user_name}! You are speaking with {!@variables.agent_persona}. Today's date is {!@variables.current_date}."
messages:
welcome: "Hello {!@variables.user_name}!"
error: "Sorry, an error occurred."
```
This allows dynamic personalization in the system prompt while still using a quoted string.
### Config Block
Defines agent metadata. **Required fields**: agent_name/developer_name, default_agent_user, agent_label, description.
**📝 NOTE: No Separate Config File!** All configuration goes directly in the `.agent` file's `config:` block. There is no separate `.agentscript`, `.agentconfig`, or similar config file format.
```agentscript
config:
agent_name: "Customer_Support_Agent"
default_agent_user: "agent.user@company.salesforce.com"
agent_label: "Customer Support"
description: "Helps customers with orders and inquiries"
agent_type: "AgentforceServiceAgent"
enable_enhanced_event_logs: False
```
**Core Fields:**
| Field | Required | Description |
|-------|----------|-------------|
| `agent_name` | Yes | API name (letters, numbers, underscores only) |
| `developer_name` | Yes | Same as agent_name - use one or the other (not both) |
| `default_agent_user` | Yes | Username for agent execution context |
| `agent_label` | Yes | Human-readable name |
| `description` | Yes | What the agent does |
**Optional Fields:**
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `agent_type` | String | `"AgentforceServiceAgent"` | Agent type: `"AgentforceServiceAgent"` or `"AgentforceEmployeeAgent"` |
| `enable_enhanced_event_logs` | Boolean | `False` | Enable detailed event logging for debugging |
| `agent_template` | String | `None` | Base template for agent behavior |
| `outbound_flow` | String | `None` | Flow to invoke for outbound messages |
| `additional_parameter__*` | Any | — | Dynamic parameters (prefix with `additional_parameter__`) |
**Example with all fields:**
```agentscript
config:
developer_name: "Enterprise_Service_Agent"
default_agent_user: "service.agent@company.salesforce.com"
agent_label: "Enterprise Service Agent"
description: "Handles complex enterprise service requests"
agent_type: "AgentforceServiceAgent"
enable_enhanced_event_logs: True
outbound_flow: "Send_Welcome_Message"
additional_parameter__tier: "enterprise"
additional_parameter__region: "AMER"
```
**NOTE**: Both `agent_name` and `developer_name` work (they're aliases for the same field). Use one or the other, not both.
### Variables Block
Declares state variables. **Linked variables first, then mutable**.
**Linked Variables** (connect to Salesforce data - REQUIRED):
```agentscript
variables:
EndUserId: linked string
source: @MessagingSession.MessagingEndUserId
description: "Messaging End User ID"
RoutableId: linked string
source: @MessagingSession.Id
description: "Messaging Session ID"
ContactId: linked string
source: @MessagingEndUser.ContactId
description: "Contact ID"
```
**Mutable Variables** (agent state):
```agentscript
variables:
# Without defaults - works in both deployment methods
user_name: mutable string
description: "The customer's name"
order_count: mutable number
description: "Number of items in cart"
is_verified: mutable boolean
description: "Whether identity is verified"
# With explicit defaults - also valid (optional)
status: mutable string = ""
description: "Current status"
counter: mutable number = 0
description: "A counter"
```
**Note**: Both syntaxes (with or without defaults) work in **both** GenAiPlannerBundle and AiAuthoringBundle deployments. Tested December 2025.
### Language Block
Locale settings. **Required for deployment**.
```agentscript
language:
default_locale: "en_US"
additional_locales: "en_GB,de,fr"
all_additional_locales: False
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `default_locale` | String | Yes | Primary locale (e.g., "en_US") |
| `additional_locales` | String | No | Comma-separated additional locales (e.g., "en_GB,de,fr") |
| `all_additional_locales` | Boolean | No | Set `True` to support all available locales |
**Locale Format**: Use standard locale codes like `en_US`, `en_GB`, `de`, `fr`, `es`, `ja`, etc.
### Topic Blocks
Define conversation topics. **Each topic requires `label:` and `description:`**.
### start_agent Block (Entry Point)
**Every agent MUST have exactly one `start_agent` block** - this is the entry point where conversations begin.
**Two forms are supported:**
```agentscript
# Named form (recommended for multi-topic agents)
start_agent topic_selector:
label: "Topic Selector"
description: "Routes users to appropriate topics"
# Unnamed form (simpler, for single-topic agents)
start_agent:
label: "Main Topic"
description: "Handles all user requests"
```
**When to use each:**
| Form | Syntax | Use When |
|------|--------|----------|
| **Named** | `start_agent topic_name:` | Multi-topic agent with routing |
| **Unnamed** | `start_agent:` | Single-topic agent, no routing needed |
**Named form** creates a topic that can be referenced with `@topic.topic_name` for transitions back to the entry point.
**Entry point example** (named, recommended):
```agentscript
start_agent topic_selector:
label: "Topic Selector"
description: "Routes users to appropriate topics"
reasoning:
instructions: ->
| Determine user intent and route.
actions:
go_orders: @utils.transition to @topic.orders
```
**Regular topic**:
```agentscript
topic orders:
label: "Order Management"
description: "Handles order inquiries"
reasoning:
instructions: ->
| Help with order questions.
actions:
back: @utils.transition to @topic.topic_selector
```
### Topic-Level System Instruction Overrides
**Topics can override the global `system:` block to change agent behavior per topic.** This enables persona switching, tone changes, or specialized behavior without creating separate agents.
```agentscript
# Global system instructions (default behavior)
system:
instructions: "You are a versatile assistant that adapts based on context."
messages:
welcome: "Welcome! I adapt my behavior based on the conversation."
error: "I encountered an issue. Please try again."
# Topic with system override - professional mode
topic professional:
label: "Professional Mode"
description: "Professional business communication"
system:
instructions: "You are a formal business professional. Use professional language, avoid casual expressions, focus on efficiency and clarity."
reasoning:
instructions: ->
| [Professional Mode Engaged]
| Respond with formal business tone.
actions:
return_general: @utils.transition to @topic.general
# Topic with different override - creative mode
topic creative:
label: "Creative Mode"
description: "Creative brainstorming assistant"
system:
instructions: "You are a creative brainstorming partner. Think outside the box, suggest unconventional ideas, be imaginative and supportive."
reasoning:
instructions: ->
| [Creative Mode Activated]
| Generate creative solutions and explore possibilities.
actions:
return_general: @utils.transition to @topic.general
# Topic without override - uses global system instructions
topic general:
label: "General"
description: "General conversation"
reasoning:
instructions: ->
| Respond in the default conversational style.
actions:
go_professional: @utils.transition to @topic.professional
go_creative: @utils.transition to @topic.creative
```
**How Topic-Level Overrides Work:**
- If a topic has a `system:` block → use that topic's instructions
- If a topic has NO `system:` block → inherit the global `system:` instructions
- Overrides apply only while in that topic; transitioning away restores the target topic's behavior
**Use Cases:**
- **Persona Switching**: Switch between professional, casual, technical modes
- **Specialized Domains**: Different expertise per topic (technical support vs billing)
- **Localization**: Different tone for different regions/contexts
- **Compliance**: Strict guidelines for sensitive topics (financial, medical)
---
## Variable Types
### Complete Type Reference
| Type | Description | Example | AiAuthoringBundle |
|------|-------------|---------|-------------------|
| `string` | Text values | `name: mutable string = "John"` | ✅ Supported |
| `number` | Numeric (integers & decimals) | `price: mutable number = 99.99` | ✅ Supported |
| `boolean` | True/False (capitalized!) | `active: mutable boolean = True` | ✅ Supported |
| `date` | YYYY-MM-DD format | `start: mutable date = 2025-01-15` | ✅ Supported |
| `datetime` | Full timestamp | `created: mutable datetime` | ✅ Supported |
| `time` | Time only | `appointment: mutable time` | ✅ Supported |
| `currency` | Money values | `total: mutable currency` | ✅ Supported |
| `id` | Salesforce Record ID | `record_id: mutable id` | ✅ Supported |
| `object` | Complex object with Lightning type | See advanced syntax below | ✅ Supported |
| `list[type]` | Array of values | `list[string]`, `list[number]` | ✅ Supported |
| `integer` | Integer values only | `count: mutable integer = 5` | ❌ NOT Supported |
| `long` | Long integers | `big_num: mutable long = 9999999999` | ❌ NOT Supported |
**⚠️ CRITICAL: `integer` and `long` types are NOT supported in AiAuthoringBundle!**
- Validation fails with: "Variable with type integer is not supported for mutable variables"
- Use `number` instead for all numeric values (works for both integers and decimals)
**⚠️ CRITICAL: Collection syntax uses SQUARE BRACKETS, not angle brackets!**
- ✅ CORRECT: `list[string]`, `list[number]`, `list[boolean]`
- ❌ WRONG: `list<string>` (causes "Unexpected '<'" syntax error)
- Only primitive types allowed in lists: `string`, `number`, `boolean`
- `list[object]` is NOT supported
**Notes**:
- Boolean values must be capitalized: `True`, `False`
### Type Restrictions: Mutable vs Linked Variables
**Mutable variables** (state you modify during conversation):
| Type | Mutable | Notes |
|------|---------|-------|
| `string` | ✅ | Primary text type |
| `number` | ✅ | Use for ALL numeric (integer + decimal) |
| `boolean` | ✅ | Must use `True`/`False` |
| `date` | ✅ | YYYY-MM-DD format |
| `timestamp` | ✅ | Use instead of `datetime` |
| `currency` | ✅ | Money values |
| `id` | ✅ | Salesforce Record IDs |
| `list[type]` | ✅ | `list[string]`, `list[number]`, `list[boolean]` only |
| `object` | ✅ | Complex types with `complex_data_type_name` |
| `datetime` | ❌ | **Use `timestamp` instead** |
| `time` | ❌ | Not supported for mutable variables |
| `integer` | ❌ | **Use `number` instead** |
| `long` | ❌ | **Use `number` instead** |
**Linked variables** (connect to Salesforce data):
| Type | Linked | Notes |
|------|--------|-------|
| `string` | ✅ | Most common for IDs as strings |
| `number` | ✅ | Numeric fields |
| `boolean` | ✅ | Checkbox fields |
| `date` | ✅ | Date fields |
| `timestamp` | ✅ | DateTime fields |
| `currency` | ✅ | Currency fields |
| `id` | ✅ | Salesforce ID fields |
| `list[*]` | ❌ | **Collections NOT supported for linked** |
| `object` | ❌ | **Complex types NOT supported for linked** |
### Advanced `object` Type with Lightning Data Types (Tested Dec 2025)
The `object` type enables fine-grained control over action inputs/outputs using Lightning data types:
```agentscript
inputs:
order_number: object
description: "The Order Number"
label: "order_number"
is_required: False
is_user_input: False
complex_data_type_name: "lightning__textType"
outputs:
order_id: object
description: "The Record ID"
label: "order_id"
complex_data_type_name: "lightning__textType"
filter_from_agent: False
is_used_by_planner: True
is_displayable: False
```
### Lightning Data Types (`complex_data_type_name`)
Lightning Types are Salesforce's complex type system for action inputs/outputs. Use them when you need explicit type mapping between AgentScript and Flows/Apex.
**When to use:**
- Passing data to/from Flows with specific Salesforce types
- Ensuring proper serialization of complex values
- Mapping record types and relationships
**Available Lightning Types:**
| Lightning Type | Use For | Base Type |
|---------------|---------|-----------|
| `lightning__textType` | Text/String values | `string` |
| `lightning__numberType` | Numeric values (integer/decimal) | `number` |
| `lightning__booleanType` | Boolean True/False | `boolean` |
| `lightning__dateTimeStringType` | DateTime as ISO string | `timestamp` |
| `lightning__recordInfoType` | Salesforce Record references | `id` |
| `lightning__currencyType` | Currency values | `currency` |
| `lightning__dateType` | Date values (no time) | `date` |
| `lightning__percentType` | Percentage values | `number` |
| `lightning__urlType` | URL/hyperlink values | `string` |
| `lightning__emailType` | Email addresses | `string` |
| `lightning__phoneType` | Phone numbers | `string` |
**Example with complex types:**
```agentscript
actions:
update_record: flow://Update_Customer_Record
inputs:
customer_id: object
description: "Customer record ID"
complex_data_type_name: "lightning__recordInfoType"
update_date: object
description: "Date of update"
complex_data_type_name: "lightning__dateTimeStringType"
outputs:
success: object
complex_data_type_name: "lightning__booleanType"
```
**Input Attributes:** `is_required`, `is_user_input`, `label`, `complex_data_type_name`
**Output Attributes and Defaults:**
| Attribute | Type | Default | Description |
|-----------|------|---------|-------------|
| `filter_from_agent` | Boolean | `False` | Hide sensitive output from LLM (still stored) |
| `is_used_by_planner` | Boolean | `True`* | Allow LLM to use output for reasoning |
| `is_displayable` | Boolean | `False` | Show output to user in chat |
| `complex_data_type_name` | String | — | Lightning type for Salesforce integration |
***Default behavior for `is_used_by_planner`:**
- Defaults to `True` UNLESS `filter_from_agent` is `True`
- When `filter_from_agent: True`, `is_used_by_planner` automatically becomes `False`
- This prevents sensitive data from reaching the LLM
**Example - Sensitive data handling:**
```agentscript
outputs:
customer_name: string
description: "Customer's name"
is_used_by_planner: True # LLM can use this
is_displayable: True # User sees this
ssn_last_four: string
description: "Last 4 of SSN (for verification)"
filter_from_agent: True # Hidden from LLM
is_used_by_planner: False # Auto-set by filter_from_agent
is_displayable: False # Don't show user
```
**⚠️ CRITICAL: `filter_from_agent` is NOT supported in AiAuthoringBundle!**
- Causes "Unexpected 'filter_from_agent'" syntax error
- Use conditional topic routing in instructions as alternative
- Works in GenAiPlannerBundle
### Data Type Mappings with Flow (Tested Dec 2025)
| Agent Script Type | Flow Data Type | Status | Notes |
|-------------------|----------------|--------|-------|
| `string` | String | ✅ Works | Standard text values |
| `number` | Number (scale=0) | ✅ Works | Integer values |
| `number` | Number (scale>0) | ✅ Works | Decimal values |
| `boolean` | Boolean | ✅ Works | Use `True`/`False` |
| `list[string]` | Text Collection | ✅ Works | Use `isCollection=true` in Flow |
| `string` | Date | ✅ Works* | *See String I/O pattern below |
| `string` | DateTime | ✅ Works* | *See String I/O pattern below |
**⚠️ All Flow inputs must be provided!** If Flow has more input variables than Agent Script defines, publish fails with "Internal Error".
### Date/DateTime Handling (No Native Types)
Agent Script does NOT have native `date` or `datetime` types. Direct type coercion between `string` (Agent Script) and `Date`/`DateTime` (Flow) will fail.
**Use the String I/O Pattern:**
1. Flow accepts String inputs (not Date/DateTime)
2. Flow parses strings internally with `DATEVALUE()` or `DATETIMEVALUE()`
3. Flow converts back to String for output with `TEXT()`
```xml
<!-- Flow variables use String, not Date -->
<variables>
<name>inp_DateString</name>
<dataType>String</dataType> <!-- NOT Date -->
<isInput>true</isInput>
</variables>
<formulas>
<name>parsedDate</name>
<dataType>Date</dataType>
<expression>DATEVALUE({!inp_DateString})</expression>
</formulas>
```
```agentscript
# Agent Script uses string type for dates
inputs:
inp_DateString: string
description: "Date in YYYY-MM-DD format"
```
---
## Resource References
Use the `@` prefix to reference resources.
| Resource | Syntax | Usage | AiAuthoringBundle |
|----------|--------|-------|-------------------|
| Variables | `@variables.name` | Access stored values | ✅ Supported |
| Actions | `@actions.name` | Invoke defined actions | ✅ Supported |
| Topics | `@topic.name` | Reference topics | ✅ Supported |
| Outputs | `@outputs.field` | Action output values | ✅ Supported |
| Utilities | `@utils.transition` | Built-in utilities | ✅ Supported |
| Utilities | `@utils.escalate` | Escalate to human | ✅ Supported |
| Utilities | `@utils.setVariables` | Set multiple variables | ⚠️ GenAiPlannerBundle only |
| Utilities | `@utils.set` | Set single variable | ⚠️ GenAiPlannerBundle only |
### @utils.setVariables Usage
**In GenAiPlannerBundle (Supported):**
```agentscript
# ✅ GenAiPlannerBundle - @utils.setVariables works
reasoning:
actions:
set_some_variables: @utils.setVariables
description: "Set user context variables"
available when @variables.needs_update == True
with user_name=... # LLM slot-fills, inherits type from variable
with visit_count=@variables.visit_count + 1 # Fixed expression
clear_session: @utils.setVariables
description: "Reset session state"
with is_logged_in=False
with cart_items=[]
```
**Key Behaviors:**
- `with var=...` - LLM slot-fills the value (inherits type from variable definition)
- `with var=@expression` - Fixed value from expression (not slot-filled)
- Types must match variable declarations
**In AiAuthoringBundle (NOT Supported):**
⚠️ `@utils.setVariables` and `@utils.set` cause "Unknown utils declaration type" errors in AiAuthoringBundle. Use the `set` keyword in instructions instead:
```agentscript
# ❌ WRONG - @utils.setVariables NOT supported in AiAuthoringBundle
reasoning:
actions:
update_state: @utils.setVariables
with user_name=...
with is_verified=True
# ✅ CORRECT - Use 'set' keyword in instructions (AiAuthoringBundle)
reasoning:
instructions: ->
| Ask the user for their name.
set @variables.user_name = ...
| Verify the user.
set @variables.is_verified = True
```
```agentscript
# Variable reference
if @variables.user_name is None:
# Action reference
invoke: @actions.get_order
with order_id=@variables.current_order_id
# Topic reference
go: @utils.transition to @topic.checkout
# Output capture
set @variables.status = @outputs.order_status
# Escalation
escalate: @utils.escalate
description: "Transfer to human agent"
```
---
## Instructions
### Syntax (CRITICAL)
Use `instructions: ->` (with space before arrow), NOT `instructions:->`.
```agentscript
# ✅ CORRECT
reasoning:
instructions: ->
| Determine user intent.
# ❌ WRONG - missing space before arrow
reasoning:
instructions:->
| Determine user intent.
```
### Prompt Mode (|)
Use `|` for natural language instructions:
```agentscript
instructions: ->
| This is line one.
| This is line two.
| Each line starts with a pipe.
```
### Procedural Mode (->)
Use `->` for logic-based instructions:
```agentscript
instructions: ->
if @variables.amount > 1000:
| This is a large order.
else:
| Standard order processing.
```
### Procedural Instructions with Inline Actions (Advanced Pattern)
**You can execute actions directly inside the `instructions:->` block for conditional data loading.** This pattern fetches data only when needed, reducing unnecessary API calls.
```agentscript
topic order_status:
label: "Order Status"
description: "Looks up and explains order status"
actions:
get_order_status:
description: "Retrieves current status for an order"
inputs:
order_id: string
description: "The unique order identifier"
outputs:
status: string
description: "Current order status"
tracking_number: string
description: "Shipping tracking number"
target: "flow://GetOrderStatus"
reasoning:
instructions:->
# Step 1: Validate input - ask for order ID if missing
if not @variables.order_id:
| Ask the customer for their order number so you can look up the status.
# Step 2: Fetch data only when we have order_id but not status yet
if @variables.order_id and not @variables.order_status:
run @actions.get_order_status
with order_id=@variables.order_id
set @variables.order_status = @outputs.status
set @variables.tracking_number = @outputs.tracking_number
# Step 3: Provide context-specific guidance based on status
| The customer's order {!@variables.order_id} has status: {!@variables.order_status}
if @variables.order_status == "pending":
| The order is being processed. Let them know:
| - Order is confirmed and being prepared
| - They'll receive tracking info within 24 hours
if @variables.order_status == "shipped":
| The order has shipped! Provide:
| - Tracking number: {!@variables.tracking_number}
| - Link to track shipment
if @variables.order_status == "delivered":
| The order was delivered. Confirm they received it.
| Ask if everything was satisfactory.
| Be proactive and helpful. Anticipate what the customer might need next.
```
**Key Pattern Features:**
- **Conditional Data Loading**: `if @variables.x and not @variables.y:` guards prevent redundant API calls
- **Action Inside Instructions**: `run @actions.x` executes directly in the instruction flow
- **State-Based Guidance**: Different instructions appear based on variable values
- **Progressive Flow**: Ask for input → Fetch data → Provide tailored response
**When to Use This Pattern:**
- Data should only be fetched after certain conditions are met
- You need to adapt instructions based on fetched data
- The action result determines what guidance to provide
**⚠️ Note on Deployment Methods:**
- This pattern uses `run` keyword which has limited support in AiAuthoringBundle
- Test thoroughly - if validation fails, consider GenAiPlannerBundle deployment
### ⚠️ CRITICAL: Slot Filling (`...`) Cannot Be Inside Conditionals (Tested Dec 2025)
**Slot filling with the `set @variables.x = ...` syntax CANNOT be placed inside conditionals!**
This causes `SyntaxError: Unexpected 'if'` during validation.
```agentscript
# ❌ WRONG - Slot filling inside conditional causes SyntaxError
instructions: ->
if @variables.name is None:
| Please provide your name.
set @variables.name = ... # FAILS! Cannot use ... inside if block
# ✅ CORRECT - Use unconditional slot filling, LLM handles context
instructions: ->
| If the user hasn't provided their name, ask for it.
set @variables.name = ...
| Once you have the name, proceed with the order.
# ✅ ALSO CORRECT - Use action with slot filling (not in conditional)
reasoning:
actions:
collect_info: @actions.collect_customer_info
with name=...
with email=...
set @variables.name = @outputs.name
```
**Why?** The `...` ellipsis is evaluated at parse time, not runtime. The parser cannot determine conditional paths, so slot filling must be at the top level of instructions.
**⚠️ CRITICAL: Pipes Cannot Be Nested Inside Pipes!**
```agentscript
# ❌ WRONG - Nested pipes cause "Start token somehow not of the form | + 2 spaces" error
instructions: ->
| Some text.
| if @variables.name is None:
| | Please provide your name. # NESTED PIPE - FAILS!
# ✅ CORRECT - Conditionals at same level as pipes, not nested inside
instructions: ->
| Some introductory text.
if @variables.name is None:
| Please provide your name.
| More text continues here.
```
### Template Expressions
Use `{!...}` for variable interpolation:
```agentscript
instructions: ->
| Hello {!@variables.user_name}!
| Your order total is ${!@variables.total}.
```
---
## Conditionals
### If/Else
```agentscript
instructions: ->
if @variables.amount > 1000:
| Large order - requires approval.
else:
| Standard order.
if @variables.status == "shipped":
| Your order is on its way!
if @variables.email is None:
| Please provide your email address.
if @variables.verified == True:
| Identity confirmed.
```
### Comparison Operators
| Operator | Meaning | Example |
|----------|---------|---------|
| `==` | Equals | `@variables.status == "active"` |
| `!=` | Not equals | `@variables.count != 0` |
| `>` | Greater than | `@variables.amount > 100` |
| `<` | Less than | `@variables.count < 10` |
| `>=` | Greater or equal | `@variables.age >= 18` |
| `<=` | Less or equal | `@variables.priority <= 5` |
### Null Checks
```agentscript
if @variables.name is None:
| Name not provided.
if @variables.email is not None:
| Email is available.
```
### Logical Operators
| Operator | Meaning | Example |
|----------|---------|---------|
| `and` | Both conditions true | `@variables.a and @variables.b` |
| `or` | At least one true | `@variables.x or @variables.y` |
| `not` | Negation | `not @variables.flag` |
```agentscript
# Combine conditions with AND
if @variables.verified == True and @variables.amount > 0:
| Processing verified request.
# Check multiple conditions with OR
if @variables.is_vip == True or @variables.loyalty_years > 5:
| Premium customer detected.
# Negate a condition
if not @variables.is_blocked:
| Access granted.
```
### N-ary Boolean Operations (3+ Operands)
**AgentScript fully supports N-ary boolean operations** - you can chain 3 or more conditions with `and` or `or` operators. This is the recommended pattern instead of nested if statements.
```agentscript
# ✅ Three+ conditions with AND
if @variables.is_authenticated and @variables.has_permission and @variables.is_active:
transition to @topic.authorized
# ✅ Three+ conditions with OR
if @variables.is_admin or @variables.is_moderator or @variables.is_owner:
transition to @topic.elevated_access
# ✅ Complex multi-condition check (4+ operands)
if @variables.verified and @variables.amount > 0 and @variables.email is not None and @variables.consent == True:
| All validation criteria met. Proceeding with order.
# ✅ N-ary in available when (action conditional availability)
actions:
process_return: @actions.process_return
description: "Process a product return"
available when @variables.eligible == True and @variables.order_id is not None and @variables.tier != "basic"
apply_premium_discount: @actions.calculate_discount
description: "Apply premium customer discount"
available when @variables.is_premium or @variables.loyalty_years > 5 or @variables.total_spent > 10000
```
**⚠️ Mixing `and`/`or`**: While you can chain multiple `and` or multiple `or` operators, avoid mixing them without clear grouping (parentheses are NOT supported). Use separate conditions for complex mixed logic.
```agentscript
# ⚠️ AVOID - Mixed and/or can be ambiguous
if @variables.a and @variables.b or @variables.c: # Unclear precedence
# ✅ PREFER - Separate conditions for clarity
if @variables.a and @variables.b:
| Path A: Both A and B are true.
if @variables.c:
| Path B: C is true.
```
### ⚠️ CRITICAL: Nested If Statements NOT Supported (Tested Dec 2025)
**Nested if statements (if inside if) cause parse errors in AiAuthoringBundle!**
```agentscript
# ❌ WRONG - Nested if causes "Missing required element" and "Unexpected 'else'"
if @variables.is_premium == True:
| User is premium.
if @variables.order_total > 1000: # NESTED IF - FAILS!
| Large order.
else:
| Regular order.
else:
| Standard user.
# ✅ CORRECT - Use flat conditionals with 'and' operators
if @variables.is_premium == True and @variables.order_total > 1000:
| Premium user with large order.
if @variables.is_premium == True and @variables.order_total <= 1000:
| Premium user with regular order.
if @variables.is_premium == False:
| Standard user.
```
### Math Operators (Tested Dec 2025)
Math operators work in both `set` statements and conditions:
```agentscript
# ✅ Addition in set statement
set @variables.counter = @variables.counter + 1
# ✅ Subtraction in set statement
set @variables.remaining = @variables.total - 100
# ✅ Math in conditions
if @variables.counter + 5 > 10:
| Counter plus 5 is greater than 10.
if @variables.total - 50 < 0:
| Total minus 50 would be negative.
```
| Operator | Works in `set` | Works in `if` | Example |
|----------|---------------|---------------|---------|
| `+` | ✅ Yes | ✅ Yes | `set @variables.x = @variables.x + 1` |
| `-` | ✅ Yes | ✅ Yes | `if @variables.total - 50 < 0:` |
### Additional Expression Features
**Length function (`len`):**
```agentscript
# Check if list is empty
if len(@variables.cart_items) == 0:
| Your cart is empty.
# Check list size for pagination
if len(@variables.results) > 10:
| Showing first 10 of {!len(@variables.results)} results.
```
**Ternary expression (`x if condition else y`):**
```agentscript
# Conditional value assignment
set @variables.greeting = "VIP" if @variables.is_premium else "Customer"
# In output context
| Hello {!"VIP" if @variables.tier == "premium" else "valued customer"}!
```
**Unary negation (`-`):**
```agentscript
# Negate a value
set @variables.offset = -@variables.amount
```
**⚠️ Grouping Parentheses:**
Parentheses `()` for expression grouping are NOT well-supported. Avoid complex expressions that require precedence grouping:
```agentscript
# ⚠️ AVOID - complex grouped expressions may not parse
if (@variables.a and @variables.b) or @variables.c:
| This may not work.
# ✅ PREFER - split into separate conditions
if @variables.a and @variables.b:
| Path A.
if @variables.c:
| Path C.
```
---
## Comments
Use the `#` symbol to add comments. Everything after `#` on a line is ignored.
```agentscript
# This is a comment - the parser ignores this line
config:
agent_name: "My_Agent" # Inline comment explaining the field
description: "A helpful agent"
# Comments help document your agent script
# Use them to explain complex logic
```
---
## Actions
### Defining Actions
```agentscript
topic my_topic:
label: "My Topic"
description: "Topic description"
actions:
get_order:
description: "Retrieves order details"
inputs:
order_id: string
description: "The order ID"
outputs:
status: string
description: "Order status"
total: number
description: "Order total"
target: "flow://Get_Order_Details"
reasoning:
instructions: ->
| Help the user with their order.
```
### Action-Level Attributes (Advanced)
Beyond `inputs`, `outputs`, `target`, and `description`, actions support additional attributes for controlling behavior:
| Attribute | Type | Description | Default |
|-----------|------|-------------|---------|
| `require_user_confirmation` | Boolean | Prompt user to confirm before executing | `False` |
| `include_in_progress_indicator` | Boolean | Show progress indicator during execution | `False` |
| `label` | String | Display label for the action | Action name |
**Example with Advanced Attributes:**
```agentscript
actions:
cancel_order:
description: "Cancels an order and issues refund"
require_user_confirmation: True # ★ Ask user before executing destructive action
include_in_progress_indicator: True # ★ Show "Processing..." indicator
label: "Cancel Order"
inputs:
order_id: string
description: "Order to cancel"
reason: string
description: "Cancellation reason"
outputs:
refund_amount: number
description: "Refund amount"
target: "flow://Cancel_Order"
```
**When to Use:**
- `require_user_confirmation: True` - For destructive operations (delete, cancel), financial transactions, or any action with significant consequences
- `include_in_progress_indicator: True` - For long-running operations where users should know processing is happening
### Referencing Actions in Instructions
You can reference actions in reasoning instructions to guide LLM behavior. This is **different from invoking actions** - it's a hint to the LLM about available capabilities.
```agentscript
reasoning:
instructions: ->
| Help the customer with their order inquiry.
|
| Available capabilities:
| - Use {!@actions.get_order_status} to look up order details when they provide an order ID
| - Use {!@actions.update_order} if they want to modify their order (requires confirmation)
| - Use {!@actions.cancel_order} if they want to cancel (requires confirmation)
|
| Always confirm destructive actions before proceeding.
actions:
lookup: @actions.get_order_status
with order_id=...
# ... other action invocations
```
**⚠️ Important**: This `{!@actions.x}` reference in instructions is **NOT invocation** - it's documentation for the LLM. Actual invocation happens in the `actions:` block within reasoning.
### Target Formats
**Common Target Types:**
| Type | Format | Example |
|------|--------|---------|
| Flow | `flow://FlowName` | `flow://Get_Order_Details` |
| Apex | `apex://ClassName.methodName` | `apex://OrderService.getOrder` |
| Prompt | `prompt://PromptTemplateName` | `prompt://Customer_Greeting` |
| Quick Action | `quickAction://ObjectName.ActionName` | `quickAction://Case.Close` |
| External Service | `externalService://ServiceName` | `externalService://PaymentAPI` |
**All 22 Valid Action Types:**
```
apex, apexRest, api, auraEnabled, cdpMlPrediction,
createCatalogItemRequest, executeIntegrationProcedure,
expressionSet, externalConnector, externalService, flow,
generatePromptResponse, integrationProcedureAction, mcpTool,
namedQuery, prompt, quickAction, retriever, runExpressionSet,
serviceCatalog, slack, standardInvocableAction
```
### ⚠️ CRITICAL: Flow Action Requirements (Both Methods)
**`flow://` actions work in BOTH AiAuthoringBundle and GenAiPlannerBundle**, but require **EXACT variable name matching**:
```
ERROR: "property account_id was not found in the available list of
properties: [inp_AccountId]"
This error appears as generic "Internal Error, try again later" in CLI.
```
**The "Internal Error" typically means your Agent Script input/output names don't match the Flow variable names!**
**✅ Correct Pattern:**
```xml
<!-- Flow variable -->
<variables>
<name>inp_AccountId</name> <!-- This is the API name -->
<isInput>true</isInput>
</variables>
```
```agentscript
# Agent Script - MUST use exact same name
inputs:
inp_AccountId: string # ← MATCHES Flow variable!
description: "Account ID"
```
**❌ Wrong Pattern (causes Internal Error):**
```agentscript
# Agent Script - different name fails!
inputs:
account_id: string # ← DOES NOT MATCH "inp_AccountId"!
description: "Account ID"
```
**Requirements for Flow Actions:**
1. Agent Script input/output names **MUST exactly match** Flow variable API names
2. Flow must be **Autolaunched Flow** (not Screen Flow)
3. Flow variables marked "Available for input" / "Available for output"
4. Flow must be deployed to org **BEFORE** agent publish
**⚠️ Flow Validation Timing:**
- `sf agent validate authoring-bundle` checks **syntax only** - does NOT verify flow exists
- `sf project deploy start` validates flow existence at **deployment time**
- Agent can PASS validation but FAIL deployment if flow is missing!
```bash
# ✅ Passes - only checks syntax
sf agent validate authoring-bundle --api-name My_Agent
# Status: COMPLETED, Errors: 0
# ❌ Fails if flow doesn't exist
sf project deploy start --source-dir .../My_Agent
# Error: "We couldn't find the flow: flow://Missing_Flow"
```
### Invoking Actions
```agentscript
reasoning:
actions:
# LLM fills input from conversation
lookup: @actions.get_order
with order_id=...
# Fixed value
default: @actions.get_order
with order_id="DEFAULT"
# Variable binding
bound: @actions.get_order
with order_id=@variables.current_order_id
# Capture outputs
full: @actions.get_order
with order_id=...
set @variables.status = @outputs.status
set @variables.total = @outputs.total
```
### Action Callbacks (Chaining)
```agentscript
process: @actions.create_order
with items=...
set @variables.order_id = @outputs.order_id
run @actions.send_confirmation
with order_id=@variables.order_id
run @actions.update_inventory
with items=@variables.cart_items
```
**Note**: Only one level of `run` nesting is supported.
### Conditional Availability
```agentscript
checkout: @actions.process_payment
with amount=@variables.total
available when @variables.cart_count > 0
available when @variables.verified == True
```
### ⚠️ CRITICAL: Complex Action Invocation Syntax Not Supported (Tested Dec 2025)
**The `{!@actions.x}` interpolation syntax does NOT work for action invocation!**
```agentscript
# ❌ WRONG - Template syntax for action invocation fails
reasoning:
actions:
invoke: {!@actions.search} # FAILS! SyntaxError
# ❌ WRONG - Dynamic action selection not supported
reasoning:
instructions: ->
| Based on the query, invoke {!@actions.search_type}. # FAILS!
# ✅ CORRECT - Define actions explicitly, LLM chooses based on description
reasoning:
instructions: ->
| Search for information based on the user's query.
actions:
search_products: @actions.product_search
with query=...
description: "Search product catalog"
search_orders: @actions.order_search
with query=...
description: "Search order history"
search_knowledge: @actions.kb_search
with query=...
description: "Search knowledge base"
```
**How it works**: Define multiple actions with clear descriptions. The LLM automatically selects the appropriate action based on context - you don't need to explicitly "invoke" actions programmatically.
---
## Lifecycle Blocks
Use `before_reasoning` and `after_reasoning` blocks for automatic initialization and cleanup.
### ⚠️ CRITICAL SYNTAX RULES for Lifecycle Blocks
| Rule | Details |
|------|---------|
| **Transition Syntax** | Use `transition to` NOT `@utils.transition to` |
| **No Pipe (`\|`)** | The pipe command is NOT supported - use only logic/actions |
| **after_reasoning May Skip** | If a transition occurs mid-topic, `after_reasoning` won't execute |
### Transition Syntax by Context (0-shot Critical)
| Context | Correct Syntax | Wrong Syntax |
|---------|----------------|--------------|
| `reasoning.actions:` | `go_x: @utils.transition to @topic.x` | `go_x: transition to @topic.x` |
| `before_reasoning:` | `transition to @topic.x` | `@utils.transition to @topic.x` ❌ |
| `after_reasoning:` | `transition to @topic.x` | `@utils.transition to @topic.x` ❌ |
| After action `set` | `transition to @topic.x` | `@utils.transition to @topic.x` |
```agentscript
# ❌ WRONG - @utils.transition doesn't work in lifecycle blocks
before_reasoning:
if @variables.expired == True:
@utils.transition to @topic.expired # FAILS!
# ✅ CORRECT - Use "transition to" (no @utils) in lifecycle blocks
before_reasoning:
if @variables.expired == True:
transition to @topic.expired # WORKS!
# ✅ CORRECT - Use @utils.transition in reasoning.actions
reasoning:
actions:
go_expired: @utils.transition to @topic.expired
available when @variables.expired == True
```
### before_reasoning
Runs **BEFORE** each reasoning step. Use for:
- Incrementing turn counters
- Refreshing context data
- Initializing session state
- Conditional routing based on state
```agentscript
topic conversation:
before_reasoning:
set @variables.turn_count = @variables.turn_count + 1
# First turn initialization
if @variables.turn_count == 1:
run @actions.get_timestamp
set @variables.session_start = @outputs.current_timestamp
# Refresh context every turn
run @actions.refresh_context
with user_id=@variables.EndUserId
set @variables.current_context = @outputs.context
reasoning:
instructions: ->
| Turn {!@variables.turn_count}: Use the context above.
```
### after_reasoning
Runs **AFTER** each reasoning step. Use for:
- Logging analytics
- Updating timestamps
- Cleanup operations
```agentscript
topic conversation:
reasoning:
instructions: ->
| Respond to the user.
after_reasoning:
run @actions.log_turn
with turn_number=@variables.turn_count
with topic="conversation"
run @actions.update_last_activity
set @variables.last_activity = @outputs.timestamp
```
### ⚠️ CRITICAL: Empty Lifecycle Blocks Cause Errors (Tested Dec 2025)
**Lifecycle blocks with only comments or whitespace cause SyntaxError!**
```agentscript
# ❌ WRONG - Empty block with just a comment causes SyntaxError
topic my_topic:
label: "My Topic"
description: "A topic"
before_reasoning:
# This is just a placeholder comment # FAILS!
reasoning:
instructions: ->
| Help the user.
# ❌ WRONG - Empty after_reasoning block
after_reasoning:
# TODO: Add logging later # FAILS!
# ✅ CORRECT - Don't include lifecycle blocks if they're empty
topic my_topic:
label: "My Topic"
description: "A topic"
reasoning:
instructions: ->
| Help the user.
# ✅ CORRECT - Include actual content if you need the block
before_reasoning:
set @variables.initialized = True
```
**Rule**: If a lifecycle block would be empty, simply omit it entirely.
### Block Order
When using lifecycle blocks, the order must be:
1. `before_reasoning:` (optional)
2. `reasoning:` (required)
3. `after_reasoning:` (optional)
```agentscript
topic my_topic:
label: "My Topic"
description: "Topic with lifecycle blocks"
before_reasoning:
# Runs first
set @variables.ready = True
reasoning:
# Main logic
instructions: ->
| Help the user.
after_reasoning:
# Runs last
run @actions.log_event
```
---
## Topic Transitions
### Basic Transition
```agentscript
reasoning:
actions:
go_orders: @utils.transition to @topic.orders
```
### Topic Delegation vs Transition
**AgentScript supports two ways to move between topics with different behaviors:**
| Feature | `@utils.transition to` | `@topic.*` (delegation) |
|---------|------------------------|-------------------------|
| Returns to calling topic? | ❌ NO (permanent handoff) | ✅ YES (can return) |
| Use in `reasoning.actions` | ✅ Yes | ✅ Yes |
| Use in lifecycle blocks | ✅ Yes (use bare `transition to`) | ❌ No |
| Typical use case | Menu routing, permanent flow changes | Consult specialist, get help, then continue |
**Transition (permanent handoff):**
```agentscript
# Control moves to orders topic and STAYS there
reasoning:
actions:
go_orders: @utils.transition to @topic.orders
# User will continue in orders topic
```
**Delegation (can return):**
```agentscript
# Control moves to specialist topic, which can return back
reasoning:
actions:
consult_specialist: @topic.specialist_topic
description: "Consult specialist for complex questions"
available when @variables.needs_expert_help == True
```
**When to use each:**
- **`@utils.transition to`**: Menu navigation, workflow steps where you don't return
- **`@topic.*`**: Getting expert help, asking a sub-topic then continuing the current flow
### ⚠️ CRITICAL: @utils.transition Cannot Have Description on Next Line (Tested Dec 2025)
**Transition actions do NOT support the `description:` field on the next line!**
```agentscript
# ❌ WRONG - description: on next line causes SyntaxError
reasoning:
actions:
go_orders: @utils.transition to @topic.orders
description: "Route to orders topic" # FAILS!
# ✅ CORRECT - Simple format only (no description)
reasoning:
actions:
go_orders: @utils.transition to @topic.orders
# ✅ CORRECT - If you need documentation, use comments
reasoning:
actions:
# Route to orders topic when user asks about orders
go_orders: @utils.transition to @topic.orders
```
**Note**: The LLM uses the **topic's `description:` field** to decide when to transition, not the action description.
### Conditional Transition
```agentscript
go_checkout: @utils.transition to @topic.checkout
available when @variables.cart_count > 0
```
### Inline Transition After Action
**You can add a `transition to` directly after an action's `set` statements to automatically change topics after the action completes.**
```agentscript
# Pattern: Action executes, then automatically transitions to next topic
search_hotels: @actions.search_hotels
with location=...
with check_in=...
with check_out=...
set @variables.search_results = @outputs.results
transition to @topic.hotel_booking # Auto-transition after action
# Complete workflow example
topic hotel_browse:
label: "Browse Hotels"
description: "Browse available hotels"
actions:
search_hotels:
description: "Search for available hotels"
inputs:
location: string
description: "City or area to search"
check_in: string
description: "Check-in date"
check_out: string
description: "Check-out date"
outputs:
results: object
description: "List of matching hotels"
target: "flow://SearchHotels"
reasoning:
instructions: ->
| Help the user find available hotels.
actions:
search: @actions.search_hotels
with location=...
with check_in=...
with check_out=...
set @variables.hotels = @outputs.results
transition to @topic.hotel_booking # Go to booking after search
topic hotel_booking:
label: "Book Hotel"
description: "Handle hotel reservation"
actions:
create_booking:
description: "Create a hotel reservation"
inputs:
hotel_id: string
description: "Selected hotel"
outputs:
confirmation: string
description: "Booking confirmation"
success: boolean
description: "Whether booking succeeded"
target: "flow://CreateBooking"
reasoning:
instructions: ->
| Available hotels: {!@variables.hotels}
| Help the user select and book a hotel.
actions:
book: @actions.create_booking
with hotel_id=...
set @variables.booking_confirmed = @outputs.success
set @variables.confirmation = @outputs.confirmation
transition to @topic.hotel_confirmation # Go to confirmation after booking
```
**Syntax Comparison:**
```agentscript
# Standalone transition (in actions block)
go_booking: @utils.transition to @topic.booking
# Inline transition (after action execution)
my_action: @actions.do_something
with param=...
set @variables.result = @outputs.result
transition to @topic.next # No @utils prefix!
```
**⚠️ Important Syntax Note:**
- Standalone transitions use: `@utils.transition to @topic.name`
- Inline transitions (after actions) use: `transition to @topic.name` (no `@utils` prefix)
### Escalation to Human
**AiAuthoringBundle** (basic escalation only):
```agentscript
topic escalation:
label: "Escalation"
description: "Transfers to human agent"
reasoning:
instructions: ->
| Transfer the conversation to a human.
actions:
# ⚠️ IMPORTANT: "escalate" is a reserved word - use "go_to_escalate" or similar
go_to_escalate: @utils.escalate
description: "Escalate to a human agent"
```
**GenAiPlannerBundle** (supports reason parameter):
```agentscript
reasoning:
actions:
escalate_human: @utils.escalate with reason="Customer requested human agent"
```
⚠️ **CRITICAL**: The `with reason="..."` syntax is **ONLY supported in GenAiPlannerBundle**!
- AiAuthoringBundle will fail with `SyntaxError: Unexpected 'with'` or `SyntaxError: Unexpected 'escalate'`
- Use the basic `@utils.escalate` with `description:` for AiAuthoringBundle agents
⚠️ **CRITICAL**: `escalate` is a **RESERVED WORD** - do NOT use as action name!
- Using `escalate:` as action name causes `SyntaxError: Unexpected 'escalate'`
- Use alternatives like `go_to_escalate:`, `transfer_to_human:`, `human_handoff:`
⚠️ **CRITICAL**: `@utils.escalate` REQUIRES `description:` field! (Tested Dec 2025)
```agentscript
# ❌ WRONG - Missing description causes ValidationError
reasoning:
actions:
transfer: @utils.escalate # FAILS! No description
# ✅ CORRECT - Must include description on next line
reasoning:
actions:
transfer: @utils.escalate
description: "Transfer to human agent"
```
**Note**: Unlike `@utils.transition` which does NOT support description, `@utils.escalate` REQUIRES it.
### Connection Block (Required for Omni-Channel Escalation)
**The connection block is REQUIRED for `@utils.escalate` to route to Omni-Channel.**
Both `connection` (singular) and `connections` (plural) forms are supported.
```agentscript
# ✅ CORRECT - Connection block for Omni-Channel routing
connection messaging:
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "Support_Queue_Flow"
escalation_message: "Transferring you to a human agent now..."
adaptive_response_allowed: True
```
**Multiple Channels** (use plural `connections:` form):
```agentscript
connections:
messaging:
escalation_message: "Transferring to messaging agent..."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "agent_support_flow"
adaptive_response_allowed: True
telephony:
escalation_message: "Routing to technical support..."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "technical_support_flow"
adaptive_response_allowed: False
```
**⚠️ CRITICAL: Connection Block Requirements (Tested Dec 2025)**
| Field | Required | Valid Values | Notes |
|-------|----------|--------------|-------|
| `outbound_route_type` | Yes | `"OmniChannelFlow"` only | ❌ `queue`, `skill`, `agent` NOT supported |
| `outbound_route_name` | Yes | Flow API name | Must exist in org |
| `escalation_message` | Yes | String message | Required when other fields present |
| `adaptive_response_allowed` | No | `True` / `False` | Allow agent to adapt responses (default: False) |
**Supported Channels:**
| Channel | Description |
|---------|-------------|
| `messaging` | Chat/messaging (Enhanced Chat, Web Chat, In-App) |
| `telephony` | Voice/phone (Service Cloud Voice) |
**Common Errors:**
- Using `outbound_route_type: "queue"` causes validation error - use `"OmniChannelFlow"`
- Missing `escalation_message` causes parse/validation error
- If referenced OmniChannelFlow doesn't exist in org, publish fails at "Publish Agent" step (BotDefinition NOT created)
```agentscript
# ❌ WRONG - "queue" not supported
connection messaging:
outbound_route_type: "queue"
outbound_route_name: "Support_Queue"
# ❌ WRONG - Missing escalation_message
connection messaging:
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "Support_Queue_Flow"
```
---
## Deployment
### Choose Your Deployment Method
| Need | Method | Command |
|------|--------|---------|
| Agent visible in Agentforce Studio | AiAuthoringBundle | `sf agent publish authoring-bundle` |
| Flow actions (`flow://`) | GenAiPlannerBundle | `sf project deploy start` |
| Escalation with reason | GenAiPlannerBundle | `sf project deploy start` |
| Variables without defaults | GenAiPlannerBundle | `sf project deploy start` |
| Both visibility AND flow actions | ❌ Not currently possible | - |
---
### AiAuthoringBundle Deployment
**Deploy command**:
```bash
sf agent publish authoring-bundle --api-name [AgentName] --target-org [alias]
```
This command:
- Validates Agent Script syntax
- Creates Bot, BotVersion, GenAi metadata
- Deploys the AiAuthoringBundle
- **Agent appears in Agentforce Studio** ✅
**⚠️ HTTP 404 Error Issue (Tested Dec 2025)**:
The `sf agent publish authoring-bundle` command may fail with `ERROR_HTTP_404` during "Retrieve Metadata" step:
- If "Publish Agent" step completed (✔), the **BotDefinition WAS created** successfully
- However, the **AiAuthoringBundle metadata is NOT deployed** to the org
- This means **agents will be INVISIBLE in Agentforce Studio UI** even though they exist!
**FIX**: After HTTP 404 error, run `sf project deploy start` to deploy the metadata:
```bash
# Deploy the AiAuthoringBundle metadata to make agent visible in UI
sf project deploy start --source-dir force-app/main/default/aiAuthoringBundles/[AgentName] --target-org [alias]
# Verify the metadata was deployed
sf org list metadata --metadata-type AiAuthoringBundle --target-org [alias]
```
**Other commands**:
```bash
# Validate without publishing
sf agent validate authoring-bundle --api-name [AgentName] --target-org [alias]
# Open in Agentforce Studio
sf org open agent --api-name [AgentName] --target-org [alias]
# Activate agent
sf agent activate --api-name [AgentName] --target-org [alias]
```
---
### GenAiPlannerBundle Deployment
**Deploy command**:
```bash
sf project deploy start --source-dir force-app/main/default/genAiPlannerBundles/[AgentName] --target-org [alias]
# Or deploy all agent bundles
sf project deploy start --metadata GenAiPlannerBundle --target-org [alias]
```
This command:
- Deploys agent to org metadata
- Supports full Agent Script syntax (flow actions, escalation with reason)
- ⚠️ **Agent does NOT appear in Agentforce Studio UI**
**Retrieve command**:
```bash
sf project retrieve start --metadata "GenAiPlannerBundle:[AgentName]" --target-org [alias]
```
**Requirements**:
- `sourceApiVersion: "65.0"` or higher in sfdx-project.json
- Flows must be deployed before agent if using `flow://` targets
---
## Common Patterns
### Simple Q&A Agent
```agentscript
system:
instructions: "You are a helpful FAQ assistant. Answer concisely."
messages:
welcome: "Hello! How can I help?"
error: "Sorry, an error occurred."
config:
agent_name: "FAQ_Agent"
default_agent_user: "agent@company.com"
agent_label: "FAQ Assistant"
description: "Answers frequently asked questions"
variables:
EndUserId: linked string
source: @MessagingSession.MessagingEndUserId
description: "End User ID"
RoutableId: linked string
source: @MessagingSession.Id
description: "Session ID"
ContactId: linked string
source: @MessagingEndUser.ContactId
description: "Contact ID"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
start_agent topic_selector:
label: "Topic Selector"
description: "Handles FAQ questions"
reasoning:
instructions: ->
| Answer the user's question.
| If unsure, offer to connect them with support.
```
### Multi-Topic Router
```agentscript
start_agent topic_selector:
label: "Topic Selector"
description: "Routes to specialized topics"
reasoning:
instructions: ->
| Determine what the user needs.
| Route to the appropriate topic.
actions:
orders: @utils.transition to @topic.orders
billing: @utils.transition to @topic.billing
support: @utils.transition to @topic.support
```
### Validation Pattern
```agentscript
instructions: ->
if @variables.email is None:
set @variables.valid = False
| Please provide your email address.
if @variables.amount <= 0:
set @variables.valid = False
| Amount must be greater than zero.
if @variables.valid == True:
| All validations passed. Proceeding.
```
---
## Reserved Words
These words cannot be used as input/output parameter names or action names:
| Reserved Word | Why Reserved | Alternative Names |
|---------------|--------------|-------------------|
| `description` | Keyword for descriptions | `case_description`, `item_description`, `desc` |
| `inputs` | Keyword for action inputs | `input_data`, `request_inputs`, `params` |
| `outputs` | Keyword for action outputs | `output_data`, `response_outputs`, `results` |
| `target` | Keyword for action target | `destination`, `endpoint`, `flow_target` |
| `label` | Keyword for topic label | `display_label`, `title`, `name` |
| `source` | Keyword for linked variables | `data_source`, `origin`, `source_field` |
| `escalate` | Reserved for `@utils.escalate` | `go_to_escalate`, `transfer_to_human`, `human_handoff` |
---
## Error Reference
| Error | Cause | Fix |
|-------|-------|-----|
| Parse error | Invalid syntax | Check indentation consistency (use tabs) |
| Unknown resource | Invalid `@` reference | Use `@variables`, `@actions`, etc. |
| Undefined variable | Variable not declared | Add to `variables:` block |
| Undefined topic | Topic not found | Add topic or fix reference |
| Invalid target | Wrong action target format | Use `flow://` or `apex://` |
| Nested run | `run` inside `run` | Flatten to sequential `run` |
| Missing label | Topic without label | Add `label:` to all topics |
| Wrong config field | Using `developer_name` | Use `agent_name` |
| Missing space | `instructions:->` | Use `instructions: ->` |
| **Internal Error, try again later** | **Flow variable names don't match** | **Ensure Agent Script input/output names EXACTLY match Flow variable API names** |
| **ERROR_HTTP_404 during Retrieve Metadata** | **AiAuthoringBundle metadata not deployed** | **Run `sf project deploy start` after publish to deploy metadata** |
| **ERROR_HTTP_404 during Publish Agent** | **OmniChannelFlow doesn't exist in org** | **Create the referenced OmniChannelFlow before publishing agent** |
| SyntaxError: Unexpected 'with' | Escalate with reason in AiAuthoringBundle | Use basic `@utils.escalate` or GenAiPlannerBundle |
| SyntaxError: Unexpected 'escalate' | `escalate` is reserved word OR invalid escalation syntax | Rename action to `go_to_escalate` or similar |
| SyntaxError: Unexpected 'run' | `run` keyword in AiAuthoringBundle | Use GenAiPlannerBundle for action callbacks |
| **Unknown utils declaration type** | **`@utils.setVariables` or `@utils.set` in AiAuthoringBundle** | **Use `set @variables.x = ...` in instructions instead** |
| **Invalid outbound_route_type** | **Connection block uses `queue`/`skill`/`agent`** | **Use `"OmniChannelFlow"` as the only valid value** |
| **Missing escalation_message** | **Connection block missing required field** | **Add `escalation_message: "..."` to connection block** |
| **Missing required element / Unexpected 'else'** | **Nested if statements (if inside if)** | **Use flat conditionals with `and` operators instead** |
| **SyntaxError: Unexpected 'if'** | **Slot filling (`...`) inside conditional** | **Move `set @variables.x = ...` outside conditionals** |
| **SyntaxError on `@utils.transition`** | **`description:` on next line of transition** | **Remove description from transition actions** |
| **ValidationError on `@utils.escalate`** | **Missing required `description:` field** | **Add `description:` on line after `@utils.escalate`** |
| **SyntaxError in lifecycle block** | **Empty `before_reasoning` or `after_reasoning`** | **Remove empty lifecycle blocks or add actual content** |
| **SyntaxError: Unexpected '{'** | **`{!@actions.x}` interpolation syntax** | **Define actions explicitly, LLM auto-selects based on description** |
---
## Anti-Patterns
| Anti-Pattern | Issue | Fix |
|--------------|-------|-----|
| Mixed tabs/spaces | Syntax error | Use tabs consistently (recommended) or spaces consistently |
| Inconsistent indentation | Syntax may fail | Ensure same indentation at same nesting level |
| `@variable.name` | Wrong syntax | Use `@variables.name` (plural) |
| `developer_name:` | Wrong field | Use `agent_name:` |
| `instructions:->` | Missing space | Use `instructions: ->` |
| Missing `label:` | Deploy fails | Add label to all topics |
| `.agentscript` | Wrong extension | Use `.agent` |
| No bundle XML | Deploy fails | Create `.bundle-meta.xml` |
| No language block | Deploy fails | Add `language:` block |
| Missing linked vars | Missing context | Add EndUserId, RoutableId, ContactId |
| **Mismatched Flow variable names** | **Internal Error** | **Agent Script input/output names MUST match Flow variable API names exactly** |
| `@utils.escalate with reason=` in AiAuthoringBundle | SyntaxError | Use basic escalation or GenAiPlannerBundle |
| `run` keyword in AiAuthoringBundle | SyntaxError | Use GenAiPlannerBundle for action callbacks |
| Expecting UI visibility with GenAiPlannerBundle | Agent not visible | Use AiAuthoringBundle for UI visibility |
| **Ignoring HTTP 404 after publish** | **Agent invisible in UI** | **Run `sf project deploy start` to deploy AiAuthoringBundle metadata** |
| **Using `escalate:` as action name** | **SyntaxError: Unexpected 'escalate'** | **Use `go_to_escalate:` or `transfer_to_human:`** |
| **Using `@utils.setVariables` or `@utils.set`** | **Unknown utils declaration type** | **Use `set @variables.x = ...` in instructions** |
| **Connection block with `outbound_route_type: "queue"`** | **Invalid value error** | **Use `"OmniChannelFlow"` only** |
| **Connection block without `escalation_message`** | **Parse/validation error** | **Add required `escalation_message` field** |
| **Connection block referencing non-existent flow** | **HTTP 404 at Publish Agent** | **Create OmniChannelFlow before publishing agent** |
| **Nested if statements (if inside if)** | **Parse errors** | **Use flat conditionals with `and` operators** |
| **Slot filling (`set x = ...`) inside conditionals** | **SyntaxError: Unexpected 'if'** | **Move slot filling outside conditionals, let LLM handle context** |
| **`description:` on `@utils.transition`** | **SyntaxError** | **Transitions don't support description - use topic description instead** |
| **`@utils.escalate` without `description:`** | **ValidationError** | **Always include `description:` field for escalation actions** |
| **Empty lifecycle blocks (comments only)** | **SyntaxError** | **Remove empty blocks or add actual logic** |
| **`{!@actions.x}` for dynamic action invocation** | **SyntaxError** | **Define multiple actions with descriptions, LLM auto-selects** |
---
## Test Matrix
| Level | Complexity | Features Tested | Status |
|-------|------------|-----------------|--------|
| 1 | Basic | system, config, single start_agent topic | ✅ Verified |
| 2 | Multi-topic | Multiple topics, @utils.transition routing | ✅ Verified |
| 3 | Variables | linked, mutable, language block, variable templates | ✅ Verified |
| 4 | Flow Actions | flow:// targets, inputs/outputs, @actions.* | ✅ Verified |
| 5 | Complex | All features + @utils.escalate, multi-action topics | ✅ Verified |
---
## Testing Best Practices
1. **Validate Early**: Run `sf agent validate authoring-bundle` before every publish
2. **Start Simple**: Begin with Level 1 (no actions) to verify basic syntax
3. **Deploy Dependencies First**: Flows and Apex must exist in org before agent publish
4. **Test in Simulated Mode First**: Use simulated preview for rapid iteration
5. **Validate Incrementally**: Test each feature addition before combining
6. **Check User Permissions**: Ensure `default_agent_user` has Agentforce permissions
7. **Use Explicit Errors**: If publish fails with "Internal Error", check dependencies first
8. **Save Transcripts**: Use `--output-dir` to capture test sessions for review
9. **Test Edge Cases**: Include unexpected inputs in test specs to validate guardrails
10. **Automate Tests**: Use `sf agent test create` for regression testing