--- name: building-voice-asa-agent description: End-to-end playbook for building a voice Agentforce Service Agent (ASA) in a demo org — the full sequence from agent-user perm set, data seeding, system-context data flows, prompt template, agent bundle authoring, live-preview checkpoint, publish/activate, through inbound + per-channel escalation routing flows and the voice-channel Setup handoff. Ties together the system-context-flow, agent-user-perms, data-library, and escalation-flow skills into one ordered build. --- # Building a Voice ASA Agent — End-to-End Playbook ## What this skill is for This is the **spine** of a voice Agentforce Service Agent build. It orders the pieces and points to the deep-dive skills for each. Use it when the ask is "build a voice service agent that looks up a customer's records, opens/creates something, answers FAQs, and escalates to a human." Proven pattern (BWAM_ASA_Sean → ClaimSecure_ASA): **hub-and-spoke router, hardcoded `DemoContactId`, verification gate, prompt-template-over-PromptFlow for reads, autolaunched flow for writes, `@utils.escalate`, per-channel escalation flows, inbound omni-flow that hands the call to the bot.** ## Companion skills (read these for the details) - **`developing-agentforce`** — the authoritative agent lifecycle (generate bundle → write `.agent` → validate → preview → publish → activate). This playbook does NOT replace its Required Steps; it sequences the surrounding infra. - **`building-system-context-agent-data-flows`** — the read/write/verify flows (all `SystemModeWithoutSharing`), and the PromptFlow empty-results bug + fix. - **`scoping-agent-user-permissions`** — the agent-user perm set (object Read-only, VA/MA=0, full FLS). - **`connecting-agent-data-library`** — the GeneralFAQ subagent + shared flex template for knowledge/citations. - **`connecting-channels-to-asa`** — the full voice + messaging story: inbound omni-flow → ASA and escalation ASA → queue/skill/direct-to-rep, and exactly what binds in metadata vs. Setup. - **`replicating-omnichannel-routing-flows`** — the escalation RoutingFlows (voice + messaging) and channel binding. - **`building-contact-center-kpis-agentwork`** — AgentWork-driven KPIs (Accepted-by-Human with genuine-human gating, Abandoned, Speed-to-Answer) + the VoiceCall/MessagingSession custom fields. - **`calling-prompt-templates-in-flows`** — template↔flow wiring. - **`handling-locked-standard-objects`** — if the demo object is a locked industry object (e.g. Claim). ## Standing user requirements (apply on every build here) - **Prefix everything** with the demo name (agent, flows, template, perm set). - **All agent-action / prompt-template flows run system context** (`SystemModeWithoutSharing`). - **Agent user: no ViewAll/ModifyAll** on any object; object Read-only + full field FLS. - **Every new field on all page layouts with FLS read+write for all profiles.** - **Related-record access via the junction first** (e.g. Contact→Claim always through `ClaimParticipant`). - Always `--json`; always `--target-org ` (the global default is usually a different org). ## Two hard rules for actions (learned the hard way — do NOT violate) **RULE 1 — Prompt templates are for READING data ONLY. Never for writes.** A `generatePromptResponse://` action (prompt template + PromptFlow) may **only read/summarize** data. It must **never** create, update, or delete a record. The template runs in a generative/data-provider context, not a transactional one — a "write" expressed through a template either silently does nothing or behaves nondeterministically. - **Reads** (pension summary, claim status, "what are my AVCs") → prompt template over a system-context PromptFlow. ✅ - **Writes** (update AVC amount, purchase a buyback, verify a member, create a case) → a real **autolaunched Flow** (`flow://…`, `SystemModeWithoutSharing`, Active) that returns the result var. ✅ - If you find yourself wanting a prompt template to "confirm and save" — stop. That's a Flow. (Real OMERS bug: the member-verify step was a prompt template; it had to be rebuilt as an autolaunched Flow returning `VERIFIED`, with the agent setting `isVerified` on the flow's output.) **RULE 2 — Every agent action needs at least ONE input parameter. Zero-input actions are invalid.** An ASA action that declares **no** inputs is rejected by the platform — the Reset/Simulator reports *"The action `` is missing the input parameters…"* even though nothing is technically "missing." The fix is to give every action ≥1 declared, genuinely-passed input. - For a data action that conceptually needs no runtime input (it hardcodes the demo persona), add a required **`Input:ContactID`** wired end-to-end and bound to a hardcoded ASA variable: - `.agent` variables block: `DemoContactId: mutable string = ""` (visibility `Internal`). - `.agent` action `inputs:` — `"Input:ContactID": string` (`is_required: True`, `complex_data_type_name: "lightning__textType"`). - `.agent` reasoning binding — `with "Input:ContactID" = @variables.DemoContactId`. - Flex template `` — `apiName=ContactID`, `referenceName=Input:ContactID`, `definition=primitive://String`, `required=true`; plus a `` map `contactId` = `{!$Input:ContactID}`. - Backing PromptFlow — an `isInput` String var `contactId`. (The subflow can still hardcode the persona; the input exists to satisfy the ≥1-input contract and is really declared/passed, so demo behavior is unchanged.) - **Adding an input to a Published flex template must be `required=false` on the template parameter side + bump `versionIdentifier`/`activeVersionIdentifier`** — see the `flex-template-input-add-required-false` memory. - **Flow actions** use **bare** input names (no `Input:` prefix) matching the flow's `isInput` var names, and output the flow's output var (not `promptResponse`). ## Loading / progress messages on actions (what the caller hears while an action runs) An action can show a short "please hold…" message while it runs, so the caller isn't left in dead air — essential for **voice** (a flow/prompt-template call can take a few seconds). The OOB knowledge action already does this (`"Getting answers"`); add the same to **every custom action you build**. **In the `.agent`, two attributes at the ACTION-DEFINITION level** — siblings of `label`/`description`/`target`/`inputs`/`outputs`, placed at the END of the action's definition block (after `outputs:`): ``` actions: Get_Program_Account: label: "Get Program Account" description: "…" target: "generatePromptResponse://Synchrony_Get_Program_Account" inputs: "Input:ContactID": string … outputs: promptResponse: string … filter_from_agent: False include_in_progress_indicator: True progress_indicator_message: "Please hold while I check on your program account" ``` - `include_in_progress_indicator: True` turns the indicator on for this action; `progress_indicator_message` is the exact text the agent surfaces while the action runs. Both are **action-level** — do NOT put them inside `inputs:`/`outputs:` or on a subagent. - Works for both `flow://` (write/verify) and `generatePromptResponse://` (read) actions. Verified on Synchrony_ASA v4: `Get_Program_Account` → *"Please hold while I check on Program Account"*, `Request_Credit_Limit_Increase` → *"Please hold while I request Credit limit increase"*. - Write the message in the agent's **first-person voice** ("Please hold while I …", "One moment while I …") — it is spoken/shown to the customer, not an internal label. Keep it short. - The OOB `AnswerQuestionsWithKnowledge` block is the reference shape (it carries `require_user_confirmation: False`, `include_in_progress_indicator: True`, `progress_indicator_message: "Getting answers"`, then `source:`/`target:`). **Decomposed-bundle equivalent (if you hand-edit the `GenAiPlannerBundle` XML instead of the `.agent`):** the per-topic/per-action `` block carries `true` and `…` (that's what the `.agent` attributes compile to). > **Preserve-on-publish gotcha.** Each `sf agent publish` cuts a NEW numbered planner bundle (`_v1`, `_v2`, …); the live content is always the highest-numbered one. If the user added loading messages (or any edit) in the Agent Builder UI, those live ONLY in the org's current bundle — **re-publishing from your older local `.agent` will silently overwrite them.** Before any re-publish, retrieve the live top-numbered `GenAiPlannerBundle:_vN`, base64-decode its `agentScript/*.agent`, and reconcile your local source first. (Retrieving the pinned `_v1` name gives you the stale original, not the live version.) ## Build sequence **0. Design + Agent Spec (HARD GATE).** Draft the Agent Spec (identity, variables incl. hardcoded `DemoContactId` + linked `VoiceCallId @VoiceCall.Id`, subagent graph, each action's target/inputs/outputs, verification factors, modality/connections). **Save it as a file and STOP for explicit user approval.** No `.agent`/flow code before approval. **1. Agent-user perm set.** Build + deploy + assign per `scoping-agent-user-permissions`. Do this early so previews have access. **2. Seed demo data.** Anonymous Apex creating the records the agent will read (and their junction rows — e.g. Claim + `ClaimParticipant` with `Roles`). Capture Ids. If the object is a locked standard object, pack structured detail into a long-text field (`handling-locked-standard-objects`). **3. Read action.** Author + deploy the **system-context subflow** (privileged SOQL) + the **thin PromptFlow wrapper** + the **flex prompt template**, per `building-system-context-agent-data-flows`. Test the template resolves against the seeded persona. **This is where the empty-results bug shows — verify real rows come back.** **4. Write + verify actions.** Author + deploy the autolaunched **create/update flow** and the **verify-identity flow** (both `SystemModeWithoutSharing`, Active). Smoke-test create returns a real record number; verify returns true for the correct factors, false otherwise. **5. (Optional) FAQ / data library.** Add the GeneralFAQ subagent reusing a published flex template per `connecting-agent-data-library`. **6. Author the agent bundle.** ```bash sf agent generate authoring-bundle --json --no-spec --name "