
Sf Ai Agentscript
- 1.3k installs
- 423 repo stars
- Updated April 27, 2026
- jaganpro/sf-skills
sf-ai-agentscript is an agent skill for build salesforce agentforce agents with agent script, topics, actions, and testing workflows.
About
The sf-ai-agentscript skill is designed for build Salesforce Agentforce agents with Agent Script, topics, actions, and testing workflows. Use this skill when the user is authoring .agent files, building finite-state topic flows, or needs repeatable control over routing, variables, actions, and publish behavior. > Start with the shortest guide first: references/activation-checklist.md > > Migrating from the Builder UI? Invoke when the user works on Salesforce Agent Script, Agentforce topics, or agent actions.
- creating or editing .agent files.
- deterministic topic routing, guards, and transitions.
- slot filling, instruction resolution, post-action loops, or FSM design.
- designing persona / tone / voice → sf-ai-agentforce-persona.
- building formal test plans or coverage loops → sf-ai-agentforce-testing.
Sf Ai Agentscript by the numbers
- 1,283 all-time installs (skills.sh)
- +6 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #351 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
sf-ai-agentscript capabilities & compatibility
- Capabilities
- creating or editing .agent files · deterministic topic routing, guards, and transit · slot filling, instruction resolution, post actio · designing persona / tone / voice → sf ai agentfo
What sf-ai-agentscript says it does
Agent Script DSL for deterministic Agentforce agents. TRIGGER when: user writes or edits .agent files, builds FSM-based agents, uses Agent Script CLI (sf agent generate authoring-b
Agent Script DSL for deterministic Agentforce agents. TRIGGER when: user writes or edits .agent files, builds FSM-based agents, uses Agent Script CLI (sf agent
npx skills add https://github.com/jaganpro/sf-skills --skill sf-ai-agentscriptAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 423 |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 27, 2026 |
| Repository | jaganpro/sf-skills ↗ |
How do I build salesforce agentforce agents with agent script, topics, actions, and testing workflows?
Build Salesforce Agentforce agents with Agent Script, topics, actions, and testing workflows.
Who is it for?
Salesforce developers implementing Agentforce Agent Script and action wiring.
Skip if: Skip for non-Salesforce agent platforms or Apex-only backends without Agent Script.
When should I use this skill?
User works on Salesforce Agent Script, Agentforce topics, or agent actions.
What you get
Completed sf-ai-agentscript workflow with documented commands, files, and expected deliverables.
- .agent DSL files
- validated authoring bundles
- published Agentforce agents
By the numbers
- Targets Salesforce API v66.0 and newer per skill compatibility metadata
Files
SF-AI-AgentScript Skill
Agent Script is the code-first path for deterministic Agentforce agents. Use this skill when the user is authoring .agent files, building finite-state topic flows, or needs repeatable control over routing, variables, actions, and publish behavior.
Start with the shortest guide first: references/activation-checklist.md
>
Migrating from the Builder UI? Use references/migration-guide.md
When This Skill Owns the Task
Use sf-ai-agentscript when the work involves:
- creating or editing
.agentfiles - deterministic topic routing, guards, and transitions
- Agent Script CLI workflows (
sf agent generate authoring-bundle,sf agent validate authoring-bundle,sf agent preview,sf agent publish authoring-bundle,sf agent activate) - slot filling, instruction resolution, post-action loops, or FSM design
Delegate elsewhere when the user is:
- maintaining Builder metadata agents (
GenAiFunction,GenAiPlugin,GenAiPromptTemplate, Models API, custom Lightning types) → sf-ai-agentforce - designing persona / tone / voice → sf-ai-agentforce-persona
- building formal test plans or coverage loops → sf-ai-agentforce-testing
If the user is in Builder Script / Canvas view but the outcome is a .agent authoring bundle, keep the work in sf-ai-agentscript.
---
Right-Size Determinism
- Determinism is a dial, not a destination.
- Use Agent Script when “mostly right” is not acceptable: gates, mandatory sequencing, explicit state transitions, compliance, or drift control.
- If a workflow is fully static and linear, use Flow or Apex instead of scripting the conversation.
- Prefer a deterministic envelope: deterministic entry/gate → flexible middle → deterministic closeout.
- More determinism is not automatically better. Start minimal, then harden only the parts that show routing drift, sequencing failures, or compliance risk.
---
Required Context to Gather First
Ask for or infer:
- agent purpose and whether Agent Script is truly the right fit
- Service Agent vs Employee Agent
- target org and publish intent
- expected actions / targets (Flow, Apex, PromptTemplate, etc.)
- whether the request is authoring, validation, preview, or publish troubleshooting
---
Activation Checklist
Before you author or fix any .agent file, verify these first:
1. Exactly one `start_agent` block 2. No mixed tabs and spaces 3. Booleans are `True` / `False` 4. No `else if` and no nested `if` 5. No top-level `actions:` block 6. No `@inputs` in `set` expressions 7. `linked` variables have no defaults 8. `linked` variables do not use `object` / `list` types 9. Use explicit `agent_type` 10. Use `@actions.` prefixes consistently 11. Use `run @actions.X` only when `X` is a topic-level action definition with `target:` 12. Do not branch directly on raw `@system_variables.user_input contains/startswith/endswith` for intent routing 13. On prompt-template outputs, prefer `is_displayable: False` + `is_used_by_planner: True` 14. Do not assume `@outputs.X` is scalar — inspect the output schema before branching or assignment
For the expanded version, use references/activation-checklist.md.
---
Non-Negotiable Rules
1) Service Agent vs Employee Agent
| Agent type | Required | Forbidden / caution |
|---|---|---|
AgentforceServiceAgent | Valid default_agent_user, correct permissions, target-org checks, prefer sf org create agent-user | Publishing without a real Einstein Agent User |
AgentforceEmployeeAgent | Explicit agent_type | Supplying default_agent_user |
Full details: references/agent-user-setup.md
2) Recommended top-level block convention
Use this order for consistency in this skill's examples and reviews:
config:
variables:
system:
connection:
knowledge:
language:
start_agent:
topic:Official Salesforce materials present top-level blocks in differing sequences, and local validation evidence indicates multiple orderings compile. Treat this as a style convention, not a standalone correctness or publish blocker.
3) Critical config fields
| Field | Rule |
|---|---|
developer_name | Must match folder / bundle name |
description | Public docs/examples should use this config field |
agent_type | Set explicitly every time |
default_agent_user | Service Agents only |
Local tooling also accepts agent_description: for compatibility, but this skill's public docs and examples should prefer description:.
4) Syntax blockers you should treat as immediate failures
else if- nested
if - comment-only
ifbodies - top-level
actions: - invocation-level
inputs:/outputs:blocks - reserved variable / field names like
descriptionandlabel
Canonical rule set: references/syntax-reference.md and references/validator-rule-catalog.md
---
Recommended Workflow
Recommended Authoring Workflow
Phase 1 — design the agent
- decide whether the problem is actually deterministic enough for Agent Script
- model topics as states and transitions as edges
- define only the variables you truly need
Phase 2 — author the .agent
- create
config,system,start_agent, and topics first - add target-backed actions with full
inputs:andoutputs: - use
available whenfor deterministic tool visibility - normalize raw intent/validation signals into booleans or enums before branching; avoid direct substring checks on raw user utterances for critical control flow
- keep post-action checks at the top of
instructions: ->
Default authoring stance
- Default to direct
.agentauthoring and edits in source control. - Use
sf agent generate authoring-bundle --no-speconly when the user wants local bundle scaffolding. - Treat
sf agent generate agent-specas optional ideation / topic bootstrap, not the default workflow. - Do not route Agent Script users toward
sf agent createorsf agent generate template.
Phase 3 — validate continuously
Validation already runs automatically on write/edit. Use the CLI before publish:
sf agent validate authoring-bundle --api-name MyAgent -o TARGET_ORG --jsonThe validator covers structure, runtime gotchas, target readiness, and org-aware Service Agent checks. Rule IDs live in references/validator-rule-catalog.md.
Phase 4 — preview smoke test
Use the preview loop before publish:
- derive 3–5 smoke utterances
- start preview with the
start/send/endsubcommands, not baresf agent preview - if you use
--authoring-bundle, always choose a mode explicitly:--simulate-actionsor--use-live-actions - inspect topic routing / action invocation / safety / grounding
- fix and rerun up to 3 times
Full loop: references/preview-test-loop.md
Phase 5 — publish and activate
sf agent publish authoring-bundle --api-name MyAgent -o TARGET_ORG --json
# Manual activation
sf agent activate --api-name MyAgent -o TARGET_ORG
# CI / deterministic activation of a known BotVersion
sf agent activate --api-name MyAgent --version <n> -o TARGET_ORG --jsonPublishing does not activate the agent. For automation, prefer --version <n> --json so activation is deterministic and machine-readable.
---
Deterministic Building Blocks
These execute as code, not suggestions:
- conditionals
available whenguards- variable checks
- direct
set/transition to run @actions.Xonly when `X` is a topic-level action definition with `target:`- variable injection into LLM-facing text
Important distinction:
- Deterministic:
set,transition to, andrun @actions.Xfor a target-backed topic action - LLM-directed:
reasoning.actions:utilities / delegations such as@utils.setVariables,@utils.transition, and{!@actions.X}instruction references
If you need deterministic behavior for something that is currently modeled as a reasoning-level utility, either:
- rewrite it as direct
set/transition to, or - promote it to a topic-level target-backed action and
runthat action
See references/instruction-resolution.md and references/architecture-patterns.md.
---
Cross-Skill Integration
Cross-Skill Orchestration
| Task | Delegate to | Why |
|---|---|---|
Build flow:// targets | sf-flow | Flow creation / validation |
| Build Apex action targets | sf-apex | @InvocableMethod and business logic |
| Test topic routing / actions | sf-ai-agentforce-testing | Formal test specs and fix loops |
| Deploy / publish | sf-deploy | Deployment orchestration |
---
High-Signal Failure Patterns
| Symptom | Likely cause | Read next |
|---|---|---|
Internal Error during publish | invalid Service Agent user or missing action I/O | references/agent-user-setup.md, references/actions-reference.md |
invalid input/output parameters on prompt template action | Target template is in Draft status — activate it first | references/action-prompt-templates.md |
| Parser rejects conditionals | else if, nested if, empty if body | references/syntax-reference.md |
| Action target issues | missing Flow / Apex target, inactive Flow, bad schemas | references/actions-reference.md |
| Prompt template runs but user sees blank response | prompt output marked is_displayable: True | references/production-gotchas.md, references/action-prompt-templates.md |
| Prompt action runs but planner behaves like output is missing | output hidden from direct display but not planner-visible | references/production-gotchas.md, references/actions-reference.md |
ACTION_NOT_IN_SCOPE on run @actions.X | run points at a utility / delegation / unresolved action instead of a topic-level target-backed definition | references/syntax-reference.md, references/instruction-resolution.md |
| Deterministic cancel / revise / URL checks behave inconsistently | raw @system_variables.user_input matching or string-method guards are being used as control-flow-critical validation | references/syntax-reference.md, references/production-gotchas.md |
@outputs.X comparisons or assignments behave unexpectedly | the action output is structured/wrapped, not a plain scalar | references/actions-reference.md, references/syntax-reference.md |
| Preview and runtime disagree | linked vars / context / known platform issues | references/known-issues.md |
| Validate passes but publish fails | org-specific user / permission / retrieve-back issue | references/production-gotchas.md, references/cli-guide.md |
---
Reference Map
Start here
- references/activation-checklist.md
- references/syntax-reference.md
- references/actions-reference.md
Publish / runtime safety
- references/agent-user-setup.md
- references/production-gotchas.md
- references/customer-web-client.md
- references/known-issues.md
Architecture / reasoning
- references/architecture-patterns.md
- references/instruction-resolution.md
- references/fsm-architecture.md
- references/patterns-quick-ref.md
Validation / testing / debugging
- references/preview-test-loop.md
- references/testing-guide.md
- references/debugging-guide.md
- references/validator-rule-catalog.md
Examples / scaffolds
- references/minimal-examples.md
- references/migration-guide.md
- assets/
- assets/agents/
- assets/patterns/
Project documentation
- references/version-history.md
- references/sources.md
---
Score Guide
| Score | Meaning |
|---|---|
| 90+ | Deploy with confidence |
| 75–89 | Good, review warnings |
| 60–74 | Needs focused revision |
| < 60 | Block publish |
Full rubric: references/scoring-rubric.md
---
Official Resources
- Agent Script Documentation
- Agent Script Recipes
- Agentforce DX Guide
- references/official-sources.md
# Hello World Employee Agent
# The minimal viable Employee Agent -- NO dedicated user, NO messaging vars
#
# Key differences from Service Agent:
# - agent_type: "AgentforceEmployeeAgent" (runs as logged-in user)
# - NO default_agent_user (would cause HTTP 500 on publish)
# - NO Messaging-linked variables (EndUserId, RoutableId, ContactId)
# - NO language block (optional for all agents, not Employee-specific)
# - NO connection block (Messaging routing is Service Agent only)
#
# For the Service Agent version, see hello-world.agent
#
# Deploy with: sf agent publish authoring-bundle --api-name Hello_World_Employee --target-org [alias]
system:
instructions: "You are a friendly internal assistant. Help employees with their questions."
# Static welcome/error text uses quotes. If you later add {!@variables.x}
# personalization here, switch that message to block form with `|`.
messages:
welcome: "Hello! How can I help you today?"
error: "I'm sorry, something went wrong. Please try again."
config:
developer_name: "Hello_World_Employee"
agent_type: "AgentforceEmployeeAgent"
agent_label: "Hello World Employee Agent"
description: "A minimal Employee Agent example -- no dedicated user needed"
start_agent main:
label: "Main"
description: "Greets employees and provides help"
reasoning:
instructions: ->
| Welcome the employee warmly.
| Ask how you can help them today.
| Be friendly and professional.# Hello World Service Agent
# The minimal viable Agentforce Service Agent - start here!
# For Employee Agent (no dedicated user), see hello-world-employee.agent
#
# This template shows the absolute minimum structure required for a working Service Agent.
# Use this as your starting point when learning Agent Script.
#
# ★ Why This Structure?
# - system: Sets agent personality and default messages
# - config: Required metadata for deployment (`developer_name` must be unique)
# - variables: Linked variables connect to Messaging context (required for deployment)
# - language: Locale settings (required for deployment)
# - start_agent: Entry point topic (exactly one required)
#
# ★ Key Validation Points (from 100-point scoring):
# - [10 pts] config block with all 4 required fields
# - [10 pts] 3 linked variables (EndUserId, RoutableId, ContactId)
# - [5 pts] language block present
# - [10 pts] At least one start_agent topic
#
# Deploy with: sf agent publish authoring-bundle --api-name Hello_World_Agent --target-org [alias]
system:
instructions: "You are a friendly assistant. Greet users warmly and help them."
# Static welcome/error text uses quotes. If you later add {!@variables.x}
# personalization here, switch that message to block form with `|`.
messages:
welcome: "Hello! I'm here to help. What can I do for you today?"
error: "I'm sorry, something went wrong. Please try again."
config:
developer_name: "Hello_World_Agent"
agent_type: "AgentforceServiceAgent"
default_agent_user: "your.user@company.com"
agent_label: "Hello World Agent"
description: "A minimal example agent to learn Agent Script basics"
variables:
# Linked variables (required) - Connect to Salesforce Messaging context
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"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
# Entry point topic - this is where every conversation starts
start_agent main:
label: "Main"
description: "Greets users and provides help"
reasoning:
instructions: ->
| Welcome the user warmly.
| Ask how you can help them today.
| Be friendly and conversational.# Multi-Topic Agent Template
# An agent with multiple conversation topics (hub-and-spoke pattern)
# Users are routed to specialized topics based on their needs
#
# Usage: Replace {{placeholders}} with your values
# Required: developer_name, agent_type, default_agent_user, agent_label, description
# Required: At least 2 topics with label and description
# NOTE: {{WelcomeMessage}} is a scaffolding placeholder, not Agent Script runtime interpolation.
# If you replace it with a system message that uses {!@variables.x}, use block form:
# welcome: |
# Hi {!@variables.user_name}!
system:
instructions: "{{SystemInstructions}}"
messages:
welcome: "{{WelcomeMessage}}"
error: "I'm sorry, I encountered an issue. Please try again."
config:
developer_name: "{{AgentApiName}}"
agent_type: "AgentforceServiceAgent"
default_agent_user: "{{AgentUser}}"
agent_label: "{{AgentLabel}}"
description: "{{AgentDescription}}"
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_intent: mutable string
description: "What the user is trying to accomplish"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
start_agent topic_selector:
label: "Topic Selector"
description: "Routes users to the appropriate topic based on their needs"
reasoning:
instructions: ->
| Determine what the user needs help with.
| Route them to the most appropriate topic.
| If unclear, ask clarifying questions.
actions:
go_to_topic_one: @utils.transition to @topic.{{topic_one_name}}
go_to_topic_two: @utils.transition to @topic.{{topic_two_name}}
go_to_topic_three: @utils.transition to @topic.{{topic_three_name}}
go_to_farewell: @utils.transition to @topic.farewell
go_to_escalation: @utils.transition to @topic.escalation
topic {{topic_one_name}}:
label: "{{TopicOneLabel}}"
description: "{{TopicOneDescription}}"
reasoning:
instructions: ->
| {{TopicOneInstructions}}
actions:
back_to_menu: @utils.transition to @topic.topic_selector
topic {{topic_two_name}}:
label: "{{TopicTwoLabel}}"
description: "{{TopicTwoDescription}}"
reasoning:
instructions: ->
| {{TopicTwoInstructions}}
actions:
back_to_menu: @utils.transition to @topic.topic_selector
topic {{topic_three_name}}:
label: "{{TopicThreeLabel}}"
description: "{{TopicThreeDescription}}"
reasoning:
instructions: ->
| {{TopicThreeInstructions}}
actions:
back_to_menu: @utils.transition to @topic.topic_selector
topic farewell:
label: "Farewell"
description: "Ends the conversation gracefully"
reasoning:
instructions: ->
| Thank the user for reaching out.
| Wish them a great day.
| Let them know they can return anytime.
topic escalation:
label: "Escalation"
description: "Handles requests to transfer to a live human agent"
reasoning:
instructions: ->
| If the user explicitly asks to speak with a human, escalate.
| Acknowledge the request and transfer gracefully.
actions:
escalate_to_human: @utils.escalate
description: "Escalate to a human agent"# Simple FAQ Agent
# A minimal working example of an Agentforce agent
# Uses pure LLM reasoning without external actions
#
# Deploy with: sf agent publish authoring-bundle --api-name Simple_FAQ_Agent --target-org [alias]
system:
instructions: "You are a helpful FAQ assistant for our company. Answer questions accurately and concisely. If you don't know the answer, say so honestly. Never share sensitive or confidential information. Keep responses friendly and professional."
messages:
welcome: "Hello! I'm your FAQ assistant. How can I help you today?"
error: "I'm sorry, I encountered an issue. Please try again."
config:
developer_name: "Simple_FAQ_Agent"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent.user@company.com"
agent_label: "Simple FAQ Agent"
description: "A minimal FAQ agent that answers common questions using AI"
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_question: mutable string
description: "The user's current question"
conversation_topic: mutable string
description: "The current topic being discussed"
question_count: mutable number
description: "Number of questions answered in this session"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
start_agent topic_selector:
label: "Topic Selector"
description: "Routes incoming questions to the FAQ handler"
reasoning:
instructions: ->
| Listen to the user's question and determine how to help.
| If the question is about a specific topic, note it.
| Route to the FAQ handler for processing.
actions:
go_to_faq_handler: @utils.transition to @topic.faq_handler
end_conversation: @utils.transition to @topic.farewell
topic faq_handler:
label: "FAQ Handler"
description: "Handles frequently asked questions and provides helpful answers"
reasoning:
instructions: ->
| Answer the user's question based on your knowledge.
| Be helpful, accurate, and concise.
| Keep responses under 3-4 sentences when possible.
| If you need more information, ask clarifying questions.
|
| Common topics you can help with:
| - Business hours and location
| - Return and refund policies
| - Shipping information
| - Product questions
| - Account and billing
|
| If the question is outside your knowledge:
| - Acknowledge you don't have that information
| - Suggest contacting customer support
| - Offer to help with something else
actions:
new_question: @utils.transition to @topic.topic_selector
end_conversation: @utils.transition to @topic.farewell
go_to_escalation: @utils.transition to @topic.escalation
topic farewell:
label: "Farewell"
description: "Ends the conversation politely"
reasoning:
instructions: ->
| Thank the user for their questions.
| Wish them a great day.
| Let them know they can return anytime for more help.
topic escalation:
label: "Escalation"
description: "Handles requests to speak with a human agent"
reasoning:
instructions: ->
| If the user wants to speak with a human, escalate gracefully.
| Acknowledge their request and transfer the conversation.
actions:
escalate_to_human: @utils.escalate
description: "Transfer to a human agent"<?xml version="1.0" encoding="UTF-8"?>
<AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<bundleType>AGENT</bundleType>
</AiAuthoringBundle>
Complete Agent Examples & Starter Scaffolds
Examples and starter scaffolds for building complete, deployable agents.
Learning Path
Service Agent Examples
| Example | Complexity | Description |
|---|---|---|
hello-world.agent | Beginner | Minimal viable Service Agent - start here |
simple-qa.agent | Beginner | Single-topic Q&A agent |
multi-topic.agent | Intermediate | Multi-topic routing agent |
production-faq.agent | Advanced | Production-ready FAQ with escalation |
Employee Agent Examples
| Example | Complexity | Description |
|---|---|---|
hello-world-employee.agent | Beginner | Minimal viable Employee Agent - no dedicated user needed |
Service vs Employee: Service Agents run as a dedicated Einstein Agent User and requiredefault_agent_user, linked Messaging variables, andconnectionblocks. Employee Agents run as the logged-in user and need none of these. See agent-user-setup.md for details.
Quick Start
1. Copy a starter example to your SFDX project if you want a scaffold:
mkdir -p force-app/main/default/aiAuthoringBundles/My_Agent
cp hello-world.agent force-app/main/default/aiAuthoringBundles/My_Agent/My_Agent.agent
cp ../metadata/bundle-meta.xml force-app/main/default/aiAuthoringBundles/My_Agent/My_Agent.bundle-meta.xml2. Validate and deploy:
sf agent validate authoring-bundle --api-name My_Agent --target-org your-org --json
sf agent publish authoring-bundle --api-name My_Agent --target-org your-org --jsonDefault repo workflow: create or edit the target .agent directly. These files are optional examples/scaffolds, not a required template system.>
System message tip: Keep staticwelcome/errormessages in quotes. If you personalize a system message with Agent Script interpolation such as{!@variables.user_name}, use block form with|. Template placeholders like{{WelcomeMessage}}in these scaffolds are pre-processing placeholders, not Agent Script runtime interpolation.
Common Top-Level Blocks
Use this ordering convention for consistency in this skill's examples.
| Block | Required | Purpose |
|---|---|---|
config: | ✅ Yes | Deployment metadata (developer_name, agent_label, agent_type, etc.) |
variables: | Optional | Data connections and state storage |
system: | ✅ Yes | Agent personality and default messages |
connection: | Optional | Escalation routing |
knowledge: | Optional | Knowledge configuration |
language: | Optional | Locale configuration |
start_agent | ✅ Yes | Entry point topic (exactly one required) |
topic | ✅ Yes | Conversation topics (one or more required) |
Official Salesforce materials can present these blocks in different sequences. This table reflects the convention used by this skill, not a universal compile rule.
Next Steps
- components/ - Reusable action and topic snippets
- patterns/ - Advanced patterns for complex behaviors
- metadata/ - Supporting metadata examples
# Simple Q&A Agent Template
# A minimal agent that handles basic questions using LLM reasoning only
# No external actions - just conversational AI
#
# Usage: Replace {{placeholders}} with your values
# Required: developer_name, agent_type, default_agent_user, agent_label, description
# NOTE: {{WelcomeMessage}} is a scaffolding placeholder, not Agent Script runtime interpolation.
# If you replace it with a system message that uses {!@variables.x}, use block form:
# welcome: |
# Hi {!@variables.user_name}!
system:
instructions: "{{SystemInstructions}}"
messages:
welcome: "{{WelcomeMessage}}"
error: "I'm sorry, I encountered an issue. Please try again."
config:
developer_name: "{{AgentApiName}}"
agent_type: "AgentforceServiceAgent"
default_agent_user: "{{AgentUser}}"
agent_label: "{{AgentLabel}}"
description: "{{AgentDescription}}"
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: "The user's current question or request"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
start_agent topic_selector:
label: "Topic Selector"
description: "Routes incoming requests to the Q&A handler"
reasoning:
instructions: ->
| Listen to the user's question.
| Route to the Q&A handler for processing.
actions:
go_to_qa_handler: @utils.transition to @topic.qa_handler
end_conversation: @utils.transition to @topic.farewell
topic qa_handler:
label: "Q&A Handler"
description: "Handles questions and provides answers"
reasoning:
instructions: ->
| Answer the user's question based on your knowledge.
| Be helpful, accurate, and concise.
| Keep responses clear and easy to understand.
| If you need more information, ask clarifying questions.
actions:
new_question: @utils.transition to @topic.topic_selector
end_conversation: @utils.transition to @topic.farewell
topic farewell:
label: "Farewell"
description: "Ends the conversation gracefully"
reasoning:
instructions: ->
| Thank the user for their questions.
| Wish them a great day.
| Let them know they can return anytime for more help./**
* @description Queueable job for AI generation using Agentforce Models API
* Generates {{Description}} for {{ObjectName}} records
* @author {{Author}}
* @date {{Date}}
*
* @requires API v61.0+ (Spring '24)
* @requires Einstein Generative AI enabled
* @requires Einstein Generative AI User permission set
*
* @example
* // Invoke from trigger or other context:
* List<Id> recordIds = new List<Id>{ '001xx000003DGXXX' };
* System.enqueueJob(new {{ClassName}}_AI_Queueable(recordIds));
*/
public with sharing class {{ClassName}}_AI_Queueable implements Queueable, Database.AllowsCallouts {
// ═══════════════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════════════
/**
* Available Models:
* - sfdc_ai__DefaultOpenAIGPT4OmniMini (Cost-effective, faster)
* - sfdc_ai__DefaultOpenAIGPT4Omni (More capable, slower)
* - sfdc_ai__DefaultAnthropic (Claude - nuanced)
* - sfdc_ai__DefaultGoogleGemini (Multimodal capable)
*/
private static final String AI_MODEL = 'sfdc_ai__DefaultOpenAIGPT4OmniMini';
/**
* Maximum records to process in a single job.
* Recommended: 10-20 for AI processing to avoid timeouts.
*/
private static final Integer MAX_RECORDS_PER_JOB = 20;
// ═══════════════════════════════════════════════════════════════════════
// INSTANCE VARIABLES
// ═══════════════════════════════════════════════════════════════════════
private List<Id> recordIds;
// ═══════════════════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Constructor
* @param recordIds List of {{ObjectName}} record IDs to process
*/
public {{ClassName}}_AI_Queueable(List<Id> recordIds) {
this.recordIds = recordIds;
}
// ═══════════════════════════════════════════════════════════════════════
// QUEUEABLE EXECUTION
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Execute the queueable job
* @param context QueueableContext
*/
public void execute(QueueableContext context) {
if (recordIds == null || recordIds.isEmpty()) {
return;
}
// Split into current batch and remaining
List<Id> currentBatch = new List<Id>();
List<Id> remainingIds = new List<Id>();
for (Integer i = 0; i < recordIds.size(); i++) {
if (i < MAX_RECORDS_PER_JOB) {
currentBatch.add(recordIds[i]);
} else {
remainingIds.add(recordIds[i]);
}
}
// Process current batch
processRecords(currentBatch);
// Chain next job if more records remain
if (!remainingIds.isEmpty() && !Test.isRunningTest()) {
System.enqueueJob(new {{ClassName}}_AI_Queueable(remainingIds));
}
}
// ═══════════════════════════════════════════════════════════════════════
// PROCESSING LOGIC
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Process a batch of records
* @param batchIds Record IDs to process in this batch
*/
private void processRecords(List<Id> batchIds) {
// Query records with fields needed for AI prompt
List<{{ObjectName}}> records = [
SELECT Id, Name
// TODO: Add fields needed for AI context
// , Description, Subject, Type
FROM {{ObjectName}}
WHERE Id IN :batchIds
WITH USER_MODE
];
List<{{ObjectName}}> toUpdate = new List<{{ObjectName}}>();
for ({{ObjectName}} record : records) {
try {
// Generate AI content
String aiContent = generateAIContent(record);
if (String.isNotBlank(aiContent)) {
// TODO: Update the target field with AI-generated content
// record.AI_Summary__c = aiContent;
toUpdate.add(record);
}
} catch (Exception e) {
// Log error but continue processing other records
logError(record.Id, e);
}
}
// Batch update
if (!toUpdate.isEmpty()) {
try {
update toUpdate;
} catch (DmlException e) {
System.debug(LoggingLevel.ERROR, 'DML Error: ' + e.getMessage());
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// AI GENERATION
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Generate AI content for a single record
* @param record The record to generate content for
* @return Generated text content
*/
private String generateAIContent({{ObjectName}} record) {
// Build the prompt with record context
String prompt = buildPrompt(record);
// Create Models API request
aiplatform.ModelsAPI.createGenerations_Request request =
new aiplatform.ModelsAPI.createGenerations_Request();
request.modelName = AI_MODEL;
aiplatform.ModelsAPI_GenerationRequest genRequest =
new aiplatform.ModelsAPI_GenerationRequest();
genRequest.prompt = prompt;
request.body = genRequest;
// Call the API
aiplatform.ModelsAPI.createGenerations_Response response =
aiplatform.ModelsAPI.createGenerations(request);
// Extract and return generated text
if (response.Code200 != null &&
response.Code200.generations != null &&
!response.Code200.generations.isEmpty()) {
return response.Code200.generations[0].text;
}
return null;
}
/**
* @description Build the AI prompt for a record
* @param record The record to build prompt for
* @return Formatted prompt string
*/
private String buildPrompt({{ObjectName}} record) {
// TODO: Customize this prompt for your use case
String prompt =
'{{PromptInstructions}}\n\n' +
'Record Information:\n' +
'- Name: ' + record.Name + '\n';
// TODO: Add more fields as needed
// '- Description: ' + record.Description + '\n' +
// '- Type: ' + record.Type + '\n';
return prompt;
}
// ═══════════════════════════════════════════════════════════════════════
// ERROR HANDLING
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Log processing errors
* @param recordId The record that failed
* @param e The exception that occurred
*/
private void logError(Id recordId, Exception e) {
System.debug(LoggingLevel.ERROR,
'{{ClassName}}_AI_Queueable Error for ' + recordId + ': ' + e.getMessage());
System.debug(LoggingLevel.ERROR, 'Stack Trace: ' + e.getStackTraceString());
// TODO: Implement custom error logging
// Options:
// 1. Create Error_Log__c record
// 2. Publish Platform Event for monitoring
// 3. Send email notification
}
// ═══════════════════════════════════════════════════════════════════════
// TEST SUPPORT
// ═══════════════════════════════════════════════════════════════════════
/**
* @description Test-visible method to verify prompt generation
* @param record Test record
* @return Generated prompt
*/
@TestVisible
private String testBuildPrompt({{ObjectName}} record) {
return buildPrompt(record);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
AUTHORING BUNDLE METADATA FILE
==============================
CRITICAL NAMING CONVENTION:
- File MUST be named: AgentName.bundle-meta.xml
- NOT: AgentName.aiAuthoringBundle-meta.xml
DIRECTORY STRUCTURE:
force-app/main/default/aiAuthoringBundles/
└── MyAgent/
├── MyAgent.agent <- Agent Script file
└── MyAgent.bundle-meta.xml <- This file (rename to match agent)
DEPLOYMENT COMMAND:
sf agent publish authoring-bundle --api-name MyAgent --target-org TARGET_ORG
DO NOT USE: sf project deploy start (will fail with "Required fields are missing: [BundleType]")
-->
<AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<bundleType>AGENT</bundleType>
<!-- Optional but recommended: version tag for tracking bundle iterations -->
<versionTag>v0.1</versionTag>
</AiAuthoringBundle>
# Apex-Based Action Template
# Define an action that calls a Salesforce Apex @InvocableMethod
# This is a PARTIAL template - define actions inside a topic block
#
# Usage: Replace {{placeholders}} with your values
# Place this inside a topic's actions: block
#
# ⚠️ The target format is apex://ClassName (NOT ClassName.MethodName)
# The runtime auto-discovers the @InvocableMethod on the class.
# ⚠️ NO GenAiFunction metadata needed for AiAuthoringBundle (Agent Script).
# Action Definition (place inside topic's actions: block)
{{action_name}}:
description: "{{ActionDescription}}"
inputs:
{{input_1_name}}: {{input_1_type}}
description: "{{Input1Description}}"
{{input_2_name}}: {{input_2_type}}
description: "{{Input2Description}}"
outputs:
{{output_1_name}}: {{output_1_type}}
description: "{{Output1Description}}"
success: boolean
description: "Whether the operation succeeded"
error_message: string
description: "Error message if operation failed"
target: "apex://{{ApexClassName}}"
# Usage in reasoning block:
#
# reasoning:
# instructions: ->
# | Help the user with their request.
# actions:
# invoke_{{action_name}}: @actions.{{action_name}}
# with {{input_1_name}}=... # LLM fills from conversation
# with {{input_2_name}}=@variables.some_var # From variable
# set @variables.result = @outputs.{{output_1_name}}
# set @variables.success = @outputs.success
# Common Apex Target Patterns:
# - apex://AccountService
# - apex://CaseService
# - apex://OrderService
# - apex://IntegrationService
# - apex://CalculationService
# Apex Class Requirements:
# - Must be global or public with sharing
# - Must have exactly ONE @InvocableMethod annotation
# - Use @InvocableVariable annotations on input/output wrapper class fields
# - No GenAiFunction metadata needed for AiAuthoringBundle (Agent Script)# Error Handling Topic Template
# A topic with validation and guard clauses for critical operations
# This is a PARTIAL template - use within a complete agent file
#
# Usage: Replace {{placeholders}} with your values
# Note: Includes validation patterns and error handling
topic {{topic_name}}:
label: "{{TopicLabel}}"
description: "{{TopicDescription}} - includes validation and error handling"
actions:
{{action_name}}:
description: "{{ActionDescription}}"
inputs:
{{input_name}}: {{input_type}}
description: "{{InputDescription}}"
outputs:
success: boolean
description: "Whether the operation succeeded"
error_message: string
description: "Error message if operation failed"
{{output_name}}: {{output_type}}
description: "{{OutputDescription}}"
target: "{{ActionTarget}}"
reasoning:
instructions: ->
# Validation guard clauses
if @variables.{{required_field}} is None:
set @variables.validation_passed = False
| I need {{RequiredFieldDescription}} before I can proceed.
| Please provide this information.
if @variables.{{amount_field}} > {{MaxAmount}}:
set @variables.validation_passed = False
| The {{AmountFieldDescription}} exceeds the maximum of {{MaxAmount}}.
| Would you like to:
| - Use the maximum allowed amount
| - Split into multiple operations
| - Contact support for a higher limit
if @variables.validation_passed == True:
| All validations passed. Proceeding with the operation.
actions:
# Only available when validation passes
execute_action: @actions.{{action_name}}
with {{input_name}}=@variables.{{input_variable}}
set @variables.operation_success = @outputs.success
set @variables.result = @outputs.{{output_name}}
available when @variables.validation_passed == True
# Handle errors
retry_operation: @utils.transition to @topic.{{topic_name}}
available when @variables.operation_success == False
back_to_menu: @utils.transition to @topic.topic_selector# Escalation Setup Pattern
# Complete agent template with connection block for human escalation
#
# ★ When To Use This Pattern:
# - Agent needs to transfer conversations to human agents
# - Using Omni-Channel for routing
# - Enhanced Chat or other messaging channels
#
# ★ Key Components:
# 1. connection messaging: block - defines routing destination
# 2. @utils.escalate action - triggers the transfer
# 3. escalation topic - handles the handoff flow
#
# ★ Prerequisites:
# - Omni-Channel configured in Salesforce
# - Omni-Channel Flow created for escalation routing
# - Messaging channel active (Enhanced Chat, etc.)
#
# This is a COMPLETE template - customize for your use case
system:
instructions: "You are a helpful customer service agent. Be professional, friendly, and helpful. If you cannot resolve an issue or the customer requests a human, transfer them to a live agent."
messages:
welcome: "Hello! I'm here to help you today. What can I assist you with?"
error: "I apologize, but I encountered an issue. Let me connect you with a human agent."
config:
developer_name: "{{AGENT_NAME}}"
agent_type: "AgentforceServiceAgent"
default_agent_user: "{{AGENT_USER_EMAIL}}"
agent_label: "{{AGENT_LABEL}}"
description: "Customer service agent with human escalation capability"
variables:
# Required linked variables for messaging context
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"
# Escalation tracking variables
escalation_requested: mutable boolean = False
description: "Whether customer requested human agent"
escalation_reason: mutable string
description: "Reason for escalation"
# ⚠️ Use 'number' not 'integer' - integer type is NOT supported in AiAuthoringBundle
attempts_before_escalation: mutable number = 0
description: "Number of attempts before escalating"
language:
default_locale: "en_US"
additional_locales: ""
all_additional_locales: False
# ★ CONNECTION BLOCK - Required for @utils.escalate to work
# This defines where escalated conversations are routed
# Use standalone `connection <channel>:` blocks (NOT a `connections:` wrapper)
connection messaging:
# ⚠️ IMPORTANT: Only "OmniChannelFlow" is supported here
outbound_route_type: "OmniChannelFlow"
# API name of your Omni-Channel Flow with required flow:// prefix
outbound_route_name: "flow://{{OMNI_CHANNEL_FLOW_NAME}}"
# ⚠️ REQUIRED: escalation_message must be included when connection block is present
escalation_message: "Transferring you to a human agent now..."
# Optional: Allow agent to adapt responses during escalation
adaptive_response_allowed: True
# ★ MULTI-CHANNEL EXAMPLE (use separate standalone connection blocks)
# connection messaging:
# outbound_route_type: "OmniChannelFlow"
# outbound_route_name: "flow://Chat_Support_Flow"
# escalation_message: "Connecting you to chat support..."
# adaptive_response_allowed: True
#
# connection voice:
# outbound_route_type: "OmniChannelFlow"
# outbound_route_name: "flow://Phone_Support_Flow"
# escalation_message: "Transferring to phone support..."
# adaptive_response_allowed: False
# Entry point
start_agent topic_selector:
label: "Topic Selector"
description: "Routes users to appropriate topics based on intent"
reasoning:
instructions: ->
| Greet the customer and determine their needs.
| If they ask for a human or live agent, route to escalation.
| Otherwise, try to help them directly.
actions:
go_to_help: @utils.transition to @topic.help
go_to_escalation: @utils.transition to @topic.escalation
available when @variables.escalation_requested == True
topic help:
label: "Help"
description: "Provides assistance to customers"
reasoning:
instructions: ->
| Help the customer with their question.
| If you cannot resolve their issue after 2-3 attempts, offer to connect them with a human.
| If they explicitly ask for a human agent at any time, transfer immediately.
|
| Phrases that indicate escalation request:
| - "talk to a human"
| - "speak to someone"
| - "real person"
| - "live agent"
| - "customer service representative"
|
| Track the customer's escalation reason in @variables.escalation_reason when they provide one.
actions:
go_to_escalation_offer: @utils.transition to @topic.escalation
go_to_escalation_now: @utils.transition to @topic.escalation
topic escalation:
label: "Escalation"
description: "Transfers conversation to human agent"
reasoning:
instructions: ->
| The customer is being transferred to a human agent.
| Acknowledge their request and apologize for any inconvenience.
| Let them know a human will be with them shortly.
|
| Say something like:
| "I understand you'd like to speak with a human agent. I'm connecting you now.
| A customer service representative will be with you shortly. Thank you for your patience."
actions:
# ★ ESCALATION ACTION
# This transfers the conversation to the queue defined in connection block
transfer_to_human: @utils.escalate
description: "Transfer to human agent when customer requests or issue cannot be resolved"
# ★ NOTE: Skill-Based and Queue-Based Routing
# ⚠️ As of Dec 2025, only "OmniChannelFlow" is supported for outbound_route_type
# "queue", "skill", and "agent" cause validation errors
# You must create an Omni-Channel Flow that routes to your desired queue/skill
# ★ Alternative: GenAiPlannerBundle Escalation with Reason
# If using GenAiPlannerBundle (not visible in Studio), you can use:
#
# actions:
# escalate_with_reason: @utils.escalate with reason="Customer requested human assistance"
#
# NOTE: The "with reason" syntax only works in GenAiPlannerBundle!
# AiAuthoringBundle will fail with SyntaxError if you use it.
# ★ Troubleshooting Escalation:
#
# Issue: "escalate" action not recognized
# Fix: Add the connection messaging: block
#
# Issue: Transfer fails silently
# Fix: Verify Omni-Channel queue exists and has available agents
#
# Issue: SyntaxError: Unexpected 'with'
# Fix: You're using AiAuthoringBundle - remove "with reason" syntax
#
# Issue: Agent user lacks permissions
# Fix: Grant Omni-Channel permissions to the default_agent_user# Flow-Based Action Template
# Define an action that calls a Salesforce Flow
# This is a PARTIAL template - define actions inside a topic block
#
# ⚠️ NOTE ON DEPLOYMENT METHODS:
# - AiAuthoringBundle: `with`/`set` clauses ARE supported (TDD validated v1.7.0+)
# - GenAiPlannerBundle: Full syntax including `with`/`set` also supported
#
# Both deployment methods support the Two-Level Action System:
# Level 1: Action definition in topic `actions:` block (with target/inputs/outputs)
# Level 2: Action invocation in `reasoning.actions:` block (with `with`/`set` clauses)
#
# Usage: Replace {{placeholders}} with your values
# Place this inside a topic's actions: block
# Action Definition (place inside topic's actions: block)
{{action_name}}:
description: "{{ActionDescription}}"
inputs:
{{input_1_name}}: {{input_1_type}}
description: "{{Input1Description}}"
{{input_2_name}}: {{input_2_type}}
description: "{{Input2Description}}"
outputs:
{{output_1_name}}: {{output_1_type}}
description: "{{Output1Description}}"
{{output_2_name}}: {{output_2_type}}
description: "{{Output2Description}}"
target: "flow://{{FlowApiName}}"
# Usage in reasoning block:
#
# ═══════════════════════════════════════════════════════════════════════════════
# ✅ Recommended Pattern — Level 2 invocation with `with`/`set` (works in both
# AiAuthoringBundle and GenAiPlannerBundle, TDD validated v1.7.0+)
# ═══════════════════════════════════════════════════════════════════════════════
#
# reasoning:
# instructions: ->
# | Help the user with their request.
# actions:
# invoke_{{action_name}}: @actions.{{action_name}}
# with {{input_1_name}}=... # LLM fills from conversation
# with {{input_2_name}}=@variables.some_var # From variable
# set @variables.result1 = @outputs.{{output_1_name}}
# set @variables.result2 = @outputs.{{output_2_name}}
#
# ═══════════════════════════════════════════════════════════════════════════════
# Alternative: LLM auto-invoke based on action description (no explicit binding)
# ═══════════════════════════════════════════════════════════════════════════════
#
# reasoning:
# instructions: ->
# | Help the user with their request.
# | Use the available actions when needed.
# actions:
# back_to_menu: @utils.transition to @topic.topic_selector
# Common Flow Target Patterns:
# - flow://Get_Account_Details
# - flow://Create_Case
# - flow://Update_Opportunity
# - flow://Send_Email_Notification
# - flow://Calculate_Discount
# Input/Output Types: string, number, boolean, list[string], object# N-ary Boolean Conditions Template
# Demonstrates using 3+ conditions with and/or operators
#
# This is a PARTIAL template - integrate into a complete agent file
#
# ★ KEY CONCEPTS:
#
# N-ary AND: All conditions must be true
# N-ary OR: At least one condition must be true
# Grouping: Use () for complex expressions
#
# ★ Supported Contexts:
# - if statements in before_reasoning/after_reasoning
# - available when clauses on actions
#
# ★ Common Mistake:
# DO NOT nest if statements. Use N-ary and/or instead.
# ❌ if a: if b: if c: (nested - INVALID)
# ✅ if a and b and c: (flat - CORRECT)
# ═══════════════════════════════════════════════════════════════
# PATTERN 1: Three+ AND conditions in lifecycle
# ═══════════════════════════════════════════════════════════════
# Example: Require multiple authentication checks
topic secure_action:
label: "Secure Action"
description: "Performs security-sensitive operations"
before_reasoning:
# All three conditions must be true
if @variables.is_authenticated and @variables.has_permission and @variables.session_valid:
transition to @topic.authorized_action
# Otherwise stay in this topic
reasoning:
instructions: ->
| User needs to authenticate before proceeding.
# ═══════════════════════════════════════════════════════════════
# PATTERN 2: Three+ OR conditions in lifecycle
# ═══════════════════════════════════════════════════════════════
# Example: Any elevated role gets access
topic admin_panel:
label: "Admin Panel"
description: "Administrative features"
before_reasoning:
# Any one of these roles grants access
if @variables.is_admin or @variables.is_moderator or @variables.is_superuser:
transition to @topic.admin_features
# Non-admins redirected
transition to @topic.access_denied
# ═══════════════════════════════════════════════════════════════
# PATTERN 3: N-ary conditions in available when
# ═══════════════════════════════════════════════════════════════
topic order_management:
label: "Order Management"
description: "Handles order operations"
reasoning:
instructions: ->
| Help the customer with their order.
actions:
# Action available only when ALL conditions met
process_return: @actions.handle_return
description: "Process a return request"
available when @variables.order_exists == True and @variables.within_return_window == True and @variables.item_eligible == True
# Action available when ANY premium tier matches
use_priority: @actions.priority_service
description: "Use priority service queue"
available when @variables.tier == "gold" or @variables.tier == "platinum" or @variables.tier == "enterprise"
# Mixed: specific product AND any valid status
expedite: @actions.expedite_order
description: "Expedite the current order"
available when @variables.product_type == "perishable" and (@variables.status == "pending" or @variables.status == "processing")
# ═══════════════════════════════════════════════════════════════
# PATTERN 4: Complex grouped conditions
# ═══════════════════════════════════════════════════════════════
topic smart_routing:
label: "Smart Routing"
description: "Routes based on complex criteria"
before_reasoning:
# Premium with any product type OR standard with warranty
if (@variables.tier == "premium" and @variables.product_type != None) or (@variables.tier == "standard" and @variables.has_warranty == True):
transition to @topic.priority_support
# ═══════════════════════════════════════════════════════════════
# ANTI-PATTERNS - DO NOT USE
# ═══════════════════════════════════════════════════════════════
# ❌ WRONG - Nested if statements (causes "Missing required element" error)
# before_reasoning:
# if @variables.a == True:
# if @variables.b == True:
# if @variables.c == True:
# transition to @topic.x
# ✅ CORRECT - Flat N-ary condition
# before_reasoning:
# if @variables.a == True and @variables.b == True and @variables.c == True:
# transition to @topic.x# Topic with Actions Template
# A topic that integrates with external systems via Flow or Apex actions
# This is a PARTIAL template - use within a complete agent file
#
# Two-Level Action System:
# Level 1: topic.actions block DEFINES actions (with target:, inputs:, outputs:)
# Level 2: reasoning.actions block INVOKES them (with @actions.name, with/set)
#
# Usage: Replace {{placeholders}} with your values
# Note: Actions defined inside topic are local to that topic
topic {{topic_name}}:
description: "{{TopicDescription}}"
# Level 1: DEFINE actions available to this topic
actions:
{{action_name}}:
description: "{{ActionDescription}}"
inputs:
{{input_name}}: {{input_type}}
description: "{{InputDescription}}"
outputs:
{{output_name}}: {{output_type}}
description: "{{OutputDescription}}"
target: "{{ActionTarget}}" # flow://FlowName or apex://ClassName
reasoning:
instructions: ->
| {{TopicInstructions}}
| Use the available actions to help the user.
| Capture and communicate results clearly.
# Level 2: INVOKE the actions defined above
actions:
invoke_action: @actions.{{action_name}}
with {{input_name}}=...
set @variables.{{result_variable}} = @outputs.{{output_name}}
back_to_menu: @utils.transition to @topic.topic_selector
description: "Return to main menu"# Deterministic Routing Template (Zero-Hallucination Pattern)
# ============================================================
#
# This template demonstrates the zero-hallucination intent classification
# pattern using action output flags to control LLM behavior.
#
# Pattern: Classify intent deterministically, route without hallucination
# Use when: Critical routing decisions that must not be influenced by LLM creativity
#
# KEY PATTERN:
# In Agentforce Assets, set action outputs with:
# - is_displayable: False (hide from direct customer display)
# - is_used_by_planner: True (planner can use for routing)
#
# This ensures the planner routes based on classification but the raw
# classification label is not directly surfaced to the customer.
system:
messages:
welcome: "Hello! How can I help you today?"
error: "I apologize, something went wrong. Let me try again."
instructions: "You are a customer service agent. Route users to the correct department based on their intent."
config:
developer_name: "DeterministicRoutingAgent"
agent_label: "Smart Router Agent"
description: "Agent demonstrating zero-hallucination intent routing pattern"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent@yourorg.com"
variables:
# Intent classification (populated by action with is_displayable: False)
classified_intent: mutable string = ""
description: "Classification result - hidden from user responses"
confidence_score: mutable number = 0
description: "Classification confidence (0-100)"
# Routing state
needs_classification: mutable boolean = True
description: "Whether user intent needs classification"
low_confidence_warning: mutable boolean = False
description: "Flag for low confidence routing"
start_agent topic_selector:
description: "Classify intent and route deterministically"
reasoning:
instructions: ->
# ====================================================
# DETERMINISTIC ROUTING (based on classified_intent)
# ====================================================
# Route ONLY when classification is complete
if @variables.needs_classification == False and @variables.confidence_score >= 80 and @variables.classified_intent == "billing":
transition to @topic.billing
if @variables.needs_classification == False and @variables.confidence_score >= 80 and @variables.classified_intent == "technical_support":
transition to @topic.technical_support
if @variables.needs_classification == False and @variables.confidence_score >= 80 and @variables.classified_intent == "sales":
transition to @topic.sales
if @variables.needs_classification == False and @variables.confidence_score >= 80 and @variables.classified_intent == "returns":
transition to @topic.returns
# LOW-CONFIDENCE: Confirm with user
if @variables.needs_classification == False and @variables.confidence_score < 80:
set @variables.low_confidence_warning = True
| I want to make sure I route you correctly.
| It sounds like you need help with **{!@variables.classified_intent}**.
| Is that correct?
# INITIAL STATE: Ask for help topic
if @variables.needs_classification == True:
| I can help with billing, technical support, sales, or returns.
| What do you need help with today?
actions:
# CRITICAL: This action's outputs must be configured in Agentforce Assets:
# - classified_intent: is_displayable=False, is_used_by_planner=True
# - confidence_score: is_displayable=False, is_used_by_planner=True
#
# This ensures LLM cannot hallucinate based on classification data
classify_intent: @actions.Classify_User_Intent
description: "Determine what the user needs help with"
with user_message = ... # LLM extracts from conversation
set @variables.classified_intent = @outputs.intent
set @variables.confidence_score = @outputs.confidence
set @variables.needs_classification = False
# Manual routing for low-confidence cases
go_billing: @utils.transition to @topic.billing
description: "Yes, I need billing help"
available when @variables.low_confidence_warning == True and @variables.classified_intent == "billing"
go_support: @utils.transition to @topic.technical_support
description: "Yes, I need technical support"
available when @variables.low_confidence_warning == True and @variables.classified_intent == "technical_support"
go_sales: @utils.transition to @topic.sales
description: "Yes, I need sales help"
available when @variables.low_confidence_warning == True and @variables.classified_intent == "sales"
go_returns: @utils.transition to @topic.returns
description: "Yes, I need returns help"
available when @variables.low_confidence_warning == True and @variables.classified_intent == "returns"
# Reclassify if user said "no"
reclassify: @utils.setVariables
description: "That's not what I need - let me clarify"
available when @variables.low_confidence_warning == True
with needs_classification = True
with classified_intent = ""
with confidence_score = 0
with low_confidence_warning = False
# ============================================================
# ROUTED TOPICS
# ============================================================
topic billing:
description: "Handle billing inquiries"
reasoning:
instructions: |
Help the customer with their billing question.
You can view invoices, explain charges, or process payments.
actions:
back: @utils.transition to @topic.topic_selector
description: "Return to main menu"
escalate_now: @utils.escalate
description: "Transfer to billing specialist"
topic technical_support:
description: "Handle technical support issues"
reasoning:
instructions: |
Help the customer with their technical issue.
Troubleshoot problems and provide solutions.
actions:
back: @utils.transition to @topic.topic_selector
description: "Return to main menu"
escalate_now: @utils.escalate
description: "Transfer to technical specialist"
topic sales:
description: "Handle sales inquiries"
reasoning:
instructions: |
Help the customer with sales questions.
Provide product information and pricing.
actions:
back: @utils.transition to @topic.topic_selector
description: "Return to main menu"
escalate_now: @utils.escalate
description: "Transfer to sales representative"
topic returns:
description: "Handle return requests"
reasoning:
instructions: |
Help the customer with their return request.
Check eligibility and process returns.
actions:
back: @utils.transition to @topic.topic_selector
description: "Return to main menu"
escalate_now: @utils.escalate
description: "Transfer to returns specialist"# Escalation Pattern Template
# ===========================
#
# This template demonstrates the complete escalation pattern with:
# - Multi-channel connection blocks (messaging, voice, web)
# - Graceful handoff with escalation messages
# - Pre-escalation data gathering
# - OmniChannel routing configuration
#
# Pattern: Multi-channel escalation with context preservation
# Use when: Any agent that needs human handoff capabilities
system:
messages:
welcome: "Hello! I'm here to help. If I can't assist you, I can connect you with a specialist."
error: "I apologize, something went wrong. Let me connect you with someone who can help."
instructions: "You are a helpful agent. When you cannot resolve an issue, escalate to a human agent with full context."
config:
developer_name: "EscalationPatternAgent"
agent_label: "Escalation Demo Agent"
description: "Agent demonstrating complete escalation patterns"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent@yourorg.com" # REQUIRED: Change to valid Einstein Agent User
# ============================================================
# CONNECTION BLOCKS (Multi-Channel Escalation)
# ============================================================
# Messaging channel (SMS, WhatsApp, etc.)
connection messaging:
escalation_message: "One moment, I'm transferring our conversation to a specialist who can better assist you."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "flow://Escalate_Messaging_To_Agent"
adaptive_response_allowed: False
# Voice channel
connection voice:
escalation_message: "Please hold while I transfer you to a specialist. They will have full context of our conversation."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "flow://Escalate_Voice_To_Agent"
adaptive_response_allowed: True
# Web chat channel
connection web:
escalation_message: "Connecting you with a live agent now. They'll be with you shortly."
outbound_route_type: "OmniChannelFlow"
outbound_route_name: "flow://Web_Chat_Escalation_Flow"
adaptive_response_allowed: False
variables:
# Customer context
customer_name: mutable string = ""
description: "Customer's name for personalization"
customer_issue: mutable string = ""
description: "Summary of customer's issue"
issue_category: mutable string = ""
description: "Category of issue for routing"
# Escalation state
escalation_reason: mutable string = ""
description: "Reason for escalation"
attempts_before_escalation: mutable number = 0
description: "Number of resolution attempts"
ready_to_escalate: mutable boolean = False
description: "Whether pre-escalation data is gathered"
start_agent entry:
description: "Entry point - greet and assess needs"
reasoning:
instructions: |
Greet the customer and understand their needs.
Try to help directly, escalate if needed.
actions:
go_support: @utils.transition to @topic.support
description: "Help with support issue"
# ============================================================
# SUPPORT TOPIC (With Escalation Triggers)
# ============================================================
topic support:
description: "Attempt to resolve issue, escalate if unable"
reasoning:
instructions: ->
# POST-ACTION: Check if escalation was triggered
if @variables.ready_to_escalate == True:
transition to @topic.pre_escalation
# Track attempts
| I'm here to help you with your issue.
if @variables.attempts_before_escalation >= 2:
| It seems like I'm having trouble resolving this.
| Would you like me to connect you with a specialist?
actions:
# Attempt resolution
try_resolve: @actions.Attempt_Resolution
description: "Try to resolve the issue"
with issue = ... # LLM extracts from conversation
set @variables.customer_issue = @outputs.issue_summary
set @variables.issue_category = @outputs.category
set @variables.attempts_before_escalation = @variables.attempts_before_escalation + 1
# User-initiated escalation
request_human: @utils.setVariables
description: "I'd like to speak with a human"
with escalation_reason = "Customer requested human agent"
with ready_to_escalate = True
# Agent-initiated escalation (after failed attempts)
escalate_complex: @utils.setVariables
description: "Connect me with a specialist"
available when @variables.attempts_before_escalation >= 2
with escalation_reason = "Unable to resolve after multiple attempts"
with ready_to_escalate = True
# ============================================================
# PRE-ESCALATION TOPIC (Gather Context)
# ============================================================
topic pre_escalation:
description: "Gather information before escalating"
reasoning:
instructions: ->
# Ensure we have customer name for personalization
if @variables.customer_name == "":
| Before I transfer you, may I have your name so the specialist can address you properly?
else:
| Thank you, {!@variables.customer_name}. Let me prepare the transfer.
# Display summary of what we'll share
if @variables.customer_name != "" and @variables.customer_issue != "":
| I'll share the following with the specialist:
| - Your issue: {!@variables.customer_issue}
| - Category: {!@variables.issue_category}
| - Reason for transfer: {!@variables.escalation_reason}
|
| Ready to connect you now.
transition to @topic.escalation
actions:
# Capture customer name
save_name: @utils.setVariables
description: "Save customer name"
available when @variables.customer_name == ""
with customer_name = ... # LLM extracts name from response
# Proceed to escalation
proceed: @utils.transition to @topic.escalation
description: "Proceed with transfer"
available when @variables.customer_name != ""
# Cancel escalation
cancel_escalation: @utils.setVariables
description: "Actually, let me try again with the bot"
with ready_to_escalate = False
with escalation_reason = ""
run @utils.transition to @topic.support
# ============================================================
# ESCALATION TOPIC (Handoff)
# ============================================================
topic escalation:
description: "Execute escalation to human agent"
actions:
Log_Escalation_Context:
description: "Persist escalation context before handoff"
inputs:
customer_name: string
description: "Customer's name"
issue_summary: string
description: "Issue summary"
category: string
description: "Issue category"
reason: string
description: "Escalation reason"
attempt_count: number
description: "Attempt count before escalation"
outputs:
logged: boolean
description: "Whether escalation context was persisted"
target: "flow://Log_Escalation_Context"
reasoning:
instructions: ->
# Log escalation context (this gets passed to human agent)
run @actions.Log_Escalation_Context
with customer_name = @variables.customer_name
with issue_summary = @variables.customer_issue
with category = @variables.issue_category
with reason = @variables.escalation_reason
with attempt_count = @variables.attempts_before_escalation
| Transferring you now. Thank you for your patience!
actions:
# The actual escalation - uses connection block configuration
handoff: @utils.escalate
description: "Transfer to human agent"
# ============================================================
# OPTIONAL: SPECIALIZED ESCALATION QUEUES
# ============================================================
topic escalate_billing:
description: "Escalate specifically to billing team"
reasoning:
instructions: |
I'm connecting you with our billing specialists.
They'll be able to help with your account questions.
actions:
# Note: In production, you'd configure this action
# to route to a specific billing queue
billing_handoff: @utils.escalate
description: "Transfer to billing team"
topic escalate_technical:
description: "Escalate specifically to technical support"
reasoning:
instructions: |
I'm connecting you with our technical support team.
They specialize in resolving complex technical issues.
actions:
tech_handoff: @utils.escalate
description: "Transfer to technical support"# Flow Action Lookup Template
# ============================
#
# This template demonstrates the pattern for calling Flow actions
# that return complex data types (SObjects, lists, custom types).
#
# Pattern: Data lookup and display using Flow actions
# Use when: Fetching records from Salesforce (Cases, Orders, Accounts)
#
# CRITICAL: When defining Flow action outputs in Agentforce Assets:
# - For SObject returns: complex_data_type_name = "lightning__recordInfoType"
# - For list[string]: complex_data_type_name = "lightning__textType"
# - For currency: complex_data_type_name = "lightning__currencyType"
system:
messages:
welcome: "Hello! I can help you look up order information."
error: "I apologize, something went wrong retrieving your data."
instructions: "You are a customer service agent helping users look up their orders."
config:
developer_name: "FlowActionLookupAgent"
agent_label: "Order Lookup Agent"
description: "Agent demonstrating Flow action patterns with complex data types"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent@yourorg.com"
variables:
# Customer context (from session)
customer_id: linked string
source: @session.customerId
description: "Customer ID from session context"
# Order data (populated by Flow action)
order_id: mutable string = ""
description: "Current order being viewed"
order_status: mutable string = ""
description: "Order status from lookup"
order_total: mutable string = ""
description: "Order total amount"
order_date: mutable string = ""
description: "Order date"
# Error handling
lookup_error: mutable boolean = False
description: "Whether lookup encountered an error"
start_agent entry:
description: "Entry point - welcome and route to order lookup"
reasoning:
instructions: |
Welcome the customer and offer to help with order lookups.
actions:
go_lookup: @utils.transition to @topic.order_lookup
description: "Start order lookup"
# ============================================================
# ORDER LOOKUP TOPIC (Flow Action Pattern)
# ============================================================
topic order_lookup:
description: "Look up order details using Flow action"
reasoning:
instructions: ->
# POST-ACTION CHECK: Display results if order was found
if @variables.order_status != "":
| **Order Details**
| - Order ID: {!@variables.order_id}
| - Status: {!@variables.order_status}
| - Total: {!@variables.order_total}
| - Date: {!@variables.order_date}
|
| Is there anything else you'd like to know about this order?
# ERROR CHECK: Handle lookup failures
if @variables.lookup_error == True:
| I couldn't find that order. Please check the order ID and try again.
set @variables.lookup_error = False
# INITIAL STATE: Ask for order ID
if @variables.order_id == "":
| What order would you like me to look up?
| Please provide your order ID.
actions:
# Flow action with SObject return type
# In Agentforce Assets, set outputs.order_record complex_data_type_name = "lightning__recordInfoType"
lookup_order: @actions.Get_Order_Details
description: "Look up order by ID"
with order_id = ... # LLM extracts from user message
include_in_progress_indicator: True
progress_indicator_message: "Looking up your order..."
set @variables.order_id = @outputs.order_id
set @variables.order_status = @outputs.status
set @variables.order_total = @outputs.total_amount
set @variables.order_date = @outputs.order_date
# Error handling: check if lookup failed
if @outputs.found == False:
set @variables.lookup_error = True
# Flow action returning a list of strings
# In Agentforce Assets, set outputs.product_names complex_data_type_name = "lightning__textType"
get_items: @actions.Get_Order_Line_Items
description: "Get list of items in the order"
available when @variables.order_id != ""
with order_id = @variables.order_id
clear_search: @utils.setVariables
description: "Search for a different order"
with order_id = ""
with order_status = ""
with order_total = ""
with order_date = ""
escalate_now: @utils.escalate
description: "Transfer to human agent"# Hub-and-Spoke Architecture Template
# ====================================
#
# This template demonstrates the Hub-and-Spoke pattern where a central
# topic_selector (hub) routes conversations to specialized topics (spokes).
#
# Pattern: Multi-purpose agents handling distinct request types
# Use when: Users may have different intents (orders, support, returns)
system:
messages:
welcome: "Welcome! I can help with orders, returns, or general support."
error: "I apologize, something went wrong. Let me try again."
instructions: "You are a customer service agent for an e-commerce company."
config:
developer_name: "HubAndSpokeAgent"
agent_label: "Customer Service Agent"
description: "Multi-purpose agent with hub-and-spoke architecture"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent@yourorg.com"
variables:
customer_id: linked string
source: @session.customerId
description: "Customer ID from session"
order_id: mutable string = ""
description: "Current order being discussed"
issue_resolved: mutable boolean = False
description: "Whether the issue has been resolved"
# ============================================================
# HUB: Central Router
# ============================================================
start_agent topic_selector:
description: "Route to appropriate topic based on user intent"
reasoning:
instructions: |
Determine what the customer needs and route accordingly:
- Order questions → orders topic
- Return/refund requests → returns topic
- General questions → support topic
actions:
go_to_orders: @utils.transition to @topic.orders
description: "Customer wants to check order status"
go_to_returns: @utils.transition to @topic.returns
description: "Customer wants to return or refund"
go_to_support: @utils.transition to @topic.support
description: "General support questions"
# ============================================================
# SPOKE: Orders Topic
# ============================================================
topic orders:
description: "Handle order status and tracking inquiries"
reasoning:
instructions: ->
| Help the customer with their order inquiry.
if @variables.order_id != "":
| Current order: {!@variables.order_id}
actions:
lookup_order: @actions.get_order_status
description: "Look up order details"
with order_id = @variables.order_id
back_to_hub: @utils.transition to @topic.topic_selector
description: "Return to main menu"
# ============================================================
# SPOKE: Returns Topic
# ============================================================
topic returns:
description: "Handle return and refund requests"
reasoning:
instructions: ->
| Help the customer with their return or refund request.
| Verify the order details before processing.
actions:
start_return: @actions.initiate_return
description: "Start a return process"
process_refund: @actions.process_refund
description: "Process a refund"
back_to_hub: @utils.transition to @topic.topic_selector
description: "Return to main menu"
# ============================================================
# SPOKE: Support Topic
# ============================================================
topic support:
description: "Handle general support questions"
reasoning:
instructions: |
Help the customer with general questions.
If the question requires specialized help, route appropriately.
actions:
escalate: @utils.escalate
description: "Transfer to human agent"
back_to_hub: @utils.transition to @topic.topic_selector
description: "Return to main menu"<?xml version="1.0" encoding="UTF-8"?>
<!--
GenAI Prompt Template: Basic Flex Template
Use Case: Create reusable Prompt Builder templates for Agentforce,
Flow, or Apex-driven generation.
Setup Steps:
1. Replace all {{placeholder}} values
2. Keep flex templates at 5 inputs or fewer
3. Deploy to org as GenAiPromptTemplate metadata
4. Publish the template version before wiring dependent actions
File Location:
force-app/main/default/genAiPromptTemplates/{{TemplateName}}.genAiPromptTemplate-meta.xml
-->
<GenAiPromptTemplate xmlns="http://soap.sforce.com/2006/04/metadata">
<developerName>{{TemplateName}}</developerName>
<masterLabel>{{TemplateLabel}}</masterLabel>
<type>einstein_gpt__flex</type>
<templateVersions>
<content>
You are an AI assistant helping with {{UseCaseDescription}}.
Context:
{!$Input:Context}
Task:
{{TaskDescription}}
Instructions:
1. {{Instruction1}}
2. {{Instruction2}}
3. {{Instruction3}}
Return a clear, structured response.
</content>
<inputs>
<apiName>Context</apiName>
<definition>primitive://String</definition>
<masterLabel>Context</masterLabel>
<referenceName>Input:Context</referenceName>
<required>true</required>
</inputs>
<primaryModel>sfdc_ai__DefaultAnthropic</primaryModel>
<status>Published</status>
</templateVersions>
</GenAiPromptTemplate>
<?xml version="1.0" encoding="UTF-8"?>
<!--
GenAiFunction Template: Apex Invocation (Agent Builder UI / GenAiPlannerBundle ONLY)
⚠️ NOT NEEDED for AiAuthoringBundle (Agent Script).
If using Agent Script (.agent files), use `target: "apex://ClassName"` directly
in your topic's actions block. See SKILL.md for details.
Use Case: Register Apex @InvocableMethod as an agent action in Agent Builder UI
- Required ONLY for GenAiPlannerBundle / Agent Builder UI path
- Works with GenAiPlugin (Topic) for organization
Prerequisites:
1. Apex class with @InvocableMethod annotation must be deployed first
2. GenAiPlugin (Topic) to organize functions (optional but recommended)
Setup Steps:
1. Replace all {{placeholder}} values
2. Deploy Apex class first
3. Create input/schema.json and output/schema.json (see below)
4. Deploy this GenAiFunction bundle
5. Optionally deploy GenAiPlugin to group functions
Bundle Structure:
force-app/main/default/genAiFunctions/
└── {{FunctionName}}/
├── {{FunctionName}}.genAiFunction-meta.xml (this file)
├── input/
│ └── schema.json (input parameters)
└── output/
└── schema.json (output parameters)
IMPORTANT (API v66.0):
- Only these XML elements are valid: description, invocationTarget,
invocationTargetType, isConfirmationRequired, masterLabel
- Do NOT use: <capability>, <genAiFunctionParameters>,
<genAiFunctionInputs>, <genAiFunctionOutputs>, <developerName>
- Input/output schemas go in schema.json files, NOT inline XML
-->
<GenAiFunction xmlns="http://soap.sforce.com/2006/04/metadata">
<description>{{FunctionDescription}}</description>
<invocationTarget>{{ApexClassName}}</invocationTarget>
<invocationTargetType>apex</invocationTargetType>
<isConfirmationRequired>{{true|false}}</isConfirmationRequired>
<masterLabel>{{FunctionLabel}}</masterLabel>
</GenAiFunction>
<!--
input/schema.json example:
{
"required": ["{{inputParam1}}"],
"properties": {
"{{inputParam1}}": {
"title": "{{Input Param 1 Label}}",
"description": "{{Input Param 1 Description}}",
"lightning:type": "lightning__textType",
"lightning:isPII": false,
"copilotAction:isUserInput": true
}
},
"lightning:type": "lightning__objectType"
}
output/schema.json example:
{
"properties": {
"{{outputParam1}}": {
"title": "{{Output Param 1 Label}}",
"description": "{{Output Param 1 Description}}",
"lightning:type": "lightning__textType",
"lightning:isPII": false,
"copilotAction:isDisplayable": true,
"copilotAction:isUsedByPlanner": true
}
},
"lightning:type": "lightning__objectType"
}
Lightning types:
- lightning__textType (String)
- lightning__numberType (Number/Decimal)
- lightning__booleanType (Boolean)
- lightning__dateType (Date)
- lightning__dateTimeStringType (DateTime — TDD validated v2.1.0)
- lightning__currencyType (Currency)
APEX CLASS REQUIREMENTS:
- Must be global or public with sharing
- Method must have @InvocableMethod annotation
- Input/output use @InvocableVariable wrapper classes
- Parameter names in schema.json must match @InvocableVariable field names
-->
<?xml version="1.0" encoding="UTF-8"?>
<!--
GenAiFunction Template: Flow Invocation (Agent Builder UI / GenAiPlannerBundle ONLY)
⚠️ NOT NEEDED for AiAuthoringBundle (Agent Script).
If using Agent Script (.agent files), use `target: "flow://FlowApiName"` directly
in your topic's actions block. See SKILL.md for details.
Use Case: Register Autolaunched Flow as an agent action in Agent Builder UI
- Required ONLY for GenAiPlannerBundle / Agent Builder UI path
- Supports HTTP callouts via Flow HTTP actions
- Works well with External Services
Prerequisites:
1. Autolaunched Flow must be deployed and active first
2. Flow must have defined input/output variables
Setup Steps:
1. Replace all {{placeholder}} values
2. Deploy Autolaunched Flow first
3. Create input/schema.json and output/schema.json (see genai-function-apex.xml for format)
4. Deploy this GenAiFunction bundle
Bundle Structure:
force-app/main/default/genAiFunctions/
└── {{FunctionName}}/
├── {{FunctionName}}.genAiFunction-meta.xml (this file)
├── input/
│ └── schema.json (input parameters)
└── output/
└── schema.json (output parameters)
IMPORTANT (API v66.0):
- Only these XML elements are valid: description, invocationTarget,
invocationTargetType, isConfirmationRequired, masterLabel
- Do NOT use: <capability>, <genAiFunctionParameters>,
<genAiFunctionInputs>, <genAiFunctionOutputs>, <developerName>
- Input/output schemas go in schema.json files, NOT inline XML
-->
<GenAiFunction xmlns="http://soap.sforce.com/2006/04/metadata">
<description>{{FunctionDescription}}</description>
<invocationTarget>{{FlowApiName}}</invocationTarget>
<invocationTargetType>flow</invocationTargetType>
<isConfirmationRequired>{{true|false}}</isConfirmationRequired>
<masterLabel>{{FunctionLabel}}</masterLabel>
</GenAiFunction>
<!--
FLOW REQUIREMENTS:
1. Flow Type: Autolaunched Flow (NOT Screen Flow)
2. Input Variables: Must be marked "Available for input"
3. Output Variables: Must be marked "Available for output"
4. Variable names in schema.json must match Flow variable names exactly
5. Flow must be Active
See genai-function-apex.xml for input/schema.json and output/schema.json format examples.
-->
<?xml version="1.0" encoding="UTF-8"?>
<!--
GenAiPlugin Template: Agent Topic/Plugin Container
Use Case: Organize GenAiFunctions into logical groups (Topics)
- Groups related functions together
- Provides topic-level instructions
- Maps to Agent Script topic concepts
Note: GenAiPlugin is the metadata equivalent of a "topic" in Agent Script.
Use this when you want to organize functions deployed via metadata
rather than Agent Script.
Setup Steps:
1. Replace all {{placeholder}} values
2. Deploy GenAiFunctions first
3. Deploy this GenAiPlugin
File Location: force-app/main/default/genAiPlugins/{{PluginName}}.genAiPlugin-meta.xml
-->
<GenAiPlugin xmlns="http://soap.sforce.com/2006/04/metadata">
<!-- Display name for the topic/plugin -->
<masterLabel>{{PluginLabel}}</masterLabel>
<!-- Description shown in Agent Builder -->
<description>{{PluginDescription}}</description>
<!-- Developer name (API name) -->
<developerName>{{PluginDeveloperName}}</developerName>
<!--
Plugin Instructions:
Natural language instructions for how the agent should use
functions in this plugin. Similar to topic instructions in Agent Script.
-->
<pluginInstructions>
{{Instructions for the agent on how to use functions in this topic.
Include guidance on when to use specific functions,
how to handle edge cases, and any constraints.}}
</pluginInstructions>
<!--
Plugin Type:
- Standard: Regular function grouping
- Copilot: For Salesforce Copilot-specific plugins
-->
<pluginType>Standard</pluginType>
<!--
Associated Functions:
List the GenAiFunction developer names that belong to this plugin.
-->
<genAiFunctions>
<function>{{GenAiFunction1DeveloperName}}</function>
</genAiFunctions>
<genAiFunctions>
<function>{{GenAiFunction2DeveloperName}}</function>
</genAiFunctions>
<!--
AGENT BUILDER INTEGRATION:
After deploying GenAiPlugin:
1. Open Agent Builder in Setup
2. Navigate to Topics
3. Your plugin appears as a Topic
4. Associated functions are available as Actions
This provides an alternative to Agent Script for more
complex deployments or when you need metadata-level control.
-->
</GenAiPlugin>
<?xml version="1.0" encoding="UTF-8"?>
<!--
HTTP Callout Flow Template for Agent Actions
Use Case: Create Flow-based API actions for agents
- Enables external API calls from Agent Script
- Uses Named Credential for secure auth
- Works with flow:// target in Agent Script
Pattern:
Agent Script → flow://{{FlowName}} → HTTP Callout → External API
Prerequisites:
1. Named Credential configured for API auth
2. sf-integration skill used to create Named Credential
Setup Steps:
1. Replace all {{placeholder}} values
2. Deploy Named Credential first
3. Deploy this Flow
4. Reference in Agent Script: target: "flow://{{FlowApiName}}"
File Location: force-app/main/default/flows/{{FlowApiName}}.flow-meta.xml
-->
<Flow xmlns="http://soap.sforce.com/2006/04/metadata">
<!-- API Name -->
<fullName>{{FlowApiName}}</fullName>
<!-- Display label -->
<label>{{FlowLabel}}</label>
<!-- Description -->
<description>HTTP callout to {{ExternalSystemName}} for agent actions</description>
<!-- Autolaunched Flow (required for agent actions) -->
<processType>AutoLaunchedFlow</processType>
<!-- API Version -->
<apiVersion>66.0</apiVersion>
<!-- Active status -->
<status>Active</status>
<!--
=========================================
INPUT VARIABLES
Must be marked "Available for Input"
=========================================
-->
<variables>
<name>input_param_1</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
<value>
<stringValue></stringValue>
</value>
</variables>
<!-- Add more input variables as needed -->
<!--
<variables>
<name>input_param_2</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>true</isInput>
<isOutput>false</isOutput>
</variables>
-->
<!--
=========================================
OUTPUT VARIABLES
Must be marked "Available for Output"
=========================================
-->
<variables>
<name>output_result</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<variables>
<name>output_status</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<variables>
<name>output_error</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>true</isOutput>
</variables>
<!--
=========================================
HTTP CALLOUT ACTION
Core Action for making HTTP requests
=========================================
-->
<actionCalls>
<name>HTTP_Callout</name>
<label>Call External API</label>
<locationX>176</locationX>
<locationY>158</locationY>
<!-- HTTP Callout Core Action -->
<actionType>httpCallout</actionType>
<!--
Named Credential for authentication
Format: callout:NamedCredentialName
-->
<actionName>callout:{{NamedCredentialName}}</actionName>
<!-- Continue on error to handle gracefully -->
<connector>
<targetReference>Check_Response</targetReference>
</connector>
<faultConnector>
<targetReference>Handle_Error</targetReference>
</faultConnector>
<!-- HTTP Method: GET, POST, PUT, PATCH, DELETE -->
<inputParameters>
<name>method</name>
<value>
<stringValue>{{GET|POST|PUT|PATCH|DELETE}}</stringValue>
</value>
</inputParameters>
<!-- API Endpoint path (appended to Named Credential base URL) -->
<inputParameters>
<name>url</name>
<value>
<elementReference>API_Endpoint</elementReference>
</value>
</inputParameters>
<!-- Request body (for POST/PUT/PATCH) -->
<inputParameters>
<name>body</name>
<value>
<elementReference>Request_Body</elementReference>
</value>
</inputParameters>
<!-- Response body -->
<outputParameters>
<assignToReference>Response_Body</assignToReference>
<name>responseBody</name>
</outputParameters>
<!-- Response status code -->
<outputParameters>
<assignToReference>Response_Status_Code</assignToReference>
<name>statusCode</name>
</outputParameters>
</actionCalls>
<!--
=========================================
FORMULAS
Build request URL and body
=========================================
-->
<formulas>
<name>API_Endpoint</name>
<dataType>String</dataType>
<!-- Build endpoint with input parameters -->
<expression>"/{{apiPath}}/" & {!input_param_1}</expression>
</formulas>
<formulas>
<name>Request_Body</name>
<dataType>String</dataType>
<!-- Build JSON request body -->
<expression>'{"param1": "' & {!input_param_1} & '"}'</expression>
</formulas>
<!--
=========================================
PRIVATE VARIABLES
For internal flow processing
=========================================
-->
<variables>
<name>Response_Body</name>
<dataType>String</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
</variables>
<variables>
<name>Response_Status_Code</name>
<dataType>Number</dataType>
<isCollection>false</isCollection>
<isInput>false</isInput>
<isOutput>false</isOutput>
<scale>0</scale>
</variables>
<!--
=========================================
DECISION: Check Response Status
=========================================
-->
<decisions>
<name>Check_Response</name>
<label>Check Response Status</label>
<locationX>176</locationX>
<locationY>278</locationY>
<defaultConnector>
<targetReference>Set_Error_Output</targetReference>
</defaultConnector>
<defaultConnectorLabel>Error</defaultConnectorLabel>
<rules>
<name>Success</name>
<conditionLogic>and</conditionLogic>
<conditions>
<leftValueReference>Response_Status_Code</leftValueReference>
<operator>GreaterThanOrEqualTo</operator>
<rightValue>
<numberValue>200</numberValue>
</rightValue>
</conditions>
<conditions>
<leftValueReference>Response_Status_Code</leftValueReference>
<operator>LessThan</operator>
<rightValue>
<numberValue>300</numberValue>
</rightValue>
</conditions>
<connector>
<targetReference>Set_Success_Output</targetReference>
</connector>
<label>Success (2xx)</label>
</rules>
</decisions>
<!--
=========================================
ASSIGNMENTS: Set Output Variables
=========================================
-->
<assignments>
<name>Set_Success_Output</name>
<label>Set Success Output</label>
<locationX>50</locationX>
<locationY>398</locationY>
<assignmentItems>
<assignToReference>output_result</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>Response_Body</elementReference>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>output_status</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Success</stringValue>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Set_Error_Output</name>
<label>Set Error Output</label>
<locationX>302</locationX>
<locationY>398</locationY>
<assignmentItems>
<assignToReference>output_status</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Error</stringValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>output_error</assignToReference>
<operator>Assign</operator>
<value>
<elementReference>Response_Body</elementReference>
</value>
</assignmentItems>
</assignments>
<assignments>
<name>Handle_Error</name>
<label>Handle Callout Error</label>
<locationX>440</locationX>
<locationY>278</locationY>
<assignmentItems>
<assignToReference>output_status</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>Error</stringValue>
</value>
</assignmentItems>
<assignmentItems>
<assignToReference>output_error</assignToReference>
<operator>Assign</operator>
<value>
<stringValue>HTTP callout failed</stringValue>
</value>
</assignmentItems>
</assignments>
<!-- Flow start -->
<start>
<locationX>50</locationX>
<locationY>0</locationY>
<connector>
<targetReference>HTTP_Callout</targetReference>
</connector>
</start>
<!--
AGENT SCRIPT USAGE:
actions:
call_api:
description: "Calls external API"
inputs:
param1: string
description: "Input parameter"
outputs:
result: string
description: "API response"
status: string
description: "Success or Error"
target: "flow://{{FlowApiName}}"
-->
</Flow>
<?xml version="1.0" encoding="UTF-8"?>
<!--
GenAI Prompt Template: Record-Grounded Template
Use Case: Prompt Builder templates that summarize or transform
information from a Salesforce record plus optional extra context.
Setup Steps:
1. Replace all {{placeholder}} values
2. Confirm the target object exists in the org
3. Keep the template within the current input limit
4. Deploy and publish the template before wiring agent actions
File Location:
force-app/main/default/genAiPromptTemplates/{{TemplateName}}.genAiPromptTemplate-meta.xml
-->
<GenAiPromptTemplate xmlns="http://soap.sforce.com/2006/04/metadata">
<developerName>{{TemplateName}}</developerName>
<masterLabel>{{TemplateLabel}}</masterLabel>
<type>einstein_gpt__recordSummary</type>
<templateVersions>
<content>
You are summarizing a {{ObjectLabel}} record.
Record Information:
- Name: {!$Input:TargetRecord.Name}
- Owner: {!$Input:TargetRecord.Owner.Name}
- Status: {!$Input:TargetRecord.Status__c}
Additional Context:
{!$Input:AdditionalNotes}
Related Information:
{!$Input:RelatedContext}
Provide a concise summary that highlights:
1. Current status and recent activity
2. Important values or signals
3. Recommended next steps
4. Risks or concerns
</content>
<inputs>
<apiName>TargetRecord</apiName>
<definition>SOBJECT://{{ObjectApiName}}</definition>
<masterLabel>Target Record</masterLabel>
<referenceName>Input:TargetRecord</referenceName>
<required>true</required>
</inputs>
<inputs>
<apiName>AdditionalNotes</apiName>
<definition>primitive://String</definition>
<masterLabel>Additional Notes</masterLabel>
<referenceName>Input:AdditionalNotes</referenceName>
<required>false</required>
</inputs>
<inputs>
<apiName>RelatedContext</apiName>
<definition>primitive://String</definition>
<masterLabel>Related Context</masterLabel>
<referenceName>Input:RelatedContext</referenceName>
<required>false</required>
</inputs>
<primaryModel>sfdc_ai__DefaultAnthropic</primaryModel>
<status>Published</status>
</templateVersions>
</GenAiPromptTemplate>
# Minimal Agent Script Starter Template
# =====================================
#
# This template provides the minimum required structure for an Agent Script.
# Use this as a starting point for simple, single-purpose agents.
#
# Required blocks: config, system, topic, start_agent
# File extension: .agent
# Tip: Quoted welcome/error messages are fine for static text.
# If you personalize a system message with {!@variables.x}, switch that message to block form with `|`.
system:
messages:
welcome: "Hello! How can I help you today?"
error: "I apologize, but something went wrong. Let me try again."
instructions: "You are a helpful assistant."
config:
developer_name: "MinimalAgent"
agent_label: "Minimal Agent"
description: "A minimal agent template to get started"
agent_type: "AgentforceServiceAgent"
default_agent_user: "agent@yourorg.com"
# Optional: Add variables for state tracking
# variables:
# counter: mutable number = 0
# session_id: linked string
# source: @session.sessionID
topic main:
description: "Main conversation handler"
reasoning:
instructions: |
Help the user with their request.
Be friendly and helpful.
start_agent entry:
description: "Entry point for all conversations"
reasoning:
instructions: |
Greet the user and route to the main topic.
actions:
begin: @utils.transition to @topic.main
description: "Start the main conversation"# Action Callbacks Pattern
# Use the `run` keyword for deterministic post-action processing
#
# ★ When To Use This Pattern:
# - You need guaranteed follow-up after an action completes
# - Audit logging or notifications must ALWAYS happen
# - Chain multiple actions where order matters
#
# ★ Key Insight:
# The `run` keyword executes AFTER the parent action completes.
# This is deterministic (not LLM-decided) - the callback ALWAYS runs.
# Use this when you can't afford to skip follow-up steps.
#
# ★ Validation Impact:
# - [5 pts] Actions with proper callback structure
# - Avoid nested run (only 1 level of nesting allowed)
#
# This is a PARTIAL template - integrate into a complete agent file
topic order_processing:
label: "Order Processing"
description: "Processes orders with guaranteed confirmation and logging"
actions:
create_order:
description: "Creates a new order in the system"
inputs:
customer_id: string
description: "Customer identifier"
items: list[string]
description: "List of item SKUs"
total: number
description: "Order total amount"
outputs:
order_id: string
description: "Generated order ID"
status: string
description: "Order creation status"
target: "flow://Create_Order"
send_confirmation:
description: "Sends order confirmation email"
inputs:
order_id: string
description: "Order ID to confirm"
customer_email: string
description: "Customer email address"
outputs:
sent: boolean
description: "Whether email was sent"
target: "flow://Send_Order_Confirmation"
log_activity:
description: "Logs activity for audit trail"
inputs:
event_type: string
description: "Type of event to log"
details: string
description: "Event details"
outputs:
logged: boolean
description: "Whether log was recorded"
target: "apex://AuditService.logEvent"
reasoning:
instructions: ->
| Help the customer place their order.
| Ensure confirmation is sent after successful orders.
| All order activities must be logged for compliance.
actions:
# This action uses callbacks to guarantee follow-up
process_order: @actions.create_order
with customer_id=@variables.customer_id
with items=...
with total=...
set @variables.order_id = @outputs.order_id
set @variables.order_status = @outputs.status
# Callback 1: Always send confirmation after order created
run @actions.send_confirmation
with order_id=@variables.order_id
with customer_email=@variables.customer_email
# Callback 2: Always log the activity
run @actions.log_activity
with event_type="ORDER_CREATED"
with details=@variables.order_id
back_to_menu: @utils.transition to @topic.topic_selector
# ★ Anti-Pattern: Nested run (DO NOT DO THIS)
#
# process: @actions.first
# run @actions.second
# run @actions.third # ❌ INVALID - nested run not allowed
#
# ★ Correct Pattern: Sequential callbacks (all at same level)
#
# process: @actions.first
# run @actions.second # ✅ First callback
# run @actions.third # ✅ Second callback (runs after second)
# ═══════════════════════════════════════════════════════════════════════════════
# ★ Pattern 2: Simple Variable Updates (No Callback Needed)
# ═══════════════════════════════════════════════════════════════════════════════
#
# For simple operations like incrementing counters or storing outputs,
# use `set` statements directly - NO `run` keyword needed.
#
# ⚠️ IMPORTANT: This pattern works in BOTH GenAiPlannerBundle AND AiAuthoringBundle
# The `run` keyword ONLY works in GenAiPlannerBundle (not AiAuthoringBundle)
# Use `set` statements for AiAuthoringBundle deployments
#
# Example: Create case and track count (works in BOTH bundle types)
topic case_management:
label: "Case Management"
description: "Creates cases and tracks statistics"
actions:
create_case:
description: "Creates a new support case"
inputs:
inp_CustomerId: string
description: "Contact ID for the case"
inp_Subject: string
description: "Subject line for the case"
outputs:
out_CaseNumber: string
description: "Generated case number"
out_CaseId: string
description: "Salesforce ID of the created case"
target: "flow://Create_Case"
reasoning:
instructions: ->
| Help the customer create support cases.
| Track case count for session statistics.
actions:
# ✅ Simple pattern - just use `set` for variable updates
create_support_case: @actions.create_case
with inp_CustomerId=@variables.ContactId
with inp_Subject=...
set @variables.case_number = @outputs.out_CaseNumber
set @variables.case_id = @outputs.out_CaseId
set @variables.cases_created = @variables.cases_created + 1 # Direct increment!
back_to_menu: @utils.transition to @topic.topic_selector
# ═══════════════════════════════════════════════════════════════════════════════
# ⛔ INVALID KEYWORDS - NEVER USE THESE
# ═══════════════════════════════════════════════════════════════════════════════
#
# The following keywords DO NOT EXIST in Agent Script. Using them causes:
# SyntaxError: Unexpected '[keyword]'
#
# ❌ internal_actions - Does not exist (Claude may invent this for "local helpers")
# ❌ helper_actions - Does not exist
# ❌ private_actions - Does not exist
# ❌ local_actions - Does not exist
#
# If you need simple variable operations after an action, use `set` directly:
#
# ❌ WRONG (internal_actions does not exist):
#
# internal_actions:
# increment_counter:
# set @variables.count = @variables.count + 1
#
# reasoning:
# actions:
# process: @actions.create_case
# run @actions.increment_counter # ❌ Can't reference internal action
#
# ✅ CORRECT (use set directly in the action block):
#
# reasoning:
# actions:
# process: @actions.create_case
# set @variables.count = @variables.count + 1 # ✅ Direct set works!# Advanced Input Bindings Pattern
# Demonstrates all parameter binding techniques for Agent Script actions
#
# ★ When To Use This Pattern:
# - Learning different ways to pass values to actions
# - Combining LLM slot filling with variable binding
# - Chaining outputs between multiple actions
# - Complex multi-input action scenarios
#
# ★ Key Insight:
# - `...` (ellipsis) = LLM extracts value from conversation (slot filling)
# - `"value"` = Fixed constant value
# - `@variables.x` = Value from stored state
# - `@outputs.x` = Value from previous action's output
#
# ★ Common Use Cases:
# - Order lookup with user-provided order ID
# - Multi-step workflows with data passing
# - Conditional parameter binding
#
# This is a PARTIAL template - integrate into a complete agent file
# Variables for demonstrating different binding patterns
variables:
# ... standard linked variables ...
current_account_id: mutable string = ""
description: "Currently selected account ID"
order_id: mutable string = ""
description: "Order ID being processed"
amount: mutable number = 0
description: "Transaction amount"
status: mutable string = ""
description: "Current operation status"
topic order_processing:
label: "Order Processing"
description: "Demonstrates advanced input binding patterns"
actions:
# Action with multiple input types
process_order:
description: "Process an order with various input methods"
inputs:
order_id: string
description: "The order ID to process"
amount: number
description: "Transaction amount in USD"
account_id: string
description: "Customer account ID"
outputs:
confirmation_number: string
description: "Order confirmation number"
processed_amount: number
description: "Final processed amount"
target: "flow://Process_Order"
get_account:
description: "Look up account details"
inputs:
account_id: string
description: "Account ID to look up"
outputs:
account_name: string
description: "Account name"
credit_limit: number
description: "Available credit"
target: "flow://Get_Account_Details"
send_notification:
description: "Send order notification"
inputs:
confirmation_number: string
description: "Order confirmation to include"
recipient_account: string
description: "Account to notify"
outputs:
sent: boolean
description: "Whether notification was sent"
target: "flow://Send_Order_Notification"
# ★ Action with transformation contracts in parameter descriptions
save_contact:
description: "Save contact information to CRM"
inputs:
full_name: string
description: "Full name in Title Case. Convert 'john smith' to 'John Smith', 'JOHN SMITH' to 'John Smith'."
phone_number: string
description: "Phone in E.164 format: +[country][number]. '07700 123456' → '+447700123456'. '(202) 555-1234' → '+12025551234'. Always add country code if missing (default +44 UK). Remove all spaces, dashes, parentheses. Final format: '+' followed by digits only."
email: string
description: "Email in lowercase, whitespace trimmed. 'John.Doe@Example.COM ' → 'john.doe@example.com'. Must contain '@' and a domain."
postal_code: string
description: "UK postcode uppercase with space. 'sw1a1aa' → 'SW1A 1AA'. 'SW1A1AA' → 'SW1A 1AA'."
outputs:
contact_id: string
description: "Created contact ID"
target: "flow://Save_Contact"
reasoning:
instructions: ->
| Help the customer process their order.
|
| INPUT BINDING EXAMPLES:
| 1. Slot filling (...) - LLM extracts from conversation
| 2. Fixed values - Always use a constant
| 3. Variable binding - Use stored state
| 4. Output chaining - Use results from previous action
|
| Ask for order details and process appropriately.
actions:
# ★ PATTERN 1: Slot Filling (LLM extracts from conversation)
# User says "Process order ORD-12345" -> LLM fills order_id="ORD-12345"
slot_fill_lookup: @actions.process_order
with order_id=...
with amount=...
with account_id=...
set @variables.order_id = @outputs.confirmation_number
# ★ PATTERN 2: Fixed Value (constant)
# Always uses the same account for default lookups
fixed_account: @actions.get_account
with account_id="001DEFAULT000001"
set @variables.status = @outputs.account_name
# ★ PATTERN 3: Variable Binding (from stored state)
# Uses the account ID saved earlier in the conversation
variable_binding: @actions.get_account
with account_id=@variables.current_account_id
set @variables.status = @outputs.account_name
# ★ PATTERN 4: Output Chaining (from previous action)
# Uses confirmation_number from process_order to send notification
chained_output: @actions.process_order
with order_id=...
with amount=@variables.amount
with account_id=@variables.current_account_id
set @variables.order_id = @outputs.confirmation_number
run @actions.send_notification
with confirmation_number=@outputs.confirmation_number
with recipient_account=@variables.current_account_id
# ★ PATTERN 5: Mixed Binding (combining patterns)
# Some inputs from LLM, some from variables, some fixed
mixed_binding: @actions.process_order
with order_id=... # LLM slot fills
with amount=@variables.amount # From variable
with account_id="001INTERNAL00001" # Fixed value
# ★ PATTERN 6: Description as Transformation Contract
# save_contact's descriptions contain normalization rules.
# Compare with process_order's generic descriptions above.
contract_binding: @actions.save_contact
with full_name=...
with phone_number=...
with email=...
with postal_code=...
# ★ Insight: Binding Pattern Decision Tree
#
# Need the LLM to extract from conversation? -> Use `...`
# Value is always the same constant? -> Use "value"
# Value was captured in a previous step? -> Use @variables.x
# Value comes from another action's output? -> Use @outputs.x (in callback)
# Need the LLM to normalize/transform the value? -> Use description contract (Pattern 6)
#
# ★ Common Mistake: Using @outputs.x outside of a `run` callback
# @outputs.x is only available inside the action's callback chain
# Store important outputs in @variables for use elsewhere
#
# ★ Why Description Contracts Work (Pattern 6)
# The LLM reads parameter descriptions IMMEDIATELY before invocation —
# closer than topic instructions. This temporal proximity makes
# descriptions the most reliable place for normalization rules.
# Not platform-enforced; for critical validation pair with a
# server-side action (see critical-input-collection.agent).# Topic Delegation vs Transition Pattern
# Understanding the difference between delegation and permanent handoffs
#
# ★ KEY CONCEPTS:
#
# DELEGATION (@topic.* syntax):
# - Syntax: action_name: @topic.topic_name
# - Control CAN return to the calling topic
# - Use for: consulting specialists, sub-tasks, getting help
#
# TRANSITION (@utils.transition syntax):
# - Syntax: action_name: @utils.transition to @topic.topic_name
# - PERMANENT handoff - control does NOT return
# - Use for: menu navigation, workflow stages, permanent routing
#
# ★ When To Use Each:
# DELEGATION: "Consult an expert then continue here"
# TRANSITION: "Go to this topic and stay there"
#
# ★ Implementation Note:
# If delegation syntax doesn't work in your deployment method,
# use the bidirectional-routing.agent pattern which manually
# tracks return context via variables.
#
# This is a PARTIAL template - integrate into a complete agent file
# ═══════════════════════════════════════════════════════════════
# PATTERN 1: Topic Delegation (can return)
# ═══════════════════════════════════════════════════════════════
# Delegation syntax in reasoning.actions:
#
# reasoning:
# actions:
# # Delegation - specialist CAN return control to this topic
# consult_expert: @topic.specialist_topic
# description: "Consult specialist for complex questions"
# available when @variables.needs_expert_help == True
#
# The specialist topic processes the request and control returns
# to the original topic when done.
# ═══════════════════════════════════════════════════════════════
# PATTERN 2: Transition (permanent handoff)
# ═══════════════════════════════════════════════════════════════
# Transition syntax in reasoning.actions:
#
# reasoning:
# actions:
# # Transition - permanent move to orders topic
# go_orders: @utils.transition to @topic.orders
#
# Control moves to orders topic and STAYS there.
# User continues in that topic until another transition occurs.
# ═══════════════════════════════════════════════════════════════
# PATTERN 3: Manual Bidirectional (fallback pattern)
# ═══════════════════════════════════════════════════════════════
# If delegation doesn't work, use variables to track return:
# See: bidirectional-routing.agent for full implementation
variables:
return_topic: mutable string = ""
description: "Topic to return to after specialist"
specialist_result: mutable string = ""
description: "Result from specialist consultation"
# Before going to specialist, store return address:
# set @variables.return_topic = "main_hub"
# go_specialist: @utils.transition to @topic.specialist
# In specialist, transition back when done:
# return_home: @utils.transition to @topic.main_hub
# ═══════════════════════════════════════════════════════════════
# COMPARISON TABLE
# ═══════════════════════════════════════════════════════════════
#
# | Feature | Delegation | Transition |
# |----------------------|-------------------|-------------------------|
# | Syntax | @topic.name | @utils.transition to |
# | Returns to caller? | YES | NO |
# | Use in actions: | YES | YES |
# | Use in lifecycle: | NO | YES (bare syntax) |
# | Best for | Consult & return | Menu/workflow routing |
#
# ═══════════════════════════════════════════════════════════════Related skills
Forks & variants (1)
Sf Ai Agentscript has 1 known copy in the catalog totaling 35 installs. They canonicalize to this original listing.
- jaganpro - 35 installs
How it compares
Choose sf-ai-agentscript when you need code-level Agent Script DSL and FSM control; use Builder-focused Agentforce skills for metadata, personas, and test harness work.
FAQ
What does sf-ai-agentscript do?
Build Salesforce Agentforce agents with Agent Script, topics, actions, and testing workflows.
When should I use sf-ai-agentscript?
User works on Salesforce Agent Script, Agentforce topics, or agent actions.
Is sf-ai-agentscript safe to install?
Review the Security Audits panel on this page before installing in production.