---
title: Add a communication platform
canonical: "https://agentbarn.dev/guides/develop/add-platform"
pubDate: "2026-08-29T00:00:00.000Z"
updatedDate: "2026-09-14T04:31:13.000Z"
author: Agent Barn
description: "Learn how to add a Platform Plugin with typed configuration, encrypted Connection credentials, provider admission, Delivery, optional capabilities, registry wiring, diagnostics, and gateway tests."
tags: [Develop and extend, How-to, Platform developers and maintainers, initiated delivery, default_delivery_target, communication platform, Platform Plugin, Communication Connection, Communications Gateway, messaging, webhook, durable delivery, provider ingress, PlatformPluginRegistry]
categories: [Guides, Develop and extend]
---

A communication platform is a trusted, code-owned Platform Plugin shipped with Agent Barn. An Agent can own zero or many Communication Connections, including multiple Connections using the same platform.

## Build at the Communications boundary

The Communications Gateway owns provider ingress, durable delivery, canonical Conversation Messages, and outbound provider delivery. Hermes and OpenClaw consume the same Runtime-neutral Communications protocol and do not implement provider transports.

```
Provider
  → shipped Platform Plugin
  → Communications Gateway
  → durable Communication Delivery and Conversation Message
  → runtime-neutral Communications protocol
  → Hermes or OpenClaw
```

A Platform Plugin is reviewed Agent Barn code, lives under `api/domains/communications/plugins/`, inherits from `PlatformPlugin` in `plugins/base.py`, and is registered explicitly in the code-owned `PlatformPluginRegistry`. Plugins are not dynamically uploaded or installed at runtime.

Slack, Telegram, Discord, and Microsoft Teams are shipped-plugin examples. The same plugin can serve multiple Connections and Agents subject to its credential-uniqueness contract.

## Keep messaging transport separate from tool integrations

| Communication platform | Tool Integration |
| --- | --- |
| Implemented by a shipped Platform Plugin | Implemented through credentials, Runtime tooling, and usually a bundled Skill |
| Carries inbound and outbound Agent messages | Gives an Agent tools for an external service |
| Configured as an Agent-owned Communication Connection | Configured through Agent Secrets or Shared Credentials |
| Owns provider settings, credentials, admission, normalization, and outbound delivery | Owns provider-specific tool authentication and commands |
| May use supervised sockets, polling, or a Connection-scoped webhook | Usually uses a CLI profile, OAuth flow, API token, or Runtime tool |
| Produces Conversation Messages and Communication Deliveries | Produces tool activity, not the Agent message transport |
| Independent of the selected Agent Runtime | May be materialized into the Runtime environment |

**Do not blur credentials and transport**

Do not add a messaging transport to `SecretProvider`. Do not add communication-platform fields to the Agent DTO. Platform credentials belong to Communication Connections and never become Agent Secrets, Shared Credentials, or Runtime provider credentials.

## Implement the Platform Plugin contract

```
key: str
display_name: str
setup_hint: str | None
post_setup_hint: str | None
schema_version: int
capabilities: frozenset[PlatformCapability]
settings_model: type[PlatformSettings]
credentials_model: type[PlatformCredentials]
credential_uniqueness_scope: CredentialUniquenessScope
```

`PlatformSettings` and `PlatformCredentials` are strict Pydantic models that reject unknown fields.

| Seam | Responsibility |
| --- | --- |
| `validate_external` | Validate credentials with the provider and return a safe external identity |
| `validate_configuration` | Validate settings and credentials and derive safe persistence metadata |
| `normalize_inbound` | Convert a provider event into canonical communication envelopes |
| `admit_inbound` | Apply provider-specific admission policy before durable acceptance |
| `enrich_inbound` | Resolve missing display names best-effort without blocking delivery |
| `send` | Deliver one normalized reply with a stable idempotency key |
| `run_ingress` | Run a supervised socket or polling session for one Connection |
| `verify_webhook` | Authenticate webhook ingress before normalization |
| `list_directory_entries` | Return safe provider directory choices for configuration |
| `processing_feedback` | Publish optional best-effort delivery-progress UX |
| `build_app_package` | Produce an optional provider application package without credentials |

