Develop and extend
How-to

Add a communication platform

Add a Platform Plugin across typed configuration, encrypted Connection credentials, provider admission, outbound Delivery, optional directory or webhook support, registry wiring, diagnostics, and gateway tests.

For
Platform developers and maintainers
On this page
  1. Build at the Communications boundary
  2. Keep messaging transport separate from tool integrations
  3. Implement the Platform Plugin contract
  4. Declare capabilities and a minimal plugin shape
  5. Normalize inbound messages before persistence
  6. Use the generic Communication Connection model
  7. Run ingress and outbound delivery in Communications
  8. Expose Connection policy through generic UI
  9. Register and test the shipped plugin
  10. Related guides

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.

Text
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 platformTool Integration
Implemented by a shipped Platform PluginImplemented through credentials, Runtime tooling, and usually a bundled Skill
Carries inbound and outbound Agent messagesGives an Agent tools for an external service
Configured as an Agent-owned Communication ConnectionConfigured through Agent Secrets or Shared Credentials
Owns provider settings, credentials, admission, normalization, and outbound deliveryOwns provider-specific tool authentication and commands
May use supervised sockets, polling, or a Connection-scoped webhookUsually uses a CLI profile, OAuth flow, API token, or Runtime tool
Produces Conversation Messages and Communication DeliveriesProduces tool activity, not the Agent message transport
Independent of the selected Agent RuntimeMay be materialized into the Runtime environment

Implement the Platform Plugin contract

Python
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.

SeamResponsibility
validate_externalValidate credentials with the provider and return a safe external identity
validate_configurationValidate settings and credentials and derive safe persistence metadata
normalize_inboundConvert a provider event into canonical communication envelopes
admit_inboundApply provider-specific admission policy before durable acceptance
enrich_inboundResolve missing display names best-effort without blocking delivery
sendDeliver one normalized reply with a stable idempotency key
run_ingressRun a supervised socket or polling session for one Connection
verify_webhookAuthenticate webhook ingress before normalization
list_directory_entriesReturn safe provider directory choices for configuration
processing_feedbackPublish optional best-effort delivery-progress UX
build_app_packageProduce 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

Text
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.

Illustrative RelayChat plugin
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

Text
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

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

Text
/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.

Shipped platform catalogue
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.

Test surfaces
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.

Documentation