Agent coordination
ACP agents bindings
Bound sessions
Mental model
- Chat surface - where people keep talking (Discord channel, Telegram topic, iMessage chat).
- ACP session - the durable Codex/Claude/Gemini runtime state OpenClaw routes to.
- Child thread/topic - an optional extra messaging surface created only by
--thread .... - Runtime workspace - the filesystem location (
cwd, repo checkout, backend workspace) where the harness runs. Independent of the chat surface.
Current-conversation binds
/acp spawn <harness> --bind here pins the current conversation to the
spawned ACP session - no child thread, same chat surface. OpenClaw keeps
owning transport, auth, safety, and delivery. Follow-up messages in that
conversation route to the same session; /new and /reset reset the session
in place; /acp close removes the binding.
Examples:
/codex bind # native Codex bind, route future messages here/codex model gpt-5.4 # tune the bound native Codex thread/codex stop # control the active native Codex turn/acp spawn codex --bind here # explicit ACP fallback for Codex/acp spawn codex --thread auto # may create a child thread/topic and bind there/acp spawn codex --bind here --cwd /workspace/repo # same chat binding, Codex runs in /workspace/repoBinding rules and exclusivity
--bind hereand--thread ...are mutually exclusive.--bind hereonly works on channels that advertise current-conversation binding; OpenClaw returns a clear unsupported message otherwise. Bindings persist across gateway restarts.- On Discord,
spawnSessionsgates child thread creation for--thread auto|here- not--bind here. - If you spawn to a different ACP agent without
--cwd, OpenClaw inherits the target agent's workspace by default. Missing inherited paths (ENOENT/ENOTDIR) fall back to the backend default; other access errors (e.g.EACCES) surface as spawn errors. - Gateway management commands stay local in bound conversations -
/acp ...commands are handled by OpenClaw even when normal follow-up text routes to the bound ACP session;/statusand/sessionalso stay local whenever command handling is enabled for that surface.
Thread-bound sessions
When thread bindings are enabled for a channel adapter:
- OpenClaw binds a thread to a target ACP session.
- Follow-up messages in that thread route to the bound ACP session.
- ACP output is delivered back to the same thread.
/session unbind, close, archive, idle timeout, or max-age expiry removes the binding./session unbinddetaches only the current conversation and leaves the ACP session running./acp close,/acp cancel,/acp status,/status, and/sessionare Gateway commands, not prompts to the ACP harness.
Required feature flags for thread-bound ACP:
acp.enabled=trueacp.dispatch.enabledis on by default (setfalseto pause automatic ACP thread dispatch; explicitsessions_spawn({ runtime: "acp" })calls still work).- Channel-adapter thread session spawns enabled (default:
true):- Discord/Telegram:
session.threadBindings.spawnSessions=true
- Discord/Telegram:
Thread binding support is adapter-specific. If the active channel adapter does not support thread bindings, OpenClaw returns a clear unsupported/unavailable message.
Thread-supporting channels
- Any channel adapter that exposes session/thread binding capability.
- Current built-in support: Discord threads/channels, Telegram topics (forum topics in groups/supergroups and DM topics).
- Plugin channels can add support through the same binding interface.
Persistent channel bindings
For non-ephemeral workflows, configure persistent ACP bindings in top-level
bindings[] entries.
Binding model
bindings[].type"acp"Marks a persistent ACP conversation binding.
bindings[].matchobjectIdentifies the target conversation. Per-channel shapes:
- Discord channel/thread:
match.channel="discord"+match.peer.id="<channelOrThreadId>" - Slack channel/DM:
match.channel="slack"+match.peer.id="<channelId|channel:<channelId>|#<channelId>|userId|user:<userId>|slack:<userId>|<@userId>>". Prefer stable Slack ids; channel bindings also match replies inside that channel's threads. - Telegram forum topic:
match.channel="telegram"+match.peer.id="<chatId>:topic:<topicId>" - WhatsApp DM/group:
match.channel="whatsapp"+match.peer.id="<E.164|group JID>". Use E.164 numbers such as+15555550123for direct chats and WhatsApp group JIDs such as120363424282127706@g.usfor groups. - iMessage DM/group:
match.channel="imessage"+match.peer.id="<handle|chat_id:*|chat_guid:*|chat_identifier:*>". Preferchat_id:*for stable group bindings.
bindings[].agentIdstringThe owning OpenClaw agent id.
bindings[].acp.mode"persistent" | "oneshot"Optional ACP override.
bindings[].acp.labelstringOptional operator-facing label.
bindings[].acp.cwdstringOptional runtime working directory.
bindings[].acp.backendstringOptional backend override.
Runtime defaults per agent
Use agents.entries.*.runtime to define ACP defaults once per agent:
agents.entries.*.runtime.type="acp"agents.entries.*.runtime.acp.agent(harness id, e.g.codexorclaude)agents.entries.*.runtime.acp.backendagents.entries.*.runtime.acp.modeagents.entries.*.runtime.acp.cwd
Override precedence for ACP bound sessions:
bindings[].acp.*agents.entries.*.runtime.acp.*- Global ACP defaults (e.g.
acp.backend)
Configured bindings also forward the owning agent's explicit model and thinking
policy. Thinking uses the agent's thinkingDefault, then per-model
agents.defaults.models["provider/model"].params.thinking, then
agents.defaults.thinkingDefault. Without configured policy, the external
harness keeps its own defaults.
Changing a configured model or thinking value updates the existing session
before its next turn without replacing the conversation. Each option is saved
only after the harness accepts it; a rejected option returns an error and keeps
that option's previous selection. Model and thinking changes are independent,
not an atomic batch. Removing a default
uses any remaining configured policy; if none remains, OpenClaw retains the
session's last selection. Omission is not a backend reset. To change thinking
explicitly, use /acp set thinking <level> with a level supported by the harness.
For Codex ACP, off only omits a fresh session's startup override. Switching an
existing session to off is unsupported and returns an error without clearing
its current reasoning effort or conversation.
Example
{ agents: { ownership: "explicit", entries: { codex: { runtime: { type: "acp", acp: { agent: "codex", backend: "acpx", mode: "persistent", cwd: "/workspace/openclaw", }, }, }, claude: { runtime: { type: "acp", acp: { agent: "claude", backend: "acpx", mode: "persistent" }, }, }, }, }, bindings: [ { type: "acp", agentId: "codex", match: { channel: "discord", accountId: "default", peer: { kind: "channel", id: "222222222222222222" }, }, acp: { label: "codex-main" }, }, { type: "acp", agentId: "claude", match: { channel: "telegram", accountId: "default", peer: { kind: "group", id: "-1001234567890:topic:42" }, }, acp: { cwd: "/workspace/repo-b" }, }, { type: "route", agentId: "main", match: { channel: "discord", accountId: "default" }, }, { type: "route", agentId: "main", match: { channel: "telegram", accountId: "default" }, }, ], channels: { discord: { guilds: { "111111111111111111": { channels: { "222222222222222222": { requireMention: false }, }, }, }, }, telegram: { groups: { "-1001234567890": { topics: { "42": { requireMention: false } }, }, }, }, },}Behavior
- OpenClaw ensures the configured ACP session exists after channel-specific admission and before use.
- Messages in that channel, topic, or chat route to the configured ACP session.
- Configured ACP bindings own their session route. Channel broadcast fan-out does not replace the configured ACP session for a matched binding.
- In bound conversations,
/newand/resetreset the same ACP session key in place. - Runtime bindings created by thread-bound spawns still apply where present.
- For cross-agent ACP spawns without an explicit
cwd, OpenClaw inherits the target agent workspace from agent config. - Missing inherited workspace paths fall back to the backend default cwd; non-missing access failures surface as spawn errors.