Persistente ACP-Bindings für Discord-Kanäle und Telegram-Topics

Status: Entwurf

Zusammenfassung

Einführung persistenter ACP-Bindings, die folgende Zuordnungen abbilden:

  • Discord-Kanäle (und ggf. bestehende Threads) sowie
  • Telegram-Forum-Topics in Gruppen/Supergruppen (chatId:topic:topicId)

auf langlebige ACP-Sessions. Der Binding-Zustand wird in Top-Level-bindings[]-Einträgen mit expliziten Binding-Typen gespeichert.

Damit wird die ACP-Nutzung in stark frequentierten Messaging-Kanälen vorhersagbar und dauerhaft, sodass Benutzer dedizierte Kanäle/Topics wie codex, claude-1 oder claude-myrepo erstellen können.

Warum

Das aktuelle threadgebundene ACP-Verhalten ist auf kurzlebige Discord-Thread-Workflows optimiert. Telegram hat kein vergleichbares Thread-Modell; es verwendet Forum-Topics in Gruppen/Supergruppen. Benutzer wünschen sich stabile, dauerhaft aktive ACP-„Workspaces” in Chat-Oberflächen, nicht nur temporäre Thread-Sessions.

Ziele

  • Dauerhafte ACP-Bindings unterstützen für:
    • Discord-Kanäle/-Threads
    • Telegram-Forum-Topics (Gruppen/Supergruppen)
  • Binding als konfigurationsgesteuerte Single Source of Truth.
  • /acp, /new, /reset, /focus und Zustellungsverhalten konsistent über Discord und Telegram halten.
  • Bestehende temporäre Binding-Flows für Ad-hoc-Nutzung beibehalten.

Nicht-Ziele

  • Vollständiger Umbau der ACP-Runtime/Session-Interna.
  • Entfernung bestehender kurzlebiger Binding-Flows.
  • Erweiterung auf alle Kanäle in der ersten Iteration.
  • Implementierung von Telegram-Kanal-Direktnachrichten-Topics (direct_messages_topic_id) in dieser Phase.
  • Implementierung von Telegram-Privatchat-Topic-Varianten in dieser Phase.

UX-Richtung

1) Zwei Binding-Typen

  • Persistentes Binding: in der Konfiguration gespeichert, beim Start abgeglichen, gedacht für „benannte Workspace”-Kanäle/Topics.
  • Temporäres Binding: nur zur Laufzeit, läuft nach Idle-/Max-Age-Policy ab.

2) Befehlsverhalten

  • /acp spawn ... --thread here|auto|off bleibt verfügbar.
  • Explizite Binding-Lifecycle-Steuerungen ergänzen:
    • /acp bind [session|agent] [--persist]
    • /acp unbind [--persist]
    • /acp status zeigt an, ob das Binding persistent oder temporary ist.
  • In gebundenen Konversationen setzen /new und /reset die gebundene ACP-Session vor Ort zurück und behalten das Binding bei.

3) Konversationsidentität

  • Kanonische Konversations-IDs verwenden:
    • Discord: Kanal-/Thread-ID.
    • Telegram-Topic: chatId:topic:topicId.
  • Telegram-Bindings niemals nur über die nackte Topic-ID keyen.

Konfigurationsmodell (Vorschlag)

Routing und persistente ACP-Binding-Konfiguration in Top-Level-bindings[] mit explizitem type-Discriminator vereinheitlichen:

{
  "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 (bestehendes Verhalten)
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "discord", "accountId": "default" },
    },
    {
      "type": "route",
      "agentId": "main",
      "match": { "channel": "telegram", "accountId": "default" },
    },
    // Persistente ACP-Konversations-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,
            },
          },
        },
      },
    },
  },
}

Minimales Beispiel (ohne Binding-spezifische 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" },
      },
    },
  ],
}

Anmerkungen:

  • bindings[].type ist explizit:
    • route: normales Agent-Routing.
    • acp: persistentes ACP-Harness-Binding für eine zugeordnete Konversation.
  • Bei type: "acp" ist match.peer.id der kanonische Konversationsschlüssel:
    • Discord-Kanal/-Thread: rohe Kanal-/Thread-ID.
    • Telegram-Topic: chatId:topic:topicId.
  • bindings[].acp.backend ist optional. Backend-Fallback-Reihenfolge:
    1. bindings[].acp.backend
    2. agents.list[].runtime.acp.backend
    3. globales acp.backend
  • mode, cwd und label folgen demselben Override-Muster (Binding-Override -> Agent-Runtime-Default -> globales/Standard-Verhalten).
  • Bestehende session.threadBindings.* und channels.discord.threadBindings.* für temporäre Binding-Policies beibehalten.
  • Persistente Einträge deklarieren den gewünschten Zustand; die Laufzeit gleicht mit den tatsächlichen ACP-Sessions/Bindings ab.
  • Pro Konversationsknoten ist genau ein aktives ACP-Binding vorgesehen.
  • Abwärtskompatibilität: fehlender type wird als route für Legacy-Einträge interpretiert.

