- **Planned (placeholder):** "The `confirm_booking` action needs an Apex class `BookingConfirmer` that accepts reservation_id (string), guest_name (string), and returns confirmation_number (string), booking_date (date)." This is a not-yet-implemented requirement.
- **Existing (implemented):** "The `fetch_weather` action uses Apex class `WeatherService`, invoked via `apex://WeatherService`. Accepts dateToCheck (date), returns maxTemp/minTemp (number)." This documents current implementation.
**Creation (sparse).** Purpose, outcomes, use cases, and planned notes about action implementations ("this action needs an Apex class that accepts X, returns Y"). No full flowchart yet.
**Build (filled).** Flowchart added with transition types labeled. Action implementations mapped (existing implementations identified with filenames, missing implementations stubbed with protocols and I/O specs). Add variables and deterministic controls only where required and justified.
**Comprehension (reverse-engineered).** Starting from an existing `.agent` file, produce a complete Agent Spec by parsing subagents, tracing transitions, analyzing actions, and documenting state. This is the "what does this agent do?" output.
**Diagnosis (reference).** Compare actual runtime behavior against the Agent Spec to find where intent and implementation diverge.
### Agent Spec Template
Use the starter spec template at `assets/agent-spec-template.md` for new agents.
**Resolve as many questions as possible from available context before asking the human.** Scan existing code, project metadata, prior conversation, and any provided requirements. Only surface questions the human must answer — never forward this list verbatim.
- What custom objects exist in the project? Scan `objects/` for `.object-meta.xml` files. Check relationships (lookup, master-detail) between objects — related objects often contain data the agent should expose even when not explicitly mentioned in the prompt.
**⚠️ MANDATORY: Run these checks immediately after determining the agent type during discovery.** Do not proceed to subagent architecture or code generation until the environment is validated.
Posture guidance is separate from architecture. Read [Posture and Determinism](posture-and-determinism.md) to choose subagent posture (scripted, mixed, agentic) based on requirements and observed failures.
1. Confirm the file normally omits `access.default_agent_user`. If the generated boilerplate includes an `access` block, remove it along with any MessagingSession linked variables and escalation subagent.
sf data query --json -q "SELECT Username FROM User WHERE Profile.UserLicense.Name = 'Einstein Agent' AND IsActive = true LIMIT 5"
```
**If results are returned:** Ask which username to use. Record choice in the Agent Spec Configuration section. Verify permissions per [Agent User Setup & Permissions](agent-user-setup.md).
**If no results are returned:** STOP. Do NOT invent a username. Ask if you should create a new user, then read [Agent User Setup & Permissions](agent-user-setup.md) for user creation instructions.
**WRONG:** Fabricating a username when query returns nothing
**Escalation Subagents.** Hand off to a human via `@utils.escalate`. This is a permanent exit — the user leaves the agent for a support channel (phone, email, chat with a human). Once triggered, the agent session ends. The escalation action does NOT return.
```agentscript
subagent escalation:
reasoning:
actions:
escalate: @utils.escalate
description: "Connect with a human agent"
```
### Single-Subagent vs. Multi-Subagent
Decide this before choosing an architecture pattern.
Use **single-subagent** if:
- The agent handles one domain only (FAQ, weather checker, status lookup)
- All interactions naturally stay in the same context
Default to the smallest architecture: one `start_agent <domain>:` execution
block and zero `subagent` blocks. Do not create an `agent_router` that merely
transitions to one domain. Add a boundary only when objective, instructions,
actions, authority, or escalation behavior changes and cannot remain coherent
in the current scope. Use
router-first (`start_agent agent_router`) when multiple genuine domains require
current-intent classification. Treat linear flow as workflow-local external
ordering, not as the default shape for a conversation.
Use [Patterns by Requirement](patterns-by-requirement.md) to choose the right
pattern for the scenario. Use [Architecture Patterns](architecture-patterns.md)
for detailed mechanics and migration guidance.
**Router-First Architecture.** One central router (`start_agent agent_router`) transitions to specialized domain subagents. Subagents may transition directly to other subagents when the workflow calls for it, or return to router when reclassification is needed. Use when the agent handles multiple distinct domains that don't naturally flow together.
go_next: @utils.transition to @subagent.verification
subagent verification:
reasoning:
actions:
go_next: @utils.transition to @subagent.details_gathering
subagent details_gathering:
reasoning:
actions:
go_next: @utils.transition to @subagent.confirmation
```
**Escalation Chain.** Tiered support where each level has increasing capabilities. First-level resolves common issues with basic actions; second-level has access to more powerful actions or broader authority; final level escalates to a human. Use when support difficulty varies and you want to resolve simple issues quickly without involving higher tiers.
```agentscript
subagent level_1_support:
reasoning:
instructions: | Try to resolve the issue using the FAQ and basic troubleshooting.
actions:
check_faq: @actions.search_faq
escalate: @utils.transition to @subagent.level_2_support
subagent level_2_support:
reasoning:
instructions: | You have access to account tools. Try to resolve before escalating.
actions:
lookup_account: @actions.get_account_details
modify_account: @actions.update_account
escalate_to_human: @utils.escalate
```
**Verification Gate.** A security or permission check before allowing access to protected subagents. The gate validates the user, then transitions to the protected subagent or denies access.
Real agents often combine patterns. A router-first agent may use a verification gate before protected subagents. A linear flow may include escalation exits at each stage. When composing, each subagent still serves exactly one role (domain, guardrail, or escalation) — the architecture pattern determines how they connect.
**Apex**: Only **invocable Apex classes** work. A regular Apex class, even if it has public methods, will not work. Invocable classes use two key annotations:
`@InvocableMethod` marks the entry point. Its attributes: `label` (human-readable name), `description` (what the method does). Read these when comprehending existing action implementations.
> ⚠️ **An Apex class can only have ONE `@InvocableMethod`.** If you need multiple actions, create separate classes — one per action.
`@InvocableVariable` marks each input and output field on the inner Request/Result classes. Its attributes: `label` (human-readable field name), `description` (what the field represents), `required` (whether the field must be provided). Use these to build action input/output definitions.
```apex
// WRONG — regular class, not invocable
public class WeatherFetcher {
public static String getWeather(String date) { ... }
}
// RIGHT — invocable class with annotated I/O (multiline annotations)
public class WeatherFetcher {
public class Request {
@InvocableVariable(
label='Date'
description='Date to check weather for'
required=true
)
public Date dateToCheck;
}
public class Result {
@InvocableVariable(
label='Max Temp'
description='Maximum temperature in Fahrenheit'
)
public Decimal maxTemp;
@InvocableVariable(
label='Min Temp'
description='Minimum temperature in Fahrenheit'
)
public Decimal minTemp;
}
@InvocableMethod(
label='Fetch Weather'
description='Gets weather forecast for a given date'
)
public static List<Result> getWeather(List<Request> requests) { ... }
> **One `@InvocableMethod` per Apex class — one class per action.** Salesforce permits **only one** `@InvocableMethod` in a given Apex class. The `apex://` target therefore names the **class**, not a method: use `apex://ClassName` — never `apex://ClassName.methodName`. Each distinct Apex-backed action MUST point at its **own** class.
>
> A common mistake is to treat one Apex class as a namespace for several related actions:
> ```agentscript
> # WRONG — 5 actions sharing one class (won't compile: >1 @InvocableMethod per class)
> The `ClassName.method` shape *looks* like ordinary OOP and invites treating one class as a home for several actions. The **verified** failure mode is the shared class: a single `CaseIntelligence` class carrying multiple `@InvocableMethod`s fails Apex compilation with `Only one method per type can be defined with: InvocableMethod`, which cascades into failed deploy, failed publish, and no grounded action calls at runtime (observed in the `enterprise-use-cases` eval run). Whether the `.method` **suffix in the target string itself** breaks resolution or is simply ignored by the runtime is not independently confirmed here — but authoring it invites the shared-class pattern above, so treat `apex://ClassName` (no suffix) as the rule.
> ```agentscript
> # RIGHT — one class per action, distinct class names, no method suffix
> When several actions are conceptually related, give each its own class with a shared prefix (e.g. `CaseIntelligence…`) rather than sharing one class. Never emit two `apex://` targets that resolve to the same class name.
**Flows**: Only **autolaunched Flows** work. Screen Flows, record-triggered Flows, and schedule-triggered Flows will not work. The Flow must start only when explicitly invoked.
Wire with: `target: "flow://FlowApiName"`
**Prompt Templates**: Salesforce Prompt Templates (custom or industry-specific).
Wire with: `target: "prompt://TemplateName"` (short form). The long form `generatePromptResponse://TemplateName` also works but prefer the short form.
Read `sfdx-project.json` and look at the `packageDirectories` array — each entry's `path` field tells you where source files live (typically `force-app/main/default/`).
**Finding invocable Apex:** Search `classes/` for files containing `@InvocableMethod`. For each match, read the class to extract the `@InvocableVariable` annotations on its inner `Request` and `Result` classes — these define the action's input and output contract. Pay attention to the `@InvocableVariable` types: they map to Agent Script types (`String` → `string`, `Boolean` → `boolean`, `Decimal` → `number`, `Integer` → `integer`, `Date` → `date`, `Datetime` → `datetime`). See the full type mapping table in "Connecting Existing Actions to Action Definitions" below.
**Finding autolaunched Flows:** Search `flows/` for `.flow-meta.xml` files. Read each file and check the `<processType>` element. Only `AutoLaunchedFlow` is valid for actions. Examine the `<variables>` elements to identify inputs (`isInput=true`) and outputs (`isOutput=true`) with their data types.
**Finding Prompt Templates:** Search `promptTemplates/` for template metadata files. Review the template's input variables and output format.
**Finding External Services:** Search `externalServiceRegistrations/` for `.externalServiceRegistration-meta.xml` files. These represent registered external APIs (REST endpoints). Check the schema for available operations, inputs, and outputs. Wire with `target: "externalService://ServiceName"`.
**Finding Standard Invocable Actions:** These are platform-provided actions (e.g., `sendEmail`, `chatterPost`). Query the org: `sf api request rest --json "/services/data/v63.0/actions/standard" -o <org-alias>` to list all available standard actions. Wire with `target: "standardInvocableAction://actionName"`.
Each `@InvocableVariable` on the request class becomes an action input; each on the result class becomes an output. The `target` field points to the existing action.
**Critical: Input and output names must exactly match the Apex `@InvocableVariable` field names, character-for-character.** If the Apex field is `dateToCheck`, the Agent Script input must be `dateToCheck` — not `date_to_check`, not `DateToCheck`. The platform validates these names at publish time; mismatches cause publish failures.
```agentscript
# WRONG — snake_case doesn't match the Apex field names
subagent orders:
actions:
check_order: @actions.check_order
target: "apex://OrderLookup"
inputs:
order_id: string # Apex field is orderId, NOT order_id
outputs:
order_date: date # Apex field is orderDate, NOT order_date
# RIGHT — names match Apex @InvocableVariable field names exactly
subagent orders:
actions:
check_order: @actions.check_order
target: "apex://OrderLookup"
description: "Look up order status"
inputs:
orderId: string # matches Request.orderId
outputs:
status: string # matches Result.status
filter_from_agent: False
amount: number # matches Result.amount (Decimal → number)
filter_from_agent: False
orderDate: date # matches Result.orderDate (Date → date)
filter_from_agent: False
```
#### Primitive Agent Script Type Mapping
Primitive types (individual and arrays) require only an Agent Script type.
`integer`, `long`, and `datetime` are valid in action I/O only — not valid for agent variables.
#### Complex Agent Script Type Mapping
Complex types (Apex classes, SObject records) require both `object` or `list[object]` AND `complex_data_type_name`. **Correct value depends on action `target`, not data shape.**
Each output requires a visibility decision: Should the agent display this value to the user, or keep it internal for routing and logic?
The `filter_from_agent` property controls this. The name is inverted — `True` means the output is **filtered out** (hidden from the user), `False` means it is **visible**.
Capture this decision during spec creation using the **Visible to User?** column in the Agent Spec template. Wrong choice causes agent to retrieve data but never display it.
| `filter_from_agent` | User sees the value? |
|---|---|
| `False` | Yes — displayed in the agent's response |
| `True` | No — available to the LLM for reasoning but not shown |
**Show** outputs the user asked for: records, summaries, computed results, status messages.
**Hide** outputs that are internal plumbing: success flags (`isSuccess`, `hasData`), IDs consumed by downstream actions, routing signals used in `available when` gates.
```agentscript
get_properties:
target: "apex://PropertyQueryService"
outputs:
properties: list[object]
filter_from_agent: False # Desired info. Show to user
**⚠️ Invalid action implementations (non-autolaunched Flow, non-invocable Apex) may pass validation and simulation-mode preview. The failure surfaces at deploy or as cryptic runtime errors in live mode.** Always verify implementation type before wiring.
When no implementation exists for an action, stub it as an invocable Apex class. Always use Apex for stubs — do not attempt to hand-craft Flow XML or Prompt Template metadata.
Second, find the default package directory by reading `sfdx-project.json` at the project root and locating the `packageDirectories` entry where `"default": true`. The `path` value in that entry is the package root (commonly `force-app`, but not guaranteed).
Third, generate an empty Apex class using the following command:
```bash
sf template generate apex class --json --name InvoiceFetcher --output-dir <PACKAGE_DIR>/main/default/classes
```
This creates both the `.cls` and `.cls-meta.xml` files. Do not create test classes for stubs.
**Stub vs. functional implementation.** If the prompt implies data access ("grounded in X data," "query Y records," "look up Z"), write functional Apex with bulkified SOQL per `assets/invocable-apex-template.cls`. Prefer static SOQL. If dynamic SOQL is required, NEVER append `WITH USER_MODE` to the query string — use `Database.query(q, AccessLevel.USER_MODE)` instead. See *Dynamic SOQL* in the template.
If the prompt does not imply data access, or if the action's data requirements are unclear, write a minimal stub — hardcoded return values only. Do not add SOQL, conditional logic, or complex inner class structures to minimal stubs.
Fourth, replace the generated class body with a stub. Use multiline `@InvocableVariable` annotations per `assets/invocable-apex-template.cls`:
```apex
public class InvoiceFetcher {
public class Request {
@InvocableVariable(
label='Invoice ID'
description='ID of the invoice to fetch'
required=true
)
public String invoiceId;
}
public class Result {
@InvocableVariable(
label='Invoice Amount'
description='Total amount of the invoice'
)
public Decimal invoiceAmount;
@InvocableVariable(
label='Due Date'
description='Payment due date'
)
public Date dueDate;
@InvocableVariable(
label='Status'
description='Current invoice status'
)
public String status;
}
@InvocableMethod(
label='Fetch Invoice'
description='Retrieves invoice details by ID'
)
public static List<Result> fetch(List<Request> requests) {
// Stub — return minimal hardcoded values to unblock deployment
Result r = new Result();
r.status = 'stub';
return new List<Result>{ r };
}
}
```
ALWAYS deploy one class at a time to isolate compile errors:
ALWAYS fix deploy errors BEFORE generating and deploying the next stub.
---
## 6. Transition Patterns
When creating a new agent, label every transition in your Agent Spec's Subagent Map as either **handoff** or **delegation**. When analyzing an existing agent, classify each transition to determine whether context flow matches the design intent.
### Handoff: Permanent Transition
A handoff is a one-way transition. The user moves to a new subagent and control never returns to the original subagent. Handoffs use `@utils.transition to` in `reasoning.actions`.
Use handoff when:
- Switching modes (preview → confirm → complete)
- Entry point routing (agent_router → domain subagents)
Delegation hands control to another subagent using `@subagent.X` in `reasoning.actions`. It signals *intent* to return, but the return does not happen automatically — the delegated subagent must explicitly transition back to the caller.
Use delegation when:
- One subagent needs advice from a specialist and should continue after
- Reusable sub-workflows (e.g., identity verification called from multiple subagents)
- A subagent needs to temporarily visit another subagent, then resume
**Critical Rule:** `@subagent.X` delegates control. It does NOT implement call-return semantics. If you want the user to return to the calling subagent, code an explicit `transition to @subagent.<caller>` in the delegated subagent. Without it, the next user utterance falls through to `agent_router`.
Instructions are suggestions the LLM *may* follow. Gates and guards are enforced by the runtime and *cannot* be bypassed. For every requirement, choose the right flow control type.
### Classifying Flow Control Requirements
**Deterministic flow control** — the runtime enforces it. Use when the requirement is non-negotiable:
- Security: "only admin users can access this"
- Financial: "never approve transactions above $10,000 without human review"
WRONG: Security rule as an instruction (LLM can ignore it)
```agentscript
subagent admin_panel:
reasoning:
instructions: ->
| Only respond if the user is an admin.
If they are not an admin, tell them access is denied.
```
The LLM may comply, or it may not — instructions are suggestions. The RIGHT approach uses a `before_reasoning` guard that the runtime enforces before the LLM is ever invoked. See Section 8 for all gating mechanisms.
### Writing Effective Instructions
Two factors govern subjective control effectiveness: instruction ordering and grounding.
**Instruction Ordering.** The runtime resolves instructions top-to-bottom — evaluating `if/else` blocks and expanding template expressions — before the LLM sees the result. The resolved text becomes the LLM's prompt. Put post-action checks first, data references next, dynamic conditional text last.
**Grounding.** The platform's grounding service validates that the agent's response matches action output data. Paraphrasing or embellishing may cause grounding failures. In the example below, `event_date` is exact action output consumed by the response.
- Use specific values: `"The event is on {!@variables.event_date}"` grounds reliably; `"The event is next week"` may not.
- Avoid transforming values: return `"Tuesday"` as-is, not `"day after Monday"`.
- Avoid embellishment instructions: `"Respond like a pirate"` increases grounding risk — embellished content has no output to ground against.
Grounding validation requires **live mode preview** (`sf agent preview --use-live-actions --json`). Simulated mode preview generates fake outputs, so grounding has nothing real to validate against.
**Naming output fields in post-action instructions.** ALWAYS specify which output fields to include in text responses. Generic instructions like "present the results clearly" let platform-injected tools hijack the response. EXAMPLE: The LLM calls `show_command` instead of composing text, producing generic "Here are the results:" message wrapper with raw structured data. This can corrupt session state, causing subsequent turns to fail with generic "something went wrong" message. Naming output fields steers the LLM toward composing a direct text response. This reliably grounds the response because it maps closely to action output values. ALWAYS include `Do NOT use the show_command tool. Always compose your response as direct text.` in post-action instructions. See *Anti-Patterns* in the *Core Language* reference for full WRONG/RIGHT example.
### Post-Action Behavior
When an action completes without triggering a transition, the subagent stays active. The runtime re-evaluates the entire subagent — resolving instructions top-to-bottom again with updated variables, then passing the new prompt to the LLM. The LLM may call the same action again. To prevent unwanted loops, see Section 9 (Action Loop Prevention).
---
## 8. Gating Patterns
### `available when` — Action Visibility Gate
An action marked `available when <condition>` is hidden from the LLM when the condition is false. The LLM cannot call an unavailable action.
The `before_reasoning` block runs before the LLM is invoked. Code here executes every time the subagent is entered. The LLM never sees it, cannot override it, and cannot skip it.
When a gate subagent (e.g., username collection) uses `after_reasoning` to transition into a routing subagent, both subagents process in the **same user turn**. The router receives the user's original message — the one that satisfied the gate — not a fresh utterance.
This means if the user said "My username is alex" and the gate transitions to a subagent router, the router's reasoning fires against "My username is alex." Since that message doesn't match any domain subagent, the router may misclassify it (e.g., routing to `off_topic`).
An action loop occurs when the LLM calls the same action repeatedly without new user input. Three things combine to cause loops:
- **No `available when` gate.** An action without an `available when` condition appears in the LLM's context every reasoning cycle. There is no mechanism that automatically hides an action after it executes — if you don't gate it, it stays visible indefinitely.
- **Variable-bound input.** When you bind an input to a variable (`with param = @variables.x`), the action is "ready to go" every cycle — the LLM doesn't need to extract values from the conversation. It can invoke the action with zero friction.
- **No post-action instructions.** The instructions don't tell the LLM what to do after the action completes, so it may call the action again.
**WRONG: All three loop conditions present**
```agentscript
subagent events:
reasoning:
instructions: ->
| Use the {!@actions.check_events} action to find events.
actions:
check_events: @actions.check_events
with interest = @variables.guest_interest # Variable-bound input
```
No gate, variable-bound input, no post-action guidance. The LLM can call `check_events` every cycle.
Tell the LLM to stop calling the action after receiving results. Name the specific output fields the LLM should include in its text response — vague instructions like "present the results" let platform tools hijack the response (see Section 7, Grounding).
```agentscript
subagent events:
reasoning:
instructions: ->
| Use {!@actions.check_events} to find events matching the guest's interest.
After you receive the results, write the data directly in your text response.
For each event, include the eventName, eventDate, and location values from
the action output. Use the exact values returned — do NOT paraphrase or round.
Do NOT call the action again — you already have the information you need.
Do NOT use the show_command tool. Always compose your response as direct text.