* @W-21955450@ Rename topic to subagent for Agent Script v2
Aligns with Agent Script v2 naming standards where `topic` is renamed
to `subagent` across all skill documentation and templates.
Changes:
- Agent Script templates: topic keyword → subagent keyword
- References: @topic.* → @subagent.*
- Documentation: Updated all skill references and guides
- Natural language references preserved in comments/descriptions
* Rename start_agent topic_selector to agent_router
Completes the topic → subagent terminology alignment by:
1. Renaming start_agent from topic_selector to agent_router (15 agent files)
2. Updating template topic declarations: topic {{placeholder}} → subagent {{placeholder}} (5 files)
3. Updating all @subagent.topic_selector references to @subagent.agent_router (35 occurrences)
4. Updating documentation: prose, examples, and diagrams (10 markdown files)
5. Updating comments to use agent_router terminology
Files affected:
- 22 agent template files
- 10 documentation/reference markdown files
- Template component files
The agent_router name is more descriptive of its actual function
(routing to different subagents) and completes the Agent Script v2
terminology standardization.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Rename files with "topic" to use "subagent" terminology
Completes the topic → subagent terminology alignment by renaming
files and updating all references:
**Files renamed (5):**
- multi-topic.agent → multi-subagent.agent
- template-single-topic.agent → template-single-subagent.agent
- template-multi-topic.agent → template-multi-subagent.agent
- topic-with-actions.agent → subagent-with-actions.agent
- agent-topic-map-diagrams.md → agent-subagent-map-diagrams.md
**References updated (6 docs):**
- Updated all filename references to point to new filenames
- Updated "Topic Map" → "Subagent Map" throughout documentation
- Updated "multi-topic"/"single-topic" → "multi-subagent"/"single-subagent"
Files modified:
- README.md, SKILL.md, agent-spec-template.md
- assets/agents/README.md, assets/README-legacy.md
- references/agent-design-and-spec-creation.md
This ensures consistent "subagent" terminology across filenames,
file content, and all documentation references.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Complete topic-to-subagent terminology update across skills
Comprehensive update replacing "topic" with "subagent" terminology throughout
the developing-agentforce and testing-agentforce skills to align with Agent
Script's `subagent` block naming.
Key changes:
- "Topic Selector" → "Subagent Router" in all agent templates and docs
- "Topic/action" → "Subagent/action" in documentation
- "Topic map" → "Subagent map" in diagram references
- Updated all architecture documentation to use "subagent" terminology
- Updated 19 .agent template files with new labels and comments
- Updated 8 reference documentation files with consistent terminology
API contract preservation:
- Test spec YAML files preserve "topic" terminology to match Testing Center API
- Added clarifying comments explaining topic/subagent equivalence in YAML files
- Field names like `expectedTopic` unchanged (Salesforce API requirement)
Preserved terms:
- "off-topic" (standard phrase for out-of-scope)
- "expectedTopic" field (Testing Center API)
- "platform topics" (Salesforce guardrail features)
32 files changed, 379 insertions(+), 366 deletions(-)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Complete comprehensive topic-to-subagent terminology update
Thorough update replacing all remaining "topic" references with "subagent"
terminology across developing-agentforce, testing-agentforce, and
observing-agentforce skills to fully align with Agent Script's `subagent`
block naming.
Key changes:
- Agent Script syntax: @topic.<name> → @subagent.<name>
- Agent Script syntax: topic.actions → subagent.actions
- Shell script patterns: ^topic → ^subagent
- Documentation: "topic instructions" → "subagent instructions"
- observing-agentforce skill: Updated all agent architecture references
- Template files: Updated all inline comments and descriptions
- Variable names in scripts: TOPIC → SUBAGENT
Specific updates:
- 45 files changed, 294 insertions, 294 deletions
- Updated all Agent Script code examples to use @subagent syntax
- Updated observing-agentforce issue classification guide
- Updated shell script patterns in diagnostic tools
- Updated Apex comments to clarify topic field maps to subagents
Preserved (as required):
- "off-topic" and "off_topic" (standard out-of-scope phrase)
- Testing Center API fields: expectedTopic, topic: in YAML
- API response fields: .topic, generatedData.topic, topic_assertion
- STDM field names: ssot__TopicApiName__c (with clarifying docs)
- Template placeholders in test specs (API values)
- "Topic hash drift" (API field behavior)
- "Email topic/purpose" (means email subject)
- Explanatory comments about API field mapping
All Agent Script syntax and documentation now consistently uses "subagent"
while preserving backward compatibility with platform API field names.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* a few more topic -> subagent replacements
---------
Co-authored-by: Steve Hetzel <shetzel@salesforce.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
17 KiB
Instruction Resolution
How Agent Script instructions are processed at runtime: from static text to dynamic LLM prompts.
1. Three Phases of Instruction Resolution
Agent Script instructions go through three distinct phases at runtime. Understanding these phases is critical for writing effective instructions and debugging unexpected behavior.
Phase 1: Pre-LLM Setup
(deterministic -- runs before the LLM sees anything)
|
v
Phase 2: LLM Reasoning
(non-deterministic -- LLM processes the assembled prompt)
|
v
Phase 3: Post-Action Loop
(deterministic -- runs after an action completes, then loops back to Phase 1)
2. Phase 1: Pre-LLM Resolution
During Phase 1, the Agent Script runtime evaluates deterministic constructs in instructions: -> blocks. This happens BEFORE the LLM sees any text.
What Happens in Phase 1
if/elseevaluation: Conditions are evaluated against current variable values. Only the matching branch is included in the prompt.- Variable injection:
{!@variables.X}tokens are replaced with current values. runexecution: Deterministicrun @actions.Xcalls execute and their outputs are captured.setexecution: Variable assignments execute immediately.transition to: If reached, the subagent switch happens immediately (LLM is never called).
Phase 1 Example
Given this instruction block:
reasoning:
instructions: ->
# 1. Post-action check (from previous loop)
if @variables.refund_approved == True:
| Your refund has been processed. Reference: {!@variables.refund_id}
transition to @subagent.confirmation
# 2. Pre-LLM data loading
if @variables.data_loaded == False:
run @actions.load_customer_profile
with customer_id = @variables.customer_id
set @variables.risk_score = @outputs.risk_score
set @variables.tier = @outputs.tier
set @variables.data_loaded = True
# 3. Dynamic instructions
| Customer tier: {!@variables.tier}, Risk score: {!@variables.risk_score}
if @variables.risk_score >= 80:
| HIGH RISK -- Offer full cash refund to retain this customer.
| Do NOT offer store credit. Prioritize retention.
if @variables.risk_score < 80:
| STANDARD -- Offer $10 store credit as goodwill.
| Only escalate to cash refund if customer insists.
First turn resolution (variables at defaults):
refund_approved == True-> False. Skip this block.data_loaded == False-> True. Executerun @actions.load_customer_profile. Variables now set.- Set
data_loaded = True. - Inject
{!@variables.tier}->"gold",{!@variables.risk_score}->85. risk_score >= 80-> True. Include high-risk instructions.risk_score < 80-> False. Skip standard instructions.
What the LLM actually sees:
Customer tier: gold, Risk score: 85
HIGH RISK -- Offer full cash refund to retain this customer.
Do NOT offer store credit. Prioritize retention.
3. Phase 2: LLM Processing
In Phase 2, the LLM receives the assembled prompt and produces a response. The LLM sees:
The 4-Message Prompt Structure
The Agent Script runtime assembles a 4-message prompt for the LLM:
| # | Message Role | Content Source | Purpose |
|---|---|---|---|
| 1 | System | system: instructions: + agent metadata |
Global persona, safety rules, capabilities |
| 2 | System | subagent: reasoning: instructions: (resolved from Phase 1) |
Subagent-specific operating instructions |
| 3 | User/Assistant | Conversation history (all turns) | Context for the current request |
| 4 | System | Available actions + their descriptions | Tool palette the LLM can choose from |
What the LLM Decides
Based on the assembled prompt, the LLM:
- Selects an action (if applicable) from the available actions list
- Fills slot parameters (
...values) from conversation context - Generates a text response to send to the user
- Decides whether to transition (if a transition action is available and appropriate)
What the LLM Does NOT See
- Raw
if/elseblocks (already resolved in Phase 1) runstatements (already executed in Phase 1)setstatements (already executed)available whenconditions (already evaluated -- hidden actions are simply absent)after_reasoningblocks (run after the LLM, not shown to it)
4. Phase 3: Post-Action Loop
After the LLM selects and executes an action, the system loops back to Phase 1 for re-resolution. This is the post-action loop pattern described in the SKILL.md architecture section.
Loop Sequence
1. Phase 1 resolves instructions (first time)
2. Phase 2: LLM reasons and selects an action
3. Action executes -> outputs captured in variables
4. Phase 1 re-resolves instructions (with updated variables)
- Post-action checks at TOP of instructions fire
- New data is injected into the prompt
5. Phase 2: LLM reasons again with updated context
6. Repeat until: transition, escalation, or no action selected
Why Post-Action Checks Go at the TOP
Place post-action checks at the TOP of instructions: -> so they fire immediately on re-resolution:
reasoning:
instructions: ->
# POST-ACTION CHECK (at TOP -- fires on re-resolution)
if @variables.order_cancelled == True:
| Your order has been cancelled successfully.
transition to @subagent.confirmation
# These instructions are for the FIRST entry (before action runs)
| I can help you cancel your order.
| What is your order number?
If the check were at the BOTTOM, the LLM would see the "ask for order number" instructions again even after the cancellation succeeded, causing confusion.
5. Recommended Instruction Order
Within a instructions: -> block, follow this order for maximum clarity:
reasoning:
instructions: ->
# 1. POST-ACTION CHECKS (deterministic transitions)
if @variables.action_completed == True:
transition to @subagent.next_step
# 2. PRE-LLM DATA LOADING (deterministic actions)
if @variables.data_needed == True:
run @actions.load_data
with id = @variables.record_id
set @variables.loaded_data = @outputs.result
# 3. CONDITIONAL INSTRUCTIONS (based on state)
if @variables.is_verified == True:
| Full access granted. You can:
| - View account details
| - Make changes
| - Request refunds
if @variables.is_verified == False:
| Please verify your identity first.
| I need your email address and order number.
# 4. STATIC INSTRUCTIONS (always included)
| Be concise and professional.
| Always confirm before making changes.
6. Common Instruction Patterns
Pattern 1: Security Gate
Prevent access to sensitive actions until identity is verified:
reasoning:
instructions: ->
if @variables.is_verified == False:
| You must verify your identity before I can help with account changes.
| Please provide your email address.
if @variables.is_verified == True:
| Identity verified. I can now help with account changes.
| What would you like to do?
actions:
update_account: @actions.update_account_info
description: "Update account information"
available when @variables.is_verified == True
with field = ...
with value = ...
The available when guard hides the action from the LLM until verification passes. The conditional instructions tell the user what to do.
Pattern 2: Data-Dependent Instructions
Load data first, then tailor instructions based on the result:
reasoning:
instructions: ->
run @actions.get_account_status
with account_id = @variables.account_id
set @variables.account_status = @outputs.status
set @variables.balance = @outputs.balance
| Account status: {!@variables.account_status}
| Current balance: {!@variables.balance}
if @variables.account_status == "delinquent":
| IMPORTANT: This account is delinquent.
| Collect payment before processing any other requests.
| Offer payment plan options if customer cannot pay in full.
if @variables.account_status == "active":
| This account is in good standing.
| Process requests normally.
Pattern 3: Action Chaining
Execute one action, then use its output to drive the next:
reasoning:
instructions: ->
# Post-action check: case was created in previous loop
if @variables.case_id != "":
| Case {!@variables.case_id} has been created.
run @actions.assign_case
with case_id = @variables.case_id
with priority = @variables.priority
transition to @subagent.case_confirmation
| I need to collect some information to create a support case.
| What is the issue you're experiencing?
Pattern 4: Multi-Condition Routing
Route based on multiple variable values:
reasoning:
instructions: ->
if @variables.intent == "billing" and @variables.is_verified == True:
| I can help with your billing question.
transition to @subagent.billing_support
if @variables.intent == "billing" and @variables.is_verified == False:
| For billing questions, I need to verify your identity first.
transition to @subagent.identity_verification
if @variables.intent == "general":
| How can I help you today?
7. Anti-Patterns to Avoid
Anti-Pattern 1: Nested If Blocks
# WRONG -- Agent Script does not support nested if or else if
if @variables.tier == "gold":
if @variables.is_verified == True:
| VIP treatment
else:
| Verify first
# CORRECT -- Use compound conditions
if @variables.tier == "gold" and @variables.is_verified == True:
| VIP treatment
if @variables.tier == "gold" and @variables.is_verified == False:
| Verify first
Anti-Pattern 2: Post-Action Check at Bottom
# WRONG -- Check at bottom; LLM sees stale instructions on re-resolution
reasoning:
instructions: ->
| What is your order number?
if @variables.order_status != "":
transition to @subagent.show_status
# CORRECT -- Check at TOP
reasoning:
instructions: ->
if @variables.order_status != "":
transition to @subagent.show_status
| What is your order number?
Anti-Pattern 3: Persona in Subagent Instructions
# WRONG -- Persona text duplicated in every subagent
reasoning:
instructions: |
You are a friendly, professional customer service agent.
Help the customer with their order.
# CORRECT -- Persona in system instructions, subagent has operational instructions only
system:
instructions: |
You are a friendly, professional customer service agent.
subagent order_support:
reasoning:
instructions: ->
| Help the customer check their order status.
| Ask for the order number if not provided.
Anti-Pattern 4: Using | When -> Is Needed
# WRONG -- Using literal mode when conditionals are needed
reasoning:
instructions: |
if @variables.is_verified == True:
Show account details.
# The above sends the literal text "if @variables.is_verified == True:" to the LLM!
# CORRECT -- Use procedural mode for conditionals
reasoning:
instructions: ->
if @variables.is_verified == True:
| Show account details.
Anti-Pattern 5: Missing Variable Injection Syntax
# WRONG -- Variable name as literal text
reasoning:
instructions: ->
| Your order ID is @variables.order_id
# CORRECT -- Use injection syntax
reasoning:
instructions: ->
| Your order ID is {!@variables.order_id}
Anti-Pattern 6: run Inside after_reasoning
While run compiles inside after_reasoning:, its runtime behavior is inconsistent across bundle types. Prefer using run in reasoning: instructions: -> or reasoning: actions: instead.
# RISKY -- run in after_reasoning has inconsistent behavior
after_reasoning:
run @actions.log_event
with event = "turn_completed"
# SAFER -- Use instructions: -> for deterministic runs
reasoning:
instructions: ->
# Post-action logging
if @variables.last_action != "":
run @actions.log_event
with event = @variables.last_action
8. Syntax Patterns Reference
Literal Mode (|)
Static text passed directly to the LLM. No evaluation occurs:
instructions: |
Help the customer with their order.
Be professional and concise.
Or with the | prefix on each line (inside procedural mode):
instructions: ->
| Help the customer with their order.
| Be professional and concise.
Procedural Mode (->)
Enables conditionals, variable injection, and deterministic actions:
instructions: ->
if @variables.condition == True:
| Text shown when condition is true.
else:
| Text shown when condition is false.
Variable Injection
| Your order {!@variables.order_id} is {!@variables.status}.
Deterministic Run
run @actions.load_data
with param = @variables.value
set @variables.result = @outputs.field
Deterministic Set
set @variables.counter = @variables.counter + 1
Deterministic Transition
transition to @subagent.next_subagent
Conditional Transition
if @variables.all_collected == True:
transition to @subagent.confirmation
9. Programmatic Trace Access
To verify how instructions were resolved at runtime, use the trace files generated by sf agent preview.
Trace File Location
.sfdx/agents/{BundleName}/sessions/{sessionId}/traces/{planId}.json
Reading Instruction Resolution from Traces
# Extract the resolved instructions that the LLM received
jq -r '.planTrace.steps[] | select(.type == "LLM_STEP") | .input' \
~/.sf/sfdx/agents/MyAgent/sessions/*/traces/*.json
# Extract the LLM's response
jq -r '.planTrace.steps[] | select(.type == "LLM_STEP") | .output' \
~/.sf/sfdx/agents/MyAgent/sessions/*/traces/*.json
# Check which variables were set during resolution
jq -r '.planTrace.steps[] | select(.type == "ACTION_STEP") | {name: .name, pre: .preVars, post: .postVars}' \
~/.sf/sfdx/agents/MyAgent/sessions/*/traces/*.json
Verifying Phase 1 Resolution
To confirm that if/else blocks resolved correctly, compare the trace's LLM_STEP input against your instructions: -> block. The LLM input should contain only the branches that matched, with all {!@variables.X} tokens replaced with actual values.
If the trace shows unexpected instruction text:
- Check that you used
->mode (not|mode) when conditionals are present - Verify variable values at the time of resolution (check
preVarson precedingACTION_STEP) - Confirm that
ifconditions use the correct comparison operators
Using STDM for Production Trace Analysis
For production agents, use the Session Trace Data Model (STDM) in Data Cloud to access trace data programmatically. The STDM captures LLM_STEP records with input and output fields that contain the resolved prompt and LLM response. This is useful for auditing instruction resolution at scale across hundreds of live sessions.
10. Resolution Across Subagent Transitions
When a subagent transition occurs (via @utils.transition to @subagent.X or transition to @subagent.X), instruction resolution starts fresh in the new subagent:
- The current subagent's remaining instructions are NOT processed
- The new subagent's
before_reasoning:runs (if present) - The new subagent's
reasoning: instructions:resolves from Phase 1 - The LLM receives the new subagent's assembled prompt
Important: Variables persist across transitions. A variable set in Subagent A is available in Subagent B. This is how you pass data between subagents:
# Subagent A: Collect data
subagent collect_info:
reasoning:
instructions: ->
| Please provide your order number.
actions:
capture_order: @actions.get_order_id
with input = ...
set @variables.order_id = @outputs.order_id
after_reasoning:
if @variables.order_id != "":
transition to @subagent.process_order
# Subagent B: Use the data
subagent process_order:
reasoning:
instructions: ->
# order_id is available from Subagent A
| Processing order {!@variables.order_id}...
run @actions.get_order_details
with order_id = @variables.order_id
set @variables.order_status = @outputs.status