Relay is a proactive contact center AI agent I built from scratch: deterministic NLU, slot filling, a vulnerable customer escalation policy, human handover with context attached, and a computed wrap up, all plain JavaScript with behavioral tests. This repo is the same scenario rebuilt on Cognigy.AI, the platform NiCE ships. Same conversation twice, one engine mine, one theirs, so you can read them side by side.
Talk to it live: the agent runs in a public webchat. Ask how long the power will be out, then mention an oxygen concentrator and watch the escalation fire. The link lives as long as the trial tenant does; the snapshot in this repo is what outlives it.
That loop is thirteen seconds of the escalation. The full video runs fifty one seconds in two halves: one unbroken session against the live Webchat endpoint, then cognigy run playbooks replaying all eight branches against the same tenant. Nothing inside the conversation is cut or sped up, and every count in the run report is read out of playbookRunResults.json rather than typed.
| Path | What it is |
|---|---|
agent/ |
The whole tenant pulled to disk with cognigy-cli: flows, intents, lexicons, endpoints with their TypeScript transformers. Mine are the Relay Outage Notice flow and the Medical Equipment lexicon. Everything else is the stock template a Cognigy trial ships with, left in place so the snapshot restores the tenant exactly as it ran |
playbooks/defs/ |
The regression suite as native Cognigy Playbooks, one file per conversation |
playbooks/playbooks.json |
The schedule file cognigy run playbooks consumes |
runs/ |
Run reports. 2026-08-02: 8 of 8 successful |
scripts/ |
The build scripts. Every resource in the tenant was created through the REST API from these, not by dragging nodes on a canvas |
docs/demo/ |
The demo video, and the GIF the section above embeds |
docs/tour/ |
The stills: webchat beats, flow canvas, NLU, lexicon, Playbook asserts, endpoints, CI |
.github/workflows/playbooks.yml |
Headless Playbook runs on a weekly schedule |
Hollowell Power & Light is replacing a forty year old transformer, so power on Brindle Row goes off Tuesday August 11 between 9:00 and 13:00. Wren, the assistant, opens the conversation before the customer notices anything, answers what the outage touches, offers SMS reminders, and the moment anyone mentions powered medical equipment it stops being a bot decision: that path escalates to a person, every time, because vulnerable customer handling is policy, never model judgment.
Eight intents (ask_duration, ask_reason, ask_compensation, sms_optin, medical_equipment, agent_request, thanks_done, greeting), 65 example sentences, and a Medical Equipment lexicon whose entries (oxygen concentrator, cpap machine, ventilator, dialysis machine, nebulizer, insulin pump, plus synonyms) fill an equipment slot the moment they appear in a message.
Start > Once, where On First Time announces the outage in two messages and Afterwards runs an intent switch with one case per intent and a clarify default. Three things the build taught me that the docs do not lead with:
- A case branch's content hangs off the case node's
next, not itschildren. Append a Say as a child and it becomes a dead subtree the engine never walks. I found this by diffing my chart's relation tree against the template project's. - A Say placed before the switch replays on every inbound message, because the whole flow runs per message. The Once node with On First Time and Afterwards children is the platform's idiom for open once, then route forever after.
- Slot filling is independent of intents. The
Medical Equipmentlexicon carries theequipmentslot on its own keyphrases, and the intent JSON has no reference to a lexicon or a slot anywhere in it. Any message containing a device fills the slot whatever intent wins, so the capture is in context even when someone mentions the device while asking about something else.relay-05-medical-escalation.jsonasserts the intent and the slot separately for that reason.
Copy config.example.json to config.json, fill in your API key and project id, then:
npm install
npm run playbooksExit code 0 means every conversation ran and every assertion held: intent matched, slot filled, exact reply text. playbookRunResults.json lands in the repo root with the step by step record. The GitHub Actions workflow runs the same suite on a weekly schedule and on demand; the first dispatched run came back green on 2026-08-02.
| In Relay, from scratch | Here, on the platform |
|---|---|
| Weighted keyword scorer with a 0.45 threshold | Intents trained on example sentences |
| Entity extractors in plain JavaScript | Lexicon entries filling the equipment slot |
| Escalation rules in the session reducer | The medical_equipment case routing to escalation copy |
18 node --test cases on the engine and its contract |
8 native Playbooks, 32 assertions on intent, slot, and text |
| Engine and scenario in git | This snapshot, pulled and restorable with cognigy-cli |
The annex on the Relay page walks the full translation, including the NICE CXone and Omilia columns.
The webchat, first the announce on first contact, then a routed answer, then the escalation:
![]() |
![]() |
![]() |
The backend behind it. The flow canvas with the Once split, the trained intent with its example sentences, and the lexicon:
The QA side. The suite in the tenant, a run opened to its assertions, the three endpoints, and the same suite green in GitHub Actions:
This runs in a Cognigy.AI trial tenant. The Webchat, Twilio SMS, and MCP Server endpoints all point at the flow; SMS needs a Twilio number wired in and the handover step speaks the escalation copy without a configured Live Agent behind it, since the trial has neither out of the box. The snapshot in agent/ is the durable artifact: the tenant can expire, the repo restores it.









