mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
* docs: add ACP persistent binding experiment plan * docs: align ACP persistent binding spec to channel-local config * docs: scope Telegram ACP bindings to forum topics only * docs: lock bound /new and /reset behavior to in-place ACP reset * ACP: add persistent discord/telegram conversation bindings * ACP: fix persistent binding reuse and discord thread parent context * docs: document channel-specific persistent ACP bindings * ACP: split persistent bindings and share conversation id helpers * ACP: defer configured binding init until preflight passes * ACP: fix discord thread parent fallback and explicit disable inheritance * ACP: keep bound /new and /reset in-place * ACP: honor configured bindings in native command flows * ACP: avoid configured fallback after runtime bind failure * docs: refine ACP bindings experiment config examples * acp: cut over to typed top-level persistent bindings * ACP bindings: harden reset recovery and native command auth * Docs: add ACP bound command auth proposal * Tests: normalize i18n registry zh-CN assertion encoding * ACP bindings: address review findings for reset and fallback routing * ACP reset: gate hooks on success and preserve /new arguments * ACP bindings: fix auth and binding-priority review findings * Telegram ACP: gate ensure on auth and accepted messages * ACP bindings: fix session-key precedence and unavailable handling * ACP reset/native commands: honor fallback targets and abort on bootstrap failure * Config schema: validate ACP binding channel and Telegram topic IDs * Discord ACP: apply configured DM bindings to native commands * ACP reset tails: dispatch through ACP after command handling * ACP tails/native reset auth: fix target dispatch and restore full auth * ACP reset detection: fallback to active ACP keys for DM contexts * Tests: type runTurn mock input in ACP dispatch test * ACP: dedup binding route bootstrap and reset target resolution * reply: align ACP reset hooks with bound session key * docs: replace personal discord ids with placeholders * fix: add changelog entry for ACP persistent bindings (#34873) (thanks @dutifulbob) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
12 KiB
12 KiB
ACP Persistent Bindings for Discord Channels and Telegram Topics
Status: Draft
Summary
Introduce persistent ACP bindings that map:
- Discord channels (and existing threads, where needed), and
- Telegram forum topics in groups/supergroups (
chatId:topic:topicId)
to long-lived ACP sessions, with binding state stored in top-level bindings[] entries using explicit binding types.
This makes ACP usage in high-traffic messaging channels predictable and durable, so users can create dedicated channels/topics such as codex, claude-1, or claude-myrepo.
Why
Current thread-bound ACP behavior is optimized for ephemeral Discord thread workflows. Telegram does not have the same thread model; it has forum topics in groups/supergroups. Users want stable, always-on ACP “workspaces” in chat surfaces, not only temporary thread sessions.
Goals
- Support durable ACP binding for:
- Discord channels/threads
- Telegram forum topics (groups/supergroups)
- Make binding source-of-truth config-driven.
- Keep
/acp,/new,/reset,/focus, and delivery behavior consistent across Discord and Telegram. - Preserve existing temporary binding flows for ad-hoc usage.
Non-Goals
- Full redesign of ACP runtime/session internals.
- Removing existing ephemeral binding flows.
- Expanding to every channel in the first iteration.
- Implementing Telegram channel direct-messages topics (
direct_messages_topic_id) in this phase. - Implementing Telegram private-chat topic variants in this phase.
UX Direction
1) Two binding types
- Persistent binding: saved in config, reconciled on startup, intended for “named workspace” channels/topics.
- Temporary binding: runtime-only, expires by idle/max-age policy.
2) Command behavior
/acp spawn ... --thread here|auto|offremains available.- Add explicit bind lifecycle controls:
/acp bind [session|agent] [--persist]/acp unbind [--persist]/acp statusincludes whether binding ispersistentortemporary.
- In bound conversations,
/newand/resetreset the bound ACP session in place and keep the binding attached.
3) Conversation identity
- Use canonical conversation IDs:
- Discord: channel/thread ID.
- Telegram topic:
chatId:topic:topicId.
- Never key Telegram bindings by bare topic ID alone.
Config Model (Proposed)
Unify routing and persistent ACP binding configuration in top-level bindings[] with explicit type discriminator:
{
"agents": {
"list": [
{
"id": "main",
"default": true,
"workspace": "~/.openclaw/workspace-main",
"runtime": { "type": "embedded" },
},
{
"id": "codex",
"workspace": "~/.openclaw/workspace-codex",
"runtime": {
"type": "acp",
"acp": {
"agent": "codex",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-a",
},
},
},
{
"id": "claude",
"workspace": "~/.openclaw/workspace-claude",
"runtime": {
"type": "acp",
"acp": {
"agent": "claude",
"backend": "acpx",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
},
],
},
"acp": {
"enabled": true,
"backend": "acpx",
"allowedAgents": ["codex", "claude"],
},
"bindings": [
// Route bindings (existing behavior)
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
// Persistent ACP conversation bindings
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
"acp": {
"label": "codex-main",
"mode": "persistent",
"cwd": "/workspace/repo-a",
"backend": "acpx",
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
"acp": {
"label": "claude-repo-b",
"mode": "persistent",
"cwd": "/workspace/repo-b",
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1001234567890:topic:42" },
},
"acp": {
"label": "tg-codex-42",
"mode": "persistent",
},
},
],
"channels": {
"discord": {
"guilds": {
"111111111111111111": {
"channels": {
"222222222222222222": {
"enabled": true,
"requireMention": false,
},
"333333333333333333": {
"enabled": true,
"requireMention": false,
},
},
},
},
},
"telegram": {
"groups": {
"-1001234567890": {
"topics": {
"42": {
"requireMention": false,
},
},
},
},
},
},
}
Minimal Example (No Per-Binding ACP Overrides)
{
"agents": {
"list": [
{ "id": "main", "default": true, "runtime": { "type": "embedded" } },
{
"id": "codex",
"runtime": {
"type": "acp",
"acp": { "agent": "codex", "backend": "acpx", "mode": "persistent" },
},
},
{
"id": "claude",
"runtime": {
"type": "acp",
"acp": { "agent": "claude", "backend": "acpx", "mode": "persistent" },
},
},
],
},
"acp": { "enabled": true, "backend": "acpx" },
"bindings": [
{
"type": "route",
"agentId": "main",
"match": { "channel": "discord", "accountId": "default" },
},
{
"type": "route",
"agentId": "main",
"match": { "channel": "telegram", "accountId": "default" },
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "222222222222222222" },
},
},
{
"type": "acp",
"agentId": "claude",
"match": {
"channel": "discord",
"accountId": "default",
"peer": { "kind": "channel", "id": "333333333333333333" },
},
},
{
"type": "acp",
"agentId": "codex",
"match": {
"channel": "telegram",
"accountId": "default",
"peer": { "kind": "group", "id": "-1009876543210:topic:5" },
},
},
],
}
Notes:
bindings[].typeis explicit:route: normal agent routing.acp: persistent ACP harness binding for a matched conversation.
- For
type: "acp",match.peer.idis the canonical conversation key:- Discord channel/thread: raw channel/thread ID.
- Telegram topic:
chatId:topic:topicId.
bindings[].acp.backendis optional. Backend fallback order:bindings[].acp.backendagents.list[].runtime.acp.backend- global
acp.backend
mode,cwd, andlabelfollow the same override pattern (binding override -> agent runtime default -> global/default behavior).- Keep existing
session.threadBindings.*andchannels.discord.threadBindings.*for temporary binding policies. - Persistent entries declare desired state; runtime reconciles to actual ACP sessions/bindings.
- One active ACP binding per conversation node is the intended model.
- Backward compatibility: missing
typeis interpreted asroutefor legacy entries.
Backend Selection
- ACP session initialization already uses configured backend selection during spawn (
acp.backendtoday). - This proposal extends spawn/reconcile logic to prefer typed ACP binding overrides:
bindings[].acp.backendfor conversation-local override.agents.list[].runtime.acp.backendfor per-agent defaults.
- If no override exists, keep current behavior (
acp.backenddefault).
Architecture Fit in Current System
Reuse existing components
SessionBindingServicealready supports channel-agnostic conversation references.- ACP spawn/bind flows already support binding through service APIs.
- Telegram already carries topic/thread context via
MessageThreadIdandchatId.
New/extended components
- Telegram binding adapter (parallel to Discord adapter):
- register adapter per Telegram account,
- resolve/list/bind/unbind/touch by canonical conversation ID.
- Typed binding resolver/index:
- split
bindings[]intorouteandacpviews, - keep
resolveAgentRouteonroutebindings only, - resolve persistent ACP intent from
acpbindings only.
- split
- Inbound binding resolution for Telegram:
- resolve bound session before route finalization (Discord already does this).
- Persistent binding reconciler:
- on startup: load configured top-level
type: "acp"bindings, ensure ACP sessions exist, ensure bindings exist. - on config change: apply deltas safely.
- on startup: load configured top-level
- Cutover model:
- no channel-local ACP binding fallback is read,
- persistent ACP bindings are sourced only from top-level
bindings[].type="acp"entries.
Phased Delivery
Phase 1: Typed binding schema foundation
- Extend config schema to support
bindings[].typediscriminator:route,acpwith optionalacpoverride object (mode,backend,cwd,label).
- Extend agent schema with runtime descriptor to mark ACP-native agents (
agents.list[].runtime.type). - Add parser/indexer split for route vs ACP bindings.
Phase 2: Runtime resolution + Discord/Telegram parity
- Resolve persistent ACP bindings from top-level
type: "acp"entries for:- Discord channels/threads,
- Telegram forum topics (
chatId:topic:topicIdcanonical IDs).
- Implement Telegram binding adapter and inbound bound-session override parity with Discord.
- Do not include Telegram direct/private topic variants in this phase.
Phase 3: Command parity and resets
- Align
/acp,/new,/reset, and/focusbehavior in bound Telegram/Discord conversations. - Ensure binding survives reset flows as configured.
Phase 4: Hardening
- Better diagnostics (
/acp status, startup reconciliation logs). - Conflict handling and health checks.
Guardrails and Policy
- Respect ACP enablement and sandbox restrictions exactly as today.
- Keep explicit account scoping (
accountId) to avoid cross-account bleed. - Fail closed on ambiguous routing.
- Keep mention/access policy behavior explicit per channel config.
Testing Plan
- Unit:
- conversation ID normalization (especially Telegram topic IDs),
- reconciler create/update/delete paths,
/acp bind --persistand unbind flows.
- Integration:
- inbound Telegram topic -> bound ACP session resolution,
- inbound Discord channel/thread -> persistent binding precedence.
- Regression:
- temporary bindings continue to work,
- unbound channels/topics keep current routing behavior.
Open Questions
- Should
/acp spawn --thread autoin Telegram topic default tohere? - Should persistent bindings always bypass mention-gating in bound conversations, or require explicit
requireMention=false? - Should
/focusgain--persistas an alias for/acp bind --persist?
Rollout
- Ship as opt-in per conversation (
bindings[].type="acp"entry present). - Start with Discord + Telegram only.
- Add docs with examples for:
- “one channel/topic per agent”
- “multiple channels/topics per same agent with different
cwd” - “team naming patterns (
codex-1,claude-repo-x)".