# Production Gotchas: Billing, Determinism & Performance Credit consumption, lifecycle hooks, determinism patterns, and performance guardrails in Agentforce. --- ## Credit Consumption Table | Operation | Credits | Notes | |-----------|---------|-------| | `@utils.transition` | FREE | Framework navigation | | `@utils.setVariables` | FREE | Framework state management | | `@utils.escalate` | FREE | Framework escalation | | `if`/`else` control flow | FREE | Deterministic resolution | | `before_reasoning` | FREE | Deterministic pre-processing (see note below) | | `after_reasoning` | FREE | Deterministic post-processing (see note below) | | `reasoning` (LLM turn) | FREE | LLM reasoning itself is not billed | | Prompt Templates | 2-16 | Per invocation (varies by complexity) | | Flow actions | 20 | Per action execution | | Apex actions | 20 | Per action execution | | Any other action | 20 | Per action execution | The `before_reasoning:` and `after_reasoning:` lifecycle hooks are validated. Content goes **directly** under the block (no `instructions:` wrapper). See "Lifecycle Hooks" section below for correct syntax. ### Cost Optimization Pattern Fetch data once in `before_reasoning:`, cache in variables, reuse across topics. ## Lifecycle Hooks ```yaml topic main: description: "Topic with lifecycle hooks" # BEFORE: Runs deterministically BEFORE LLM sees instructions before_reasoning: # Content goes DIRECTLY here (NO instructions: wrapper!) set @variables.pre_processed = True set @variables.customer_tier = "gold" # LLM reasoning phase reasoning: instructions: -> | Customer tier: {!@variables.customer_tier} | How can I help you today? # AFTER: Runs deterministically AFTER LLM finishes reasoning after_reasoning: # Content goes DIRECTLY here (NO instructions: wrapper!) set @variables.interaction_logged = True if @variables.needs_audit == True: set @variables.audit_flag = True ``` **Key Points:** - Content goes **directly** under `before_reasoning:` / `after_reasoning:` (NO `instructions:` wrapper) - Reliable primitives: `set`, `if`/`else`, `transition to`. `run` has inconsistent runtime behavior across bundle types — use it in `reasoning.actions:` or `instructions: ->` instead - `before_reasoning:` is FREE (no credit cost) - use for data prep - `after_reasoning:` is FREE (no credit cost) - use for logging, cleanup - `transition to` works in `after_reasoning:` — but if a topic transitions mid-reasoning, the original topic's `after_reasoning:` does NOT run **❌ WRONG Syntax (causes compile error):** ```yaml before_reasoning: instructions: -> # ❌ NO! Don't wrap with instructions: set @variables.x = True ``` **✅ CORRECT Syntax:** ```yaml before_reasoning: set @variables.x = True # ✅ Direct content under the block ``` ## Supervision vs Handoff | Term | Syntax | Behavior | Use When | |------|--------|----------|----------| | **Handoff** | `@utils.transition to @topic.X` | Control transfers completely, child generates final response | Checkout, escalation, terminal states | | **Supervision** | `@topic.X` (as action reference) | Parent orchestrates, child returns, parent synthesizes | Expert consultation, sub-tasks | ```yaml # HANDOFF - child topic takes over completely: checkout: @utils.transition to @topic.order_checkout description: "Proceed to checkout" # → @topic.order_checkout generates the user-facing response # SUPERVISION - parent remains in control: get_advice: @topic.product_expert description: "Consult product expert" # → @topic.product_expert returns, parent topic synthesizes final response ``` **KNOWN BUG**: Adding ANY new action in Canvas view may inadvertently change Supervision references to Handoff transitions. ## Action Output Flags for Zero-Hallucination Routing Control what the LLM can see and say. When defining actions in Agentforce Assets, use these output flags: | Flag | Effect | Use When | |------|--------|----------| | `filter_from_agent: True` | LLM **cannot** show this value to user | Preventing hallucinated responses (GA standard) | | `is_used_by_planner: True` | LLM **can** reason about this value | Decision-making, routing | **Zero-Hallucination Intent Classification Pattern:** ```yaml # In Agentforce Assets - Action Definition outputs: outputs: intent_classification: string filter_from_agent: True # LLM cannot show this to user (GA standard) is_used_by_planner: True # LLM can use for routing decisions # In Agent Script - LLM routes but cannot hallucinate: topic intent_router: reasoning: instructions: -> run @actions.classify_intent set @variables.intent = @outputs.intent_classification if @variables.intent == "refund": transition to @topic.refunds if @variables.intent == "order_status": transition to @topic.orders ``` ## Action I/O Metadata Properties Complete reference for all metadata properties available on action definitions, inputs, and outputs. **Action-Level Properties:** | Property | Type | Effect | |----------|------|--------| | `label` | String | Display name in UI | | `description` | String | LLM reads this for decision-making | | `require_user_confirmation` | Boolean | Request user confirmation before execution (compiles; runtime no-op per Issue 6) | | `include_in_progress_indicator` | Boolean | Show spinner during execution | | `progress_indicator_message` | String | Custom spinner text | **Input Properties:** | Property | Type | Effect | |----------|------|--------| | `description` | String | Explains parameter to LLM | | `label` | String | Display name in UI | | `is_required` | Boolean | Marks input as mandatory for LLM | | `is_user_input` | Boolean | LLM extracts value from conversation | | `complex_data_type_name` | String | Lightning type mapping | **Output Properties:** | Property | Type | Effect | |----------|------|--------| | `description` | String | Explains output to LLM | | `label` | String | Display name in UI | | `filter_from_agent` | Boolean | `True` = hide from user display (GA standard) | | `is_displayable` | Boolean | `False` = hide from user (compile-valid alias) | | `is_used_by_planner` | Boolean | `True` = LLM can reason about value | | `developer_name` | String | Overrides the parameter's developer name | | `complex_data_type_name` | String | Lightning type mapping | `filter_from_agent: True` is the GA standard name. `is_displayable: False` is a compile-valid alias. ### User Input Pattern With `is_user_input: True`: ```yaml inputs: customer_name: string description: "Customer's full name" is_user_input: True # LLM pulls from what user already said is_required: True # Must have a value before action executes ``` ## Action Chaining with `run` Keyword Parent action may complain about inputs needed by chained action - this is expected. ```yaml process_order: @actions.create_order with customer_id = @variables.customer_id run @actions.send_confirmation # Chains after create_order completes set @variables.order_id = @outputs.id ``` KNOWN BUG: Chained actions with Prompt Templates don't properly map inputs using `Input:Query` format. For prompt template action definitions, input binding syntax, and grounded data patterns, see [Action Prompt Templates](action-prompt-templates.md). ## Latch Variable Pattern for Topic Re-entry Topic selector doesn't properly re-evaluate after user provides missing input. Use a "latch" variable to force re-entry: ```yaml variables: verification_in_progress: mutable boolean = False start_agent topic_selector: reasoning: instructions: -> if @variables.verification_in_progress == True: transition to @topic.verification | How can I help you today? actions: start_verify: @topic.verification description: "Start identity verification" set @variables.verification_in_progress = True topic verification: reasoning: instructions: -> | Please provide your email to verify your identity. actions: verify: @actions.verify_identity with email = ... set @variables.verified = @outputs.success set @variables.verification_in_progress = False ``` ## Loop Protection Guardrail Agent Scripts have a built-in guardrail that limits iterations to approximately **3-4 loops** before breaking out and returning to the Topic Selector. **Best Practice**: Map out your execution paths and test for unintended circular references between topics. ## Token & Size Limits | Limit Type | Value | Notes | |------------|-------|-------| | Max response size | 1,048,576 bytes (1MB) | Per agent response | | Plan trace limit (Frontend) | 1M characters | For debugging UI | | Transformed plan trace (Backend) | 32k tokens | Internal processing | | Active/Committed Agents per org | 100 max | Org limit | ## Progress Indicators ```yaml actions: fetch_data: @actions.get_customer_data description: "Fetch customer information" include_in_progress_indicator: True progress_indicator_message: "Fetching your account details..." ``` ## VS Code Pull/Push NOT Supported ```bash # ❌ ERROR when using source tracking: Failed to retrieve components using source tracking: [SfError [UnsupportedBundleTypeError]: Unsupported Bundle Type: AiAuthoringBundle # ✅ WORKAROUND - Use CLI directly: sf project retrieve start --json -m AiAuthoringBundle:MyAgent sf agent publish authoring-bundle --json --api-name MyAgent -o TARGET_ORG ``` ## `@inputs` Scope Lifecycle (Silent Failure) `@inputs` is only available in `with` directives during action invocation. Using `@inputs` in a post-action `set` causes **silent runtime failure** — the action executes but the `set` silently drops, leaving the variable unchanged. No trace error; the FunctionStep shows no output capture. ```agentscript # WRONG — silent failure, @inputs out of scope after action executes run @actions.get_station_status with station_name = ... set @variables.station = @inputs.station_name # FAILS SILENTLY # RIGHT — use @outputs (if action echoes the value) or capture input before the call set @variables.station = @variables.selected_station # capture before run @actions.get_station_status with station_name = @variables.station set @variables.status = @outputs.status # @outputs is valid here ``` **Diagnosis:** A FunctionStep that completes with no output capture (set directives dropped) indicates an `@inputs` scope violation. The action succeeds — only the assignment fails. Similarly, `@outputs` is only available in `set` and `if` directives immediately following the action invocation — not in instructions, pipe lines, or later actions. ## Reserved `@InvocableVariable` Keywords Certain common words cannot be used as `@InvocableVariable` names in Apex classes called by Agent Script. Using them causes "SyntaxError: Unexpected '{keyword}'" during agent script compilation. (Validated March 2026) **Reserved names (cannot use as `@InvocableVariable`):** | Reserved Name | Workaround | Example | |---------------|------------|---------| | `model` | `vehicle_model`, `data_model`, `model_name` | `@InvocableVariable public String vehicle_model;` | | `description` | `issue_description`, `desc_text`, `description_field` | `@InvocableVariable public String issue_description;` | | `label` | `label_text`, `display_label`, `label_field` | `@InvocableVariable public String label_text;` | **How it manifests:** - Apex compiles and deploys successfully (these are valid Apex identifiers) - Error only appears when the Agent Script compiler processes the action's I/O schema - Error message: `SyntaxError: Unexpected 'model'` (or `description`, `label`) - Fix: Rename the `@InvocableVariable` in Apex, redeploy, then republish the agent ## Language Block Quirks - Hebrew and Indonesian appear **twice** in the language dropdown - Selecting from the second set causes save errors - Use `adaptive_response_allowed: True` for automatic language adaptation