使用方法
- 复制安装命令,在自己的智能体环境安装前先检查源码包。
- 从原始文档核对所需工具、凭据和支持的宿主。
- 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/sshvarzman-salesforce/claude-skills --skill routing-inbound-conversations-to-asa开始前需了解
- 安装不包含模型订阅、API 额度或访问文件的权限。
- 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。
Routing inbound conversations to an Agentforce Service Agent (ASA)
What this skill is for
An inbound RoutingFlow is what Omni-Channel runs when a new VoiceCall or MessagingSession arrives, to decide who picks it up. To route the conversation to an Agentforce Service Agent (ASA — the bot/copilot answers first, before any human), the flow's routeWork action uses `routingType=Copilot` and points at the agent's BotDefinition — plus a REQUIRED fallback queue.
This is the mirror image of the replicating-omnichannel-routing-flows skill:
- That skill = escalation (agent/bot → human queue),
routingType=QueueBased, invoked via@utils.escalate. - This skill = inbound (channel → ASA agent),
routingType=Copilot, invoked by the channel's inbound-flow assignment.
Two flows, one per channel: a Voice variant (VoiceCall, sfdc_phone, links VoiceCall.Contact__c) and a Messaging variant (MessagingSession, sfdc_livemessage, links MessagingEndUser.ContactId).
Ready-to-edit, verified templates ship in assets/:
assets/inbound-asa-voice.flow-meta.xmlassets/inbound-asa-messaging.flow-meta.xml
Both are ground-truthed from the live, Active CitiBank flows CitiBank_ASA_Inbound_Voice / CitiBank_ASA_Inbound_Messaging.
THE headline lesson: routeWork to a copilot still REQUIRES a fallback queue
The single most common failure: authoring a routingType=Copilot routeWork with only the copilot target and no queue. It looks logically complete — you're routing to the agent, why name a queue? — but the platform requires a fallback `queueId` + `queueLabel` anyway. Omni needs a queue to fall back to when the agent can't take the work (capacity, offline, decline, timeout). Without it, routing fails.
So a copilot routeWork carries both:
| Purpose | Params |
|---|---|
| Primary target (the ASA agent) | routingType=Copilot, copilotId, copilotLabel |
| Required fallback | queueId (a real 00G… Group.Id), queueLabel |
| Channel | serviceChannelDevName, serviceChannelId, serviceChannelLabel |
| Work record | recordId |
If you copy an escalation flow and just "swap QueueBased for Copilot," you'll drop the queue and it breaks. Keep the queue.
The routeWork action — copilot routing, per channel
Voice (sfdc_phone)
<inputParameters><name>recordId</name><value><elementReference>recordId</elementReference></value></inputParameters>
<inputParameters><name>serviceChannelLabel</name><value><stringValue>Phone</stringValue></value></inputParameters>
<inputParameters><name>serviceChannelDevName</name><value><stringValue>sfdc_phone</stringValue></value></inputParameters>
<inputParameters><name>routingType</name><value><stringValue>Copilot</stringValue></value></inputParameters>
<inputParameters><name>copilotLabel</name><value><stringValue>CitiBank ASA</stringValue></value></inputParameters>
<inputParameters>
<name>serviceChannelId</name>
<value><setupReference>sfdc_phone</setupReference><setupReferenceType>ServiceChannel</setupReferenceType></value>
</inputParameters>
<inputParameters>
<name>copilotId</name>
<value><setupReference>CitiBank_ASA</setupReference><setupReferenceType>BotDefinition</setupReferenceType></value>
</inputParameters>
<!-- REQUIRED fallback queue -->
<inputParameters><name>queueLabel</name><value><stringValue>General Voice</stringValue></value></inputParameters>
<inputParameters><name>queueId</name><value><stringValue>00GKc000000wPROMA2</stringValue></value></inputParameters>Notable: on the voice flow the agent is expressed as a `setupReference` to `BotDefinition` (the agent's DeveloperName, e.g. CitiBank_ASA) and the channel as a setupReference to ServiceChannel. versionString = 2.0.0 on this action.
Messaging (sfdc_livemessage)
<inputParameters><name>recordId</name><value><elementReference>recordId</elementReference></value></inputParameters>
<inputParameters><name>serviceChannelId</name><value><stringValue>0N9Kc000000h7oJKAQ</stringValue></value></inputParameters>
<inputParameters><name>serviceChannelLabel</name><value><stringValue>LiveMessage</stringValue></value></inputParameters>
<inputParameters><name>serviceChannelDevName</name><value><stringValue>sfdc_livemessage</stringValue></value></inputParameters>
<inputParameters><name>routingType</name><value><stringValue>Copilot</stringValue></value></inputParameters>
<!-- REQUIRED fallback queue -->
<inputParameters><name>queueId</name><value><stringValue>00GKc000000wPRNMA2</stringValue></value></inputParameters>
<inputParameters><name>queueLabel</name><value><stringValue>Messaging</stringValue></value></inputParameters>
<inputParameters><name>copilotId</name><value><stringValue>0XxKc000000UW41KAG</stringValue></value></inputParameters>
<inputParameters><name>copilotLabel</name><value><stringValue>CitiBank ASA</stringValue></value></inputParameters>Notable difference: on the messaging flow copilotId and serviceChannelId are raw Id strings (0Xx… copilot config Id, 0N9… channel Id), NOT setupReferences. (0Xx is the messaging agent-config record, not the BotDefinition 0Xx→ query it, see below.) No versionString on this action.
Both patterns deploy and run. The voice setupReference form is more portable (resolves by dev name); the messaging raw-Id form is what Flow Builder writes for messaging. Either works as long as the Ids/dev names resolve in the target org.Full flow structure (both channels)
Start
→ resolve the phone to search on
Voice: Read_Voice_Call → decide (phone input? else VoiceCall.FromPhoneNumber)
Messaging: Load_Messaging_User (MessagingEndUser by input_record.MessagingEndUserId)
→ Load_Contact_by_Phone_Number actionType=findMatchingIndividuals, searchObject=Contact, searchFields=Phone
→ Loop_Search_Results over .contactIds → Load_Found_Contact (Contact by Id) → contact
→ Check_Found_Contact (contact != null?)
Found: link the Contact
Voice: recordUpdate VoiceCall.Contact__c = contact.Id (+ optional Customer_Demo__c stamp)
Messaging: assign MessagingEndUser.ContactId = contact.Id → Update_Messaging_User → stamp session
No: just stamp the conversation record (no contact link)
→ Route_to_ASA routeWork, routingType=Copilot, copilot target + REQUIRED fallback queue- Contact resolution is the dual-channel crux (same as everywhere): voice links the found Contact onto the custom `VoiceCall.Contact__c` lookup; messaging writes `MessagingEndUser.ContactId`, which surfaces downstream as `MessagingSession.EndUserContactId`. (See the
messaging-inbound-omniflow-patternandlinking-individuals-by-phone-in-flowsmemories/skills.) - `findMatchingIndividuals` is the standard phone→Contact matcher; loop its
.contactIdsand load the first. - The messaging flow's input variable is `input_record` (SObject MessagingSession) in addition to
recordId(String) — it readsinput_record.MessagingEndUserIdto find the MessagingEndUser. The voice flow only needsrecordId+ aphoneinput.
Build / fix procedure
- Find the ASA agent + its ids.
# BotDefinition dev name (voice setupReference form):
sf data query --target-org <ORG> --json -q "SELECT Id, DeveloperName, MasterLabel FROM BotDefinition WHERE DeveloperName='<Agent>'"For the messaging copilotId raw string, the value is the agent's messaging routing-config Id (0Xx…). The reliable way to get the exact ids/labels is to read an already-working inbound flow in the org (retrieve it) — or copy them from the voice flow's BotDefinition and let Flow Builder resolve. If unsure, author voice with the setupReference/BotDefinition form (portable) and mirror it for messaging.
- Find a fallback queue (REQUIRED).
sf data query --target-org <ORG> --json -q "SELECT Id, DeveloperName, Name FROM Group WHERE Type='Queue'"Pick the voice queue for the voice flow, the messaging queue for the messaging flow. Put its 00G… Id in queueId and its label in queueLabel. Do not omit this.
- Confirm the messaging ServiceChannel Id (messaging flow only):
sf data query --target-org <ORG> --json -q "SELECT Id, DeveloperName FROM ServiceChannel WHERE DeveloperName='sfdc_livemessage'"- Copy the matching asset into
flows/, rename to your flow's API name, and swap the<<...>>tokens. KeeproutingType=Copilot,serviceChannelDevName, and BOTH the copilot target and the fallback queue.
- Deploy Active:
sf project deploy start --metadata Flow:<YourFlowName> --target-org <ORG> --json- Wire the flow to the channel (Setup, not metadata for the assignment itself).
- Voice: the SCV/Omni voice channel's inbound flow is set in Setup (the voice channel → flow assignment). This is the flow that runs when a call lands.
- Messaging: the messaging channel (Embedded / MIAW / etc.) names its routing/inbound flow in the channel config. For metadata-managed channels the flow is referenced in the channel definition; otherwise set it in Setup.
In practice on this stack, the inbound-flow assignment per channel is Setup-side — deploying the flow Active does not by itself bind it; point the channel at it.
- Verify:
sf data query --target-org <ORG> --json -q "SELECT ApiName, ProcessType, IsActive FROM FlowDefinitionView WHERE ApiName='<YourFlowName>'"ProcessType=RoutingFlow, IsActive=true. Then place a test call / start a test chat and confirm it lands on the ASA agent, and that Contact__c / EndUserContactId got linked.
Gotchas
- Fallback queue is mandatory for `routingType=Copilot`. #1 mistake. The agent is the primary target; the queue is the required fallback. Keep both.
- Voice vs. messaging express the agent differently. Voice =
copilotIdassetupReference/BotDefinition; messaging =copilotIdas a raw0Xx…string. Don't force one form onto the other channel unless you've confirmed the id resolves. - `serviceChannelId`: voice uses a
setupReferencetoServiceChannel(sfdc_phone); messaging uses a raw0N9…channel Id string. Both alongsideserviceChannelDevName. - Messaging needs `input_record` (MessagingSession SObject) input, not just
recordId— it readsMessagingEndUserIdfrom it. - Contact link target differs:
VoiceCall.Contact__c(custom lookup) vsMessagingEndUser.ContactId(→ surfaces asMessagingSession.EndUserContactId). - No `triggerType` on Start — an inbound RoutingFlow has a bare Start with just a connector, same as escalation flows.
- `FlowDefinitionView` is a regular object — query it WITHOUT
--use-tooling-api(tooling API rejects it withINVALID_TYPE).
Relationship to other skills
- Escalation (agent → human queue):
replicating-omnichannel-routing-flows(routingType=QueueBased,@utils.escalate). - Phone→Contact matching detail:
linking-individuals-by-phone-in-flows. - Messaging EndUser→Contact link + channel wiring: the
messaging-inbound-omniflow-patternmemory.



