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 OpenClawA 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 |
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: CredentialUniquenessScopePlatformSettings 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_feedbackCapabilities 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.
InboundAdmissionContextsupplies 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
GLOBALPlugins 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-platformsEach 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.pyCover 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.