mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-05 06:41:42 +08:00
691 lines
30 KiB
Markdown
691 lines
30 KiB
Markdown
# Actions Reference
|
|
|
|
Complete guide to Agent Actions in Agentforce: Flow, Apex, API actions, and escalation routing.
|
|
|
|
> Primary reference for action properties, target types, and action wiring.
|
|
> Pair with `feature-validity.md` for utility-vs-target property caveats.
|
|
|
|
---
|
|
|
|
## Action Properties Reference
|
|
|
|
All actions in Agent Script support these properties:
|
|
|
|
### Action Definition Properties
|
|
|
|
| Property | Type | Required | Description |
|
|
|----------|------|----------|-------------|
|
|
| `target` | String | Yes | Executable target (see Action Target Types below) |
|
|
| `description` | String | Yes | Explains behavior for LLM decision-making |
|
|
| `label` | String | No | Display name in UI; auto-generated from action name if omitted |
|
|
| `inputs` | Object | No | Input parameters and requirements |
|
|
| `outputs` | Object | No | Return parameters |
|
|
| `available_when` | Expression | No | Conditional availability for the LLM |
|
|
| `require_user_confirmation` | Boolean | No | Ask user to confirm before execution; defaults to `False` |
|
|
| `include_in_progress_indicator` | Boolean | No | Show progress indicator during execution; defaults to `False` |
|
|
| `progress_indicator_message` | String | No | Custom message shown during execution (e.g., "Processing your request...") |
|
|
|
|
> **Note**: `label`, `require_user_confirmation`, `include_in_progress_indicator`, and `progress_indicator_message` are valid on action definitions with `target:` but NOT on `@utils.transition` utility actions.
|
|
|
|
### Input Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `description` | String | Explains the input parameter to LLM; auto-generated from field name if omitted |
|
|
| `label` | String | Display name in UI; auto-generated from field name if omitted |
|
|
| `is_required` | Boolean | Marks input as mandatory for the LLM; defaults to `False` |
|
|
| `is_user_input` | Boolean | LLM extracts value from conversation context; defaults to `False` |
|
|
| `complex_data_type_name` | String | Lightning data type mapping (required for complex types) |
|
|
|
|
### Output Properties
|
|
|
|
| Property | Type | Description |
|
|
|----------|------|-------------|
|
|
| `description` | String | Explains the output parameter to LLM; auto-generated from field name if omitted |
|
|
| `label` | String | Display name in UI; auto-generated from field name if omitted |
|
|
| `filter_from_agent` | Boolean | `True` = exclude output from agent context; defaults to `False` |
|
|
| `is_used_by_planner` | Boolean | `True` = LLM can reason about this value for routing decisions; defaults to `False` |
|
|
| `complex_data_type_name` | String | Lightning data type mapping (required for complex types) |
|
|
| `is_displayable` | Boolean | `False` = hide from user display (compile-valid alias for `filter_from_agent: True`) |
|
|
|
|
> **Note**: `filter_from_agent: True` is the GA standard. `is_displayable: False` is a compile-valid alias with the same effect.
|
|
|
|
> **Safety**: For service agents (customer-facing), internal business metrics (risk scores, retention tiers, churn probability, internal classification codes) should be `filter_from_agent: True` so the LLM can use them for reasoning but they don't appear in customer-facing responses.
|
|
|
|
### Zero-Hallucination Intent Classification Pattern
|
|
|
|
Use `filter_from_agent: True` + `is_used_by_planner: True` to let the LLM route based on action outputs without being able to show them to the user:
|
|
|
|
```agentscript
|
|
outputs:
|
|
intent_classification: string
|
|
filter_from_agent: True # LLM cannot show this to user
|
|
is_used_by_planner: True # LLM can use for routing decisions
|
|
```
|
|
|
|
This prevents the LLM from fabricating classification results — it must invoke the action to get the value, then can only use it for routing decisions.
|
|
|
|
### Example with All Properties
|
|
|
|
```agentscript
|
|
actions:
|
|
process_payment:
|
|
description: "Processes payment for the order"
|
|
require_user_confirmation: True # Ask user before executing
|
|
include_in_progress_indicator: True
|
|
inputs:
|
|
amount: number
|
|
description: "Payment amount"
|
|
card_token: string
|
|
description: "Tokenized card number"
|
|
outputs:
|
|
transaction_id: string
|
|
description: "Transaction reference"
|
|
card_last_four: string
|
|
description: "Last 4 digits of card"
|
|
filter_from_agent: True # Hide from LLM context
|
|
target: "flow://Process_Payment"
|
|
available_when: @variables.cart_total > 0
|
|
```
|
|
|
|
---
|
|
|
|
## Action Target Types (Complete Reference)
|
|
|
|
AgentScript supports the following action types. Use the correct protocol for your integration.
|
|
|
|
| Short Name | Long Name | Description | Use Case |
|
|
|------------|-----------|-------------|----------|
|
|
| `apex` | `apex` | Apex Class | Custom business logic; requires `@InvocableMethod` |
|
|
| `prompt` | `generatePromptResponse` | Prompt Template | AI-generated responses |
|
|
| `flow` | `flow` | Salesforce Flow | Custom business logic; Must be Autolaunched |
|
|
| `standardInvocableAction` | `standardInvocableAction` | Built-in Salesforce actions | Send email, create task, etc. |
|
|
| `externalService` | `externalService` | External API via OpenAPI schema | External system calls |
|
|
| `quickAction` | `quickAction` | Object-specific quick actions | Log call, create related record |
|
|
| `api` | `api` | REST API calls | Direct API invocation |
|
|
| `apexRest` | `apexRest` | Custom REST endpoints | Custom @RestResource classes |
|
|
| `serviceCatalog` | `createCatalogItemRequest` | Service Catalog | Service catalog requests |
|
|
| `integrationProcedureAction` | `executeIntegrationProcedure` | OmniStudio Integration | Industry Cloud procedures |
|
|
| `expressionSet` | `runExpressionSet` | Expression calculations | Decision matrix, calculations |
|
|
| `cdpMlPrediction` | `cdpMlPrediction` | CDP ML predictions | Data Cloud predictions |
|
|
| `externalConnector` | `externalConnector` | External system connector | Pre-built connectors |
|
|
| `slack` | `slack` | Slack integration | Slack messaging |
|
|
| `namedQuery` | `namedQuery` | Predefined queries | Saved SOQL queries |
|
|
| `auraEnabled` | `auraEnabled` | Lightning component methods | @AuraEnabled Apex methods |
|
|
| `mcpTool` | `mcpTool` | Model Context Protocol | MCP tool integrations |
|
|
| `retriever` | `retriever` | Knowledge retrieval | RAG/knowledge base queries |
|
|
|
|
**Target Format**: `<type>://<DeveloperName>` (e.g., `apex://GetAccountInfo`, `prompt://Send_Email`)
|
|
|
|
**Common Examples:**
|
|
```agentscript
|
|
# Apex action
|
|
target: "apex://CustomerServiceController"
|
|
|
|
# Flow action
|
|
target: "flow://Get_Customer_Orders"
|
|
|
|
# Prompt template
|
|
target: "generatePromptResponse://Email_Draft_Template"
|
|
|
|
# Standard invocable action (built-in Salesforce)
|
|
target: "standardInvocableAction://sendEmail"
|
|
|
|
# External service (API call)
|
|
target: "externalService://Stripe_Payment_API"
|
|
```
|
|
|
|
---
|
|
|
|
## Action Invocation Methods
|
|
|
|
Agent Script provides two ways to invoke actions:
|
|
|
|
- **Actions Block** (`actions:` in `reasoning:`) — LLM chooses which to execute
|
|
- **Deterministic** (`run @actions.name`) — always executes when code path is reached
|
|
|
|
### Invoking Actions with `actions` Blocks
|
|
|
|
The LLM automatically selects appropriate actions from those defined in the `reasoning.actions` block:
|
|
|
|
```agentscript
|
|
subagent order_management:
|
|
description: "Handles order inquiries"
|
|
|
|
reasoning:
|
|
instructions: ->
|
|
| Help the customer with their order.
|
|
| When they ask about an order, look it up.
|
|
actions:
|
|
# LLM automatically selects this when appropriate
|
|
lookup: @actions.get_order
|
|
with order_id=...
|
|
set @variables.order_status = @outputs.status
|
|
|
|
actions:
|
|
get_order:
|
|
description: "Retrieves order information"
|
|
inputs:
|
|
order_id: string
|
|
description: "The order ID"
|
|
outputs:
|
|
status: string
|
|
description: "Order status"
|
|
target: "flow://Get_Order_Details"
|
|
```
|
|
|
|
You can also reference action definitions inside `instructions:` using `{!@actions.name}` interpolation. This gives the LLM richer context about available actions.
|
|
|
|
```agentscript
|
|
reasoning:
|
|
instructions: ->
|
|
| To look up an order, use {!@actions.get_order}.
|
|
| To check shipping status, use {!@actions.track_shipment}.
|
|
```
|
|
|
|
> See [patterns-by-requirement.md](patterns-by-requirement.md) and
|
|
> [architecture-patterns.md](architecture-patterns.md) for pattern selection and usage guidance.
|
|
|
|
### Invoking Actions Deterministically with `run`
|
|
|
|
The `run` keyword uses the common deterministic action mechanism in
|
|
`instructions: ->`, action callbacks, and lifecycle blocks such as
|
|
`before_reasoning` and `after_reasoning`. What changes is when the containing
|
|
block runs, not whether `run` is valid there.
|
|
|
|
```agentscript
|
|
# Refresh immediately before the reasoning loop.
|
|
before_reasoning:
|
|
run @actions.refresh_context
|
|
with user_id=@variables.EndUserId
|
|
set @variables.current_context = @outputs.context
|
|
|
|
# Chain deterministic work from an invoked reasoning action.
|
|
create: @actions.create_order
|
|
with customer_id=@variables.customer_id
|
|
run @actions.send_confirmation
|
|
with order_id=@outputs.id
|
|
set @variables.order_id = @outputs.id
|
|
|
|
# Load data during instruction resolution.
|
|
reasoning:
|
|
instructions: ->
|
|
run @actions.load_customer
|
|
with id=@variables.customer_id
|
|
set @variables.name = @outputs.name
|
|
|
|
# Log after the reasoning loop completes.
|
|
after_reasoning:
|
|
run @actions.log_turn
|
|
with session_id=@variables.RoutableId
|
|
```
|
|
|
|
Lifecycle timing still matters: for example, a transition may end the current
|
|
path before a later hook is reached. Validate the intended trace; do not treat
|
|
that timing question as a syntax restriction on `run`.
|
|
|
|
---
|
|
|
|
## Action Type 1: Flow Actions
|
|
|
|
### When to Use
|
|
|
|
- Standard Salesforce data operations (CRUD)
|
|
- Business logic that can be expressed in Flow
|
|
- Autolaunched flows for guided logic execution (screen flows are not valid action targets)
|
|
- Approval processes
|
|
|
|
### Implementation
|
|
|
|
```yaml
|
|
actions:
|
|
create_case:
|
|
description: "Creates a new support case for the customer"
|
|
inputs:
|
|
subject:
|
|
type: string
|
|
description: "Case subject line"
|
|
description:
|
|
type: string
|
|
description: "Detailed case description"
|
|
priority:
|
|
type: string
|
|
description: "Case priority (Low, Medium, High, Urgent)"
|
|
outputs:
|
|
caseNumber:
|
|
type: string
|
|
description: "Created case number"
|
|
caseId:
|
|
type: string
|
|
description: "Case record ID"
|
|
target: "flow://Create_Support_Case"
|
|
```
|
|
|
|
### Flow Requirements
|
|
|
|
For an action to work with agents, the Flow must:
|
|
|
|
1. **Be Autolaunched** — `processType: AutoLaunchedFlow`
|
|
2. **Have Input Variables** — Marked as `isInput: true`
|
|
3. **Have Output Variables** — Marked as `isOutput: true`
|
|
4. **Be Active** — `status: Active`
|
|
|
|
**Flow Variable Example:**
|
|
```xml
|
|
<variables>
|
|
<name>subject</name>
|
|
<dataType>String</dataType>
|
|
<isCollection>false</isCollection>
|
|
<isInput>true</isInput>
|
|
<isOutput>false</isOutput>
|
|
</variables>
|
|
```
|
|
|
|
### Best Practices
|
|
|
|
| Practice | Description |
|
|
|----------|-------------|
|
|
| Descriptive names | Use clear Flow API names that describe the action |
|
|
| Error handling | Include fault paths in your Flow |
|
|
| Bulkification | Design Flows to handle multiple records |
|
|
| Governor limits | Avoid SOQL/DML in loops |
|
|
|
|
---
|
|
|
|
## Action Type 2: Apex Actions
|
|
|
|
### When to Use
|
|
|
|
- Complex calculations or algorithms
|
|
- Custom integrations requiring Apex
|
|
- Operations not possible in Flow
|
|
- Bulk data processing
|
|
- When you need full control over execution
|
|
|
|
#### Step 1: Create Apex Class with @InvocableMethod
|
|
|
|
> ⚠️ **An Apex class can only have ONE `@InvocableMethod`.** If you need multiple actions, create separate classes — one per action.
|
|
|
|
```apex
|
|
public with sharing class CalculateDiscountAction {
|
|
|
|
// ─── REQUEST WRAPPER ──────────────────────────────────────────
|
|
// Each @InvocableVariable field name becomes an action input
|
|
// name in the .agent file. Must match character-for-character.
|
|
|
|
public class DiscountRequest {
|
|
@InvocableVariable(
|
|
label='Order Amount'
|
|
description='Total order amount before discount'
|
|
required=true
|
|
)
|
|
public Decimal orderAmount;
|
|
|
|
@InvocableVariable(
|
|
label='Customer Tier'
|
|
description='Customer membership tier (Bronze, Silver, Gold, Platinum)'
|
|
required=true
|
|
)
|
|
public String customerTier;
|
|
}
|
|
|
|
// ─── RESULT WRAPPER ───────────────────────────────────────────
|
|
// Each @InvocableVariable field name becomes an action output
|
|
// name in the .agent file. Must match character-for-character.
|
|
|
|
public class DiscountResult {
|
|
@InvocableVariable(
|
|
label='Discount Percentage'
|
|
description='Applied discount percentage'
|
|
)
|
|
public Decimal discountPercentage;
|
|
|
|
@InvocableVariable(
|
|
label='Final Amount'
|
|
description='Final order amount after discount'
|
|
)
|
|
public Decimal finalAmount;
|
|
}
|
|
|
|
@InvocableMethod(
|
|
label='Calculate Discount'
|
|
description='Calculates discount based on order amount and customer tier'
|
|
)
|
|
public static List<DiscountResult> calculateDiscount(List<DiscountRequest> requests) {
|
|
List<DiscountResult> results = new List<DiscountResult>();
|
|
for (DiscountRequest req : requests) {
|
|
DiscountResult result = new DiscountResult();
|
|
result.discountPercentage = getTierDiscount(req.customerTier);
|
|
result.finalAmount = req.orderAmount * (1 - result.discountPercentage / 100);
|
|
results.add(result);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static Decimal getTierDiscount(String tier) {
|
|
Map<String, Decimal> tierDiscounts = new Map<String, Decimal>{
|
|
'Bronze' => 5, 'Silver' => 10, 'Gold' => 15, 'Platinum' => 20
|
|
};
|
|
return tierDiscounts.containsKey(tier) ? tierDiscounts.get(tier) : 0;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Step 2: Reference DIRECTLY in Agent Script via `apex://`
|
|
|
|
```yaml
|
|
subagent discount_calculator:
|
|
description: "Calculates discount for customer order"
|
|
|
|
# Level 1: Action DEFINITION with target
|
|
actions:
|
|
calculate_discount:
|
|
description: "Calculates discount based on order amount and customer tier"
|
|
inputs:
|
|
orderAmount: number
|
|
description: "The total order amount before discount"
|
|
customerTier: string
|
|
description: "Customer membership tier"
|
|
outputs:
|
|
discountPercentage: number
|
|
description: "Applied discount percentage"
|
|
finalAmount: number
|
|
description: "Final order amount after discount"
|
|
target: "apex://CalculateDiscountAction"
|
|
|
|
reasoning:
|
|
instructions: |
|
|
Help the customer calculate their discount.
|
|
# Level 2: Action INVOCATION referencing the Level 1 definition
|
|
actions:
|
|
calc: @actions.calculate_discount
|
|
with orderAmount=...
|
|
with customerTier=@variables.tier
|
|
set @variables.final_amount = @outputs.finalAmount
|
|
```
|
|
|
|
#### I/O Name Matching Rules
|
|
|
|
Action `inputs:` and `outputs:` names in Agent Script must **exactly match** the `@InvocableVariable` field names in the Apex class:
|
|
|
|
```agentscript
|
|
# Given this Apex field:
|
|
# @InvocableVariable
|
|
# public Decimal orderAmount;
|
|
|
|
# ❌ WRONG — snake_case doesn't match camelCase field name
|
|
inputs:
|
|
order_amount: number
|
|
|
|
# ❌ WRONG — different name entirely
|
|
inputs:
|
|
amount: number
|
|
|
|
# ✅ CORRECT — exact match to Apex field name
|
|
inputs:
|
|
orderAmount: number
|
|
```
|
|
|
|
> **Partial Output Pattern**: You can declare a **subset** of the target's outputs in your action definition — you don't need to map every output parameter. This is useful when you only need one field from a multi-output action.
|
|
|
|
#### Bare @InvocableMethod Pattern (NOT Compatible)
|
|
|
|
Apex classes using bare `List<String>` parameters without `@InvocableVariable` wrapper classes are **incompatible** with Agent Script. The framework cannot discover bindable parameter names without `@InvocableVariable` annotations.
|
|
|
|
```apex
|
|
// ❌ WRONG — bare parameters, no wrappers (Agent Script cannot bind inputs/outputs)
|
|
public class BareAction {
|
|
@InvocableMethod(label='Bare Action')
|
|
public static List<String> execute(List<String> inputs) {
|
|
return inputs;
|
|
}
|
|
}
|
|
|
|
// ✅ CORRECT — wrapper classes with @InvocableVariable
|
|
public class WrappedAction {
|
|
public class Request {
|
|
@InvocableVariable(
|
|
label='Input Text'
|
|
description='Text to process'
|
|
required=true
|
|
)
|
|
public String inputText;
|
|
}
|
|
public class Response {
|
|
@InvocableVariable(
|
|
label='Output Text'
|
|
description='Processed result'
|
|
)
|
|
public String outputText;
|
|
}
|
|
@InvocableMethod(label='Wrapped Action')
|
|
public static List<Response> execute(List<Request> requests) { ... }
|
|
}
|
|
```
|
|
|
|
> ⚠️ **Namespace Warning (Unresolved)**: In namespaced packages, `apex://ClassName` may fail at publish time with "invocable action does not exist," even when the Apex class is confirmed deployed via SOQL. It is unclear whether namespace prefix syntax is required (e.g., `apex://ns__ClassName`). If you encounter this in a namespaced org, try: (1) `apex://ns__ClassName` format, (2) wrapping the Apex in a Flow and using `flow://` instead. See [known-issues.md](known-issues.md#issue-2-sf-agent-publish-fails-with-namespace-prefix-on-apex-targets) for tracking.
|
|
|
|
---
|
|
|
|
## Action Type 3: API Actions (External System Integration)
|
|
|
|
### Architecture
|
|
|
|
```text
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ API ACTION ARCHITECTURE │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Agent Script │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ flow://HTTP_Callout_Flow │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ HTTP Callout Action (in Flow) │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ Named Credential (Authentication) │
|
|
│ │ │
|
|
│ ▼ │
|
|
│ External API │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Implementation Steps
|
|
|
|
1. **Create Named Credential** (via the `integration-connectivity-generate` skill)
|
|
2. **Create HTTP Callout Flow** wrapping the external call
|
|
3. **Reference Flow in Agent Script** with `flow://` target
|
|
|
|
### Security Considerations
|
|
|
|
| Consideration | Implementation |
|
|
|---------------|----------------|
|
|
| Authentication | Always use Named Credentials (never hardcode secrets) |
|
|
| Permissions | Use Permission Sets to grant Named Principal access |
|
|
| Error handling | Implement fault paths in Flow |
|
|
| Logging | Log callout details for debugging |
|
|
| Timeouts | Set appropriate timeout values |
|
|
|
|
---
|
|
|
|
## Connection Block (Escalation Routing)
|
|
|
|
The `connection` block enables escalation to human agents via Omni-Channel. Always use `connection messaging:` (singular).
|
|
|
|
> **Service agents only.** The `connection messaging:` block and `@utils.escalate` are only valid for `AgentforceServiceAgent`. Employee agents (`AgentforceEmployeeAgent`) MUST NOT include a `connection` block or `@utils.escalate` actions — including them causes silent failures or "unknown error" at publish time. For employee agents, use `@utils.transition` to a help subagent or an action that creates a support case instead.
|
|
|
|
### Basic Syntax
|
|
|
|
```agentscript
|
|
# ❌ WRONG — plural wrapper block (invalid syntax)
|
|
connections:
|
|
messaging:
|
|
escalation_message: "Transferring you to a human agent..."
|
|
outbound_route_type: "OmniChannelFlow"
|
|
outbound_route_name: "flow://Support_Queue_Flow"
|
|
|
|
# ✅ CORRECT — singular with channel type
|
|
connection messaging:
|
|
outbound_route_type: "OmniChannelFlow"
|
|
outbound_route_name: "flow://Support_Queue_Flow"
|
|
escalation_message: "Transferring you to a human agent..."
|
|
adaptive_response_allowed: True
|
|
```
|
|
|
|
### Multiple Channels
|
|
|
|
Each channel gets its own top-level `connection <channel>:` block:
|
|
|
|
```agentscript
|
|
connection messaging:
|
|
escalation_message: "Transferring to messaging agent..."
|
|
outbound_route_type: "OmniChannelFlow"
|
|
outbound_route_name: "flow://Agent_Support_Flow"
|
|
adaptive_response_allowed: True
|
|
```
|
|
|
|
### Connection Block Properties
|
|
|
|
| Property | Type | Required | Description |
|
|
|----------|------|----------|-------------|
|
|
| `outbound_route_type` | String | Yes | `"OmniChannelFlow"` is the only validated value. |
|
|
| `outbound_route_name` | String | Yes | API name of Omni-Channel Flow (must exist in org) |
|
|
| `escalation_message` | String | Yes | Message shown to user during transfer |
|
|
| `adaptive_response_allowed` | Boolean | No | Allow agent to adapt responses during escalation (default: False) |
|
|
|
|
### Supported Channels
|
|
|
|
| Channel | Description | Use Case |
|
|
|---------|-------------|----------|
|
|
| `messaging` | Chat/messaging channels; also the escalation-routing surface | Enhanced Chat, Web Chat, In-App; human escalation via `@utils.escalate` |
|
|
| `customer_web_client` | Enhanced Chat v2 (ECv2) surface — the voice-capable connection ADLC authors | Voice agents, Agent Builder Preview (see [Voice Modality Reference](voice-modality-reference.md)) |
|
|
| `telephony` | Voice/phone routing channel | Service Cloud Voice, phone support (channel attachment is UI-only) |
|
|
|
|
> **Voice agents** use `connection customer_web_client:` (ECv2) as the authored voice surface. `telephony` (Service Cloud Voice) is a real channel, but attaching a phone number / SIP endpoint is a UI-only step — see the voice reference. Do **not** author a `connection voice:` block; it does not exist.
|
|
|
|
**CRITICAL**: Values like `"queue"`, `"skill"`, `"agent"` for `outbound_route_type` cause validation errors!
|
|
|
|
### Escalation Action
|
|
|
|
```agentscript
|
|
actions:
|
|
transfer_to_human: @utils.escalate
|
|
description: "Transfer to human agent"
|
|
```
|
|
|
|
### Prerequisites for Escalation
|
|
|
|
1. Omni-Channel configured in Salesforce
|
|
2. Omni-Channel Flow created and deployed
|
|
3. Connection block in agent script
|
|
4. Messaging channel active (Enhanced Chat, etc.)
|
|
|
|
---
|
|
|
|
## Voice-Safe Action Authoring
|
|
|
|
When an agent has a `modality voice:` block (see [Voice Modality Reference](voice-modality-reference.md)), its actions need extra care: the planner **narrates** action descriptions and parameter names, and action outputs get spoken aloud. An action authored for a chat agent can embarrass a voice deployment. Apply these rules to every action a voice agent can invoke. (Latency-specific action patterns — sync writes, bulky retrieval — live in [voice-latency-heuristics.md](voice-latency-heuristics.md).)
|
|
|
|
### 1. Descriptions must be voice-safe
|
|
|
|
The planner reads (verbatim or paraphrased) the action `description` when it narrates its plan. Bad descriptions produce bad narration.
|
|
|
|
- **Good:** `"Look up an account by phone number or account number. Returns the account name, status, and balance."`
|
|
- **Bad:** `"AccountLookup runs SOQL against Account where Phone__c OR External_Id__c matches identifier; returns Account.Id, Account.Name, Account.Status__c."`
|
|
|
|
Rules: plain English, no SOQL, no field API names (`__c`), no backticks; one or two sentences (what it does, then what it returns); if the action is slow, say so; never include example payloads.
|
|
|
|
### 2. Parameter names must survive being spoken
|
|
|
|
The model sometimes narrates parameter names to itself ("I'll need your account eye dee"). Avoid names that break in speech.
|
|
|
|
- **Bad:** `accountId` → "account eye dee"; `custId`; `phone_num_e164`; `dt_from`.
|
|
- **Good:** `phone_number`, `account_number`, `email`, `order_number`, `zip_code`.
|
|
|
|
Rules: full words, snake_case, no cryptic suffixes (`__c`, `_pk`, `_ref`); prefer "number" over "id" for anything the caller says out loud.
|
|
|
|
### 3. Enumerate small value sets in the description
|
|
|
|
When a parameter has a small closed set (≤ ~10) of valid values, list them in the input `description` — Agent Script has no `enum` or `pattern` input attribute (the supported input properties are `description`, `label`, `is_required`, `is_user_input`, `complex_data_type_name`; see "Input Properties" above). Naming the values inline lets the planner route the caller's utterance to a canonical value in one shot instead of asking a clarifying question, and the model maps synonyms:
|
|
|
|
```agentscript
|
|
inputs:
|
|
priority: string
|
|
description: "Case priority — one of: low, medium, high, urgent. Map the caller's words to the closest value."
|
|
```
|
|
|
|
Keep the listed values short and lowercase. For open-ended inputs (names, order numbers) describe the expected format in words (e.g. "a 6-digit order number") rather than trying to enforce it — the planner has no format-validation attribute.
|
|
|
|
### 4. Wrap internal IDs in a lookup step
|
|
|
|
If an action needs an internal ID (record ID, case number, 18-char Salesforce ID) the caller doesn't know, **don't ask the caller for it.** Add or reuse a lookup action that resolves from what the caller *can* say (phone, email, order number, account name) to the internal ID, and instruct the agent to call the lookup first. Never speak an internal ID back to the caller.
|
|
|
|
### 5. Voice-friendly error shapes
|
|
|
|
When an action fails, its error goes through the model's next spoken turn. Return structured errors, not raw exceptions:
|
|
|
|
```yaml
|
|
success: false
|
|
error_code: "account_not_found"
|
|
message: "No account matched the input."
|
|
suggested_next_step: "Ask the caller to spell their last name or provide their phone number."
|
|
```
|
|
|
|
A `suggested_next_step` field gives the model a scripted recovery path. Never surface stack traces or SOQL faults — they get read aloud. Pair with the empty-result fallback instruction rule in the voice reference.
|
|
|
|
### 6. Ack phrases for slow actions
|
|
|
|
Any action over ~800ms (SOQL, external HTTP, chained callouts, retrieval) feels slow on voice. The fix is **instructional**, not in the action: add a per-action filler-phrase directive in the agent's instructions (see the ack-phrase rule in [voice-modality-reference.md](voice-modality-reference.md)).
|
|
|
|
### 7. Confirm before state-changing actions
|
|
|
|
Any action that changes customer-visible state (`update_address`, `cancel_subscription`, `schedule_appointment`, `submit_payment`) must be paired with an instruction-level read-back-and-confirm rule. Payment and cancellation confirmations may have legally required phrasing — **flag those for a human**, don't auto-author them.
|
|
|
|
> **What NOT to auto-change:** parameter names on actions called from other systems (breaking change), fixed value sets that are wire-level contracts with a downstream system, and legal confirmation phrasing on payment/cancellation actions. Surface these with a suggested rewrite and let a human decide.
|
|
|
|
---
|
|
|
|
## Cross-Skill Integration
|
|
|
|
### Orchestration Order for API Actions
|
|
|
|
When building agents with external API integrations, follow this order (each step names the skill that owns it):
|
|
|
|
1. **`integration-connectivity-generate`** → Named Credential + External Service
|
|
2. **`platform-apex-generate`** → `@InvocableMethod` (if custom logic)
|
|
3. **`automation-flow-generate`** → Flow wrapper (HTTP Callout / Apex)
|
|
4. **`platform-metadata-deploy`** → Deploy all metadata to org
|
|
5. **`agentforce-generate`** (this skill) → Agent with `flow://` target
|
|
6. **`platform-metadata-deploy`** → Publish (`sf agent publish authoring-bundle`)
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
| Issue | Cause | Solution |
|
|
|-------|-------|----------|
|
|
| `Tool target 'X' is not an action definition` | Action not defined in subagent `actions:` block, or target doesn't exist in org | Define action with `target:` in subagent-level `actions:` block; ensure Apex class/Flow is deployed |
|
|
| `invalid input 'X'` or `invalid output 'X'` | I/O name doesn't match `@InvocableVariable` field name in Apex | Use exact field names from the Apex wrapper class (case-sensitive) |
|
|
| `Internal Error` with inputs-only action | Action has `inputs:` but no `outputs:` block | Add `outputs:` block — the server-side compiler requires it (see known-issues.md Issue 15) |
|
|
| `Internal Error` with bare @InvocableMethod | Apex uses `List<String>` without `@InvocableVariable` wrappers | Refactor Apex to use wrapper classes with `@InvocableVariable` annotations |
|
|
| `apex://` target not found | Apex class not deployed or missing `@InvocableMethod` | Deploy class first, ensure it has `@InvocableMethod` annotation |
|
|
| Flow action fails | Flow not active or not Autolaunched | Activate the Flow; ensure it's Autolaunched (not Screen) |
|
|
| API action timeout | External system slow | Increase timeout, add retry logic |
|
|
| Permission denied | Missing Named Principal access | Grant Permission Set |
|
|
|
|
### Debugging Tips
|
|
|
|
1. **Check deployment status:** `sf project deploy report`
|
|
2. **Test Flow independently:** Use Flow debugger in Setup with sample inputs
|
|
3. **Check agent logs:** Agent Builder → Logs
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [patterns-by-requirement.md](patterns-by-requirement.md) — Requirement-to-pattern selection
|
|
- [action-prompt-templates.md](action-prompt-templates.md) — Prompt template invocation (`generatePromptResponse://`)
|
|
- [architecture-patterns.md](architecture-patterns.md) — Router-first architecture and transition mechanics
|
|
- [feature-validity.md](feature-validity.md) — Property validity by context (`@utils.transition` vs `target:` actions)
|