Optional seams remain unsupported unless the corresponding capability is declared and tested.

## Declare capabilities and a minimal plugin shape

```
directory_discovery
application_provisioning
webhook_ingress
attachments
threads
mentions
processing_feedback
```

Capabilities are exposed through the Product API platform descriptor and drive generic UI behavior. For example, `directory_discovery` enables channel, user, guild, or role selection; `application_provisioning` enables a downloadable app package; `webhook_ingress` creates a generated webhook URL; and `processing_feedback` allows best-effort provider indicators without changing durable Delivery state.

```
class RelayChatSettings(PlatformSettings):
    # Define provider policy and routing fields.
    pass

class RelayChatCredentials(PlatformCredentials):
    # Define only credentials required by the provider.
    pass

class RelayChatPlatformPlugin(PlatformPlugin):
    key = "relaychat"
    display_name = "RelayChat"
    schema_version = 1
    settings_model = RelayChatSettings
    credentials_model = RelayChatCredentials
    capabilities = frozenset({...})
    credential_uniqueness_scope = CredentialUniquenessScope.GLOBAL

    def validate_external(self, settings, credentials) -> str | None:
        ...

    def normalize_inbound(self, settings, payload) -> InboundAdmissionResult:
        ...

    def send(self, settings, credentials, envelope, *, idempotency_key: str) -> str:
        ...
```

A socket or polling provider additionally implements `run_ingress`. A webhook provider implements `verify_webhook` and declares `WEBHOOK_INGRESS`. Do not add Runtime factories, Agent builders, Kubernetes provider secrets, or Runtime-specific channel branches.

## Normalize inbound messages before persistence

```
schema_version
provider_message_id
occurred_at
location
sender
text
mentions
attachments
reply_to_provider_message_id
provider_metadata

location
├── id
├── type: CHANNEL or DM
├── display_name
└── thread_id
```

-   Provider message identity must be stable enough for idempotent acceptance.
-   Provider payloads normalize before durable persistence.
-   Admission returns a typed disposition such as accepted, mention required, user denied, channel denied, ignored, or malformed.
-   Denied or ignored events do not create Communication Deliveries.
-   `InboundAdmissionContext` supplies durable conversation or thread ownership; plugins do not query SQL or keep authoritative ownership only in memory.
-   Provider payload content and credentials never enter operational diagnostics.

## Use the generic Communication Connection model

Platform configuration is stored in the generic `CommunicationConnection` model. A new Platform Plugin normally needs no provider-specific database table or Agent schema migration; add a migration only when the shared Communications persistence contract changes.

A Connection records Organization and Agent ownership, `platform_key`, operator-facing name, enabled state, plugin schema version, validated settings, encrypted credentials, safe external identity, credential fingerprint and scope, observed provider status, optimistic-concurrency revision, and retirement time.

-   An Agent can have multiple Connections, including multiple Connections for one platform; active Connection names are unique for that Agent.
-   Credentials are encrypted independently of Agent Secrets and omitted from read responses.
-   Updates use the Connection revision for optimistic concurrency; retiring one releases its credential identity.
-   Connection changes reconcile the provider session without rebuilding or restarting the Agent Runtime.

### Enforce credential uniqueness safely

```
NONE
AGENT
ORGANIZATION
GLOBAL
```

Plugins select the scope and derive a non-secret credential fingerprint. Shared persistence enforces uniqueness with the platform key, scope key, and fingerprint. The fingerprint is equality metadata, not an authentication credential; never log or return it.

## Run ingress and outbound delivery in Communications

### Supervised ingress

