A communication platform carries messages between people and an Agent. Adding one requires more than accepting a bot token: Agent Barn must persist a typed configuration, enforce runtime compatibility, generate safe Hermes or OpenClaw resources, control who can invoke the Agent, ingest conversation activity, expose health, and provide a complete onboarding and configuration experience.
This guide covers communication transports such as Slack, Microsoft Teams, Telegram, and Discord. For external tools such as GitHub, Jira, Confluence, Google Workspace, or Pipedrive, use Add an integration.
- Audience
- Platform developers and maintainers
- Repository
agent-barn- Deployment namespace
- Existing deployment resources continue to use the
agent-farmnamespace conventions.
What you will accomplish
By the end of this guide, you will be able to:
- Distinguish a communication platform from an external provider integration
- Define a runtime and transport compatibility contract
- Add typed, encrypted platform configuration
- Extend Agent creation, updates, reads, start, stop, and deletion
- Generate platform configuration for Hermes, OpenClaw, or both
- Enforce channel, group, direct-message, and mention policies
- Add a provider-authenticated webhook when the platform requires one
- Preserve platform identity in telemetry and conversation activity
- Extend runtime health checks and monitoring
- Add onboarding and configuration UI
- Verify the platform through unit, API, browser, runtime, and Kubernetes tests
Before you begin
You should have:
- A working Agent Barn development environment
- Docker for migrated PostgreSQL integration tests
- Access to a non-production account on the target platform
- Test application or bot credentials
- The platform’s official bot, messaging, webhook, and authorization documentation
- A confirmed integration mechanism in Hermes, OpenClaw, or both
- A clear decision about direct messages, groups, channels, mentions, and identity restrictions
Read these guides first:
Understand the platform boundary
A communication platform and an integration belong to different contracts.
| Communication platform | External provider integration |
|---|---|
| Carries inbound and outbound Agent messages | Gives the Agent tools for an external service |
| Stored as Agent.platform | Stored as an Agent Secret provider |
| Has dedicated platform configuration | Uses a provider-specific credential content model |
| Participates directly in Agent startup | Is materialized after platform runtime setup |
| Controls groups, channels, DMs, and mentions | Controls service scopes and tool capabilities |
| May require a public webhook or persistent connection | Usually uses a CLI profile, OAuth token, or runtime capability |
| Determines conversation identity and routing | Does not determine the Agent’s primary message transport |
Current runtime matrix
| Runtime | Slack | Teams | Telegram | Discord |
|---|---|---|---|---|
| Hermes | Yes | No | Yes | Yes |
| OpenClaw | Yes | Yes | Yes | Yes |
Runtime and platform are separate persisted choices:
agent.agent_type → openclaw or hermes
agent.platform → slack, teams, telegram, or discordA new platform does not have to support both runtimes. It must, however:
- Declare the supported combinations
- Reject unsupported combinations in the API
- Hide or disable incompatible choices in the web app
- Document the resulting matrix
- Test every supported combination
- Test rejection of every unsupported combination
Do not silently fall back to a different runtime.
Plan the platform contract
Before editing AgentPlatform, define the complete platform contract.
Transport
Decide how messages reach the runtime:
- Persistent socket connection
- Long polling
- Provider webhook
- Public API callback relayed to the Agent pod
- A runtime-native gateway plugin
For a webhook platform, decide:
- Which public URL the provider calls
- Which component validates the provider signature or bearer credential
- Whether the API handles the event or proxies it to the runtime
- Which headers must be preserved
- Which internal Service port receives the request
- How retries and duplicate deliveries are handled
- What happens when the Agent is stopped
- Whether the callback URL depends on API_EXTERNAL_URL
Credentials
Identify:
- Required credentials
- Optional credentials
- Which values are secret
- Which values may be safely returned
- Whether credentials can be validated before persistence
- Whether one bot credential may be used by multiple Agents
- Whether rotation changes the external bot identity
- Whether credentials are runtime-specific
Message access
Define independent policies for:
- Shared groups, workspaces, guilds, or servers
- Individual channels or chats
- Direct messages
- Allowed users
- Allowed roles
- Home or alert destinations
- Mention requirements
- Thread or reply behavior
Use fail-closed defaults unless the product contract deliberately requires open access.
Lifecycle
Decide:
- Whether creation leaves the Agent stopped
- Whether the canonical UI starts it during provisioning
- Whether API creation itself auto-starts it
- What credential failures produce 400 versus ERROR
- Which platform settings can be changed
- Whether changes require Apply or Apply & Restart
- How stop, delete, and credential rotation affect the external connection
Activity
Define how the runtime identifies:
- Message ID
- Session key
- Channel or chat ID
- Channel display name
- Thread ID
- Sender ID
- Sender display name
- Conversation type
- Inbound versus outbound direction
- Tool-call identity
Do not derive these rules after deployment from whatever happens to appear in logs.
Add the persisted configuration
The authoritative Agent platform models live in api/domains/agents/models.py. A platform addition normally needs:
- A new AgentPlatform value
- A dedicated database configuration model
- Create fields
- Update fields
- A read-safe configuration DTO
- A field on AgentRead
- Compatibility validation between platform and runtime
class AgentPlatform(str, enum.Enum):
# Existing platforms...
RELAYCHAT = "relaychat"Use a dedicated configuration table:
class AgentRelayChatConfig(BaseModel, table=True):
__tablename__: str = "agent_relaychat_config"
agent_id: UUID = SqlField(
foreign_key="agent.id",
nullable=False,
unique=True,
ondelete="CASCADE",
)
bot_token_encrypted: str = SqlField(nullable=False)
workspace_ids: list[str] = SqlField(
default_factory=list,
sa_column=Column(sa.JSON(), nullable=False, server_default="[]"),
)
channel_ids: list[str] = SqlField(
default_factory=list,
sa_column=Column(sa.JSON(), nullable=False, server_default="[]"),
)
allowed_user_ids: list[str] = SqlField(
default_factory=list,
sa_column=Column(sa.JSON(), nullable=False, server_default="[]"),
)
group_policy: str = SqlField(
default="allowlist",
sa_column=Column(sa.String(), nullable=False, server_default="allowlist"),
)
dm_policy: str = SqlField(
default="off",
sa_column=Column(sa.String(), nullable=False, server_default="off"),
)Prefer platform-specific policy enums when the values form a real closed contract.
class RelayChatGroupPolicy(str, enum.Enum):
OPEN = "open"
ALLOWLIST = "allowlist"
class RelayChatDmPolicy(str, enum.Enum):
OFF = "off"
OPEN = "open"
ALLOWLIST = "allowlist"Separate secrets from safe configuration
The read DTO must exclude secrets.
class AgentRelayChatConfigRead(PydanticBaseModel):
model_config = ConfigDict(from_attributes=True)
workspace_ids: list[str]
channel_ids: list[str]
allowed_user_ids: list[str]
group_policy: RelayChatGroupPolicy
dm_policy: RelayChatDmPolicy
bot_username: str | None = NoneExtend create and update contracts
Add platform-specific fields to AgentCreate and AgentUpdate.
class AgentCreate(PydanticBaseModel):
relaychat_bot_token: str | None = Field(default=None, min_length=1)
relaychat_workspace_ids: list[str] = Field(default_factory=list)
relaychat_channel_ids: list[str] = Field(default_factory=list)
relaychat_allowed_user_ids: list[str] = Field(default_factory=list)
relaychat_group_policy: RelayChatGroupPolicy = RelayChatGroupPolicy.ALLOWLIST
relaychat_dm_policy: RelayChatDmPolicy = RelayChatDmPolicy.OFFValidate required credentials and runtime compatibility in the request model.
@model_validator(mode="after")
def validate_platform_credentials(self):
if (
self.platform == AgentPlatform.RELAYCHAT
and self.agent_type not in {AgentType.OPENCLAW}
):
raise ValueError("RelayChat is supported only by OpenClaw")
if (
self.platform == AgentPlatform.RELAYCHAT
and not self.relaychat_bot_token
):
raise ValueError(
"relaychat_bot_token is required for RelayChat agents"
)
return selfReject fields belonging to another platform during updates. A Slack Agent must not silently accept RelayChat configuration, and a RelayChat Agent must not silently accept Slack fields.
Preserve platform immutability
The platform is selected when the Agent is created. It is not changed through AgentUpdate. Moving an existing Agent to another platform would require:
- A new credential contract
- Removal of old platform configuration
- Creation of new configuration
- Potential runtime compatibility changes
- Conversation-history interpretation
- External bot lifecycle handling
- A transaction covering the complete transition
Do not introduce platform mutation as a side effect of adding a new platform.
Add the database migration
A new communication platform requires an Alembic migration. The migration normally:
- Replaces ck_agent_platform with a constraint that includes the new value
- Creates the platform configuration table
- Adds a foreign key to agent.id with ON DELETE CASCADE
- Makes agent_id unique
- Adds indexes or constraints required by the platform
- Defines safe server defaults for JSON collections and policies
- Provides a deliberate downgrade
An illustrative constraint change is:
op.drop_constraint("ck_agent_platform", "agent", type_="check")
op.create_check_constraint(
"ck_agent_platform",
"agent",
(
"platform IN "
"('slack', 'teams', 'telegram', 'discord', 'relaychat')"
),
)The current agent.platform column is a bounded string. Confirm that the new persisted value fits its database length, or widen the column in the migration.
Decide whether bot identities must be unique
Slack and Discord enforce distinct active bot tokens across Agents. Their configuration tables store:
- The encrypted token for runtime use
- A SHA-256 token hash for equality checks
- A unique partial index over non-null hashes
The hash is not a credential substitute. It exists only to enforce uniqueness without comparing decrypted values.
If a platform prohibits one bot identity from serving multiple Agent deployments, add the same kind of database-backed invariant. Do not rely only on an application pre-check; concurrent requests must still be stopped by the database.
Translate the named uniqueness constraint into a stable 409 Conflict.
Extend Agent lifecycle orchestration
Agent lifecycle behavior is orchestrated in api/domains/agents/service.py. The current implementation uses explicit platform branches. Keep the new behavior typed and locally discoverable rather than introducing an untyped configuration blob.
Add service field groups
Define the platform’s complete update field set.
_RELAYCHAT_CONFIG_FIELDS = frozenset(
{
"relaychat_bot_token",
"relaychat_workspace_ids",
"relaychat_channel_ids",
"relaychat_allowed_user_ids",
"relaychat_group_policy",
"relaychat_dm_policy",
}
)Add secret-bearing fields to _CREDENTIAL_FIELDS so rotating them requires agent.secret.manage.
_CREDENTIAL_FIELDS = frozenset(
{
# Existing credential fields...
"relaychat_bot_token",
}
)Routing and access-policy fields use agent.update. Credential mutation additionally requires agent.secret.manage.
Extend creation
During creation:
- Require agent.create
- Validate runtime compatibility
- Validate credentials before persistence when possible
- Validate any global bot-identity uniqueness rule
- Create the Organization-owned Agent and creator access atomically
- Encrypt credentials
- Persist the platform configuration
- Persist Skills and integrations
- Follow the chosen start behavior
- Return only read-safe configuration
Do not leave a partially created Agent if platform configuration persistence fails. Follow the existing cleanup or explicit transaction pattern appropriate to the surrounding creation flow.
Extend reads and lists
Update:
- Detail hydration
- Paginated list hydration
- Platform-specific bulk repository reads
- AgentRead
- Safe platform configuration mapping
- UI Zod schemas
- Test fixtures
List behavior must remain Organization- and Agent Access-scoped before count and pagination.
Extend updates
For stopped Agents:
- Reject fields belonging to a different platform
- Revalidate rotated credentials
- Encrypt new secret values
- Preserve omitted secrets
- Update only explicitly supplied fields
- Recheck any uniqueness invariant
- Persist routing and policy fields
- Return read-safe configuration
Running Agent configuration changes use the existing Apply & Restart workflow. The API still independently rejects direct updates while the Agent is running.
Extend start
Add a platform branch that:
- Loads the platform configuration
- Fails clearly if the configuration row is missing
- Decrypts credentials
- Revalidates credentials when start-time validation is part of the contract
- Builds runtime-specific configuration
- Builds a Kubernetes Secret
- Builds a Service with any required ports
- Selects the proper Deployment builder
- Continues through shared integrations, Skills, ingest, and Kubernetes assembly
A recoverable credential or connection failure may move the Agent to ERROR. A successful later start clears the previous error.
Extend stop and delete
Verify that stop and delete:
- Snapshot logs where applicable
- Remove active Kubernetes resources
- Preserve historical Agent identity and activity
- Release token uniqueness state when the Agent is deleted
- Do not log credentials
- Preserve lifecycle Domain Event behavior
- Return the expected status and authorization errors
Build runtime configuration
Runtime builders live under api/domains/agents/builders/. Add explicit builders for each supported runtime.
OpenClaw
An OpenClaw platform normally needs:
- A channel configuration block
- A binding to the main Agent
- A secret builder
- Environment variable names expected by the runtime plugin
- Plugin installation or restoration
- Mention, group, DM, and identity policy mapping
- Telemetry compatibility
- Health behavior
An illustrative configuration builder is:
def build_openclaw_config_overlay_relaychat(
model: str,
litellm_base_url: str,
channel_ids: list[str] | None = None,
allowed_user_ids: list[str] | None = None,
group_policy: str = "allowlist",
dm_policy: str = "off",
approval_mode: str = "auto",
) -> dict:
return _openclaw_config_core(
model,
litellm_base_url,
binding_channel="relaychat",
channels={
"relaychat": {
"enabled": True,
"groupPolicy": group_policy,
"dmPolicy": dm_policy,
"allowFrom": allowed_user_ids or [],
"requireMention": True,
"channels": {
channel_id: {
"enabled": True,
"requireMention": True,
}
for channel_id in (channel_ids or [])
},
}
},
)The Secret builder should include only required runtime values:
def build_secret_relaychat(
agent_id: UUID,
org_id: UUID,
namespace: str,
relaychat_bot_token: str,
litellm_api_key: str,
litellm_base_url: str,
) -> client.V1Secret:
return client.V1Secret(
metadata=client.V1ObjectMeta(
name=_resource_name(agent_id),
namespace=namespace,
labels=_labels(agent_id, org_id),
),
string_data={
"RELAYCHAT_BOT_TOKEN": relaychat_bot_token,
"LITELLM_API_KEY": litellm_api_key,
"LITELLM_BASE_URL": litellm_base_url,
"AGENT_PLATFORM": "relaychat",
},
)Register new builder exports in api/domains/agents/builders/__init__.py.
If the connector is an optional OpenClaw package, update:
- The pinned base image
- Runtime initialization
- Startup installation or restoration
- Base-image smoke tests
- Workflow contract checks
Hermes
A Hermes platform normally needs:
- A platform-specific Hermes configuration section
- Display configuration
- Platform credentials in the Kubernetes Secret
- AGENT_PLATFORM
- Access-control plugins when upstream controls are insufficient
- ConfigMap inclusion for those plugins
- Startup installation
- Health and circuit-breaker awareness
- Pinned-image verification
Hermes access-control plugins should fail closed. Missing IDs, malformed events, or unavailable configuration must not accidentally allow messages.
Keep secrets out of the ConfigMap
Kubernetes Secret data may contain
- Bot tokens
- App tokens
- App passwords
- Signing secrets
- Runtime API keys
- Ingest keys
- LiteLLM keys
ConfigMaps may contain
- Runtime configuration without credential values
- Access policies
- Setup scripts that read environment variables
- Plugin source
- Rendered Agent Markdown
- Skill manifests
Enforce message access controls
Every supported platform must define safe shared-room behavior.
Mention gating
In a shared channel or group, an Agent responds only when explicitly mentioned.
A fresh mention is required for each shared-room message whenever the runtime can enforce it. Previous thread participation must not grant indefinite permission to respond.
Direct messages are exempt from mention gating but remain subject to the direct-message policy.
Do not rely on runtime defaults. Generate the setting explicitly so an upstream default change cannot silently open access.
Group and channel boundaries
Treat these as separate decisions:
- Which workspaces, servers, guilds, or groups are allowed
- Which channels or chats inside them are allowed
- Whether the group policy is open or allowlisted
- Whether an empty allowlist denies all or means unrestricted
Document and test the empty-list behavior.
User and role boundaries
A platform may support:
- Explicit user IDs
- Role IDs
- Organization or workspace membership
- Open user access inside an allowed channel boundary
If open user access is disabled, require at least one allowed user or role. Enforce this in both request validation and runtime configuration.
Direct messages
Use an explicit policy:
| Policy | Behavior |
|---|---|
off | Ignore all direct messages |
allowlist | Accept direct messages only from configured users |
open | Accept direct messages from any platform user who can reach the bot |
Do not represent off as an undocumented empty allowlist unless the runtime mapping and tests prove that behavior.
Runtime parity
When both runtimes support the platform, verify that the same product policy produces equivalent behavior even when runtime configuration differs. Equivalent product behavior is more important than identical configuration syntax.
Add inbound message delivery
Choose one delivery topology.
Persistent connection or polling
For socket or polling transports:
- The runtime connects directly to the platform
- Credentials are injected into the pod
- No public Agent callback route is required
- Start-time credential validation should fail clearly
- Connection loss must be visible through health
Provider webhook
For webhook transports, add a thin public route and keep platform workflow in the service or runtime. A route may:
- Read the raw body
- Preserve the headers needed for authentication
- Delegate to the service
- Return the proxied status, body, and content type
The service must:
- Resolve the Agent without crossing tenant boundaries
- Return 404 for missing, deleted, or wrong-platform Agents
- Reject delivery while the Agent is not running
- Identify the correct internal Agent Service
- Proxy to the platform-specific port and path
- Preserve only required headers
- Bound request size and timeout according to the provider contract
- Ensure that the request is authenticated either before proxying or by the runtime receiving it
Public callback URL
If the platform needs a callback URL, derive it from the configured external API URL:
https://agent-barn.example.com/api/v1/webhooks/<platform>/<agent-id>/messagesReturn the URL as safe Agent metadata when operators must copy it into the provider console.
If the Agent pod needs a new webhook port, extend the shared Service builder with a named port. Do not expose the pod directly through a separate public ingress when the API relay is the intended boundary.
Extend telemetry and activity
Both runtimes report normalized messages and tool-call state to the Ingest API using a fresh per-start ingest key. A platform addition must preserve:
- Agent ID
- Message ID
- Session key
- Conversation type
- Direction
- Channel or chat identity
- Thread identity
- Sender identity
- Occurrence time
- Tool-call external identity
Runtime telemetry
If the existing telemetry plugin already observes the new channel, add contract fixtures proving its payloads.
If the runtime emits a different record shape:
- Extend the runtime-side normalization
- Validate posted payloads against the real Ingest models
- Cover inbound and outbound messages
- Cover threads and direct messages
- Cover pending and completed tool calls
- Run the contract inside the pinned runtime image when behavior depends on upstream internals
Conversation parsing
Inspect api/domains/conversations/. Update platform-specific session prefixes or fallback parsers when required. Do not add parser branches when the normalized push path already provides the complete contract.
Identity enrichment
Agent Barn enriches missing user and channel names best-effort. If the platform offers safe directory lookup:
- Add a small infrastructure client
- Decrypt the platform credential only at the service boundary
- Resolve only missing IDs
- Bound timeouts
- Cache provider lookups when appropriate
- Treat enrichment failure as non-fatal
- Avoid logging tokens or full provider responses
Update both Ingest-time enrichment and Conversation read-time enrichment when the platform needs them.
Teams currently provides no equivalent directory enrichment. A new platform may also deliberately return stored IDs when no safe lookup exists.
Add health and observability
A platform is incomplete if operators cannot distinguish a healthy Agent from a disconnected bot. Review:
api/domains/agents/scripts/hermes/healthz-server.py
api/domains/agents/scripts/openclaw/healthz-server.jsExtend health behavior to cover:
- Missing credentials
- Invalid or revoked credentials
- Runtime channel connection state
- Platform circuit-breaker state
- Manual pause versus failed connection
- Provider timeouts
- Unsupported AGENT_PLATFORM values
Health checks must not mutate provider state.
When the platform uses Hermes, preserve the distinction between:
- A recoverable platform connection failure
- A circuit breaker opened by repeated failures
- A platform intentionally paused by an operator
Update:
- Health unit tests
- Runtime builder tests
- Kubernetes readiness behavior
- Base-image smoke tests
- Platform-specific metrics only when they express a stable operational contract
The Agent Service and monitoring discovery should continue using the stable Agent Service labels. Do not create a new monitoring topology solely for one communication platform.
Add the web app experience
A platform contribution must update every web app boundary that treats the platform as a closed union.
API schemas and types
Update:
ui/src/features/agents/schemas.ts
ui/src/features/agents/hooks/use-create-agent.ts
ui/src/features/agents/hooks/use-update-agent.tsAdd the platform to:
AgentSchema.platformCreate request platform unionsUpdate request fieldsPlatform-specific read schemaAgent response schemaTest fixtures
Hiring flow
Update:
ui/src/features/agents/components/hire-dialog.tsx
ui/src/features/agents/components/hire-dialog-steps.tsxAdd:
- Platform choice
- Runtime compatibility behavior
- Credential step
- Provider setup instructions
- Required-field validation
- Channel and DM policy defaults
- Create payload mapping
- Provisioning behavior
- Completion behavior
- Accessible labels
- Playwright coverage
If the platform requires a downloadable application manifest, generate only fields grounded in the provider specification. Treat URLs, permission IDs, scopes, and icon requirements as versioned external contracts.
Agent configuration
Update:
ui/src/features/agents/components/agent-channel-settings.tsx
ui/src/features/agents/components/agent-keys-settings.tsxProvide:
- Read-only configuration summary
- Credential rotation
- Channel or group editing
- User and role restrictions
- Direct-message policy
- Callback endpoint display when applicable
- Apply or Apply & Restart behavior
- Permission-aware controls
Reuse the shared shadcn Select primitives for user-facing selectors. Keep SelectItem components inside a SelectGroup. Do not introduce browser-native confirmation dialogs.
Platform presentation
Update platform-sensitive display surfaces such as:
- Agent cards
- Platform badges
- Filters
- Empty states
- Provisioning text
- Activity labels
- Fixtures and page-object support
Do not infer runtime compatibility only in presentation code. The backend remains authoritative.
Test the complete platform
Cover each contract at the lowest reliable layer, then test the assembled workflow.
| Concern | Minimum coverage |
|---|---|
| Platform value | Request and response schema tests |
| Runtime compatibility | Valid combinations and rejected combinations |
| Required credentials | Missing, blank, and malformed input |
| Credential encryption | Plaintext absent from persisted rows and read responses |
| Configuration model | Defaults, constraints, and cascade behavior |
| Migration | Fresh install and upgrade to the new head |
| Bot uniqueness | Pre-check, database race protection, and release on deletion |
| Create | Platform config is persisted and safe metadata returned |
| Update | Routing changes, credential rotation, wrong-platform rejection |
| Running update | 409 direct update behavior |
| Authorization | agent.update, agent.secret.manage, inaccessible Agent, cross-Organization |
| Start | Missing config, invalid credentials, resource generation, status transition |
| Stop | Log snapshot and runtime cleanup |
| Delete | Runtime cleanup, soft deletion, and token-hash release |
| OpenClaw builder | Exact channel, binding, policy, secret, and plugin config |
| Hermes builder | Exact platform, policy plugins, secret, and ConfigMap config |
| Mention gating | Every supported runtime/platform pair |
| Group policy | Open and allowlisted behavior |
| DM policy | Off, allowlisted, and open behavior |
| User restrictions | Open-user and restricted-user behavior |
| Webhook | Authentication headers, wrong platform, stopped Agent, internal proxy |
| Telemetry | Posted payload validates against real Ingest models |
| Conversations | Platform session identity, threads, and names |
| Health | Valid, invalid, disconnected, paused, and unsupported states |
| Web app | Hiring, configuration, rotation, channel policy, and restart |
| Runtime image | Connector exists and its expected contract works in the pinned image |
Representative test locations include:
api/tests/unit/test_openclaw_builders.py
api/tests/unit/test_hermes_builders.py
api/tests/unit/test_healthz_server_metrics.py
api/tests/unit/test_hermes_telemetry_push_plugin.py
api/tests/unit/test_openclaw_telemetry_push_plugin.py
api/tests/integration/test_agents.py
api/tests/integration/test_agent_rbac.py
api/tests/integration/test_ingest.py
api/tests/integration/test_conversations.py
api/tests/integration/test_tool_calls.py
api/tests/integration/test_kubernetes_client.py
ui/tests/e2e/hire-dialog.spec.ts
ui/tests/e2e/agent-configuration-page.spec.ts
ui/tests/pages/data-support/agent-data-support.po.tsRun the relevant repository checks:
make check-api
make check-migrations
make test-api
make test-api-k8s
make lint-ui
make check-ui
make test-uiRun API coverage when evaluating untested branches:
make coverageWhen the platform changes a runtime image, also run the matching image build, smoke test, and workflow contract checks.
Verify the platform end to end
Use a non-production provider application.
- Create the platform application or bot.
- Grant only the required scopes, permissions, intents, or events.
- Select a supported Agent runtime.
- Choose the new communication platform.
- Enter the required credentials.
- Configure the most restrictive practical group and DM policies.
- Create the Agent.
- Confirm the API response excludes credential values.
- Confirm the configuration row contains encrypted credentials.
- Start the Agent.
- Confirm the expected Kubernetes Secret, ConfigMap, Service, and Deployment exist.
- Confirm the pod becomes ready.
- Send an unmentioned message in a shared room and confirm the Agent does not respond.
- Mention the Agent and confirm it responds.
- Send a second unmentioned message in the same thread and confirm the documented fresh-mention behavior.
- Test an unauthorized channel, group, user, or role.
- Test direct messages under every supported policy.
- Confirm inbound and outbound messages appear in Agent activity.
- Confirm tool calls appear with stable identities.
- Rotate the platform credential through Apply & Restart.
- Revoke the old credential and confirm the Agent uses the replacement.
- Stop the Agent and verify delivery behavior while stopped.
- Delete a test Agent and confirm runtime resources are removed.
- Review logs, Domain Events, activity, and generated resources for accidental secret exposure.
For a webhook platform, also:
- Configure the public callback URL.
- Send a provider-authenticated callback.
- Confirm the API relays it to the correct running Agent.
- Confirm a wrong-platform Agent returns 404.
- Confirm a stopped Agent rejects delivery.
- Confirm invalid signatures or credentials are rejected.
- Confirm duplicate provider delivery follows the documented idempotency behavior.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Platform value returns 422 | Enum, database constraint, or UI union is incomplete | AgentPlatform, migration, Zod schema, request types |
| Runtime/platform pair returns 422 | The selected runtime is unsupported | Compatibility validator and UI choice logic |
| Agent creation succeeds without a config row | Creation branch or repository save is missing | Agent Service platform branch and cleanup behavior |
| Credentials appear in the API response | Unsafe configuration DTO | Platform read model and UI schema |
| Credential rotation is allowed without secret permission | Field is absent from _CREDENTIAL_FIELDS | Credential-field classification and RBAC tests |
| Wrong-platform fields are accepted | Cross-platform update validation is incomplete | Platform field sets and rejection matrix |
| Agent starts but cannot receive messages | Runtime connector or transport configuration is incomplete | Plugin, binding, webhook, token, Service port |
| Agent responds to unmentioned messages | Mention gating was left to a runtime default | Explicit builder configuration and runtime contract tests |
| Agent ignores every group message | Empty allowlist semantics or group mapping is wrong | Group policy, IDs, and runtime translation |
| Direct messages remain open | off was mapped incorrectly | DM policy translation and access plugins |
| Hermes works but OpenClaw fails | Only one builder, script, or image was updated | Runtime-specific builder and base-image contract |
| OpenClaw works but Hermes fails | Hermes plugin or startup installation is missing | ConfigMap plugin files, startup script, health state |
| Webhook returns 404 | Agent is absent, deleted, or belongs to another platform | Callback path and platform check |
| Webhook returns 503 | Agent is not running | Lifecycle state and Agent pod |
| Webhook reaches the wrong port | Service or proxy configuration is incomplete | Named port, target port, and runtime webhook path |
| Messages appear without names | Directory enrichment is absent or failed | Platform client, IDs, timeout, and non-fatal fallback |
| Activity is missing | Telemetry plugin does not recognize the platform | Runtime payload, session keys, Ingest contract |
| Health reports unsupported platform | Health server lacks the new platform branch | AGENT_PLATFORM handling in both runtime health servers |
| Platform does not appear in hiring | Platform union or wizard sequence is incomplete | Platform choice, steps, state, and payload mapping |
| Existing deployment fails migration | ck_agent_platform or string length was not updated safely | Alembic migration and PostgreSQL upgrade test |
| Two Agents use one forbidden bot identity | No database uniqueness invariant exists | Token hash, partial unique index, and conflict translation |
Completion checklist
Work through each card before opening the pull request.
-
Contract
-
Persistence and API
-
Migration
-
Lifecycle
-
Runtime and messaging safety
-
Webhook and transport
-
Activity and health
-
Web app
-
Documentation and verification
Source map
| Concern | Authoritative source |
|---|---|
| Platform enum and configuration models | api/domains/agents/models.py |
| Agent lifecycle orchestration | api/domains/agents/service.py |
| Platform configuration persistence | api/domains/agents/repository.py |
| Agent HTTP routes | api/domains/agents/routes.py |
| Platform-specific routes | api/domains/agents/slack_routes.py, api/domains/agents/webhook_routes.py |
| Shared Kubernetes resources | api/domains/agents/builders/common.py |
| Hermes builders | api/domains/agents/builders/hermes.py |
| OpenClaw builders | api/domains/agents/builders/openclaw.py |
| Runtime builder exports | api/domains/agents/builders/__init__.py |
| Hermes startup and plugins | api/domains/agents/scripts/hermes/ |
| OpenClaw startup and plugins | api/domains/agents/scripts/openclaw/ |
| Hermes base image | hermes-base/ |
| OpenClaw base image | openclaw-base/ |
| Kubernetes client | api/infrastructure/kubernetes/ |
| Ingest normalization | api/domains/ingest/ |
| Conversation parsing and reads | api/domains/conversations/ |
| Tool-call persistence | api/domains/tool_calls/ |
| Provider-specific clients | api/infrastructure/slack/, api/infrastructure/telegram/, api/infrastructure/discord/ |
| Web app Agent contracts | ui/src/features/agents/schemas.ts |
| Agent creation and updates | ui/src/features/agents/hooks/ |
| Hiring flow | ui/src/features/agents/components/hire-dialog.tsx, hire-dialog-steps.tsx |
| Channel configuration | ui/src/features/agents/components/agent-channel-settings.tsx |
| Credential configuration | ui/src/features/agents/components/agent-keys-settings.tsx |
| Agent product contract | docs/features/agents.md |
| Runtime contract | docs/architecture/runtime-and-deployment.md |
| Activity and telemetry contract | docs/features/activity-and-ingest.md |
| Verification rules | docs/guidelines/testing.md |