# 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 1. **`if`/`else` evaluation**: Conditions are evaluated against current variable values. Only the matching branch is included in the prompt. 2. **Variable injection**: `{!@variables.X}` tokens are replaced with current values. 3. **`run` execution**: Deterministic `run @actions.X` calls execute and their outputs are captured. 4. **`set` execution**: Variable assignments execute immediately. 5. **`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. Execute `run @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: 1. **Selects an action** (if applicable) from the available actions list 2. **Fills slot parameters** (`...` values) from conversation context 3. **Generates a text response** to send to the user 4. **Decides whether to transition** (if a transition action is available and appropriate) ### What the LLM Does NOT See - Raw `if`/`else` blocks (already resolved in Phase 1) - `run` statements (already executed in Phase 1) - `set` statements (already executed) - `available when` conditions (already evaluated -- hidden actions are simply absent) - `after_reasoning` blocks (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 ```bash # 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: 1. Check that you used `->` mode (not `|` mode) when conditionals are present 2. Verify variable values at the time of resolution (check `preVars` on preceding `ACTION_STEP`) 3. Confirm that `if` conditions 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: 1. The current subagent's remaining instructions are NOT processed 2. The new subagent's `before_reasoning:` runs (if present) 3. The new subagent's `reasoning: instructions:` resolves from Phase 1 4. 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 ```