"Prompt engineering is like writing laws in poetry - beautiful, but not enforceable."
Agent Script transforms agent development from prompt-based suggestions to code-enforced guarantees. This skill guides you through writing, debugging, testing, and deploying Agentforce agents using the Agent Script DSL.
⚠️ CRITICAL WARNINGS
API & Version Requirements
| API Version | 65.0+ | Required for Agent Script support
| License | Agentforce | Required for agent authoring
| Einstein Agent User
| Required
| Must exist in org for default_agent_user
| File Extension
| .agent
| Single file contains entire agent definition
MANDATORY Pre-Deployment Checks
-
default_agent_userMUST be valid - Query:SELECT Username FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true -
No mixed tabs/spaces - Use consistent indentation (2-space, 3-space, or tabs - never mix)
-
Booleans are capitalized - Use
True/False, nottrue/false -
Exactly one
start_agentblock - Multiple entry points cause compilation failure
⛔ SYNTAX CONSTRAINTS (Validated via Testing + Official Spec)
| No nested if statements
| if x: then if y: (nested)
| if x and y: (compound) OR flatten to sequential ifs
| No top-level actions: block
| actions: at root level
| Actions only inside topic.reasoning.actions:
| No inputs:/outputs: in actions
| inputs: block inside action
| Use with for inputs, set for outputs
| One available when per action
| Two available when clauses
| available when A and B
| Avoid reserved action names
| escalate: @utils.escalate
| escalate_now: @utils.escalate
| ... is slot-filling only
| my_var: mutable string = ...
| my_var: mutable string = ""
| No defaults on linked vars
| id: linked string = ""
| id: linked string + source:
| Linked vars: no object/list
| data: linked object
| Use linked string or parse in Flow
| Post-action only on @actions
| @utils.X with set/run
| Only @actions.X supports post-action
| agent_name must match folder
| Folder: MyAgent, config: my_agent
| Both must be identical (case-sensitive)
| Reserved field names
| description: string, label: string
| Use descriptions, label_text, or suffix with _field
🔴 Reserved Field Names (Breaking in Recent Releases)
Common field names that cause parse errors:
❌ RESERVED (cannot use as variable/field names):
description, label, is_required, is_displayable, is_used_by_planner
✅ WORKAROUNDS:
description → descriptions, description_text, desc_field
label → label_text, display_label, label_field
🔴 Features NOT Valid in Current Release (TDD Validated 2026-01-20)
These features appear in documentation or recipes but do NOT compile in Winter '26.
| label: on topics
| agentforce.guide
| Unexpected 'label'
| ❌ NOT valid anywhere
| label: on actions
| agentforce.guide
| Unexpected 'label'
| ❌ NOT valid anywhere
| always_expect_input:
| Some docs
| Unexpected 'always_expect_input'
| ❌ NOT implemented
| require_user_confirmation: on transitions
| Recipes
| Unexpected 'require_user_confirmation'
| ❌ NOT valid on @utils.transition
| include_in_progress_indicator: on transitions
| Recipes
| Unexpected 'include_in_progress_indicator'
| ❌ NOT valid on @utils.transition
| output_instructions: on transitions
| Recipes
| Unexpected 'output_instructions'
| ❌ NOT valid on @utils.transition
| progress_indicator_message: on transitions
| Recipes
| Unexpected 'progress_indicator_message'
| ❌ May only work on flow:// targets
What DOES work on @utils.transition actions:
actions:
go_next: @utils.transition to @topic.next
description: "Navigate to next topic" # ✅ ONLY this works
Note: Some of these may work on flow:// action targets (not validated). The @utils.transition utility action has limited property support.
🔴 complex_data_type_name Mapping Table (Critical for Actions)
"#1 source of compile errors" - Use this table when defining action inputs/outputs in Agentforce Assets.
| string
| (none needed)
| Primitive type
| number
| (none needed)
| Primitive type
| boolean
| (none needed)
| Primitive type
| object (SObject)
| lightning__recordInfoType
| Use for Account, Contact, etc.
| list[string]
| lightning__textType
| Collection of text values
| list[object]
| lightning__textType
| Serialized as JSON text
| Apex Inner Class
| @apexClassType/NamespacedClass__InnerClass
| Namespace required
| Custom LWC Type
| lightning__c__CustomTypeName
| Custom component types
| Currency field
| lightning__currencyType
| For monetary values
Pro Tip: Don't manually edit complex_data_type_name - use the UI dropdown in Agentforce Assets > Action Definition, then export/import the action definition.
⚠️ Canvas View Corruption Bugs
CRITICAL: Canvas view can silently corrupt Agent Script syntax. Make complex edits in Script view.
| ==
| {! OPERATOR.EQUAL }
| Breaks conditionals
| if condition:
| if condition (missing colon)
| Parse error
| with email =
| with @inputs.email =
| Invalid syntax
| 4-space indent | De-indented (breaks nesting) | Structure lost
| @topic.X (supervision)
| @utils.transition to @topic.X (handoff)
| Changes return behavior
| A and B
| A {! and } B
| Breaks compound conditions
Safe Workflow:
-
Use Script view for all structural edits (conditionals, actions, transitions)
-
Use Canvas only for visual validation and simple text changes
-
Always review in Script view after any Canvas edit
⚠️ Preview Mode Critical Bugs
CRITICAL REFRESH BUG: Browser refresh required after every Agent Script save before preview works properly.
| Linked vars in context, not state
| "Cannot access 'X': Not a declared field in dict"
| Convert to mutable + hardcode for testing
| Output property access fails | Silent failure, no error | Assign to variable first, then use in conditional
| Simulate vs Live behavior differs | Works in Simulate, fails in Live | Test in BOTH modes before committing
Pattern for Testing Linked Variables:
# ❌ DOESN'T WORK IN PREVIEW (linked var from session):
RoutableId: linked string
source: @MessagingSession.Id
# ✅ WORKAROUND FOR TESTING (hardcode value):
RoutableId: mutable string = "test-session-123"
description: "MessagingSession Id (hardcoded for testing)"
# After testing, switch back to linked for production
Output Property Access Pattern:
# ❌ DOESN'T WORK IN PREVIEW (direct output access):
if @actions.check_status.result == "approved":
| Approved!
# ✅ CORRECT (assign to variable first):
set @variables.status = @outputs.result
if @variables.status == "approved":
| Approved!
No Nested if - Two Valid Approaches
# ❌ WRONG - Nested if (causes SyntaxError)
if @variables.software_cost > 0:
if @variables.software_cost <= 500:
| Auto-approve this software request.
# ✅ CORRECT Approach 1 - Compound condition (when logic allows)
if @variables.software_cost > 0 and @variables.software_cost <= 500:
| Auto-approve this software request.
# ✅ CORRECT Approach 2 - Flatten to sequential ifs (for separate messages)
if @variables.order_verified == False or @variables.payment_confirmed == False:
| ❌ **PROCESSING BLOCKED**
| Missing requirements:
if @variables.order_verified == False:
| - Order verification pending
if @variables.payment_confirmed == False:
| - Payment confirmation pending
When to use each: Use compound conditions when logic permits (single condition block). Use flattening when you need separate conditional outputs that can't be combined.
... is Slot-Filling Syntax (LLM Extracts from Conversation)
# ❌ WRONG - Using ... as default value
order_id: mutable string = ...
# ✅ CORRECT - Use ... only in action parameter binding
reasoning:
actions:
search: @actions.search_products
with query=... # LLM extracts from user message
with category=... # LLM decides based on context
with limit=10 # Fixed value
Post-Action Directives: Only on @actions.*
# ❌ WRONG - @utils does NOT support set/run/if
go_next: @utils.transition to @topic.main
set @variables.visited = True # ERROR!
# ✅ CORRECT - Only @actions.* supports post-action
process: @actions.process_order
with order_id=@variables.order_id
set @variables.status = @outputs.status # ✅ Works
run @actions.send_notification # ✅ Works
if @outputs.needs_review: # ✅ Works
transition to @topic.review
Helper Topic Pattern (For Demo Agents Without Flows/Apex)
When you need to set variables without backend actions, use dedicated "helper topics":
# Main topic offers LLM-selectable action
topic verify_employee:
reasoning:
actions:
complete_verification: @utils.transition to @topic.verification_success
description: "Mark employee as verified"
available when @variables.employee_verified == False
# Helper topic sets variables in instructions, then returns
topic verification_success:
description: "Set verified state and return"
reasoning:
instructions: ->
set @variables.employee_verified = True
set @variables.employee_name = "Demo Employee"
| ✓ Identity verified!
transition to @topic.verify_employee # Return to parent
Why this works: set statements ARE valid inside instructions: -> blocks. The topic loop pattern lets you change state without Flows/Apex.
💰 PRODUCTION GOTCHAS: Billing, Determinism & Performance
Credit Consumption Table
Key insight: Framework operations are FREE. Only actions that invoke external services consume credits.
| @utils.transition
| FREE
| Framework navigation
| @utils.setVariables
| FREE
| Framework state management
| @utils.escalate
| FREE
| Framework escalation
| if/else control flow
| FREE
| Deterministic resolution
| before_reasoning
| FREE
| Deterministic pre-processing (see note below)
| after_reasoning
| FREE
| Deterministic post-processing (see note below)
| reasoning (LLM turn)
| FREE
| LLM reasoning itself is not billed
| Prompt Templates | 2-16 | Per invocation (varies by complexity)
| Flow actions | 20 | Per action execution
| Apex actions | 20 | Per action execution
| Any other action | 20 | Per action execution
✅ Lifecycle Hooks Validated (v1.3.0): The before_reasoning: and after_reasoning: lifecycle hooks are now TDD-validated. Content goes directly under the block (no instructions: wrapper). See "Lifecycle Hooks" section below for correct syntax.
Cost Optimization Pattern: Fetch data once in before_reasoning:, cache in variables, reuse across topics.
Lifecycle Hooks: before_reasoning: and after_reasoning:
TDD Validated (2026-01-20): These hooks enable deterministic pre/post-processing around LLM reasoning.
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.pre_processed = True
set @variables.customer_tier = "gold"
# LLM reasoning phase
reasoning:
instructions: ->
| Customer tier: {!@variables.customer_tier}
| How can I help you today?
# AFTER: Runs deterministically AFTER LLM finishes reasoning
after_reasoning:
# Content goes DIRECTLY here (NO instructions: wrapper!)
set @variables.interaction_logged = True
if @variables.needs_audit == True:
set @variables.audit_flag = True
Key Points:
-
Content goes directly under
before_reasoning:/after_reasoning:(NOinstructions:wrapper) -
Supports
set,if,runstatements (same as proceduralinstructions: ->) -
before_reasoning:is FREE (no credit cost) - use for data prep -
after_reasoning:is FREE (no credit cost) - use for logging, cleanup
❌ WRONG Syntax (causes compile error):
before_reasoning:
instructions: -> # ❌ NO! Don't wrap with instructions:
set @variables.x = True
✅ CORRECT Syntax:
before_reasoning:
set @variables.x = True # ✅ Direct content under the block
Supervision vs Handoff (Clarified Terminology)
| Handoff
| @utils.transition to @topic.X
| Control transfers completely, child generates final response
| Checkout, escalation, terminal states
| Supervision
| @topic.X (as action reference)
| Parent orchestrates, child returns, parent synthesizes
| Expert consultation, sub-tasks
# HANDOFF - child topic takes over completely:
checkout: @utils.transition to @topic.order_checkout
description: "Proceed to checkout"
# → @topic.order_checkout generates the user-facing response
# SUPERVISION - parent remains in control:
get_advice: @topic.product_expert
description: "Consult product expert"
# → @topic.product_expert returns, parent topic synthesizes final response
KNOWN BUG: Adding ANY new action in Canvas view may inadvertently change Supervision references to Handoff transitions.
Action Output Flags for Zero-Hallucination Routing
Key Pattern for Determinism: Control what the LLM can see and say.
When defining actions in Agentforce Assets, use these output flags:
| is_displayable: False
| LLM cannot show this value to user
| Preventing hallucinated responses
| is_used_by_planner: True
| LLM can reason about this value
| Decision-making, routing
Zero-Hallucination Intent Classification Pattern:
# In Agentforce Assets - Action Definition outputs:
outputs:
intent_classification: string
is_displayable: False # LLM cannot show this to user
is_used_by_planner: True # LLM can use for routing decisions
# In Agent Script - LLM routes but cannot hallucinate:
topic intent_router:
reasoning:
instructions: ->
run @actions.classify_intent
set @variables.intent = @outputs.intent_classification
if @variables.intent == "refund":
transition to @topic.refunds
if @variables.intent == "order_status":
transition to @topic.orders
Action Chaining with run Keyword
Known quirk: Parent action may complain about inputs needed by chained action - this is expected.
# Chained action execution:
process_order: @actions.create_order
with customer_id = @variables.customer_id
run @actions.send_confirmation # Chains after create_order completes
set @variables.order_id = @outputs.id
KNOWN BUG: Chained actions with Prompt Templates don't properly map inputs using Input:Query format:
# ❌ MAY NOT WORK with Prompt Templates:
run @actions.transform_recommendation
with "Input:Reco_Input" = @variables.ProductReco
# ⚠️ TRY THIS (may still have issues):
run @actions.transform_recommendation
with Reco_Input = @variables.ProductReco
Latch Variable Pattern for Topic Re-entry
Problem: Topic selector doesn't properly re-evaluate after user provides missing input.
Solution: Use a "latch" variable to force re-entry:
variables:
verification_in_progress: mutable boolean = False
start_agent topic_selector:
reasoning:
instructions: ->
# LATCH CHECK - force re-entry if verification was started
if @variables.verification_in_progress == True:
transition to @topic.verification
| How can I help you today?
actions:
start_verify: @topic.verification
description: "Start identity verification"
# Set latch when user chooses this action
set @variables.verification_in_progress = True
topic verification:
reasoning:
instructions: ->
| Please provide your email to verify your identity.
actions:
verify: @actions.verify_identity
with email = ...
set @variables.verified = @outputs.success
# Clear latch when verification completes
set @variables.verification_in_progress = False
Loop Protection Guardrail
Agent Scripts have a built-in guardrail that limits iterations to approximately 3-4 loops before breaking out and returning to the Topic Selector.
Best Practice: Map out your execution paths - particularly topic transitions. Ensure testing covers all paths and specifically check for unintended circular references between topics.
Token & Size Limits
| Max response size | 1,048,576 bytes (1MB) | Per agent response
| Plan trace limit (Frontend) | 1M characters | For debugging UI
| Transformed plan trace (Backend) | 32k tokens | Internal processing
| Active/Committed Agents per org | 100 max | Org limit
Progress Indicators
Add user feedback during long-running actions:
actions:
fetch_data: @actions.get_customer_data
description: "Fetch customer information"
include_in_progress_indicator: True
progress_indicator_message: "Fetching your account details..."
VS Code Pull/Push NOT Supported
# ❌ ERROR when using source tracking:
Failed to retrieve components using source tracking:
[SfError [UnsupportedBundleTypeError]: Unsupported Bundle Type: AiAuthoringBundle
# ✅ WORKAROUND - Use CLI directly:
sf project retrieve start -m AiAuthoringBundle:MyAgent
sf agent publish authoring-bundle --source-dir ./force-app/main/default/aiAuthoringBundles/MyAgent
Language Block Quirks
-
Hebrew and Indonesian appear twice in the language dropdown
-
Selecting from the second set causes save errors
-
Use
adaptive_response_allowed: Truefor automatic language adaptation
language:
locale: en_US
adaptive_response_allowed: True # Allow language adaptation
Cross-Skill Orchestration
| Before Agent Script
| /sf-flow - Create Flows for flow:// action targets
| ⚠️ REQUIRED
| After Agent Script
| /sf-ai-agentforce-testing - Test topic routing and actions
| ✅ RECOMMENDED
| For Deployment
| /sf-deploy - Publish agent with sf agent publish
| ⚠️ REQUIRED
📋 QUICK REFERENCE: Agent Script Syntax
Block Structure (CORRECTED Order per Official Spec)
config: # 1. Required: Agent metadata (agent_name, default_agent_user)
variables: # 2. Optional: State management (mutable/linked)
system: # 3. Required: Global messages and instructions
connections: # 4. Optional: Escalation routing
knowledge: # 5. Optional: Knowledge base config
language: # 6. Optional: Locale settings
start_agent: # 7. Required: Entry point (exactly one)
topic: # 8. Required: Conversation topics (one or more)
Naming Rules (All Identifiers)
-
Only letters, numbers, underscores
-
Must begin with a letter
-
No spaces, no consecutive underscores, cannot end with underscore
-
Maximum 80 characters
Instruction Syntax Patterns
| instructions: |
| Literal multi-line (no expressions)
| instructions: | Help the user.
| instructions: ->
| Procedural (enables expressions)
| instructions: -> if @variables.x:
| | text
| Literal text for LLM prompt
| | Hello + variable injection
| if @variables.x:
| Conditional (resolves before LLM)
| if @variables.verified == True:
| run @actions.x
| Execute action during resolution
| run @actions.load_customer
| set @var = @outputs.y
| Capture action output
| set @variables.risk = @outputs.score
| set @var = value
| Set variable in instructions
| set @variables.count = 0
| {!@variables.x}
| Variable injection in text
| Risk score: {!@variables.risk}
| {!expr if cond else alt}
| Conditional interpolation
| {!@variables.status if @variables.status else "pending"}
| available when
| Control action visibility to LLM
| available when @variables.verified == True
| with param=...
| LLM slot-filling (extracts from conversation)
| with query=...
| with param=value
| Fixed parameter value
| with limit=10
Transition vs Delegation (CRITICAL DISTINCTION)
| @utils.transition to @topic.X
| Permanent handoff
| ❌ No
| Checkout, escalation, final states
| @topic.X (in reasoning.actions)
| Delegation
| ✅ Yes
| Get expert advice, sub-tasks
| transition to @topic.X (inline)
| Deterministic jump
| ❌ No
| Post-action routing, gates
# Delegation - returns to current topic after specialist finishes
consulting: @topic.expert_topic
description: "Get expert advice"
# Transition - permanent handoff, no return
checkout: @utils.transition to @topic.checkout
description: "Proceed to purchase"
Expression Operators (Safe Subset)
| Comparison
| ==, <> (not-equal), <, <=, >, >=, is, is not
|
| Logical
| and, or, not
|
| Arithmetic
| +, -
| ❌ *, /, %
| Access
| . (property), [] (index)
|
| Conditional
| x if condition else y
|
Variable Types
| mutable
| Read/write state
| string, number, boolean, object, date, timestamp, currency, id, list[T]
| ✅ Yes
| linked
| Read-only from source
| string, number, boolean, date, timestamp, currency, id
| ❌ No (has source:)
⚠️ Linked variables CANNOT use object or list types
Variable vs Action I/O Type Matrix
Critical distinction: Some types are valid ONLY for action inputs/outputs, NOT for Agent Script variables.
| string
| ✅
| ✅
| Universal
| number
| ✅
| ✅
| Universal
| boolean
| ✅
| ✅
| Universal
| date
| ✅
| ✅
| Universal
| currency
| ✅
| ✅
| Universal
| id
| ✅
| ✅
| Salesforce IDs
| list
| ✅ (mutable only)
| ✅
| Collections
| object
| ✅ (mutable only)
| ✅
| ⚠️ Not for linked vars
| datetime
| ❌
| ✅
| Actions only
| time
| ❌
| ✅
| Actions only
| integer
| ❌
| ✅
| Actions only
| long
| ❌
| ✅
| Actions only
Source: AGENT_SCRIPT.md rules document from trailheadapps/agent-script-recipes
Action Target Protocols
| flow
| flow://
| Data operations, business logic
| ✅ TDD
| apex
| apex://
| Custom calculations, validation
| ✅ TDD
| prompt
| generatePromptResponse://
| Grounded LLM responses
| ✅ TDD
| api
| api://
| REST API calls
| ✅ TDD
| retriever
| retriever://
| RAG knowledge search
| ✅ TDD
| externalService
| externalService://
| Third-party APIs via Named Credentials
| ✅ TDD
| standardInvocableAction
| standardInvocableAction://
| Built-in SF actions (email, tasks)
| ✅ TDD
| datacloudDataGraphAction
| datacloudDataGraphAction://
| Data Cloud graph queries
| 📋 Spec
| datacloudSegmentAction
| datacloudSegmentAction://
| Data Cloud segment operations
| 📋 Spec
| triggerByKnowledgeSource
| triggerByKnowledgeSource://
| Knowledge article triggers
| 📋 Spec
| contextGrounding
| contextGrounding://
| Context grounding for LLM
| 📋 Spec
| predictiveAI
| predictiveAI://
| Einstein prediction models
| 📋 Spec
| runAction
| runAction://
| Execute sub-actions
| 📋 Spec
| external
| external://
| External service calls
| 📋 Spec
| copilotAction
| copilotAction://
| Salesforce Copilot actions
| 📋 Spec
| @topic.X
| (inline)
| Topic delegation (returns to parent)
| ✅ TDD
Legend: ✅ TDD = Validated via deployment testing | 📋 Spec = Documented in AGENT_SCRIPT.md spec (requires specific org setup to test)
Connection Block (Full Escalation Pattern)
connections:
# Messaging channel escalation
connection messaging:
escalation_message: "One moment, I'm transferring our conversation to get you more help."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "<flow://Escalate_Messaging_To_Live_Agent>"
adaptive_response_allowed: False
# Voice channel escalation
connection voice:
escalation_message: "Please hold while I transfer you to a specialist."
outbound_route_type: "Queue"
outbound_route_name: "Support_Queue"
adaptive_response_allowed: True
# Web chat escalation
connection web:
escalation_message: "Connecting you with a live agent now."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "<flow://Web_Chat_Escalation>"
Key Properties:
| escalation_message
| ✅
| Message shown to user during handoff
| outbound_route_type
| ✅
| OmniChannelFlow, Queue, or Skill
| outbound_route_name
| ✅
| Flow API name or Queue name
| adaptive_response_allowed
| ❌
| Allow LLM to adapt escalation message
🔄 WORKFLOW: Agent Development Lifecycle
Phase 1: Requirements & Design
- Identify deterministic vs. subjective logic
Deterministic: Security checks, financial thresholds, data lookups, counters
-
Subjective: Greetings, context understanding, natural language generation
-
Design FSM architecture - Map topics as states, transitions as edges
-
Define variables - Mutable for state tracking, linked for session context
Phase 2: Agent Script Authoring
-
Create
.agentfile with required blocks -
Write topics with instruction resolution pattern:
Post-action checks at TOP (triggers on loop)
-
Pre-LLM data loading
-
Dynamic instructions for LLM
-
Configure actions with appropriate target protocols
-
Add
available whenguards to enforce security
Phase 3: Validation (LSP + CLI)
AUTOMATIC: LSP validation runs on every Write/Edit to .agent files. Errors are reported with line numbers and autofix suggestions.
LSP Validation Loop (Find Error → Autofix)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Write/Edit │ ──▶ │ LSP Analyze │ ──▶ │ Report │
│ .agent file │ │ (automatic) │ │ Errors │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌────────────────────────────────────────┘
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Claude │ ──▶ │ Apply Fix │ ──▶ │ Re-validate │
│ Suggests Fix│ │ (Edit tool) │ │ (loop) │
└─────────────┘ └─────────────┘ └─────────────┘
LSP Checks (Automatic)
| Mixed tabs/spaces | ❌ Error | Convert to consistent spacing
| Lowercase booleans (true/false)
| ❌ Error
| Capitalize to True/False
| Missing required blocks | ❌ Error | Add missing block template
| Missing default_agent_user
| ❌ Error
| Add placeholder with comment
| Mutable + linked conflict | ❌ Error | Remove conflicting modifier
| Undefined topic references | ⚠️ Warning | Create topic stub
| Post-action check position | ⚠️ Warning | Move to top of instructions
CLI Validation (Before Deploy)
# Validate authoring bundle syntax
sf agent validate authoring-bundle --source-dir ./force-app/main/default/aiAuthoringBundles/MyAgent
Manual Checks
-
default_agent_userexists and is active Einstein Agent User -
All topic references resolve to existing topics
-
Action targets (
flow://,apex://, etc.) exist in org
Phase 4: Testing (Delegate to /sf-ai-agentforce-testing)
-
Batch testing - Run up to 100 test cases simultaneously
-
Quality metrics - Completeness, Coherence, Topic/Action Assertions
-
LLM-as-Judge - Automated scoring against golden responses
Phase 5: Deployment
⚠️ CRITICAL: Use sf agent publish authoring-bundle, NOT sf project deploy start
-
Create bundle directory:
force-app/main/default/aiAuthoringBundles/AgentName/ -
Add files:
AgentName.agent - Your Agent Script
-
AgentName.bundle-meta.xml- Metadata XML (NOT.aiAuthoringBundle-meta.xml) -
Publish:
sf agent publish authoring-bundle --source-dir ./force-app/main/default/aiAuthoringBundles/AgentName -
Monitor - Use trace debugging for production issues
Phase 6: CLI Operations
# Retrieve from org
sf agent retrieve --name MyAgent --target-org sandbox
# Validate syntax
sf agent validate authoring-bundle --source-dir ./force-app/main/default/aiAuthoringBundles/MyAgent
# Publish to org (NOT sf project deploy!)
sf agent publish authoring-bundle --source-dir ./force-app/main/default/aiAuthoringBundles/MyAgent
Bundle Structure (CRITICAL)
force-app/main/default/aiAuthoringBundles/
└── MyAgent/
├── MyAgent.agent # Agent Script file
└── MyAgent.bundle-meta.xml # NOT .aiAuthoringBundle-meta.xml!
bundle-meta.xml content:
<?xml version="1.0" encoding="UTF-8"?>
<AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<bundleType>AGENT</bundleType>
</AiAuthoringBundle>
📊 SCORING SYSTEM (100 Points)
Categories
| Structure & Syntax | 20 | Block ordering, indentation consistency, required fields present
| Deterministic Logic
| 25
| Security via available when, post-action checks, proper conditionals
| Instruction Resolution
| 20
| Correct use of -> vs |, template injection, action execution
| FSM Architecture | 15 | Clear topic separation, explicit transitions, state management
| Action Configuration | 10 | Correct protocols, input/output mapping, error handling
| Deployment Readiness
| 10
| Valid default_agent_user, no compilation errors, metadata complete
Scoring Rubric Details
Structure & Syntax (20 points)
| 20 | All required blocks present, consistent indentation, valid identifiers
| 15 | Minor issues (e.g., inconsistent spacing within tolerance)
| 10 | Missing optional blocks that would improve clarity
| 5 | Block ordering issues or mixed indentation
| 0 | Missing required blocks or compilation failures
Deterministic Logic (25 points)
| 25
| All security actions guarded with available when, post-action patterns used
| 20 | Most guards present, minor gaps in deterministic enforcement
| 15 | Some security logic relies on prompts instead of guards
| 10
| Critical actions lack available when guards
| 0 | Security logic entirely prompt-based (LLM can bypass)
Instruction Resolution (20 points)
| 20 | Arrow syntax for complex logic, proper template injection, correct action execution
| 15 | Mostly correct, minor syntax issues
| 10 | Uses pipe syntax where arrow needed, template injection errors
| 5 | Incorrect phase ordering (data loads after LLM sees instructions)
| 0 | Fundamental misunderstanding of resolution order
FSM Architecture (15 points)
| 15 | Clear topic boundaries, explicit transitions, appropriate escalation paths
| 12 | Good structure with minor redundancy
| 9 | Topics too broad or transitions unclear
| 5 | Monolithic topic handling multiple concerns
| 0 | No topic separation, all logic in start_agent
Action Configuration (10 points)
| 10 | Correct protocols, proper I/O mapping, descriptions present
| 8 | Minor issues (missing descriptions)
| 5 | Wrong protocol for use case
| 2 | Input/output mapping errors
| 0 | Actions don't compile
Deployment Readiness (10 points)
| 10 | Valid user, clean validation, metadata complete
| 8 | Minor warnings
| 5 | Validation errors that need fixing
| 2 | Missing metadata files
| 0 | Cannot deploy
Score Thresholds
| 90-100 | ⭐⭐⭐⭐⭐ Excellent | Deploy with confidence
| 80-89 | ⭐⭐⭐⭐ Very Good | Minor improvements recommended
| 70-79 | ⭐⭐⭐ Good | Review flagged issues before deploy
| 60-69 | ⭐⭐ Needs Work | Address issues before deploy
| <60 | ⭐ Critical | BLOCK - Fix critical issues
Score Report Format
📊 AGENT SCRIPT SCORE REPORT
════════════════════════════════════════
Score: 85/100 ⭐⭐⭐⭐ Very Good
├─ Structure & Syntax: 18/20 (90%)
├─ Deterministic Logic: 22/25 (88%)
├─ Instruction Resolution: 16/20 (80%)
├─ FSM Architecture: 12/15 (80%)
├─ Action Configuration: 9/10 (90%)
└─ Deployment Readiness: 8/10 (80%)
Issues:
⚠️ [Deterministic] Missing `available when` on process_refund action
⚠️ [Resolution] Post-action check should be at TOP of instructions
✓ All Structure & Syntax checks passed
✓ All Action Configuration checks passed
🔧 THE 6 DETERMINISTIC BUILDING BLOCKS
These execute as code, not suggestions. The LLM cannot override them.
| 1
| Conditionals
| if/else resolves before LLM
| if @variables.attempts >= 3:
| 2
| Topic Filters
| Control action visibility
| available when @variables.verified == True
| 3
| Variable Checks
| Numeric/boolean comparisons
| if @variables.churn_risk >= 80:
| 4
| Inline Actions
| Immediate execution
| run @actions.load_customer
| 5
| Utility Actions
| Built-in helpers
| @utils.transition, @utils.escalate
| 6
| Variable Injection
| Template values
| {!@variables.customer_name}
📐 ARCHITECTURE PATTERNS
Pattern 1: Hub and Spoke
Central router (hub) to specialized topics (spokes). Use for multi-purpose agents.
┌─────────────┐
│ topic_sel │
│ (hub) │
└──────┬──────┘
┌─────────┼─────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│refunds │ │ orders │ │support │
└────────┘ └────────┘ └────────┘
Pattern 2: Verification Gate
Security gate before protected topics. Mandatory for sensitive data.
┌─────────┐ ┌──────────┐ ┌───────────┐
│ entry │ ──▶ │ VERIFY │ ──▶ │ protected │
└─────────┘ │ (GATE) │ │ topics │
└────┬─────┘ └───────────┘
│ 3 fails
▼
┌──────────┐
│ lockout │
└──────────┘
Pattern 3: Post-Action Loop
Topic re-resolves after action completes - put checks at TOP.
topic refund:
reasoning:
instructions: ->
# POST-ACTION CHECK (at TOP - triggers on next loop)
if @variables.refund_status == "Approved":
run @actions.create_crm_case
transition to @topic.success
# PRE-LLM DATA LOADING
run @actions.check_churn_risk
set @variables.risk = @outputs.score
# DYNAMIC INSTRUCTIONS FOR LLM
if @variables.risk >= 80:
| Offer full refund to retain customer.
else:
| Offer $10 credit instead.
🐛 DEBUGGING: Trace Analysis
The 6 Span Types
| ➡️ topic_enter
| Execution enters a topic
| ▶ before_reasoning
| Deterministic pre-processing
| 🧠 reasoning
| LLM processes instructions
| ⚡ action_call
| Action invoked
| → transition
| Topic navigation
| ✓ after_reasoning
| Deterministic post-processing
Debugging Workflow
-
Interaction Details - Quick understanding of what happened
-
Trace Waterfall - Technical view with exact prompts, latencies
-
Variable State - Entry vs Exit values reveal when state was ignored
-
Script View - Red squiggles show syntax errors
Common Debug Patterns
| Wrong policy applied
| Variable Entry values
| Change mutable to linked with source:
| Action executed without auth
| available when presence
| Add guard clause
| LLM ignores variable | Instruction resolution order | Move data load before LLM text
| Infinite loop | Transition conditions | Add exit condition
⚠️ COMMON ISSUES & FIXES
| Internal Error, try again later
| Invalid default_agent_user
| Query: sf data query -q "SELECT Username FROM User WHERE Profile.Name = 'Einstein Agent User'" -o TARGET_ORG
| Default agent user X could not be found
| User doesn't exist in target org
| Query the specific target org (user formats vary: some use user@orgid.ext)
| No .agent file found in directory
| agent_name doesn't match folder
| Make agent_name identical to folder name (case-sensitive)
| SyntaxError: cannot mix spaces and tabs
| Mixed indentation
| Use consistent spacing throughout
| SyntaxError: Unexpected 'if'
| Nested if statements
| Use compound condition: if A and B: or flatten to sequential ifs
| SyntaxError: Unexpected 'actions'
| Top-level actions block
| Move actions inside topic.reasoning.actions:
| SyntaxError: Unexpected 'inputs'
| inputs: block in action
| Use with param=value syntax instead
| SyntaxError: Unexpected 'outputs'
| outputs: block in action
| Use set @variables.x = @outputs.y instead
| SyntaxError: Unexpected 'set'
| set after @utils.setVariables
| Use Helper Topic Pattern (set in instructions: ->)
| Duplicate 'available when' clause
| Multiple guards on action
| Combine: available when A and B
| Unexpected 'escalate'
| Reserved action name
| Rename to escalate_now or escalate_to_human
| Transition to undefined topic
| Typo in topic reference
| Check spelling, ensure topic exists
| Variables cannot be both mutable AND linked
| Conflicting modifiers
| Choose one: mutable for state, linked for external
| Required fields missing: [BundleType]
| Using wrong deploy command
| Use sf agent publish authoring-bundle, NOT sf project deploy start
| Cannot find a bundle-meta.xml file
| Wrong file naming
| Rename to AgentName.bundle-meta.xml, NOT .aiAuthoringBundle-meta.xml
| ValidationError: Tool target 'X' is not an action definition
| Action references non-existent Flow/Apex
| Create the action definition first, or use Helper Topic Pattern
| LLM bypasses security check
| Using prompts for security
| Use available when guards instead
| Post-action logic doesn't run | Check not at TOP | Move post-action check to first lines
| Wrong data retrieved | Missing filter | Wrap retriever in Flow with filter inputs
| Variables don't change
| Using @utils.setVariables with set
| Post-action set only works on @actions.*, use Helper Topics
Deployment Gotchas (Validated by Testing)
| AgentName.aiAuthoringBundle-meta.xml
| AgentName.bundle-meta.xml
| sf project deploy start
| sf agent publish authoring-bundle
| sf agent validate --source-dir
| sf agent validate authoring-bundle --source-dir
| Query user from wrong org
| Query target org specifically with -o flag
Einstein Agent User Format (Org-Specific)
Einstein Agent User formats vary between orgs:
-
Production/Partner orgs: Often use
username@orgid.extformat (e.g.,resort_manager@00dak00000gdgwu480119933.ext) -
Dev orgs: May use
username.suffix@orgfarm.salesforce.comformat
MANDATORY: Ask user to confirm which Einstein Agent User to use when creating a new agent.
Always query the specific target org:
# Query target org specifically
sf data query -q "SELECT Username FROM User WHERE Profile.Name = 'Einstein Agent User' AND IsActive = true" -o YOUR_TARGET_ORG
Present the results to the user and ask them to select which user to use for default_agent_user.
⚠️ A user existing in one org does NOT mean it exists in another. Always verify in the deployment target.
📚 DOCUMENT MAP (Progressive Disclosure)
Tier 2: Resource Guides (Comprehensive)
| Syntax reference | resources/syntax-reference.md | Complete block & expression syntax
| FSM design | resources/fsm-architecture.md | State machine patterns & examples
| Instruction resolution | resources/instruction-resolution.md | Three-phase execution model
| Data & multi-agent | resources/grounding-multiagent.md | Retriever actions & SOMA patterns
| Debugging | resources/debugging-guide.md | Trace analysis & forensics
| Testing | resources/testing-guide.md | Batch testing & quality metrics
Tier 3: Quick References (Docs)
| CLI commands | docs/cli-guide.md | sf agent retrieve/validate/deploy
| Patterns | docs/patterns-quick-ref.md | Decision tree for pattern selection
🔗 CROSS-SKILL INTEGRATION
MANDATORY Delegations
| Create Flows for flow:// targets
| /sf-flow
| Flows must exist before agent uses them
| Test agent routing & actions
| /sf-ai-agentforce-testing
| Specialized testing patterns
| Deploy agent to org
| /sf-deploy
| Proper deployment validation
Integration Patterns
| /sf-ai-agentscript
| /sf-flow
| Create Flow, then reference in agent
| /sf-ai-agentscript
| /sf-apex
| Create Apex class, then use apex:// protocol
| /sf-ai-agentscript
| /sf-integration
| Set up Named Credentials for externalService://
✅ DEPLOYMENT CHECKLIST
Configuration
default_agent_user is valid Einstein Agent User
agent_name uses snake_case (no spaces)
Syntax
No mixed tabs/spaces
Booleans use True/False
Variable names use snake_case
Structure
Exactly one start_agent block
At least one topic block
All transitions reference existing topics
Security
Critical actions have available when guards
Session data uses linked variables (not mutable)
Testing
sf agent validate --source-dir ./my-agent passes
Preview mode tested before activation
🚀 MINIMAL WORKING EXAMPLE
config:
agent_name: "simple_agent"
agent_label: "Simple Agent"
description: "A minimal working agent example"
default_agent_user: "agent_user@yourorg.com"
system:
messages:
welcome: "Hello! How can I help you today?"
error: "Sorry, something went wrong."
instructions: "You are a helpful customer service agent."
variables:
customer_verified: mutable boolean = False
topic main:
description: "Main conversation handler"
reasoning:
instructions: ->
if @variables.customer_verified == True:
| You are speaking with a verified customer.
| Help them with their request.
else:
| Please verify the customer's identity first.
actions:
verify: @actions.verify_customer
description: "Verify customer identity"
set @variables.customer_verified = @outputs.verified
start_agent entry:
description: "Entry point for all conversations"
reasoning:
instructions: |
Greet the customer and route to the main topic.
actions:
go_main: @utils.transition to @topic.main
description: "Navigate to main conversation"
📖 OFFICIAL RESOURCES
📚 SOURCES & ACKNOWLEDGMENTS
This skill draws from multiple authoritative sources:
| trailheadapps/agent-script-recipes | 20 reference recipes across 4 categories, AGENT_SCRIPT.md rules document, variable patterns, action target catalog
| Salesforce Official Documentation | Core syntax, API references, deployment guides
| TDD Validation (this skill) | 13 validation agents confirming current-release syntax compatibility
| Tribal knowledge interviews | Canvas View bugs, VS Code limitations, credit consumption patterns
| agentforce.guide | Unofficial but useful examples (note: some patterns don't compile in current release)
⚠️ Note on Feature Validation: Some patterns from external sources (e.g., always_expect_input:, label: property, certain action properties on transitions) do NOT compile in Winter '26. The before_reasoning:/after_reasoning: lifecycle hooks ARE valid but require direct content (no instructions: wrapper) - see the Lifecycle Hooks section for correct syntax. This skill documents only patterns that pass TDD validation.
🏷️ VERSION HISTORY
| 1.3.0
| 2026-01-20
| Lifecycle hooks validated: Added full documentation for before_reasoning: and after_reasoning: with CORRECT syntax (content directly under block, NO instructions: wrapper). Added "Features NOT Valid in Current Release" section documenting 7 features that appear in docs/recipes but don't compile (label on topics/actions, always_expect_input, action properties on transitions). Updated validation_agents count to 13. Confirmed @utils.transition only supports description: property.
| 1.2.0
| 2026-01-20
| Gap analysis vs agent-script-recipes: Expanded Action Target Protocols from 7 to 16 (with validation status indicators), added Variable vs Action I/O Type Matrix, added lifecycle hooks note with TDD validation caveat, added Sources & Acknowledgments section, documented future/planned features notice. TDD validation confirmed label: IS reserved (SKILL.md was correct), before_reasoning:/after_reasoning: syntax from recipes does NOT compile in current release
| 1.1.0
| 2026-01-20
| "Ultimate Guide" tribal knowledge integration: Added complex_data_type_name mapping table, Canvas View corruption bugs, Reserved field names, Preview mode workarounds, Credit consumption table, Supervision vs Handoff clarification, Action output flags for zero-hallucination routing, Latch variable pattern, Loop protection guardrails, Token/size limits, Progress indicators, Connection block escalation patterns, VS Code limitations, Language block quirks. Added 4 new templates: flow-action-lookup, prompt-rag-search, deterministic-routing, escalation-pattern
| 1.0.4
| 2026-01-19
| Progressive testing validation (Quiz_Master, Expense_Calculator, Order_Processor): Added constraints for no top-level actions: block, no inputs:/outputs: in reasoning.actions, expanded nested-if guidance with flattening approach, added new SyntaxError entries to common issues
| 1.0.3 | 2026-01-19 | Added Einstein Agent User interview requirement - mandatory user confirmation when creating new agents
| 1.0.2
| 2026-01-19
| Major corrections from GitHub reference: Fixed block order (config→system), added Helper Topic Pattern, transition vs delegation, expression operators (+/- only), naming rules (80 char max), slot-filling ... syntax, post-action directives (@actions.* only)
| 1.0.1 | 2026-01-19 | Added syntax constraints from 0-shot testing: no nested if, one available when per action, reserved action names
| 1.0.0 | 2026-01 | Initial release with 8-module coverage