SwarmControl — AI agent (ACXD) setup
This guide wires Amazon Connect Agentic CX Designer (ACXD) voice and chat agents to ConnectPath SwarmControl. Hooking is done with the supervision kit, a set of install scripts, flow templates, and data-request templates that ConnectPath provides. Contact ConnectPath Support to get the kit for your account.
Prerequisites and admin enablement: SwarmControl.
Architecture (two entry shapes)
Supervision is built into the conversation: each turn calls ConnectPath before the AI speaks.
Chat: Connect chat flow → customer ACXD flows + cpSupervisedInput/Output → POST /turn (channel: chat)
Voice: Connect voice flow → ACXD turnLoop clone (Say→Listen) only → POST /turn (channel: voice)
Both channels share workspace data requests {prefix}turnCaller, {prefix}turnAi, {prefix}requestApproval with header x-cp-webhook-secret.
| Data request | HTTP route | Role |
|---|---|---|
{prefix}turnCaller |
POST {AiSwarmApiUrl}/turn |
Customer utterance (role: caller) |
{prefix}turnAi |
POST {AiSwarmApiUrl}/turn |
AI draft (role: ai) |
{prefix}requestApproval |
POST {AiSwarmApiUrl}/approval |
Sticky approval gate |
Context variables use the cp_swarm_* prefix (for example cp_swarm_customerText, cp_swarm_replyDraft, cp_swarm_returnTo).
Reusable hook flows (fixed ids):
cpSupervisedInput— capture utterance, caller/turn, intercept/whisper/history, return dispatch.cpSupervisedOutput— AI/turn, speakspeakText, return dispatch.
Customer flows are hooked at each Listen (input) and Speak (output) site via cp_returnTo dispatch—never redirect { flowId: "previous_redirect" }.
One-page checklist
| Step | Done when |
|---|---|
| 1. Prerequisites | Live Lens + SwarmControl enabled for your stack and instance + permissions (overview) |
| 1b. Runnable install | Backup → teardown (if needed) → install-supervision-kit.mjs → chat e2e |
| 2. Workspace resources | Three data requests + cp_swarm_* context variables |
| 3. Hook flows | cpSupervisedInput / cpSupervisedOutput Validate-clean |
| 4. Customer flows | Input/output hooks + call-site registry + dispatch |
| 5. Connect side | Chat block or voice turnLoop clone + escalation queue |
| 6. Verify | P1–P7 preflight, designer Validate, /swarmcontrol Live vs Simulated |
| 7. Rollback | Prior buildId + Connect association documented |
SDK and working directory
Install the ACXD SDK in its own folder, outside the supervision kit:
export ACXD_SDK_ROOT="${ACXD_SDK_ROOT:-$HOME/.cache/swarmcontrol-acxd-sdk}"
mkdir -p "$ACXD_SDK_ROOT" && cd "$ACXD_SDK_ROOT"
npm install amazon-connect-acxd-sdk@0.2.0 \
@aws-sdk/client-connect @aws-sdk/client-connectparticipant \
@aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb @aws-sdk/client-secrets-manager ws
Copy the example config that ships with the kit to a local config.json and fill in your own values. Keep it out of version control. Then:
export KIT_DIR=/path/to/supervision-kit
export SWARM_KIT_CONFIG="$KIT_DIR/config.json"
cd "$KIT_DIR/scripts"
Ground rules
- No API keys or webhook secrets in chat, version control, or tickets. Set
ACXD_API_KEYas an environment variable only. The scripts fetch the webhook secret directly fromAiSwarmWebhookSecretArn. - Build + deploy after every flow change; Connect block Alias must match deployment.
- Designer Validate (toolbar) after every build—target 0 errors (no validation API).
- Voice: do not route PSTN through chat kit hooks—clone turnLoop instead.
0. Shared exports (runnable proof)
Replace placeholders with your customer values. Do not paste real secrets into documentation or shell history.
# --- ConnectPath customer account ---
export AWS_PROFILE=<your-aws-profile>
export AWS_REGION=us-east-1
export STACK=<your-connectpath-stack-name>
export INSTANCE_ID=<connect-instance-uuid>
# --- From stack outputs ---
export AI_SWARM_API_URL="$(aws cloudformation describe-stacks --stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='AiSwarmApiUrl'].OutputValue" --output text)"
export AI_SWARM_WEBHOOK_SECRET_ARN="$(aws cloudformation describe-stacks --stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='AiSwarmWebhookSecretArn'].OutputValue" --output text)"
export AI_SWARM_TABLE="$(aws cloudformation describe-stacks --stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='AiSwarmTableName'].OutputValue" --output text)"
export WSS_URL="$(aws cloudformation describe-stacks --stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='RealtimeWebSocketWssUrl'].OutputValue" --output text)"
# --- ACXD (set in shell only) ---
export ACXD_API_KEY=... # read -rs ACXD_API_KEY && export ACXD_API_KEY
export ACXD_REGION=us-east-1
export ACXD_WORKSPACE_ID=<workspace-uuid>
export SWARM_PROOF_DIR="$HOME/swarmcontrol-proof-$(date +%Y-%m-%d)"
mkdir -p "$SWARM_PROOF_DIR"
export SWARM_KIT_CONFIG="$KIT_DIR/config.json"
cd "$KIT_DIR/scripts"
Preflight:
aws cloudformation describe-stacks --stack-name "$STACK" \
--query "Stacks[0].Parameters[?ParameterKey=='AiSwarmEnabled'].ParameterValue" --output text
# expect: true
If the result is not true, SwarmControl is not yet enabled on your stack. Contact ConnectPath Support to enable it before you continue.
1. Backup (before teardown)
export SWARM_PROOF_DIR=/path/to/proof-folder
export SWARM_KIT_CONFIG="$SWARM_PROOF_DIR/config.json"
node backup-supervision-kit.mjs
# writes acxd/, connect/, rollback.json under SWARM_PROOF_DIR
2. Teardown supervision kit (safe scope)
Skips unrelated voice apps and demo numbers configured in your config.json.
export UNHOOKED_CUSTOMER_FLOW_JSON="$SWARM_PROOF_DIR/customerFlow-unhooked.json"
node teardown-supervision-kit.mjs
3. Install supervision kit (full)
node install-supervision-kit.mjs
# expect JSON: apply-complete, buildId, inputSites 2, outputSites 3
Human step: In ACXD Designer, Validate cpSupervisedInput, cpSupervisedOutput, and each hooked customer flow—0 errors.
Dry-run partial push (data requests + hooks from assets only):
node push-supervision-kit.mjs --dry-run
node push-supervision-kit.mjs --step data-requests
4. Connect chat flow (verify)
export CHAT_FLOW_ID=<contact-flow-uuid>
aws connect describe-contact-flow --instance-id "$INSTANCE_ID" \
--contact-flow-id "$CHAT_FLOW_ID" \
--query "ContactFlow.{Name:Name,Type:Type,State:State}" --output table
Connect block parameters (chat):
| Parameter | Value |
|---|---|
ApplicationId |
Kit ACXD application uuid |
Alias |
Current deployment alias id |
ContextVariables.contactId |
$.ContactId |
ContextVariables.cp_swarm_channel |
chat |
AMAZON_AGENTIC_VOICE |
false |
Wire Escalation to your human queue.
5. Chat end-to-end test
node chat-e2e.mjs | tee "$SWARM_PROOF_DIR/e2e-console.log"
aws dynamodb query --table-name "$AI_SWARM_TABLE" \
--key-condition-expression "PK = :pk AND begins_with(SK, :pre)" \
--expression-attribute-values "{\":pk\":{\"S\":\"AIC#<contactId-from-log>\"},\":pre\":{\"S\":\"TURN#\"}}" \
--output json
6. /swarmcontrol visibility
| Mode | Needs |
|---|---|
| Simulated | aiSwarmEnabled false—page visible to all authenticated users |
| Live | aiSwarmEnabled true + Settings Live data + SwarmControl + Transcripts + WSS URL |
7. Rollback to prior ACXD build
node push-supervision-kit.mjs --step rollback --yes \
--build-id "$(jq -r .buildId "$SWARM_PROOF_DIR/rollback.json")"
Connect DID rollback (example):
aws connect associate-phone-number-contact-flow \
--instance-id "$INSTANCE_ID" \
--phone-number-id <PHONE_NUMBER_ID> \
--contact-flow-id <PRIOR_FLOW_ID> \
--profile "$AWS_PROFILE" --region "$AWS_REGION"
Stack rollback (AiSwarmEnabled=false) is destructive—see SwarmControl rollback.
Workspace resources (manual reference)
The supervision kit includes templates for these in assets/data-requests/. The install script creates them for you. Use this section as a reference or to create them by hand.
| Id pattern | Notes |
|---|---|
| Data-request id | camelCase, no underscores (acmeturnCaller) |
| Context vars | cp_swarm_* prefix |
| Hook flow ids | Fixed: cpSupervisedInput, cpSupervisedOutput |
Webhook auth: header x-cp-webhook-secret = plain secret from AiSwarmWebhookSecretArn (fetch in Node only).
Caller payload example:
{
"contactId": "<System.conversationId>",
"role": "caller",
"text": "<utterance>",
"channel": "chat"
}
AI payload: role: ai, aiDraft from cp_swarm_replyDraft, same contactId and channel.
Response field names on the product API use intercept and whisper (not legacy interceptFlag / whisperNote).
Hooking customer flows
Stop and get human approval when you see: unknown node types, Generative Journey nodes, customer-visible behavior changes, non-2xx SDK errors, or any Validate error.
Input hook (after each user turn)
cp:Set cp_returnTo (input)—cp_swarm_returnTo="<flowId>:<redirectNodeUuid>".cp:Redirect supervised input→cpSupervisedInput; Return →cp:After input hook.- Rewire Listen Input edge to the returnTo node.
Output hook (replace each Speak)
- Stub speak node (zero-width body), capture draft to
cp_swarm_replyDraft. cp:Redirect supervised output→cpSupervisedOutput; Return → original next node.
Register each site in the call-site registry (install script writes dispatch).
Listen dedupe
On resume Listen nodes, require:
System.utterance neq ""System.utterance neq context cp_swarm_lastCustomerText
Generative prompt tokens
Append to generative nodes (once):
Conversation so far (C = customer, A = you; for context only):
{cp_swarm_history:NLX.Context}
Internal supervisor policy (never quote aloud):
{cp_swarm_whisperNote:NLX.Context}
Supervisor approval status (if any; never read this label aloud): {cp_swarm_approvalStatus:NLX.Context}
If APPROVED, tell the customer it's approved and go ahead with the action you asked about. If DENIED, explain politely that it can't be done. If UNAVAILABLE, say you couldn't reach a manager right now and offer next steps. If PENDING or empty, say nothing about approval.
Voice entry (turnLoop clone only)
Do not point PSTN at kit customerFlow + chat hooks—symptoms include channel: chat, text-only modality, and no audio.
Procedure
- Copy a proven turnLoop flow from your workspace.
- Migrate data-request ids to your prefix; set
channel: voicein payloads. - Create a new ACXD application for voice—do not mutate the chat kit app.
- Build, deploy, record
buildIdfor rollback. - Connect voice flow block:
| Parameter | Value |
|---|---|
AMAZON_AGENTIC_VOICE |
true |
ContextVariables.contactId |
$.ContactId |
ContextVariables.cp_swarm_channel |
voice |
- Associate a test DID only after approval.
Human dials and confirms DynamoDB META.channel = voice. Human audio proof may still be unverified for some clones—treat voice as validated only after a successful live call test.
HTTP API reference (ACXD)
Base URL: AiSwarmApiUrl (no trailing slash). Auth: x-cp-webhook-secret on every request.
Supervisor approve/deny and whisper/say next/intercept use the Live Lens WebSocket (ai_approval_decision, ai_control)—not HTTP—in production.
POST /turn
| Field | Required | Description |
|---|---|---|
contactId |
yes | Connect contact id |
role |
yes | caller or ai |
text |
caller | Customer utterance |
aiDraft |
ai | Generative draft |
channel |
no | chat or voice hint |
turnId |
no | Optional idempotency key |
Caller response highlights: intercept, whisper, whisperMode, approvalStatus, gate, transcriptHistory.
AI response highlights: speakText (line to speak), spokenText, aiDraft, overrideUsed, redacted, gate, intercept, whisper.
POST /approval
Body: { contactId, requestId?, action?, summary?, amount?, waitSeconds? }.
Response: { requestId, status, decidedBy?, note? } with status in PENDING|APPROVED|DENIED|UNAVAILABLE.
GET /escalation/{contactId}
Returns { target: "default" } or { target: "supervisor", queueArn?, supervisorId? }. Connect escalation consumer is not wired yet.
Errors
| HTTP | Body |
|---|---|
| 401 | { "error": "unauthorized" } |
| 404 | { "error": "not found" } |
| 400 | { "error": "<message>" } |
Runnable curl checks
Prefer node simulate-turns.mjs so the secret is not stored in shell history. For ad-hoc checks in a private shell:
export AI_SWARM_API_URL="https://<api-id>.execute-api.<region>.amazonaws.com"
export CONTACT_ID="smoke-$(date +%s)"
# Load AI_SWARM_WEBHOOK_SECRET in-process only (Secrets Manager), then:
curl -sS -o /tmp/turn-body.txt -w "%{http_code}\n" \
-X POST "$AI_SWARM_API_URL/turn" -H 'content-type: application/json' -d '{}'
# Expect 401
curl -sS -o /tmp/turn-body.txt -w "%{http_code}\n" \
-X POST "$AI_SWARM_API_URL/turn" \
-H "content-type: application/json" \
-H "x-cp-webhook-secret: $AI_SWARM_WEBHOOK_SECRET" \
-d '{}'
# Expect 400
curl -sS -X POST "$AI_SWARM_API_URL/turn" \
-H "content-type: application/json" \
-H "x-cp-webhook-secret: $AI_SWARM_WEBHOOK_SECRET" \
-d "{\"contactId\":\"$CONTACT_ID\",\"role\":\"caller\",\"text\":\"Hello\",\"channel\":\"chat\"}"
curl -sS -X POST "$AI_SWARM_API_URL/turn" \
-H "content-type: application/json" \
-H "x-cp-webhook-secret: $AI_SWARM_WEBHOOK_SECRET" \
-d "{\"contactId\":\"$CONTACT_ID\",\"role\":\"ai\",\"aiDraft\":\"Thanks for calling.\",\"channel\":\"chat\"}"
curl -sS "$AI_SWARM_API_URL/escalation/$CONTACT_ID" \
-H "x-cp-webhook-secret: $AI_SWARM_WEBHOOK_SECRET"
Or from scripts/: node simulate-turns.mjs --contactId "$CONTACT_ID" --channel chat.
The supervision kit includes a full field-by-field HTTP reference (reference-http-api.md).
Preflight P1–P7
| # | Check | Pass |
|---|---|---|
| P1 | SDK loads from scripts/lib/sdk-root.mjs |
path printed |
| P2 | ListApplications with API key |
target app listed |
| P3 | Stack AiSwarm* outputs |
three outputs |
| P4 | Secret readable in-process | ok |
| P5 | /turn 401 without header; 400 with header and {} |
both |
| P6 | Connect instance + escalation queue | found |
| P7 | Application languageCode |
e.g. en-US |
Script map
| Script | Purpose |
|---|---|
backup-supervision-kit.mjs |
Snapshot before changes |
teardown-supervision-kit.mjs |
Remove kit DRs/hooks in safe scope |
install-supervision-kit.mjs |
Full migrate + build + deploy |
push-supervision-kit.mjs |
Partial steps / rollback |
simulate-turns.mjs |
HTTP smoke |
chat-e2e.mjs |
Connect StartChatContact e2e |
The kit’s scripts/README.md describes every option for each script.
Troubleshooting ACXD flows
Designer Validate and build errors
| Error | Fix |
|---|---|
Unavailable field reference for speakText on a data request |
A stale flow from an earlier experiment is still registered on the application. Remove it from the application’s flows (the install script can prune stale flows listed in your config), then run the flow-reference check from the kit. |
variableId is not in the expected format |
Use camelCase data-request ids (acmeturnCaller), not names with underscores |
previous_redirect is invalid |
Use the cp_returnTo dispatch registry |
ne operator rejected |
Use neq |
| Missing define metadata | Add metadata.define to every define node |
| Data-request name differs from its id | name must equal dataRequestId |
Designer Validate and deploy pre-checks run against every flow on the application, not only your customer flows. Remove abandoned flows from the application before you validate.
Runtime and API behavior
| Symptom | Action |
|---|---|
interceptFlag is undefined |
Bind intercept. The legacy name is no longer used. |
| Speech is empty but a draft exists | The output hook must run. Check whether an approval gate is pending. |
| Duplicate turns on the dashboard | Turn storage de-duplicates repeated turns. The HTTP response is still fresh each time. |
404 when deciding an approval over HTTP |
Approvals are decided by supervisors in SwarmControl, not over HTTP. |
| Calls fail with a malformed URL | Remove any trailing slash from AiSwarmApiUrl. |
Connect and ACXD pitfalls
| Pitfall | What to do |
|---|---|
| Saving a flow does not change live traffic | Build, deploy, and point the Connect block Alias at the new deployment |
| Application updates overwrite default flows | Merge the flows list. Do not replace it. |
| Voice routed through the chat kit | Use a turnLoop clone for voice |
| Generative Journey nodes | Not supported. Migrate to standard generative nodes. |
| Webhook header shows masked in the designer | Expected. Confirm with the /turn 401/400 check (P5). |
Expect roughly 1.3 to 2.5 seconds of added latency per turn from supervision.
Contacting support
Include the contact id, ACXD build id, Connect flow id, the host of your AiSwarmApiUrl (never the secret), a Validate screenshot, and your P5 status codes.