For sockets, gateways, or polling, implement `run_ingress`, emit payloads through the supplied callback, and invoke the connected callback when usable. `PlatformIngressSupervisor` manages enabled Connections, using database ingress leases so one Communications replica owns each session with bounded retry and backoff.

### Webhook ingress

Declare `WEBHOOK_INGRESS`, implement `verify_webhook`, and configure the provider to call:

```
/communications/v1/webhooks/{connection_id}
```

The gateway resolves the Connection, loads its plugin, decrypts and validates credentials, delegates authentication, applies admission and normalization, then persists accepted messages and Deliveries.

Runtime replies return through the Communications protocol as outbound Deliveries. The processor leases the next eligible Delivery, resolves the Connection and plugin, decrypts credentials, calls `send` with a deterministic non-secret idempotency key, records success or failure, and preserves Conversation ordering. The plugin returns the provider message ID after successful delivery.

### Implement initiated delivery deliberately

`AGENT_INITIATED_DELIVERY` is an optional Platform capability. Slack implements it; other shipped plugins do not yet advertise it. A supporting plugin must resolve outbound targets, enforce its Connection policies, and revalidate the resolved target before provider delivery. The `AgentInitiatedDeliverySettings` mixin supplies an optional `default_delivery_target`; a setting or capability declaration by itself is not an implementation.

The shared acceptance service owns Agent/Connection ownership checks, execution context, allowed destination forms, idempotency, and atomic message/Delivery persistence. An interactive execution may use only an explicit target on its inbound Connection. A scheduled execution may use its recorded origin or configured default. Preserve those boundaries when adding a plugin, and add contracts for target ambiguity, permission failures, changed policies, retries, and cross-Organization isolation.

## Expose Connection policy through generic UI

Plugins apply provider-specific group, channel, user, role, direct-message, mention, and thread policies before durable acceptance. Defaults should fail closed unless the product contract explicitly says otherwise. Policies are Connection-owned, so one Agent’s Connections can differ; thread ownership is Connection-scoped, and Slack thread behavior is not a universal provider rule.

```
GET /api/v1/organizations/{organization_id}/communication-platforms
```

Each descriptor supplies `key`, `display_name`, setup hints, `schema_version`, capabilities, `settings_schema`, and `credentials_schema`. The Connection interface renders generic settings and credential fields from these schemas.

A new platform may still need an icon or provider label, directory mappings, specialized setup guidance, capability-specific controls, an application-package action, or provider fixtures. Do not add it to the Agent hiring flow, Agent platform union, Runtime selector, or Runtime compatibility UI.

## Register and test the shipped plugin

Register the plugin in `api/infrastructure/app.py` when constructing `PlatformPluginRegistry`. The registry rejects empty, non-canonical, duplicate keys and non-positive schema versions, requires lowercase canonical keys, returns descriptors in stable key order, and is the release’s authoritative Platform catalogue.

```
api/tests/unit/test_communications_plugins.py
api/tests/unit/test_communications_gateway.py
api/tests/unit/test_communications_supervisor.py
api/tests/integration/test_communication_connections.py
api/tests/integration/test_communication_deliveries.py
```

Cover registry metadata and duplicate keys; strict models; external validation and safe identity; credential encryption and redaction; uniqueness; Connection creation, update, revision conflict, retirement, and authorization; normalization and typed admission; supported policies; idempotent acceptance; outbound idempotency; webhook or supervised ingress; lease, reconnect, and retry behavior; failure categorization without leaks; optional capabilities; and gateway behavior with both Runtimes through the shared protocol.

## Related guides

-   [Communication Connections](/guides/agents/communication-connections) and [channel access](/guides/agents/channel-access)
-   [Develop against the API](/guides/develop/api) and [Domain Events](/guides/develop/domain-events)
-   [Agent Lifecycle](/guides/agents/lifecycle) and [Health and Logs](/guides/agents/health-and-logs)
