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
Email Email 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 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:

  1. Webhook hits POST /api/channels/webhook/{connectorGuid} (anonymous -- no JWT required)
  2. Service finds all enabled inbound bindings for the connector
  3. Each binding's adapter parses the webhook payload into a ChannelEnvelope
  4. If the adapter supports mentions and the binding has a BotExternalUserId, detect @mentions
  5. Bridge creates an internal Message via IMessageService:
    • Content prefixed with [{adapterType}:{bindingName}] {senderName}: {content}
    • Recipient is the binding's IdentityId
    • Priority is Important if the message @mentions the bot, otherwise Normal
  6. Record a ChannelMessageMapping (external message ID to internal message ID)
  7. Publish a ChannelInboundEvent on the event bus

Outbound Flow

When an AI identity sends a message through a channel:

  1. POST /api/channels/send with channelBindingId, content, and optional threadId
  2. Service finds the binding and its connector
  3. Adapter's SendAsync posts to the external platform
  4. On success, record a ChannelMessageMapping
  5. Publish a ChannelOutboundEvent on 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 the FeedSeedService from 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:

  • TenantId
  • ChannelBindingId
  • IdentityId (for inbound: which identity received it)
  • AdapterTypeKey
  • ExternalSenderName (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"]
  }
]