# Agent Script Syntax Reference

> The continued line is indented further than the | line and does NOT start with a new |.

- Skill: `tools-only/agent-script-syntax-reference-6` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/agent-script-syntax-reference-6`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/agent-script-syntax-reference-6/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/agent-script-syntax-reference-6

---

<!-- Parent: sf-ai-agentscript/SKILL.md -->
# Agent Script Syntax Reference

> Complete syntax guide for the Agent Script DSL. Your entire agent in one `.agent` file.

---

## 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)

```yaml
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)

```yaml
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_user` must 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)

```yaml
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 multiline `description: |` 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**: `timestamp` and `currency` compile as variable types but are absent from official GA documentation. Prefer `date` for date/time variables and `number` for 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`, not `true`/`false`

---

### 4. language: Block (Optional)

```yaml
language:
  default: "en_US"
  supported: ["en_US", "es_ES", "fr_FR"]
```

---

### 5. knowledge: Block (Optional)

```yaml
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)

```yaml
# 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, NOT `connections:`). The plural `connections:` wrapper is invalid — see [known-issues.md](known-issues.md#issue-16) 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)

```yaml
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:

```yaml
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)

```yaml
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

```yaml
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 if` is NOT supported**: Agent Script does not have an `else if` keyword. Nested `if` inside `else:` is also invalid. Use compound conditions (`if A and B:`) or flatten to sequential `if` statements.

### Multiline String Continuation

Long literal text can span multiple lines using indented continuation:

```yaml
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

```yaml
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:**
```yaml
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:` and `target:` (no `inputs:`/`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).

```yaml
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`
- `run` has **inconsistent runtime behavior** in lifecycle blocks across bundle types — use it in `reasoning.actions:` or `instructions: ->` instead
- `transition to` works in `after_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 for `generatePromptResponse://`. 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_variables` is a separate namespace from `@variables`. The `user_input` system 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

```yaml
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)

```yaml
| 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:**
```yaml
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_name` field of action input/output definitions in Agentforce Assets, not in the `.agent` file 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

```yaml
# ❌ 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

```yaml
# ❌ 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

```yaml
# ❌ 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 None` when 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`.

```yaml
# ❌ 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=...
```

