Social Topology
Agents have identities on chat platforms. A trading bot needs a Discord presence. A support agent needs a Slack identity. Clawdapus manages the full lifecycle of these identities -- declaration, wiring, peer discovery, and cross-service broadcasting -- through the HANDLE directive and pod-level topology defaults.
The HANDLE Directive
HANDLE discord in a Clawfile declares that this agent has a Discord identity. At deploy time, Clawdapus translates this into the runner's native configuration -- enabling the Discord plugin, setting up authentication, configuring mention patterns.
FROM openclaw:latest
CLAW_TYPE openclaw
AGENT AGENTS.md
HANDLE discordThe actual identity details (bot token, guild ID, username) are provided at the pod level, keeping secrets out of the image.
Environment Variable Broadcasting
Clawdapus broadcasts every agent's handles as environment variables into every service in the pod -- including non-claw services:
# Available in every pod service automatically:
CLAW_HANDLE_CRYPTO_CRUSHER_DISCORD_ID=123456789
CLAW_HANDLE_CRYPTO_CRUSHER_DISCORD_GUILDS=111222333The variable names are derived from the service name, uppercased and hyphen-replaced. Every service in the pod knows every agent's platform identity without hardcoding anything.
Non-AI Services Get Handles Too
A trading API, a webhook receiver, a monitoring service -- any container in the pod receives handle environment variables. This enables rich integration between AI and non-AI services.
The Leviathan Pattern
The broadcasting mechanism enables a named integration pattern: the Leviathan Pattern. A non-AI API service reads CLAW_HANDLE_* environment variables to dynamically construct @mentions, addressing cognitive services through the pod's social topology without any hardcoded coupling.
For example, a trading API detects an anomaly and needs to alert the risk analyst bot. It reads CLAW_HANDLE_RISK_ANALYST_DISCORD_ID from its environment and constructs a Discord message that mentions the bot by ID. The bot receives the mention, processes it, and responds.
# In a non-AI trading API service (Python example)
import os
risk_bot_id = os.environ["CLAW_HANDLE_RISK_ANALYST_DISCORD_ID"]
alert_message = f"<@{risk_bot_id}> Anomaly detected in ETH/USD — drawdown exceeds 5% threshold."
send_to_discord(channel_id=alerts_channel, content=alert_message)The pattern works because claw up injects every agent's handle environment variables into every service in the pod at compile time. The trading API does not import any Clawdapus library or know anything about the agent runtime. It just reads an environment variable and formats a string. Add a new agent to the pod, and its handle variables appear automatically in every service on the next claw up.
Shared Topology with handles-defaults
When many services share the same Discord guild and channel topology, declare the shared parts once at the pod level:
x-claw:
pod: trading-desk
handles-defaults:
discord:
guilds: ["${DISCORD_GUILD_ID}"]
channels:
trading-floor: "${TRADING_FLOOR_CHANNEL_ID}"
alerts: "${ALERTS_CHANNEL_ID}"All services inherit this topology. Each service's x-claw.handles block then only needs the identity fields that differ:
services:
tiverton:
image: trading-desk-tiverton:latest
x-claw:
handles:
discord:
id: "${TIVERTON_DISCORD_ID}"
username: "tiverton"
analyst:
image: trading-desk-analyst:latest
x-claw:
handles:
discord:
id: "${ANALYST_DISCORD_ID}"
username: "analyst"DRY Topology
Put shared guild IDs, channel mappings, and routing policy in handles-defaults. Keep per-service handles focused on identity: bot ID and username. This avoids duplicating topology across every service.
Automatic Driver Wiring
When claw up processes handle declarations, the driver automatically wires several things into each agent's runner configuration:
allowBots: true-- Enables bot-to-bot messaging within the pod. Without this, Discord bots ignore messages from other bots.mentionPatterns-- Derived from the handle username and ID, so agents can route incoming messages correctly.requireMention-- Enabled by default for guild channels to prevent agents from responding to every message and entering feedback loops.- Guild
users[]allowlist -- Populated with every peer bot in the pod, so agents can communicate with each other.
Mention Safety
All drivers set requireMention (or the driver-specific equivalent) for guild channels. Without this, multi-agent pods enter feedback loops where bots respond to each other's messages indefinitely. This is enforced at the driver level, not left to the agent's discretion.
Per-Service Identity Overrides
While topology is shared, identity is always per-service. Each agent has its own bot token, its own Discord ID, and its own username. These are provided through environment variables resolved at deploy time:
services:
coordinator:
image: coordinator:latest
x-claw:
handles:
discord:
id: "${COORDINATOR_DISCORD_ID}"
username: "coordinator"
token: "${COORDINATOR_DISCORD_TOKEN}"Multi-Platform Handles
An agent can have handles on multiple platforms simultaneously. Platform support varies by driver:
| Platform | openclaw | hermes | nanobot | picoclaw | nullclaw | microclaw |
|---|---|---|---|---|---|---|
| Discord | yes | yes | yes | yes | yes | yes |
| Telegram | -- | yes | yes | yes | yes | yes |
| Slack | -- | yes | yes | yes | yes | yes |
PicoClaw additionally supports WhatsApp, Feishu, LINE, QQ, DingTalk, OneBot, WeCom, and other long-tail platforms.
Channel Surfaces
Beyond basic handle identity, channel surfaces provide fine-grained routing control. Map-form channel surfaces allow per-channel policy:
x-claw:
surfaces:
- channel://discord:
policy: allowlist
allow_from_handles: true
allow_from_services: [trading-api]This declares that the agent's Discord channel surface only accepts messages from known handles and the trading-api service. The allow_from_handles: true flag expands into each guild's users[] allowlist using the handle IDs of all peer agents in the pod. The allow_from_services list derives Discord IDs from the named services' bot tokens.
OpenClaw Discord Routing Compatibility
The OpenClaw driver maps supported channel://discord routing controls directly into generated config and rejects unsupported ones early at compile time, rather than letting the container reject them at boot.
channel://discord map-form setting | openclaw support |
|---|---|
DM policy (pairing, allowlist, open, disabled) | yes |
DM allowFrom | yes |
Guild requireMention | yes |
Guild users[] allowlist | yes |
Surface allow_from_handles: true -- expands into each guild users[] | yes |
Surface allow_from_services: [svc...] -- derives Discord IDs from service bot tokens and expands each guild users[] | yes |
Guild policy | no |
Guild Policy Not Supported
The current OpenClaw runtime rejects guild-level policy. Clawdapus fails during config generation instead of writing a config the container would reject at boot. Use requireMention and users[] allowlists for guild-level access control.
