Communication Channels
Atamaia's channel system connects AI identities to external communication platforms. Slack, Discord, Telegram, Matrix, Email, Home Assistant, n8n, webhooks, RSS feeds -- each is an adapter that plugs into a uniform send/receive architecture. Messages flow in through webhooks, get bridged to Atamaia's internal message system, and flow out through adapter-specific APIs.
This is not a chatbot framework. It is infrastructure for AI identities that exist across multiple platforms simultaneously, maintaining coherent conversation state through Atamaia's memory and identity systems.
Architecture
External Platform (Slack, Discord, etc.)
│
▼ webhook
ChannelsController.ReceiveWebhook (anonymous)
│
▼
ChannelService.ProcessWebhookAsync
├── Find connector by GUID
├── Find enabled inbound bindings
├── For each binding:
│ ├── Get adapter (IChannelInbound)
│ ├── Parse webhook payload → ChannelEnvelope
│ ├── Detect @mentions (IChannelMention)
│ ├── Bridge to internal Message system
│ ├── Record message mapping
│ └── Publish ChannelInboundEvent
└── Return
AI Identity decides to respond
│
▼
ChannelService.SendAsync
├── Find binding
├── Get adapter (IChannelOutbound)
├── Build ChannelEnvelope
├── Send via adapter
├── Record message mapping
└── Publish ChannelOutboundEvent
Core Concepts
Connector
An ExternalConnector represents a single set of credentials for an external platform. One Slack workspace bot token, one Discord bot, one Telegram bot API key. The connector holds the credentials (encrypted at rest). A connector can have many bindings.
Channel Binding
A ChannelBinding routes a specific external channel through a connector. One Slack workspace (connector) might watch 5 different Slack channels (5 bindings). Each binding specifies:
| Field | Description |
|---|---|
ConnectorId |
Parent connector (credentials) |
AdapterTypeKey |
Which adapter handles this binding (e.g. "slack", "discord", "telegram") |
ExternalChannelId |
The platform-specific channel identifier |
Name |
Human-readable name |
IdentityId |
Which AI identity responds on this channel |
BotExternalUserId |
The bot's user ID on the platform (for @mention detection) |
DirectionId |
Inbound, Outbound, or Bidirectional |
ChannelConfigJson |
Adapter-specific configuration |
Enabled |
Whether this binding is active |
ManagedBy |
"seed" (config-file managed) or null (UI-managed) |
Channel Envelope
The platform-agnostic message format:
public record ChannelEnvelope
{
public string Content { get; init; }
public string? CleanContent { get; init; } // Content with @mentions stripped
public string ExternalChannelId { get; init; }
public string? ExternalMessageId { get; init; }
public string? ExternalThreadId { get; init; }
public string? ExternalSenderName { get; init; }
public bool MentionsBot { get; init; }
public ChannelDirection Direction { get; init; }
public ChannelMessageType Type { get; init; } // Message, Edit, Delete, Reaction, Event
public long ConnectorId { get; init; }
public long ChannelBindingId { get; init; }
}
Channel Instance
Runtime context for adapter operations, built from the binding + connector:
public record ChannelInstance
{
public long ConnectorId { get; init; }
public long ChannelBindingId { get; init; }
public string AdapterTypeKey { get; init; }
public string ExternalChannelId { get; init; }
public string? BotExternalUserId { get; init; }
public long? IdentityId { get; init; }
public string? CredentialsJson { get; init; } // Decrypted at runtime
public string? ConnectorConfigJson { get; init; }
public string? ChannelConfigJson { get; init; }
}
Adapters
Each platform has an adapter implementing one or more interfaces:
Adapter Interfaces
| Interface | Methods | Purpose |
|---|---|---|
IChannelAdapter |
TypeKey, DisplayName, Category, Capabilities, ValidateConfig, TestConnection | Base metadata and lifecycle |
IChannelOutbound |
Send, Edit, Delete, React | Send messages to external platforms |
IChannelInbound |
ProcessWebhook, Poll | Receive messages from external platforms |
IChannelMention |
ContainsMention, StripMentions, FormatMention | @mention handling |
IChannelThreading |
ExtractThreadId, ApplyThreadTarget | Thread/reply support |
IChannelEvent |
Trigger, ProcessEventWebhook | Automation/event platforms |
Built-in Adapters
| Adapter | Category | Capabilities |
|---|---|---|
| Slack | Messaging | Send, Receive, Edit, Delete, React, Thread, Mention |
| Discord | Messaging | Send, Receive, Edit, Delete, React, Thread, Mention |
| Telegram | Messaging | Send, Receive, Edit, Delete, Thread |
| Matrix | Messaging | Send, Receive, Edit, Delete, Thread |
| Signal | Messaging | Send, Receive |
| Send, Receive | ||
| Webhook | Webhook | Send, Receive |
| Home Assistant | Automation | Send, Receive, Event |
| n8n | Automation | Send, Receive, Event |
| Feed (RSS/Atom) | Feed | Receive, Poll |
Adapter Categories
| Category | Description |
|---|---|
| Messaging | Conversational platforms (Slack, Discord, Telegram, Matrix, Signal) |
| Automation | Event-driven platforms (Home Assistant, n8n) |
| Email send/receive | |
| Webhook | Generic HTTP webhooks |
| Feed | RSS/Atom feed polling |
Capabilities
Each adapter declares its capabilities:
| Capability | Description |
|---|---|
| Send | Can send outbound messages |
| Receive | Can receive inbound messages |
| Edit | Can edit previously sent messages |
| Delete | Can delete messages |
| React | Can add emoji reactions |
| Thread | Supports threaded conversations |
| Mention | Supports @mention detection and formatting |
| Event | Can send/receive automation events |
| Poll | Can poll for new content (vs. webhook push) |
Message Bridge
The ChannelBridgeService connects external messages to Atamaia's internal message system.
Inbound Flow
When an external message arrives:
- Webhook hits
POST /api/channels/webhook/{connectorGuid}(anonymous -- no JWT required) - Service finds all enabled inbound bindings for the connector
- Each binding's adapter parses the webhook payload into a
ChannelEnvelope - If the adapter supports mentions and the binding has a
BotExternalUserId, detect @mentions - Bridge creates an internal
MessageviaIMessageService:- Content prefixed with
[{adapterType}:{bindingName}] {senderName}: {content} - Recipient is the binding's
IdentityId - Priority is
Importantif the message @mentions the bot, otherwiseNormal
- Content prefixed with
- Record a
ChannelMessageMapping(external message ID to internal message ID) - Publish a
ChannelInboundEventon the event bus
Outbound Flow
When an AI identity sends a message through a channel:
POST /api/channels/sendwithchannelBindingId,content, and optionalthreadId- Service finds the binding and its connector
- Adapter's
SendAsyncposts to the external platform - On success, record a
ChannelMessageMapping - Publish a
ChannelOutboundEventon the event bus
Message Mappings
Every message that crosses the bridge gets a mapping record:
GET /api/channels/bindings/{bindingId}/mappings
{
"id": 42,
"channelBindingId": 3,
"atamaiaMessageId": 156,
"externalMessageId": "1234567890.123456",
"externalThreadId": "1234567890.000001",
"direction": "Inbound",
"createdAtUtc": "2026-03-05T10:00:00Z"
}
This enables:
- Edit propagation (edit on Slack -> edit internal message -> edit on Discord)
- Delete propagation
- Thread correlation across platforms
- Audit trail of what was said where
Credential Security
Connector credentials are encrypted at rest using AES-256-GCM via ISecretEncryptor. The ChannelService decrypts credentials only at the moment of use:
private async Task<ChannelInstance> BuildInstanceAsync(ChannelBinding binding, ...)
{
string? decryptedCredentials = null;
if (connector?.CredentialsJson is not null)
{
try { decryptedCredentials = encryptor.Decrypt(connector.CredentialsJson); }
catch { decryptedCredentials = connector.CredentialsJson; }
}
// ... build instance with decrypted credentials
}
Credentials never appear in logs, API responses, or error messages.
Config-Managed vs UI-Managed Bindings
The ManagedBy field distinguishes between:
- Seed-managed (
ManagedBy = "seed"): Created and updated by theFeedSeedServicefrom a configuration file. The seed service can modify these without conflict. - UI-managed (
ManagedBy = null): Created or edited via the web UI or API. The seed service will not touch these.
When a user edits a seed-managed binding in the UI, ManagedBy is set to null, detaching it from the config file.
Event Bus Integration
Channel events are published to the IAtamaiaEventBus:
| Event | Published When |
|---|---|
ChannelInboundEvent |
External message received and bridged |
ChannelOutboundEvent |
Message sent to external platform |
These events carry:
TenantIdChannelBindingIdIdentityId(for inbound: which identity received it)AdapterTypeKeyExternalSenderName(inbound)Content(inbound, cleaned)MentionsBot(inbound)ExternalMessageId(outbound)
Agent runs subscribe to these events for real-time awareness of channel activity.
Agent Tool Integration
Agents can interact with channels through the tool system:
| Tool | Description |
|---|---|
channel_list |
List available channel bindings |
channel_send |
Send a message through a channel binding |
These are opt-in tools in the default policy -- agents must be explicitly granted channel access.
API Reference
| Method | Endpoint | Auth | Permission | Description |
|---|---|---|---|---|
| GET | /api/channels/bindings |
JWT | ChannelView | List bindings (filter by connectorId) |
| GET | /api/channels/bindings/{id} |
JWT | ChannelView | Get binding detail |
| POST | /api/channels/bindings |
JWT | ChannelCreate | Create binding |
| PUT | /api/channels/bindings/{id} |
JWT | ChannelEdit | Update binding |
| DELETE | /api/channels/bindings/{id} |
JWT | ChannelDelete | Soft delete binding |
| POST | /api/channels/send |
JWT | ChannelSend | Send message through binding |
| POST | /api/channels/webhook/{connectorGuid} |
None | -- | Receive webhook (anonymous) |
| GET | /api/channels/adapters |
JWT | ChannelView | List available adapters |
| GET | /api/channels/bindings/{bindingId}/mappings |
JWT | ChannelView | List message mappings |
Request/Response Examples
Create a Slack binding:
POST /api/channels/bindings
{
"connectorId": 1,
"adapterTypeKey": "slack",
"externalChannelId": "C0123456789",
"name": "#general",
"identityId": 2,
"botExternalUserId": "U0123456789",
"direction": "Bidirectional",
"channelConfigJson": "{\"unfurlLinks\": false}"
}
Send a message:
POST /api/channels/send
{
"channelBindingId": 3,
"content": "Research complete. Summary posted to the project docs.",
"threadId": "1234567890.000001"
}
Response:
{
"success": true,
"externalMessageId": "1234567890.123457"
}
List adapters:
GET /api/channels/adapters
[
{
"typeKey": "slack",
"displayName": "Slack",
"category": "Messaging",
"capabilities": ["Send", "Receive", "Edit", "Delete", "React", "Thread", "Mention"]
},
{
"typeKey": "discord",
"displayName": "Discord",
"category": "Messaging",
"capabilities": ["Send", "Receive", "Edit", "Delete", "React", "Thread", "Mention"]
},
{
"typeKey": "homeassistant",
"displayName": "Home Assistant",
"category": "Automation",
"capabilities": ["Send", "Receive", "Event"]
}
]