channel / overview
Every channel. One thread.
The problem it removes
Every product that talks to people ends up rebuilding the same machinery: a webhook per platform, a message table per provider, glue code to show "the conversation" in one place, and a fragile mapping between what the agent said and what was actually delivered. That machinery is undifferentiated — and it keeps breaking as platforms change.
Channel turns all of it into domain state. Inbound and outbound messages from every platform land as channel_messages records on the agent thread's context. Subscriptions, delivery locks and queues persist in the same domain. There is no message API to build and no sync job to babysit: UI reads InstantDB reactively, and the agent reacts to inbound messages like it reacts to anything else.
How it fits the Ekairos domains
agent_threads ──one── event_contexts ──many── event_items
│ │
└─many─ channel_messages ─one┘
(web · email · whatsapp · slack · ...)The agent domain owns the thread — a thin indirection over a context. The events domain owns the durable conversation state. Channel adds the communication surface: every message that crosses a platform boundary is recorded canonically and linked to that same context, so useThread returns the whole multichannel conversation with one reactive query.
What you write vs what you get
An application owns exactly two pieces of custom code:
1. A webhook mount → app/api/channels/[platform]/route.ts
2. A send endpoint → app/api/channels/send/route.tsEverything else is provided: platform delivery, canonical persistence, runtime state, reactive queries, and plug & play UI components that work the moment the schema is pushed — because they query channel_messages directly. That is the magic: the components depend on a schema, not on your backend.