Backend-Auswahl

  • Die ACP-Session-Initialisierung nutzt bereits die konfigurierte Backend-Auswahl beim Spawn (acp.backend heute).
  • Dieser Vorschlag erweitert die Spawn-/Abgleichlogik um typisierte ACP-Binding-Overrides:
    • bindings[].acp.backend für konversationslokale Overrides.
    • agents.list[].runtime.acp.backend für Agent-spezifische Defaults.
  • Wenn kein Override existiert, wird das aktuelle Verhalten beibehalten (acp.backend-Standard).

Architekturpassung im aktuellen System

Bestehende Komponenten wiederverwenden

  • SessionBindingService unterstützt bereits kanalunabhängige Konversationsreferenzen.
  • ACP-Spawn-/Bind-Flows unterstützen bereits Binding über Service-APIs.
  • Telegram transportiert bereits Topic-/Thread-Kontext über MessageThreadId und chatId.

Neue/erweiterte Komponenten

  • Telegram-Binding-Adapter (parallel zum Discord-Adapter):
    • Adapter pro Telegram-Account registrieren,
    • Auflösen/Auflisten/Binden/Lösen/Touch per kanonischer Konversations-ID.
  • Typisierter Binding-Resolver/-Index:
    • bindings[] in route- und acp-Ansichten aufteilen,
    • resolveAgentRoute nur auf route-Bindings anwenden,
    • persistente ACP-Absicht nur aus acp-Bindings auflösen.
  • Eingehende Binding-Auflösung für Telegram:
    • Gebundene Session vor der Route-Finalisierung auflösen (Discord macht das bereits).
  • Persistenter Binding-Abgleicher:
    • Beim Start: konfigurierte Top-Level-type: "acp"-Bindings laden, sicherstellen dass ACP-Sessions existieren, sicherstellen dass Bindings existieren.
    • Bei Konfigurationsänderung: Deltas sicher anwenden.
  • Umstellungsmodell:
    • Kein kanalspezifischer ACP-Binding-Fallback wird gelesen,
    • persistente ACP-Bindings werden ausschließlich aus Top-Level-bindings[].type="acp"-Einträgen bezogen.

Schrittweise Auslieferung

Phase 1: Typisierte Binding-Schema-Grundlage

  • Konfigurationsschema um bindings[].type-Discriminator erweitern:
    • route,
    • acp mit optionalem acp-Override-Objekt (mode, backend, cwd, label).
  • Agent-Schema um Runtime-Deskriptor erweitern, um ACP-native Agents zu kennzeichnen (agents.list[].runtime.type).
  • Parser-/Indexer-Aufteilung für Route- vs. ACP-Bindings hinzufügen.

Phase 2: Laufzeit-Auflösung + Discord/Telegram-Parität

  • Persistente ACP-Bindings aus Top-Level-type: "acp"-Einträgen auflösen für:
    • Discord-Kanäle/-Threads,
    • Telegram-Forum-Topics (chatId:topic:topicId kanonische IDs).
  • Telegram-Binding-Adapter und eingehende gebundene-Session-Override-Parität mit Discord implementieren.
  • Telegram-Direkt-/Privat-Topic-Varianten nicht in dieser Phase einschließen.

Phase 3: Befehlsparität und Resets

  • /acp, /new, /reset und /focus-Verhalten in gebundenen Telegram-/Discord-Konversationen angleichen.
  • Sicherstellen, dass Bindings Reset-Flows wie konfiguriert überleben.

Phase 4: Härtung

  • Bessere Diagnose (/acp status, Abgleich-Logs beim Start).
  • Konfliktbehandlung und Health-Checks.

Leitplanken und Richtlinien

  • ACP-Aktivierung und Sandbox-Einschränkungen exakt wie heute respektieren.
  • Explizites Account-Scoping (accountId) beibehalten, um Account-übergreifende Vermischung zu vermeiden.
  • Bei mehrdeutigem Routing: geschlossen fehlschlagen.
  • Mention-/Zugangsrichtlinien-Verhalten je Kanalkonfiguration explizit halten.

Testplan

  • Unit:
    • Konversations-ID-Normalisierung (insbesondere Telegram-Topic-IDs),
    • Abgleicher-Create/Update/Delete-Pfade,
    • /acp bind --persist- und Unbind-Flows.
  • Integration:
    • Eingehendes Telegram-Topic -> gebundene ACP-Session-Auflösung,
    • Eingehender Discord-Kanal/-Thread -> persistentes Binding-Vorrang.
  • Regression:
    • Temporäre Bindings funktionieren weiterhin,
    • Ungebundene Kanäle/Topics behalten das aktuelle Routing-Verhalten bei.

Offene Fragen

  • Sollte /acp spawn --thread auto im Telegram-Topic standardmäßig here verwenden?
  • Sollten persistente Bindings immer Mention-Gating in gebundenen Konversationen umgehen oder explizites requireMention=false erfordern?
  • Sollte /focus ein --persist als Alias für /acp bind --persist erhalten?

Rollout

  • Als Opt-in pro Konversation ausliefern (bindings[].type="acp"-Eintrag vorhanden).
  • Zunächst nur Discord + Telegram.
  • Dokumentation mit Beispielen hinzufügen für:
    • „ein Kanal/Topic pro Agent”
    • „mehrere Kanäle/Topics pro Agent mit unterschiedlichem cwd
    • „Team-Benennungsmuster (codex-1, claude-repo-x)”.