OpenClaw is a multi-channel message routing layer wrapped around an embedded "coding agent" runtime (pi-agent-core). It enables AI agents to operate across messaging platforms (Telegram, Discord, Slack, Signal, iMessage, WhatsApp, etc.) with unified tool access, session management, and extensibility.
┌─────────────────────────────────────────────────────────────────┐
│ Channel Integrations │
│ (Telegram, Discord, Slack, Signal, iMessage, WhatsApp, etc.) │
└──────────────────────────┬──────────────────────────────────────┘
│ Normalized MsgContext
▼
┌─────────────────────────────────────────────────────────────────┐
│ Auto-Reply Orchestration │
│ (Command handling, Session management, Reply routing) │
└──────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Embedded Agent Runtime │
│ (LLM execution, Tools, Streaming, Fallbacks) │
└─────────────────────────────────────────────────────────────────┘
Per-channel adapters normalize platform-specific events into a shared MsgContext and deliver replies back.
src/telegram/bot.ts- Telegram Bot API integrationsrc/discord/- Discord bot integrationsrc/slack/- Slack integrationsrc/signal/- Signal integrationsrc/imessage/- iMessage integrationsrc/web/- WhatsApp Web integration
Defined in src/channels/plugins/types.plugin.ts, plugins can implement:
outbound- Message deliveryactions- Channel-specific operations (send, react, moderate, create threads)threading- Thread/topic supportmentions- Mention stripping/parsingsecurity- Allowlists and DM policiesstatus- Health/configuration checkssetup,pairing,login- Channel-specific setup flows
src/channels/registry.ts maintains canonical channel IDs and ordering:
telegram,whatsapp,discord,googlechat,slack,signal,imessage
The "brainstem" that decides how to handle inbound messages.
src/auto-reply/reply/get-reply.ts - getReplyFromConfig()
This function:
- Normalizes inbound context
- Resolves directives (model selection, elevated mode, streaming)
- Handles inline actions
- Routes to commands OR invokes the LLM agent
src/auto-reply/reply/session.ts - initSessionState()
- Session key ties everything together: agent selection, tool policy, transcript location
- Sessions are scoped per-sender or per-group (configurable)
- Reset triggers:
/new,/reset, or age-based freshness policies - Persists delivery fields for routing/announce-back
src/auto-reply/reply/commands-core.ts
Commands can bypass the LLM entirely. The handler pipeline iterates through registered handlers and can short-circuit with a direct reply.
src/auto-reply/reply/reply-dispatcher.ts
Serializes tool → block → final outbound sequences with optional "human delay" between blocks.
Executes LLM "turns" with tools, streaming, retries, and fallback models.
src/agents/pi-embedded-runner/run.ts-runEmbeddedPiAgent()src/agents/pi-embedded-runner/run/attempt.ts-runEmbeddedAttempt()
The execution loop:
- Builds tools
- Configures sandbox (if enabled)
- Loads skills prompt and bootstrap context
- Creates/loads session transcript (JSONL)
- Streams events back (assistant text, tool results, reasoning)
src/agents/pi-embedded-subscribe.ts - subscribeEmbeddedPiSession()
Aggregates and emits:
- Assistant text chunks
- Tool summaries/outputs
- Reasoning stream (optional)
- Suppresses duplicates when messaging tools already sent content
src/agents/openclaw-tools.ts:
message- Cross-channel message sendingsessions_send,sessions_spawn,sessions_list/history- Multi-session managementgateway,cron,nodes- Infrastructure toolsbrowser,canvas,tts,image- Media/interaction toolsweb_search,web_fetch- Web access
src/agents/pi-tools.ts - createOpenClawCodingTools():
- Filesystem:
read,write,edit - Process execution with policy enforcement
src/agents/tools/memory-tool.ts:
memory_search- Semantic recall from MEMORY.md + memory/*.mdmemory_get- Direct memory retrieval
Policies are layered and merged:
Global (cfg.tools)
└── Agent-specific (cfg.agents.list[].tools)
└── Provider/model-specific (tools.byProvider)
└── Group policy (per channel group)
└── Sandbox policy (workspace access)
└── Subagent policy (default-deny for sensitive tools)
Key files:
src/agents/tool-policy.ts- Profiles and groups (group:fs,group:runtime, etc.)src/agents/pi-tools.policy.ts- Policy resolution and merging
OpenClaw doesn't use long-lived "Agent" objects. Instead, agent identity is configuration-driven and session-key-driven.
src/agents/agent-scope.ts:
resolveDefaultAgentId(cfg)resolveSessionAgentId({ sessionKey, config })resolveAgentWorkspaceDir(cfg, agentId)
cfg.agents.defaults- Default workspace, model, typing interval, etc.cfg.agents.list[]- Per-agent overrides (workspace, model, tools, sandbox, subagents)
- Persisted as JSONL transcripts per session (
SessionManager) - Session metadata in session store (
SessionEntry) - Reset policies prevent unbounded growth
memory_searchandmemory_gettools- Sources:
MEMORY.md,memory/*.md, optionally transcripts - Gated by
resolveMemorySearchConfig(cfg, agentId)
src/auto-reply/reply/history.ts:
- In-memory rolling map with LRU eviction
- Provides "messages since last reply" context for group chats
Defined in src/plugins/types.ts:
before_agent_start,agent_end- Lifecycle hooksbefore_tool_call,after_tool_call,tool_result_persist- Tool hooksmessage_received,message_sending,message_sent- Message hooks- Compaction, session, and gateway hooks
src/plugins/runtime.ts- Global plugin registry- Plugins loaded from bundled/global/workspace/config paths
src/plugins/tools.ts - resolvePluginTools()
- Plugins can expose their own tools
- Gated by
toolAllowlist
- Identity: Session key
- Content: Transcript file (JSONL)
- Metadata:
SessionEntry(last delivery, toggles, etc.)
Each agent invocation has a unique runId.
Events published via src/infra/agent-events.ts:
lifecycle(start/end/error)assistant(text chunks)tool(tool calls/results)error
- Implement
ChannelPlugincontract - Register via plugin registry
- Implement adapters: inbound normalization, outbound delivery, actions, threading
- Core: Add to
createOpenClawTools()orcreateOpenClawCodingTools() - Plugin: Implement plugin tool factories in
resolvePluginTools()
Implement hooks defined in src/plugins/types.ts
- Plugin commands (registered before built-ins)
- Or add to
HANDLERS[]incommands-core.ts
- Tool profiles/groups:
src/agents/tool-policy.ts - Policy merging:
src/agents/pi-tools.policy.ts
| What | Where |
|---|---|
| Inbound normalization / session keys | src/<channel>/* |
| Main reply entrypoint | src/auto-reply/reply/get-reply.ts |
| Session state logic | src/auto-reply/reply/session.ts |
| Command handling | src/auto-reply/reply/commands-core.ts |
| LLM execution orchestration | src/auto-reply/reply/agent-runner.ts |
| Fallback + run loop | src/auto-reply/reply/agent-runner-execution.ts |
| Embedded Pi agent runtime | src/agents/pi-embedded-runner/run.ts |
| Tools and tool policies | src/agents/pi-tools.ts, src/agents/tool-policy.ts |
| Plugins and hooks | src/plugins/types.ts, src/plugins/runtime.ts |
| Channel plugin contracts | src/channels/plugins/types.plugin.ts |