unify/gateway/)
is a single FastAPI application assembled by create_app() in
app.py
and launched with python -m unify.gateway serve (the CLI in
__main__.py
also provides doctor, urls, setup, smoke, and wizard
subcommands). It runs as a separate process from the
ConversationManager.
Channels vs adapters
The two sub-packages split along an ingress/egress line — but precisely:channels/hosts the outbound and admin APIs: sending messages, placing calls, provisioning numbers, managing watches and installs. Nearly all routes require the admin bearer token (admin_auth_dependencyincommon/auth.py). A few unauthenticated routes live here too where the provider calls back mid-flow (Twilio’s TwiML and conference-status callbacks).adapters/hosts inbound normalization: provider webhooks and internal Console routes, each validated (Twilio signatures, Slack signing secrets, OAuth state), resolved to a target assistant, and reduced to a{thread, event}envelope.
GatewayConnection in
channels/discord/gateway.py,
supervised by bot_manager.py) rather than a webhook — DMs always route,
guild messages only on @mention. And social verification
(channels/social/views.py)
is a synchronous Twilio send that returns the code to the caller — it never
publishes an envelope.
Channel modules
Each channel is a sub-package underchannels/ with a views.py exposing
its router(s):
| Channel | Highlights | External SDKs |
|---|---|---|
phone/ | send_call, send_text, create_phone_number (buys a Twilio number, wires webhooks, creates the matching LiveKit SIP trunk), dispatch_livekit_agent, hang-up and conference teardown | twilio, livekit.api |
whatsapp/ | send (freeform vs template by 24-hour window), send_call with call-permission handling, sender provisioning and pool assignment | Twilio WA, LiveKit SIP |
gmail/ | send_email (threading headers), watch_email (Pub/Sub push watch), attachment fetch | googleapiclient, google.oauth2 |
outlook/ | Same shape over Microsoft Graph subscriptions | msgraph via common/graph.py |
email/ | Provider-agnostic dispatcher — routes send_email to Gmail or Outlook based on how the assistant’s mailbox is connected | — |
teams/ | Chats, group chats, channels, watches, and meeting creation (create_meeting.py: instant links vs scheduled calendar events) | msgraph |
slack/ | Workspace install upsert, send_slack_message, user lookup by email | Slack Web API |
discord/ | send_discord_message, bot pool registration and status | Discord REST |
drive/ / sharepoint/ | File browsing for the workspace file picker (not messaging) | Google Drive API, msgraph |
unillm/ | chat_completions proxy authenticated by user API key (the one channel not admin-authed) | unillm |
Adapter modules
| Adapter | Webhooks handled | Envelope threads produced |
|---|---|---|
twilio.py | SMS, voice calls, call status, WhatsApp messages/reactions/calls/call status | msg, call, call_answered, call_not_answered, whatsapp, whatsapp_reaction, whatsapp_call, … |
google.py | Gmail Pub/Sub push notifications, Google OAuth callback and revoke | email |
microsoft.py | Outlook and Teams Graph notifications, Microsoft OAuth callback, a shared /microsoft/router delegator | email, teams_chat, teams_channel |
slack.py | Slack Events API, with a two-pass sender-identity resolution for coordinator routing and message dedup | slack |
internal.py | Console/runtime routes: chat messages, reactions, attachments, Unify Meet rings, system events, pre-hire chat logging, wake-ups | unify_message, unify_meet, api_message, unity_system_event, … |
adapters/common.py:
resolve the assistant (get_assistant), resolve the channel route where
pools are involved (resolve_phone_route, resolve_whatsapp_route — this
is how shared numbers map (pool number, sender) to the right assistant),
activate the runtime, then publish_runtime_event().
Credentials
unify/gateway/credentials/
holds operator infrastructure credentials — Twilio account, Slack
signing secret, OAuth client IDs, LiveKit keys — behind the
CredentialStore protocol (base.py), with EnvCredentialStore (env.py)
as the OSS implementation reading environment variables. The package
docstring draws the line explicitly: this is distinct from
unify.secret_manager, which holds assistant-owned user secrets
(per-assistant OAuth tokens like GOOGLE_ACCESS_TOKEN live in Orchestra
assistant secrets, not here).
Adding a channel: the seams
The gateway is designed so a new channel touches a predictable set of files:- Outbound — a new sub-package under
channels/with an admin-authenticated router; mount it via the prefix table increate_app(). - Inbound — a webhook handler under
adapters/that validates, resolves the assistant, and publishes an envelope with a newthreadkey (register it inKNOWN_THREADSinenvelopes.py). - Runtime — a typed event class in
events.py, an entry inCommsManager.events_map, and an@EventHandler.registerhandler — covered in the conversation runtime. - Send path — a method on
CommsPrimitivesplus its brain-tool wrapper.