service-agentforce-channel-configure: Wire an Agentforce agent to a channel
Adds inbound routing between an existing channel and an existing Agentforce agent. The agent receives work items from the channel; a fallback queue handles overflow when the agent is unavailable.
This skill is generic — it works for any Agentforce agent, not just the Help Agent template.
Scope
In scope:
- Resolving or creating a fallback queue with the correct
QueueSobject SobjectType
- Branch A (Enhanced Chat / Enhanced Messaging): deploying
sessionHandlerType=AgentforceServiceAgent + sessionHandlerQueue on an existing MessagingChannel, then binding SessionHandlerId via Data API PATCH
- Branch B (Voice): assumes the phone number and
PstnVoice MessagingChannel already exist (provisioned by the caller, e.g. service-helpagent-coordinate), then creating an inbound RoutingFlow (routingType: Copilot) that routes to the agent with the queue as fallback
- Branch C (Email-to-Case): inbound routing via direct case-owner assignment or an Omni-Channel RoutingFlow, plus deploying a
BotEmailDefinition (Email Configuration) that links the agent to Service Email and binding it to the routing address
- Optional outbound escalation: delegating the verified agent-to-human handoff contract to
service-agentforce-human-escalation-configure
Out of scope:
- Creating the agent — use
agentforce-generate or service-helpagent-coordinate
- Creating the MessagingChannel — use
service-digital-engagement-channel-configure
- Creating the Embedded Service Deployment — use
service-digital-engagement-deployment-configure
- Creating the Voice or Email-to-Case channel infrastructure
Required inputs
- Agent
DeveloperName and agent label (MasterLabel) — must be an existing, active agent
- Channel type — one of: Enhanced Chat, Enhanced Messaging (3rd-party), Voice, Email-to-Case
- Channel identifier — MessagingChannel
DeveloperName (Branch A), or the channel name/context (Branches B/C)
- Target org alias
Workflow
Steps are sequential. Read references/channel-types.md first to confirm the routing branch before proceeding.
Phase 0 — Production write-guard (mandatory, before any write)
This skill performs metadata/data writes (MessagingChannel edits, RoutingFlow deploys, agent republish). Before any write, classify the target org and refuse real production:
sf data query --target-org $ORG --json \
--query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1"
safe_to_write is true only when IsSandbox=true, OR TrialExpirationDate is non-null (trial/CDO), OR OrganizationType is Developer Edition / Base Edition.
- If
safe_to_write is false, stop — state plainly that this is a real production customer org and escalation/channel wiring will not be applied. Do not proceed to any write phase.
- If
safe_to_write is true, show the write plan (which MessagingChannel/RoutingFlow/agent will change) and get explicit user confirmation of the target org before continuing.
Never bypass this gate — a mistaken production write here reroutes live customer traffic.
Phase 1 — Verify agent and resolve queue
Confirm the agent exists and has an active version:
# Get the definition
sf data query --target-org $ORG --json \
--query "SELECT Id, DeveloperName, MasterLabel FROM BotDefinition WHERE DeveloperName='{AGENT_DEVELOPER_NAME}'"
# Check for an Active version
sf data query --target-org $ORG --json \
--query "SELECT Id, Status FROM BotVersion WHERE BotDefinitionId='{BOT_DEFINITION_ID}' AND Status='Active' LIMIT 1"
Stop with a clear message if the definition is not found or no version has Status = Active.
Branch A caveat — an Active BotVersion is necessary but NOT sufficient to bind as sessionHandlerAsa. The platform only accepts an agent that is provisioned/connected as a deployable Agentforce Service Agent (typically an ExternalCopilot). Binding an agent that is merely Active fails the Phase 2 deploy with Only active Agentforce Service Agents are supported for a Messaging Channel. Confirm bindability read-only before writing: if any MessagingChannel on the org already uses sessionHandlerType=AgentforceServiceAgent, retrieve it (sf project retrieve start --metadata "MessagingChannel:{EXISTING}" --target-org $ORG) and read its <sessionHandlerAsa> — that set is the org's provably-bindable ASAs. If the chosen agent is not a provisioned ASA and no bindable ASA exists, stop and report BLOCKED: provide an agent that is provisioned as an Agentforce Service Agent.
Resolve the fallback queue and routing configuration — follow references/queue-resolution.md:
- Determine SobjectType from the channel type (see
references/channel-types.md)
- Query existing compatible queues; present via
AskUserQuestion or create new
- Query for an existing
QueueRoutingConfig; create one with the correct capacity percentage if absent
- Capture
QUEUE_DEVELOPER_NAME, QUEUE_NAME, and QUEUE_ID
Phase 2 — Wire inbound routing
Live-traffic warning gate (runs before any branch)
Before making any routing change, detect whether the channel already has active inbound routing (Branch A: non-empty SessionHandlerType; Branches B/C: any active RoutingFlow assigned to the service channel). If it does, first check whether the user's prompt already answered the timing choice ("do not cut over" / "wire manually" / "review first" → defer silently; "cut over now" / "activate immediately" → proceed silently). Only if the prompt is silent, warn via AskUserQuestion and let the user choose "Re-route now" or "Set up, then wire manually" — and on any ambiguous or no-selection response, default to the deferred path (never to a live re-route). When deferred, set DEFER_INBOUND_ROUTING=true, skip the channel-activation step in the chosen branch, and print the manual wiring instructions at the end of Phase 2.
If the channel has no existing routing, skip this gate entirely and proceed directly.
Full detection queries, exact AskUserQuestion block, deferred-flow rules per branch, and manual-wiring copy: references/live-traffic-gate.md.
Branch A — Enhanced Chat / Enhanced Messaging (3rd-party)
No RoutingFlow required. Deploy the MessagingChannel with sessionHandlerType + sessionHandlerQueue only, then bind the bot via a Data API PATCH. sessionHandlerAsa is not accepted by the Metadata API at v67 — the deploy silently drops it and SessionHandlerId stays null unless you run the PATCH. The bot must be Active before the PATCH ("Only active Agentforce Service Agents are supported" otherwise).
Run all five steps in order. Perform retrieve and edit in the current SFDX project, so the deploy reads the edited .messagingChannel-meta.xml from force-app; do not use a temporary directory.
Retrieve the current MessagingChannel metadata into the working-directory project:
sf project retrieve start \
--metadata "MessagingChannel:{CHANNEL_DEVELOPER_NAME}" \
--target-org $ORG
Edit the retrieved .messagingChannel-meta.xml in place — set exactly these two fields (do NOT add <sessionHandlerAsa>):
<sessionHandlerType>AgentforceServiceAgent</sessionHandlerType>
<sessionHandlerQueue>{QUEUE_DEVELOPER_NAME}</sessionHandlerQueue>
Apply this edit with the file-editing tool (Edit/Write) so the change is saved to the retrieved file at force-app/main/default/messagingChannels/{CHANNEL_DEVELOPER_NAME}.messagingChannel-meta.xml in the working directory — do not hand-edit it through an inline sed/cat heredoc into a temp path. The deploy in step 3 must read this same on-disk file.
Deploy:
sf project deploy start \
--metadata "MessagingChannel:{CHANNEL_DEVELOPER_NAME}" \
--target-org $ORG
If the deploy fails with Only active Agentforce Service Agents are supported for a Messaging Channel, the agent is not a bindable ASA on this org (see the Phase 1 caveat). Do not retry with the same agent — the channel is left unchanged (the failed deploy is atomic). Report BLOCKED with the remediation: bind an agent already provisioned as an Agentforce Service Agent, or provision this one, then re-run.
Bind the bot via Data API PATCH:
CHAN_ID=$(sf data query --target-org $ORG --json \
--query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{CHANNEL_DEVELOPER_NAME}'" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
BOT_ID=$(sf data query --target-org $ORG --json \
--query "SELECT Id FROM BotDefinition WHERE DeveloperName='{AGENT_DEVELOPER_NAME}'" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
QUEUE_ID=$(sf data query --target-org $ORG --json \
--query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{QUEUE_DEVELOPER_NAME}'" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
sf api request rest --method PATCH -o $ORG \
"/services/data/v67.0/sobjects/MessagingChannel/${CHAN_ID}" \
--body "{\"SessionHandlerId\":\"${BOT_ID}\",\"FallbackQueueId\":\"${QUEUE_ID}\"}"
# Expected: HTTP 204
Verify:
sf data query --target-org $ORG --json \
--query "SELECT SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE Id='${CHAN_ID}'"
Both SessionHandlerId and FallbackQueueId must be non-null.
No agent file changes — no republish needed. Proceed to Phase 3 (optional).
Branch B — Voice
Wire the PstnVoice MessagingChannel through an inbound Copilot RoutingFlow, with the queue as fallback, and add the required modality voice: block before republishing the agent.
Follow references/channel-branch-voice.md end to end. Highlights:
- Step 0 — reuse an existing
PstnVoice MessagingChannel or have service-helpagent-coordinate provision one first using that skill's Voice channel reference; abort if the org uses a partner telephony provider (see references/channel-types.md).
- Steps 1–3 — write and deploy the inbound RoutingFlow using the template in
references/routing-flow.md, verifying ActiveVersionId is non-null.
- Step 4 — deploy a
MessagingChannel metadata file for {CHANNEL_DEVELOPER_NAME} with sessionHandlerType=Flow, sessionHandlerFlow={FLOW_DEVELOPER_NAME}, sessionHandlerQueue={QUEUE_DEVELOPER_NAME}. Without this the flow is never executed and calls hang up. Verify SessionHandlerId starts with 300.
- Step 5 — append the platform-default
modality voice: block (voice_id UgBBYS2sOqTuMpoF3BR0, "Mark", en_US) to the .agent file if missing; do not ask the user. Republish per references/agent-wiring.md.
Proceed to Phase 3 (optional).
Branch C — Email-to-Case
Let the user choose direct case-owner assignment or an Omni-Channel Copilot RoutingFlow. This branch requires API v68.0+, a connection service_email: surface, and a BotEmailDefinition.
Follow references/channel-branch-email.md end to end. Load-bearing gotchas (full walkthrough in that file):
- Step 1 — bind routing-address fields via Tooling-API PATCH or a
--metadata-dir Settings:Case deploy, never a source --metadata Settings:Case deploy (it reads sourceApiVersion and mutates the user's project).
- Step 2 — choose inbound routing (
AskUserQuestion): case-owner (caseOwner = the agent's bot user) or Omni-Channel flow (Copilot RoutingFlow + routingFlow/fallbackQueue). Resolve the bot user either way; routing fields go in the Step 4d deploy.
- Step 3 — the agent is already active, so add the mandatory
connection service_email: surface via deactivate → publish → activate (a plain publish fails with couldn't find the default agent user). Batch the outbound route here too if escalation is wanted.
- Step 4 — BotEmailDefinition: preflight the save-time gates, validate the reply template, deploy via
--metadata-dir, then bind the routing address in one Settings:Case deploy (botEmailDefinition + Step 2 routing fields + the required casePriority). Stop on failure.
Branch C is complete once the routing-address binding is verified. Proceed to Phase 3.
Phase 3 — Outbound escalation (optional)
Authoritative owner: full agent-to-human escalation is owned by service-agentforce-human-escalation-configure. This includes the escalation topic, planner coupling, staffed human queue, outbound flow, failure-threshold directives, republish, and deterministic verification.
After inbound routing is confirmed, ask the user:
"Inbound routing is now set up — the channel will route to [agent name]. Do you also want to configure outbound escalation so the agent can hand off to a human when requested?"
If yes, delegate to service-agentforce-human-escalation-configure. Pass the resolved agent,
channel type, context object, and fallback queue as known inputs. Do not duplicate that skill's
write or verification steps here.
Rules / constraints
| Rule |
Rationale |
| Verify the agent exists and is Active before making any changes |
Wiring a channel to a non-existent or inactive agent silently fails at runtime |
If the channel already has active inbound routing, honor an explicit defer/cutover intent in the prompt without asking; otherwise warn via AskUserQuestion and default to defer on ambiguity |
Re-routing takes effect immediately and affects live traffic — queue and RoutingFlow creation always proceed; only the activation step is gated, and the safe default is non-destructive |
| When deferred, print exact manual wiring instructions before Phase 3 |
The operator needs to know precisely what to run when they're ready to cut over |
| Never modify the MessagingChannel without retrieving the current metadata first |
Overwriting without retrieval discards existing settings |
Branch A: no RoutingFlow, no agent republish; deploy sessionHandlerType + sessionHandlerQueue via metadata, then bind SessionHandlerId via Data API PATCH |
sessionHandlerAsa is not accepted by the Metadata API at v67 — the deploy silently drops it, so bot binding must happen via the Data API PATCH after deploy. Bot must be Active before the PATCH |
| Branches B/C: always create a new RoutingFlow — never reuse existing org flows |
OOB platform flows commonly have ActiveVersionId: null and cannot be referenced |
Branches B/C: use routingType: Copilot and copilotLabel — not QueueBased |
QueueBased routes to the queue directly; Copilot routes to the agent first with the queue as fallback |
Queue Id must be queried and embedded in the RoutingFlow XML |
The queueId parameter requires a hardcoded 18-char record Id — do not leave it empty |
Queue naming: {ChannelTypeLabel} Queue |
Named after the channel type, not the agent |
| Outbound escalation is optional — never block inbound routing completion on it |
Inbound and outbound are independent; inbound wiring is complete without the outbound step |
Branch C: deploy BotEmailDefinition via --metadata-dir, never --metadata BotEmailDefinition:<name> |
Not in the CLI's SDR registry, so the named-type deploy fails; metadata-format works |
Branch C: do not wire an agent carrying a ServiceCustomerVerification topic to email |
Detect and stop — do not auto-remove (may be legitimate for a multi-surface agent). See channel-branch-email.md Step 4a |
Verification checklist
Queue
Branch A — MessagingChannel
Branches B/C — RoutingFlow
Branch C — Email routing address
Branch C — BotEmailDefinition
Optional Phase 3 — Outbound escalation
Reference file index
| File |
When to read |
references/channel-types.md |
Phase 1 — determine SobjectType and routing branch |
references/queue-resolution.md |
Phase 1 — queue lookup, creation, and Id capture |
references/live-traffic-gate.md |
Phase 2 — detection queries, deferred-flow rules, and manual wiring copy for the live-traffic warning gate |
references/channel-branch-voice.md |
Branch B — full Voice inbound wiring: PstnVoice channel selection, RoutingFlow, MessagingChannel assignment, modality voice: republish |
references/channel-branch-email.md |
Branch C — full Email-to-Case wiring: API v68.0+ precondition gate, CaseSettings flags, EmailRoutingAddress + read-modify-write patch, inbound RoutingFlow, mandatory connection service_email: surface block, headless BotEmailDefinition deploy, routing-address binding |
references/botemaildefinition.md |
Branch C Step 4 — BotEmailDefinition fields, save-time validation order, ASA template rules, --metadata-dir deploy recipe, ServiceEmail surface prerequisite, composite org gate |
references/routing-flow.md |
Branches B/C — inbound RoutingFlow XML template, deploy, verify |
references/agent-wiring.md |
Phase 3 (optional) — outbound escalation connection block |
assets/BotEmailDefinition.botEmailDefinition-meta.xml |
Branch C Step 4c — starting template for the BotEmailDefinition source file |
assets/mdapi-package.xml |
Branch C Step 4c — metadata-format package.xml for the --metadata-dir deploy |
assets/email/unfiled$public/AgentforceForServiceEmailTemplate.email + .email-meta.xml |
Branch C Step 4b (fallback) — minimal ASA-compliant SFX reply template when the user has none |
scripts/validate-botemaildefinition.py |
Branch C Step 4c — validate the BotEmailDefinition file before deploy |
scripts/validate-emailtemplate.py |
Branch C Step 4b (fallback) — validate the SFX template before deploy |
1---2name: service-agentforce-channel-configure3description: Wires an existing, active Agentforce agent to a channel by resolving a fallback queue, setting up inbound routing (either PATCH SessionHandlerId on the MessagingChannel, or an inbound RoutingFlow for Voice/Email), and optionally configuring outbound escalation. Use when the user wants to add a channel to an existing agent, connect an agent to a messaging or voice channel, route Voice or Email-to-Case to an Agentforce agent, or set up a fallback queue for an agent channel. Also applies to an existing Help Agent. The channel infrastructure must already exist — this skill adds routing. DO NOT TRIGGER when the agent does not yet exist or still needs Help Agent setup (use agentforce-generate or service-helpagent-coordinate), when setting up Email-to-Case with an agent end-to-end (use service-email-to-case-configure), when creating the MessagingChannel itself (use service-digital-engagement-channel-configure), or when creating an Embedded Service Deployment (use service-digital-engagement-deployment-configure).4---5
6# service-agentforce-channel-configure: Wire an Agentforce agent to a channel
7
8Adds inbound routing between an existing channel and an existing Agentforce agent. The agent receives work items from the channel; a fallback queue handles overflow when the agent is unavailable.
9
10This skill is generic — it works for any Agentforce agent, not just the Help Agent template.
11
12## Scope
13
14**In scope:**
15- Resolving or creating a fallback queue with the correct `QueueSobject` SobjectType
16- Branch A (Enhanced Chat / Enhanced Messaging): deploying `sessionHandlerType=AgentforceServiceAgent` + `sessionHandlerQueue` on an existing MessagingChannel, then binding `SessionHandlerId` via Data API PATCH
17- Branch B (Voice): assumes the phone number and `PstnVoice` MessagingChannel already exist (provisioned by the caller, e.g. `service-helpagent-coordinate`), then creating an inbound RoutingFlow (`routingType: Copilot`) that routes to the agent with the queue as fallback
18- Branch C (Email-to-Case): inbound routing via direct case-owner assignment or an Omni-Channel RoutingFlow, plus deploying a `BotEmailDefinition` (Email Configuration) that links the agent to Service Email and binding it to the routing address
19- Optional outbound escalation: delegating the verified agent-to-human handoff contract to `service-agentforce-human-escalation-configure`
20
21**Out of scope:**
22- Creating the agent — use `agentforce-generate` or `service-helpagent-coordinate`
23- Creating the MessagingChannel — use `service-digital-engagement-channel-configure`
24- Creating the Embedded Service Deployment — use `service-digital-engagement-deployment-configure`
25- Creating the Voice or Email-to-Case channel infrastructure
26
27---
28
29## Required inputs
30
31- **Agent `DeveloperName`** and **agent label** (`MasterLabel`) — must be an existing, active agent
32- **Channel type** — one of: Enhanced Chat, Enhanced Messaging (3rd-party), Voice, Email-to-Case
33- **Channel identifier** — MessagingChannel `DeveloperName` (Branch A), or the channel name/context (Branches B/C)
34- **Target org alias**
35
36---
37
38## Workflow
39
40Steps are sequential. Read `references/channel-types.md` first to confirm the routing branch before proceeding.
41
42### Phase 0 — Production write-guard (mandatory, before any write)
43
44This skill performs **metadata/data writes** (MessagingChannel edits, RoutingFlow deploys, agent republish). Before any write, classify the target org and refuse real production:
45
46```bash
47sf data query --target-org $ORG --json \
48 --query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1"
49```
50
51- `safe_to_write` is **true** only when `IsSandbox=true`, OR `TrialExpirationDate` is non-null (trial/CDO), OR `OrganizationType` is `Developer Edition` / `Base Edition`.
52- If `safe_to_write` is false, **stop** — state plainly that this is a real production customer org and escalation/channel wiring will not be applied. Do not proceed to any write phase.
53- If `safe_to_write` is true, show the write plan (which MessagingChannel/RoutingFlow/agent will change) and get explicit user confirmation of the target org before continuing.
54
55Never bypass this gate — a mistaken production write here reroutes live customer traffic.
56
57### Phase 1 — Verify agent and resolve queue
58
591. **Confirm the agent exists and has an active version:**
60 ```bash
61 # Get the definition
62 sf data query --target-org $ORG --json \
63 --query "SELECT Id, DeveloperName, MasterLabel FROM BotDefinition WHERE DeveloperName='{AGENT_DEVELOPER_NAME}'"
64
65 # Check for an Active version
66 sf data query --target-org $ORG --json \
67 --query "SELECT Id, Status FROM BotVersion WHERE BotDefinitionId='{BOT_DEFINITION_ID}' AND Status='Active' LIMIT 1"
68 ```
69 Stop with a clear message if the definition is not found or no version has `Status = Active`.
70
71 > **Branch A caveat — an Active BotVersion is necessary but NOT sufficient to bind as `sessionHandlerAsa`.** The platform only accepts an agent that is provisioned/connected as a *deployable Agentforce Service Agent* (typically an `ExternalCopilot`). Binding an agent that is merely Active fails the Phase 2 deploy with `Only active Agentforce Service Agents are supported for a Messaging Channel`. Confirm bindability read-only before writing: if any MessagingChannel on the org already uses `sessionHandlerType=AgentforceServiceAgent`, retrieve it (`sf project retrieve start --metadata "MessagingChannel:{EXISTING}" --target-org $ORG`) and read its `<sessionHandlerAsa>` — that set is the org's provably-bindable ASAs. If the chosen agent is not a provisioned ASA and no bindable ASA exists, stop and report `BLOCKED`: *provide an agent that is provisioned as an Agentforce Service Agent.*
72
732. **Resolve the fallback queue and routing configuration** — follow `references/queue-resolution.md`:
74 - Determine SobjectType from the channel type (see `references/channel-types.md`)
75 - Query existing compatible queues; present via `AskUserQuestion` or create new
76 - Query for an existing `QueueRoutingConfig`; create one with the correct capacity percentage if absent
77 - Capture `QUEUE_DEVELOPER_NAME`, `QUEUE_NAME`, and `QUEUE_ID`
78
79---
80
81### Phase 2 — Wire inbound routing
82
83#### Live-traffic warning gate (runs before any branch)
84
85Before making any routing change, detect whether the channel already has active inbound routing (Branch A: non-empty `SessionHandlerType`; Branches B/C: any active RoutingFlow assigned to the service channel). If it does, first check whether the user's prompt already answered the timing choice ("do not cut over" / "wire manually" / "review first" → defer silently; "cut over now" / "activate immediately" → proceed silently). Only if the prompt is silent, warn via `AskUserQuestion` and let the user choose **"Re-route now"** or **"Set up, then wire manually"** — and on any ambiguous or no-selection response, default to the deferred path (never to a live re-route). When deferred, set `DEFER_INBOUND_ROUTING=true`, skip the channel-activation step in the chosen branch, and print the manual wiring instructions at the end of Phase 2.
86
87If the channel has no existing routing, skip this gate entirely and proceed directly.
88
89Full detection queries, exact `AskUserQuestion` block, deferred-flow rules per branch, and manual-wiring copy: `references/live-traffic-gate.md`.
90
91---
92
93#### Branch A — Enhanced Chat / Enhanced Messaging (3rd-party)
94
95No RoutingFlow required. Deploy the MessagingChannel with `sessionHandlerType` + `sessionHandlerQueue` only, then bind the bot via a Data API PATCH. `sessionHandlerAsa` is not accepted by the Metadata API at v67 — the deploy silently drops it and `SessionHandlerId` stays null unless you run the PATCH. The bot must be Active before the PATCH ("Only active Agentforce Service Agents are supported" otherwise).
96
97Run all five steps in order. Perform retrieve and edit in the **current SFDX project**, so the deploy reads the edited `.messagingChannel-meta.xml` from `force-app`; do not use a temporary directory.
98
991. **Retrieve the current MessagingChannel metadata** into the working-directory project:
100 ```bash
101 sf project retrieve start \
102 --metadata "MessagingChannel:{CHANNEL_DEVELOPER_NAME}" \
103 --target-org $ORG
104 ```
105
1062. **Edit the retrieved `.messagingChannel-meta.xml` in place** — set exactly these two fields (do NOT add `<sessionHandlerAsa>`):
107 ```xml
108 <sessionHandlerType>AgentforceServiceAgent</sessionHandlerType>
109 <sessionHandlerQueue>{QUEUE_DEVELOPER_NAME}</sessionHandlerQueue>
110 ```
111 Apply this edit with the file-editing tool (Edit/Write) so the change is saved to the retrieved file at `force-app/main/default/messagingChannels/{CHANNEL_DEVELOPER_NAME}.messagingChannel-meta.xml` in the working directory — do **not** hand-edit it through an inline `sed`/`cat` heredoc into a temp path. The deploy in step 3 must read this same on-disk file.
112
1133. **Deploy:**
114 ```bash
115 sf project deploy start \
116 --metadata "MessagingChannel:{CHANNEL_DEVELOPER_NAME}" \
117 --target-org $ORG
118 ```
119 If the deploy fails with `Only active Agentforce Service Agents are supported for a Messaging Channel`, the agent is not a bindable ASA on this org (see the Phase 1 caveat). Do **not** retry with the same agent — the channel is left unchanged (the failed deploy is atomic). Report `BLOCKED` with the remediation: bind an agent already provisioned as an Agentforce Service Agent, or provision this one, then re-run.
120
1214. **Bind the bot via Data API PATCH:**
122 ```bash
123 CHAN_ID=$(sf data query --target-org $ORG --json \
124 --query "SELECT Id FROM MessagingChannel WHERE DeveloperName='{CHANNEL_DEVELOPER_NAME}'" \
125 | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
126 BOT_ID=$(sf data query --target-org $ORG --json \
127 --query "SELECT Id FROM BotDefinition WHERE DeveloperName='{AGENT_DEVELOPER_NAME}'" \
128 | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
129 QUEUE_ID=$(sf data query --target-org $ORG --json \
130 --query "SELECT Id FROM Group WHERE Type='Queue' AND DeveloperName='{QUEUE_DEVELOPER_NAME}'" \
131 | python3 -c "import sys,json; print(json.load(sys.stdin)['result']['records'][0]['Id'])")
132
133 sf api request rest --method PATCH -o $ORG \
134 "/services/data/v67.0/sobjects/MessagingChannel/${CHAN_ID}" \
135 --body "{\"SessionHandlerId\":\"${BOT_ID}\",\"FallbackQueueId\":\"${QUEUE_ID}\"}"
136 # Expected: HTTP 204
137 ```
138
1395. **Verify:**
140 ```bash
141 sf data query --target-org $ORG --json \
142 --query "SELECT SessionHandlerId, FallbackQueueId FROM MessagingChannel WHERE Id='${CHAN_ID}'"
143 ```
144 Both `SessionHandlerId` and `FallbackQueueId` must be non-null.
145
146No agent file changes — no republish needed. Proceed to Phase 3 (optional).
147
148---
149
150#### Branch B — Voice
151
152Wire the `PstnVoice` MessagingChannel through an inbound `Copilot` RoutingFlow, with the queue as fallback, and add the required `modality voice:` block before republishing the agent.
153
154Follow `references/channel-branch-voice.md` end to end. Highlights:
155
156- Step 0 — reuse an existing `PstnVoice` MessagingChannel or have `service-helpagent-coordinate` provision one first using that skill's Voice channel reference; abort if the org uses a partner telephony provider (see `references/channel-types.md`).
157- Steps 1–3 — write and deploy the inbound RoutingFlow using the template in `references/routing-flow.md`, verifying `ActiveVersionId` is non-null.
158- Step 4 — deploy a `MessagingChannel` metadata file for `{CHANNEL_DEVELOPER_NAME}` with `sessionHandlerType=Flow`, `sessionHandlerFlow={FLOW_DEVELOPER_NAME}`, `sessionHandlerQueue={QUEUE_DEVELOPER_NAME}`. Without this the flow is never executed and calls hang up. Verify `SessionHandlerId` starts with `300`.
159- Step 5 — append the platform-default `modality voice:` block (voice_id `UgBBYS2sOqTuMpoF3BR0`, "Mark", en_US) to the `.agent` file if missing; do not ask the user. Republish per `references/agent-wiring.md`.
160
161Proceed to Phase 3 (optional).
162
163---
164
165#### Branch C — Email-to-Case
166
167Let the user choose direct case-owner assignment or an Omni-Channel `Copilot` RoutingFlow. This branch requires API v68.0+, a `connection service_email:` surface, and a BotEmailDefinition.
168
169Follow `references/channel-branch-email.md` end to end. Load-bearing gotchas (full walkthrough in that file):
170
171- Step 1 — bind routing-address fields via Tooling-API PATCH or a `--metadata-dir` `Settings:Case` deploy, **never** a source `--metadata Settings:Case` deploy (it reads `sourceApiVersion` and mutates the user's project).
172- Step 2 — choose inbound routing (`AskUserQuestion`): **case-owner** (`caseOwner` = the agent's bot user) or **Omni-Channel flow** (`Copilot` RoutingFlow + `routingFlow`/`fallbackQueue`). Resolve the bot user either way; routing fields go in the Step 4d deploy.
173- Step 3 — the agent is already active, so add the mandatory `connection service_email:` surface via **deactivate → publish → activate** (a plain publish fails with `couldn't find the default agent user`). Batch the outbound route here too if escalation is wanted.
174- Step 4 — BotEmailDefinition: preflight the save-time gates, validate the reply template, deploy via `--metadata-dir`, then bind the routing address in one `Settings:Case` deploy (`botEmailDefinition` + Step 2 routing fields + the required `casePriority`). Stop on failure.
175
176Branch C is complete once the routing-address binding is verified. Proceed to Phase 3.
177
178---
179
180### Phase 3 — Outbound escalation (optional)
181
182> **Authoritative owner:** full agent-to-human escalation is owned by **`service-agentforce-human-escalation-configure`**. This includes the escalation topic, planner coupling, staffed human queue, outbound flow, failure-threshold directives, republish, and deterministic verification.
183
184After inbound routing is confirmed, ask the user:
185
186> *"Inbound routing is now set up — the channel will route to [agent name]. Do you also want to configure outbound escalation so the agent can hand off to a human when requested?"*
187
188If yes, delegate to `service-agentforce-human-escalation-configure`. Pass the resolved agent,
189channel type, context object, and fallback queue as known inputs. Do not duplicate that skill's
190write or verification steps here.
191
192---
193
194## Rules / constraints
195
196| Rule | Rationale |
197|---|---|
198| Verify the agent exists and is Active before making any changes | Wiring a channel to a non-existent or inactive agent silently fails at runtime |
199| If the channel already has active inbound routing, honor an explicit defer/cutover intent in the prompt without asking; otherwise warn via `AskUserQuestion` and default to defer on ambiguity | Re-routing takes effect immediately and affects live traffic — queue and RoutingFlow creation always proceed; only the activation step is gated, and the safe default is non-destructive |
200| When deferred, print exact manual wiring instructions before Phase 3 | The operator needs to know precisely what to run when they're ready to cut over |
201| Never modify the MessagingChannel without retrieving the current metadata first | Overwriting without retrieval discards existing settings |
202| Branch A: no RoutingFlow, no agent republish; deploy `sessionHandlerType` + `sessionHandlerQueue` via metadata, then bind `SessionHandlerId` via Data API PATCH | `sessionHandlerAsa` is not accepted by the Metadata API at v67 — the deploy silently drops it, so bot binding must happen via the Data API PATCH after deploy. Bot must be Active before the PATCH |
203| Branches B/C: always create a new RoutingFlow — never reuse existing org flows | OOB platform flows commonly have `ActiveVersionId: null` and cannot be referenced |
204| Branches B/C: use `routingType: Copilot` and `copilotLabel` — not `QueueBased` | `QueueBased` routes to the queue directly; `Copilot` routes to the agent first with the queue as fallback |
205| Queue `Id` must be queried and embedded in the RoutingFlow XML | The `queueId` parameter requires a hardcoded 18-char record Id — do not leave it empty |
206| Queue naming: `{ChannelTypeLabel} Queue` | Named after the channel type, not the agent |
207| Outbound escalation is optional — never block inbound routing completion on it | Inbound and outbound are independent; inbound wiring is complete without the outbound step |
208| Branch C: deploy BotEmailDefinition via `--metadata-dir`, never `--metadata BotEmailDefinition:<name>` | Not in the CLI's SDR registry, so the named-type deploy fails; metadata-format works |
209| Branch C: do not wire an agent carrying a `ServiceCustomerVerification` topic to email | Detect and stop — do not auto-remove (may be legitimate for a multi-surface agent). See `channel-branch-email.md` Step 4a |
210
211---
212
213## Verification checklist
214
215### Queue
216- [ ] Queue has a `QueueSobject` record with the correct `SobjectType` for the channel type
217- [ ] Running user is a member of the queue (if newly created)
218- [ ] Queue has a `QueueRoutingConfig` with the correct `CapacityPercentage` (50 / 100 / 25 for Chat / Voice / Email)
219
220### Branch A — MessagingChannel
221- [ ] `SessionHandlerType = AgentforceServiceAgent` after deploy
222- [ ] Bot is Active before the Data API PATCH
223- [ ] `SessionHandlerId` is non-null after the Data API PATCH (matches the bot's `BotDefinition.Id`, starts with `0Xx`)
224- [ ] `FallbackQueueId` is non-null after the Data API PATCH (matches the resolved queue Id)
225
226### Branches B/C — RoutingFlow
227- [ ] RoutingFlow `ActiveVersionId` is non-null
228- [ ] `routingType = Copilot` in the flow's `routeWork` action
229- [ ] `copilotLabel` matches the agent's exact `MasterLabel`
230- [ ] `queueId` is populated (non-empty)
231
232### Branch C — Email routing address
233- [ ] If new: `EmailRoutingAddress` record created with correct `PersonalName` and `Address`
234- [ ] If new: CaseSettings patched with `caseOrigin`, `saveEmailHeaders: true`, `addressType: EmailToCase`
235- [ ] If new: user informed that a verification email was sent to the support address (non-blocking)
236
237### Branch C — BotEmailDefinition
238- [ ] Preflight passed: agent is `EinsteinServiceAgent` with a bot user holding `agentforceServiceAgentUser`; active version carries the `ServiceEmail` surface; no `ServiceCustomerVerification` topic on the email agent
239- [ ] Reply `EmailTemplate` is SFX, HTML, public, and contains `[[[GENERATED_CONTENT]]]` + `[[[LEGAL_DISCLOSURE]]]`
240- [ ] Headless path (API ≥68): `BotEmailDefinition` deployed via `--metadata-dir` with `success: true`; `legalDisclaimer`/`signature` ≥10 chars
241- [ ] Routing address's `botEmailDefinition` child set to the deployed component `fullName`
242- [ ] Inbound routing set on the same routing address: `caseOwner`+`caseOwnerType` or `routingFlow`+`fallbackQueue`, with `casePriority` present
243
244### Optional Phase 3 — Outbound escalation
245- [ ] `service-agentforce-human-escalation-configure` returned `CONFIGURED` or `ALREADY-CONFIGURED`
246
247---
248
249## Reference file index
250
251| File | When to read |
252|---|---|
253| `references/channel-types.md` | Phase 1 — determine SobjectType and routing branch |
254| `references/queue-resolution.md` | Phase 1 — queue lookup, creation, and Id capture |
255| `references/live-traffic-gate.md` | Phase 2 — detection queries, deferred-flow rules, and manual wiring copy for the live-traffic warning gate |
256| `references/channel-branch-voice.md` | Branch B — full Voice inbound wiring: PstnVoice channel selection, RoutingFlow, MessagingChannel assignment, `modality voice:` republish |
257| `references/channel-branch-email.md` | Branch C — full Email-to-Case wiring: API v68.0+ precondition gate, CaseSettings flags, EmailRoutingAddress + read-modify-write patch, inbound RoutingFlow, mandatory `connection service_email:` surface block, headless BotEmailDefinition deploy, routing-address binding |
258| `references/botemaildefinition.md` | Branch C Step 4 — BotEmailDefinition fields, save-time validation order, ASA template rules, `--metadata-dir` deploy recipe, `ServiceEmail` surface prerequisite, composite org gate |
259| `references/routing-flow.md` | Branches B/C — inbound RoutingFlow XML template, deploy, verify |
260| `references/agent-wiring.md` | Phase 3 (optional) — outbound escalation `connection` block |
261| `assets/BotEmailDefinition.botEmailDefinition-meta.xml` | Branch C Step 4c — starting template for the BotEmailDefinition source file |
262| `assets/mdapi-package.xml` | Branch C Step 4c — metadata-format `package.xml` for the `--metadata-dir` deploy |
263| `assets/email/unfiled$public/AgentforceForServiceEmailTemplate.email` + `.email-meta.xml` | Branch C Step 4b (fallback) — minimal ASA-compliant SFX reply template when the user has none |
264| `scripts/validate-botemaildefinition.py` | Branch C Step 4c — validate the BotEmailDefinition file before deploy |
265| `scripts/validate-emailtemplate.py` | Branch C Step 4b (fallback) — validate the SFX template before deploy |