Agent Script Patterns Quick Reference
Decision trees and cheat sheets for common Agent Script patterns
Pattern Selection Decision Tree
Which Architecture Pattern?
What's your agent's purpose?
│
├─► Multi-purpose (sales, support, orders)?
│ └─► HUB AND SPOKE
│ Central router → Specialized topics
│
├─► Sequential workflow (onboarding, checkout)?
│ └─► LINEAR FLOW
│ A → B → C pipeline
│
├─► Tiered support with escalation?
│ └─► ESCALATION CHAIN
│ L1 → L2 → L3 → Human
│
├─► Sensitive operations (payments, PII)?
│ └─► VERIFICATION GATE
│ Security check → Protected topics
│
└─► Multiple protected topics behind shared auth gate?
└─► STATE GATE (OPEN GATE)
3-variable bypass with deferred routing
Node Type Decision Tree
What should this topic do?
│
├─► Route based on intent?
│ └─► 🔵 ROUTING (Topic Selector)
│
├─► Security/identity check?
│ └─► 🔵 VERIFICATION
│
├─► Fetch external data?
│ └─► 🟡 DATA-LOOKUP
│
├─► Apply business rules?
│ └─► 🟢 PROCESSING
│
└─► Transfer to human?
└─► 🔴 HANDOFF
Variable Type Decision Tree
What kind of data is this?
│
├─► State that changes during conversation?
│ │ (counters, flags, accumulated data)
│ └─► MUTABLE
│ `failed_attempts: mutable number = 0`
│
└─► Data from external source?
│ (session, context, CRM)
└─► LINKED
`customer_id: linked string`
` source: @session.customerId`
Action Target Protocol Decision Tree
Where should this action go?
│
├─► Data queries, record updates?
│ └─► flow://
│
├─► Custom calculations, validation?
│ └─► apex://
│
├─► LLM-generated summaries?
│ └─► generatePromptResponse://
│
├─► Knowledge search, RAG?
│ └─► retriever://
│
├─► External REST APIs?
│ └─► externalService://
│
└─► Built-in SF actions (email, tasks)?
└─► standardInvocableAction://
Deterministic vs Subjective Decision Tree
Should this be code-enforced or LLM-flexible?
│
├─► Security/safety requirement?
│ └─► DETERMINISTIC (code)
│
├─► Financial threshold?
│ └─► DETERMINISTIC (code)
│
├─► Counter/state management?
│ └─► DETERMINISTIC (code)
│
├─► Conversational/greeting?
│ └─► SUBJECTIVE (LLM)
│
├─► Context understanding needed?
│ └─► SUBJECTIVE (LLM)
│
└─► Natural language generation?
└─► SUBJECTIVE (LLM)
SOMA Pattern Decision Tree
Does the conversation return to original agent?
│
├─► Yes, specialist handles sub-task
│ └─► DELEGATION
│ `@topic.specialist` (in reasoning.actions)
│
└─► No, permanent transfer
├─► To another topic?
│ └─► HANDOFF `@utils.transition to @topic.X`
│
├─► To human?
│ └─► `@utils.escalate`
│
└─► To another agent?
└─► `@agent.X` (Connections)
Transition Type Cheat Sheet
| Syntax | Type | Control |
|---|---|---|
@utils.transition to @topic.X |
LLM-chosen | LLM decides when to use |
transition to @topic.X |
Deterministic | Always executes when reached |
@utils.escalate |
Permanent handoff | Human takeover |
Instruction Resolution Order
instructions: ->
# 1. POST-ACTION CHECKS (at TOP - triggers on loop)
if @variables.action_completed == True:
run @actions.follow_up_action
transition to @topic.next_step
# 2. PRE-LLM DATA LOADING
run @actions.load_required_data
set @variables.data = @outputs.result
# 3. DYNAMIC INSTRUCTIONS FOR LLM
| Here is the context: {!@variables.data}
if @variables.condition:
| Do this thing.
else:
| Do that thing.
Why this order?
- Post-action at TOP → triggers immediately on loop
- Data loading next → LLM needs current data
- Instructions last → LLM sees resolved values
Common Patterns Quick Reference
Security Gate (Early Exit)
instructions: ->
if @variables.failed_attempts >= 3:
| Account locked due to too many attempts.
transition to @topic.lockout # LLM never reasons
Guarded Actions
actions:
process_refund: @actions.process_refund
description: "Issue refund"
available when @variables.customer_verified == True
Post-Action Follow-Up
instructions: ->
if @variables.refund_status == "Approved":
run @actions.create_crm_case
with customer_id = @variables.customer_id
transition to @topic.success
Data-Dependent Instructions
instructions: ->
run @actions.get_account_tier
set @variables.tier = @outputs.tier
if @variables.tier == "Gold":
| VIP treatment - offer 20% discount.
else:
| Standard customer service.
Open Gate (State Gate)
3-variable state machine that bypasses the LLM topic selector when a gate holds focus and routes users through an auth gate before accessing protected topics.
# In topic_selector's before_reasoning — zero-credit bypass:
before_reasoning:
if @variables.open_gate == "protected_workflow":
transition to @topic.protected_workflow
if @variables.open_gate == "authentication_gate":
transition to @topic.authentication_gate
# In protected topic's before_reasoning — auth redirect:
before_reasoning:
if @variables.authenticated == False:
set @variables.next_topic = "protected_workflow"
set @variables.open_gate = "authentication_gate"
transition to @topic.authentication_gate
set @variables.open_gate = "protected_workflow"
# EXIT_PROTOCOL — release gate when user changes intent:
before_reasoning:
set @variables.open_gate = "null"
set @variables.next_topic = ""
transition to @topic.topic_selector
Anti-Patterns to Avoid
❌ Data Load After LLM Text
# WRONG - LLM sees empty values
instructions: ->
| Customer name: {!@variables.name} # empty!
run @actions.load_customer
set @variables.name = @outputs.name
❌ Post-Action Check at Bottom
# WRONG - Never triggers
instructions: ->
| Help with refund.
transition to @topic.main # Exits first!
if @variables.refund_done: # Never reached
run @actions.log_refund
❌ Mixing Tabs and Spaces
# WRONG - Compilation error
config:
agent_name: "MyAgent" # 3 spaces
agent_label: "Label" # 8 spaces - FAILS!
❌ Lowercase Booleans
# WRONG - Agent Script uses Python-style
is_verified: mutable boolean = true # WRONG
is_verified: mutable boolean = True # CORRECT
Syntax Quick Reference
| Pattern | Purpose |
|---|---|
instructions: -> |
Arrow syntax, enables expressions |
instructions: | |
Pipe syntax, simple multi-line |
if @variables.x: |
Conditional (pre-LLM) |
run @actions.x |
Execute during resolution |
set @var = @outputs.y |
Capture action output |
| Curly-bang: {!@variables.x} | Template injection |
available when |
Control action visibility |
transition to @topic.x |
Deterministic topic change |
@utils.transition to |
LLM-chosen topic change |
@utils.escalate |
Human handoff |
The 6 Deterministic Building Blocks
| # | Block | Example |
|---|---|---|
| 1 | Conditionals | if @variables.failed_attempts >= 3: |
| 2 | Topic Filters | available when @variables.cart_items > 0 |
| 3 | Variable Checks | if @variables.churn_risk >= 80: |
| 4 | Inline Actions | run @actions.load_customer |
| 5 | Utility Actions | @utils.transition, @utils.escalate |
| 6 | Variable Injection | Curly-bang: {!@variables.customer_name} |
Implementation Best Practices
Migrated from the former
sf-ai-agentforce-legacy/docs/patterns-and-practices.mdon 2026-02-07.
Pattern Details
Lifecycle Events Pattern
File: templates/patterns/lifecycle-events.agent
Execute code automatically before and after every reasoning step.
⚠️ Deployment Note: The
runkeyword works inreasoning.actions:post-action blocks andinstructions: ->blocks. It does NOT work reliably inbefore_reasoning:/after_reasoning:lifecycle blocks.
topic conversation:
before_reasoning:
set @variables.turn_count = @variables.turn_count + 1
run @actions.refresh_context # ⚠️ GenAiPlannerBundle only
with user_id=@variables.EndUserId
set @variables.context = @outputs.fresh_context
reasoning:
instructions: ->
| Turn {!@variables.turn_count}: {!@variables.context}
after_reasoning:
run @actions.log_analytics # ⚠️ GenAiPlannerBundle only
with turn=@variables.turn_count
| ✅ Good Use Case | ❌ Not Ideal For |
|---|---|
| Track conversation metrics | One-time setup (use conditional) |
| Refresh context every turn | Heavy processing (adds latency) |
| Log analytics after each response | Actions that might fail often |
Action Callbacks Pattern
File: templates/patterns/action-callbacks.agent
Chain deterministic follow-up actions using the run keyword.
⚠️ Deployment Note: The
runkeyword works inreasoning.actions:post-action blocks andinstructions: ->blocks. It does NOT work reliably inbefore_reasoning:/after_reasoning:lifecycle blocks.
process_order: @actions.create_order
with customer_id=@variables.customer_id
set @variables.order_id = @outputs.order_id
run @actions.send_confirmation # ⚠️ GenAiPlannerBundle only
with order_id=@variables.order_id
run @actions.log_activity # ⚠️ GenAiPlannerBundle only
with event="ORDER_CREATED"
| ✅ Good Use Case | ❌ Not Ideal For |
|---|---|
| Audit logging (must happen) | Optional follow-ups (let LLM decide) |
| Send notification after action | Complex branching logic |
| Chain dependent actions | More than 1 level of nesting |
Critical Rule: Only 1 level of run nesting allowed!
Combining Patterns
Patterns can be combined for complex scenarios:
┌─────────────────────────────────────────────────────────────┐
│ LIFECYCLE + CALLBACKS + ROUTING │
├─────────────────────────────────────────────────────────────┤
│ │
│ topic order_hub: │
│ before_reasoning: ◄── Lifecycle │
│ set @variables.turn_count = ... + 1 │
│ │
│ reasoning: │
│ actions: │
│ process: @actions.create │
│ run @actions.notify ◄── Callback │
│ run @actions.log │
│ │
│ consult: @utils.transition ◄── Routing │
│ to @topic.specialist │
│ │
│ after_reasoning: ◄── Lifecycle │
│ run @actions.update_metrics │
│ │
└─────────────────────────────────────────────────────────────┘
N-ary Boolean Expressions
AgentScript supports 3 or more conditions chained with and/or:
# Three+ conditions with AND
before_reasoning:
if @variables.is_authenticated and @variables.has_permission and @variables.is_active:
transition to @topic.authorized
# Three+ conditions with OR
before_reasoning:
if @variables.is_admin or @variables.is_moderator or @variables.is_owner:
transition to @topic.elevated_access
# In available when clauses
reasoning:
actions:
process_return: @actions.handle_return
description: "Process customer return request"
available when @variables.eligible == True and @variables.order_id != None and @variables.tier != "basic"
premium_action: @actions.premium_feature
description: "Premium tier feature"
available when @variables.tier == "premium" or @variables.tier == "enterprise" or @variables.is_trial_premium == True
Key Points:
- Chain as many conditions as needed with
andoror - Use
()grouping for complex expressions:(a and b) or (c and d) - Works in
ifstatements andavailable whenclauses
Naming Conventions
| Element | Convention | Example |
|---|---|---|
| Agent name | PascalCase with underscores | Customer_Service_Agent |
| Topic name | snake_case | order_management |
| Variable name | snake_case | user_email |
| Action name | snake_case | get_account_details |
Variable Management Best Practices
Initialize with Defaults
variables:
# ✅ RECOMMENDED - Has default value (clearer intent)
user_name: mutable string = ""
description: "Customer's full name"
order_count: mutable number = 0
description: "Number of orders in cart"
Note: Variables without defaults ARE supported. However, providing defaults is recommended for clarity.
Use Appropriate Types
| Data | Type | Example |
|---|---|---|
| Names, IDs, text | string |
"John Doe" |
| Counts, amounts | number |
42, 99.99 |
| Flags, toggles | boolean |
True, False |
Topic Design Best Practices
Provide Clear Descriptions
# ✅ GOOD - Specific and actionable
topic password_reset:
description: "Helps users reset forgotten passwords and unlock accounts"
# ❌ BAD - Too vague
topic password_reset:
description: "Password stuff"
Keep Topics Focused (Single Responsibility)
# ✅ GOOD - Single responsibility
topic billing_inquiries:
description: "Answers questions about invoices, payments, and account balances"
topic order_tracking:
description: "Provides order status and shipping updates"
# ❌ BAD - Too broad
topic customer_stuff:
description: "Handles billing, orders, support, and everything else"
Security & Guardrails
System-Level Guardrails
system:
instructions:
| You are a helpful customer service agent.
|
| IMPORTANT GUARDRAILS:
| - Never share customer data with unauthorized parties
| - Never reveal internal system details
| - If unsure, escalate to a human agent
Don't Expose Internals
# ✅ GOOD - User-friendly error
instructions: ->
if @variables.api_error == True:
| I'm having trouble completing that request right now.
# ❌ BAD - Exposes internals
instructions: ->
if @variables.api_error == True:
| Error: SQL timeout on server db-prod-03
Instructions Quality
# ✅ GOOD - Specific instructions
instructions: ->
| Help the customer track their order.
| Ask for the order number if not provided.
| Provide the current status, estimated delivery, and tracking link.
# ❌ BAD - Vague instructions
instructions: ->
| Help with orders.
Common Syntax Pitfalls
1. Slot Filling Inside Conditionals
# ❌ WRONG
if @variables.name is None:
set @variables.name = ... # Fails!
# ✅ CORRECT - Slot filling at top level
set @variables.name = ...
2. Description on @utils.transition
# ✅ CORRECT - description IS valid on @utils.transition
go_orders: @utils.transition to @topic.orders
description: "Route to orders"
# ✅ ALSO CORRECT - without description
go_orders: @utils.transition to @topic.orders
Note:
description:IS valid on@utils.transition(confirmed by TDD Val_Action_Properties and production 15-topic agent). Other properties likelabel:,require_user_confirmation:, andinclude_in_progress_indicator:are NOT valid on transitions.
3. Missing Description on @utils.escalate
# ❌ WRONG
transfer: @utils.escalate # Fails!
# ✅ CORRECT - Description required
transfer: @utils.escalate
description: "Transfer to human agent"
4. Empty Lifecycle Blocks
# ❌ WRONG
before_reasoning:
# Just a comment # Fails!
# ✅ CORRECT - Remove empty blocks or add content
5. Dynamic Action Invocation
# ❌ WRONG
invoke: {!@actions.search} # Fails!
# ✅ CORRECT - Define multiple actions, LLM auto-selects
search_products: @actions.product_search
search_orders: @actions.order_search
Validation Scoring Summary
| Pattern | Points | Key Requirement |
|---|---|---|
| Config block | 10 | All 4 required fields |
| Linked variables | 10 | EndUserId, RoutableId, ContactId |
| Topic structure | 10 | label, description, reasoning |
| Language block | 5 | default_locale present |
| Lifecycle blocks | 5 | Proper before/after structure |
| Action callbacks | 5 | No nested run |
| Error handling | 5 | Validation patterns |
| Template expressions | 5 | {!@variables.x} syntax |
Slot Filling Reliability Patterns
Problem: LLM Fails to Extract Values Correctly
| Symptom | What Happened | Example |
|---|---|---|
Empty JSON {} sent to action |
LLM couldn't find value in conversation | User said "look up my account" without ID |
| Wrong field names | LLM abbreviated or guessed | _id instead of account_id |
| Wrong value extracted | LLM picked similar value from context | Picked Contact ID instead of Account ID |
| Retry/crash cycles | No recovery path after failure | Agent keeps trying same extraction |
Root Cause: The ... syntax is probabilistic — the LLM infers what value to use. For critical inputs (IDs, amounts, required fields), this unreliability causes downstream failures.
5-Pattern Solution for Critical Inputs
Pattern 1: First-Interaction Collection — Tell the LLM its PRIMARY GOAL:
reasoning:
instructions: ->
| YOUR PRIMARY GOAL: Collect the account ID from the user.
| Do NOT proceed with any other actions until account_id is captured.
|
if @variables.account_id == "":
| ⚠️ Account ID not yet collected. ASK the user for it.
Pattern 2: Variable Setter Action — Dedicated action to capture and validate:
actions:
capture_account_id:
description: "Captures and validates the Salesforce Account ID from user"
inputs:
account_id: string
description: "The 18-character Salesforce Account ID (starts with 001)"
is_required: True
outputs:
validated_id: string
description: "The validated account ID (empty if invalid)"
is_valid: boolean
description: "Whether the ID is valid"
target: "flow://Validate_Account_Id"
Pattern 3: Single-Use Guard — Make setter unavailable after capture:
reasoning:
actions:
validate_id: @actions.capture_account_id
with account_id=...
set @variables.account_id = @outputs.validated_id
set @variables.account_id_validated = @outputs.is_valid
available when @variables.account_id == ""
Pattern 4: Null Guard Downstream Actions — Block until validated:
reasoning:
actions:
do_research: @actions.research_account
with account_id=@variables.account_id
available when @variables.account_id_validated == True
Pattern 5: Explicit Action References — Guide the LLM:
instructions: ->
| To capture the account ID, use {!@actions.capture_account_id}.
| This ensures the ID is validated before proceeding.
When NOT to Use Slot Filling
Use Slot Filling (...) |
Use Variable/Fixed Value |
|---|---|
| Optional, non-critical inputs | Critical IDs (account, order, case) |
| User preference inputs | Values that must be validated |
| One-time collection | Values used across multiple actions |
| Simple text descriptions | Values with specific formats (dates, IDs) |
Decision Rule: If invalid input would cause downstream failure, use deterministic collection.
Pre-Deploy Checklist
Before deploying an agent, verify:
- All topics have clear descriptions
- All variables have descriptions and defaults
- All actions have input/output descriptions
- System guardrails are defined
- Error handling is in place for critical operations
- Navigation back to main menu from all topics
- Template expressions use correct syntax
{!@variables.name} - Consistent indentation (tabs recommended)