Reviewed-on: MoA/agent#1 Co-authored-by: highperfocused <highperfocused@pm.me> Co-committed-by: highperfocused <highperfocused@pm.me>
6.1 KiB
Channels: Web UI, Slack, Matrix, and custom adapters
This document explains how channels work in the gateway and how to integrate transports like Web UI, Slack, Matrix, or your own chat source.
What is a "channel" in this project?
In this gateway, a channel is not a separate server object you provision.
A channel is simply an upstream conversation context (for example: a Slack thread, a Matrix room thread, or a browser chat tab) that maps to one stable conversationId.
The gateway uses that conversationId to keep one long-lived agent session.
How channels are represented internally
You have two integration options:
-
Direct chat endpoints
POST /v1/chatPOST /v1/chat/stream- You provide
conversationIddirectly.
-
Adapter endpoints (recommended for Slack/Matrix/etc.)
POST /v1/adapters/chatPOST /v1/adapters/chat/stream- You provide channel/thread fields; the gateway derives
conversationIdfor you.
Adapter ID format:
conversationId = source:workspaceId:channelId:threadIdadapterKey = source:workspaceId:channelId:threadId:userId
Field behavior on adapter routes:
source(optional, default:generic)workspaceId(optional, default:default)channelId(required)threadId(optional, default:root)userId(optional, default:anonymous)
Important constraints:
- segment values must be strings
channelIdmust not be empty- segment values must not contain
:
If your upstream IDs contain : (common in Matrix), sanitize/encode them before sending to adapter routes.
Do I need to "create" a channel first?
Usually: no.
A conversation/session is auto-created on first message. You can optionally pre-create one via:
POST /v1/conversations
But for most channel integrations, you just start sending messages to chat endpoints with stable IDs.
Requirements checklist (all channels)
- Gateway is running (
RUN_MODE=gateway) - Model/provider credentials are configured (
PROVIDER,MODEL,API_KEYor provider-specific key) - Your adapter/client can reach the gateway URL
- If
GATEWAY_AUTH_TOKENis set, sendAuthorization: Bearer <token> - For browser apps on another origin, set
GATEWAY_CORS_ORIGIN - If you want restart-safe sessions, set
SESSION_PERSIST=true
Web UI channel
Built-in UI
The built-in UI is available at GET / when:
GATEWAY_ENABLE_WEB_UI=true
How it works:
- sends messages to
/v1/chat/stream - stores
conversationIdin browser local storage - "New session" clears local
conversationIdand starts a new thread
Custom web app
If you build your own web frontend, call /v1/chat or /v1/chat/stream and choose your own ID format, e.g.:
web:<userId>:<chatId>
Example:
{
"conversationId": "web:user-42:chat-main",
"message": "Summarize the last response"
}
Slack channel integration
There is no built-in Slack bot in this repository; you run a small Slack adapter service.
Typical adapter needs:
- Slack bot token + app configuration (events/interactions)
- webhook/event receiver in your adapter
- code that forwards messages to gateway and posts replies back to Slack
Recommended mapping from Slack event payload:
source:"slack"workspaceId: Slack team/workspace ID (e.g.T123)channelId: Slack channel ID (e.g.C456) requiredthreadId: Slackthread_ts(or fallback strategy)userId: Slack user ID (e.g.U789)
Example request to gateway:
curl -N -X POST http://localhost:8787/v1/adapters/chat/stream \
-H 'content-type: application/json' \
-d '{
"source": "slack",
"workspaceId": "T123",
"channelId": "C456",
"threadId": "1712233.991",
"userId": "U789",
"message": "Please summarize this thread"
}'
Threading strategy tip:
- use a stable
threadIdper Slack thread to keep context isolated per thread - if you omit
threadId, gateway usesroot(all messages for that channel collapse into one thread context)
Matrix channel integration
There is no built-in Matrix bot in this repository; use a Matrix bot/bridge process as adapter.
Typical adapter needs:
- Matrix bot account + access token
- event listener for room messages
- logic to send assistant output back into room/thread
Recommended mapping:
source:"matrix"workspaceId: homeserver or deployment identifier (optional but useful)channelId: Matrixroom_id(required, encoded/sanitized for adapter route)threadId: Matrix thread root event ID (encoded/sanitized, or omit forroot)userId: Matrix sender ID (encoded/sanitized)
Because adapter segments cannot contain :, Matrix IDs should be encoded in the adapter, e.g.:
const encodeSegment = (value: string) => encodeURIComponent(value);
Example payload (encoded values):
{
"source": "matrix",
"workspaceId": "matrix.org",
"channelId": "%21roomid%3Amatrix.org",
"threadId": "%24rootEventId%3Amatrix.org",
"userId": "%40alice%3Amatrix.org",
"message": "What did we decide in this thread?"
}
Alternative: call /v1/chat directly and provide your own conversationId format if you do not want per-segment adapter normalization.
Custom channel template (Discord, Telegram, email, etc.)
For any source, map your upstream identifiers into adapter fields and keep that mapping stable.
Minimal payload:
{
"channelId": "your-channel-id",
"message": "Hello"
}
Full payload template:
{
"source": "custom",
"workspaceId": "tenant-a",
"channelId": "channel-123",
"threadId": "thread-456",
"userId": "user-789",
"message": "Hello"
}
Best practices
- Keep
threadIdstable per thread (do not generate random values per message) - Include
userIdfor audit/telemetry (adapterKey) even though it does not changeconversationId - Avoid
:in any adapter segment value - Use streaming endpoints for better UX (
/stream) - Persist sessions in production (
SESSION_PERSIST=true) - Add retry/backoff logic in your adapter for network failures
Related docs
- Gateway internals/API:
docs/gateway.md - Built-in browser UI:
docs/web-ui.md