Skip to main content

概要

本計画では、スレッド機能を備えたチャネル(まずは Discord)において、OpenClaw が実稼働レベルのライフサイクル管理とリカバリ(復旧)機能を備えた ACP コーディングエージェントをどのようにサポートすべきかを定義します。 関連ドキュメント: 目指すべきユーザー体験:
  • ユーザーが ACP セッションを開始(spawn)するか、既存のセッションをスレッドに固定(focus)する。
  • そのスレッド内でのユーザーメッセージは、紐付けられた ACP セッションにルーティングされる。
  • エージェントの出力は、同じスレッド内のペルソナとしてストリーミング返信される。
  • セッションは永続化させることも、明示的なクリーンアップ制御を伴う使い捨て(ワンショット)にすることも可能。

決定事項のサマリー

長期的な推奨案は、以下のハイブリッドアーキテクチャです:
  • OpenClaw コアが ACP コントロールプレーンの責務を負う
    • セッションの識別とメタデータ管理
    • スレッドの紐付けとルーティングの決定
    • 配信の不変条件(Invariants)の維持と重複の抑制
    • ライフサイクルのクリーンアップとリカバリのセマンティクス
  • ACP ランタイムバックエンドはプラグイン化可能にする
    • 最初のバックエンドは acpx ベースのプラグインサービス
    • ランタイム側で ACP トランスポート(通信)、キューイング、キャンセル、再接続を処理
OpenClaw コアで ACP トランスポートの内部実装を再発明すべきではありません。 また、ルーティングのために純粋なプラグインのみによるインターセプトパスに依存すべきでもありません。

目指すべきアーキテクチャ

ACP を、プラグイン可能なランタイムアダプターを備えた OpenClaw の「ファーストクラスのコントロールプレーン」として扱います。 遵守すべき不変条件:
  • すべての ACP スレッドバインディング(紐付け)は、有効な ACP セッションレコードを参照していること。
  • すべての ACP セッションは、明示的なライフサイクル状態(creating, idle, running, cancelling, closed, error)を持つこと。
  • すべての ACP 実行は、明示的な実行状態(queued, running, completed, failed, cancelled)を持つこと。
  • 起動(spawn)、紐付け(bind)、および最初のエンキュー(enqueue)はアトミック(不可分)に行われること。
  • コマンドの再試行はべき等(idempotent)であること(二重実行や Discord への二重出力を防ぐ)。
  • 紐付けられたスレッドへのチャネル出力は、ACP 実行イベントの投影(Projection)であり、アドホックな副作用ではないこと。
長期的な所有モデル:
  • AcpSessionManager が唯一の ACP 書き込み・調整役となる。
  • マネージャーはまずゲートウェイプロセス内に配置されるが、将来的に同じインターフェースのまま専用のサイドカーへ移動することも可能。
  • ACP セッションキーごとに、マネージャーが 1 つのメモリ内アクター(シリアル化されたコマンド実行)を所有する。
  • アダプター(acpx や将来のバックエンド)は、トランスポートとランタイムの実装のみを担う。
長期的な永続化モデル:
  • ACP コントロールプレーンの状態を、OpenClaw 状態ディレクトリ配下の専用 SQLite ストア(WAL モード)に移行する。
  • SessionEntry.acp は、移行中の互換性のための投影データとして保持し、「真実のソース」にはしない。
  • ACP イベントを追記専用(append-only)で保存し、リプレイ(再生)、クラッシュリカバリ、および確定的な配信をサポートする。

導入戦略(ゴールへの架け橋)

  • 短期的な橋渡し:
    • 現在のスレッド紐付けメカニズムと既存の ACP 構成設定を維持。
    • メタデータの欠落バグを修正し、ACP のターンを単一のコア ACP ブランチ経由でルーティング。
    • べき等性キーと、安全側に倒した(fail-closed)ルーティングチェックを即座に導入。
  • 長期的な切り替え:
    • ACP の「真実のソース」をコントロールプレーン DB とアクターへ移行。
    • スレッドへの配信を純粋なイベント投影ベースに変更。
    • 日和見的なセッションエントリのメタデータに依存していたレガシーなフォールバック挙動を削除。

なぜ「純粋なプラグイン」だけでは不十分なのか

現在のプラグインフックだけでは、コア側の変更なしにエンドツーエンドの ACP セッションルーティングを実現するには不十分です。
  • スレッドバインディングからのインバウンドルーティングは、まずコア側のディスパッチでセッションキーとして解決される必要がある。
  • メッセージフックは fire-and-forget(投げっぱなし)であり、メインの返信パスを遮断(short-circuit)できない。
  • プラグインコマンドは制御操作には適しているが、ターンごとのディスパッチフローを置き換えるのには向かない。
結論:
  • ACP ランタイムはプラグイン化が可能。
  • ACP ルーティングの分岐はコア側に存在する必要がある。

再利用可能な既存の基盤

以下の実装は既に存在し、今後も正統なものとして維持されます:
  • スレッドバインディングのターゲットとして subagentacp をサポート。
  • インバウンドのスレッドルーティング上書き(通常のディスパッチ前の解決)。
  • 返信配信時における Webhook を介したアウトバウンドのスレッドアイデンティティ。
  • ACP ターゲットと互換性のある /focus および /unfocus フロー。
  • 起動時に復元可能な永続バインディングストア。
  • アーカイブ、削除、フォーカス解除、リセット時などのバインド解除ライフサイクル。
本計画は、これらの基盤を置き換えるのではなく、拡張するものです。

アーキテクチャ

境界モデル

コア (OpenClaw 本体に実装):
  • 返信パイプライン内での ACP セッションモードのディスパッチ分岐。
  • 親チャネルとスレッドへの二重出力を避けるための配信調停。
  • ACP コントロールプレーンの永続化(移行中は SessionEntry.acp への投影を含む)。
  • セッションのリセット/削除に連動したライフサイクルのバインド解除とランタイムの切り離し。
プラグインバックエンド (acpx 実装):
  • ACP ランタイムワーカーの監視(supervision)。
  • acpx プロセスの起動とイベントのパース(解析)。
  • ACP コマンドハンドラー (/acp ...) とオペレーター用 UI。
  • バックエンド固有の構成既定値と診断機能。

ランタイム所有モデル

  • 1 つのゲートウェイプロセスが ACP の調整状態を所有する。
  • ACP の実行は、acpx バックエンドを介して監視された子プロセスで実行される。
  • プロセス管理戦略はメッセージごとではなく、アクティブな ACP セッションキーごとに長期間維持する。
これにより、プロンプトごとの起動コストを避け、キャンセルや再接続のセマンティクスを安定させます。

コアランタイムコントラクト (契約)

ルーティングコードが CLI の詳細に依存せず、ディスパッチロジックを変更することなくバックエンドを切り替えられるよう、コア側に ACP ランタイムコントラクトを導入します:
export type AcpRuntimePromptMode = "prompt" | "steer";

export type AcpRuntimeHandle = {
  sessionKey: string;
  backend: string;
  runtimeSessionName: string;
};

export type AcpRuntimeEvent =
  | { type: "text_delta"; stream: "output" | "thought"; text: string }
  | { type: "tool_call"; name: string; argumentsText: string }
  | { type: "done"; usage?: Record<string, number> }
  | { type: "error"; code: string; message: string; retryable?: boolean };

export interface AcpRuntime {
  ensureSession(input: {
    sessionKey: string;
    agent: string;
    mode: "persistent" | "oneshot";
    cwd?: string;
    env?: Record<string, string>;
    idempotencyKey: string;
  }): Promise<AcpRuntimeHandle>;

  submit(input: {
    handle: AcpRuntimeHandle;
    text: string;
    mode: AcpRuntimePromptMode;
    idempotencyKey: string;
  }): Promise<{ runtimeRunId: string }>;

  stream(input: {
    handle: AcpRuntimeHandle;
    runtimeRunId: string;
    onEvent: (event: AcpRuntimeEvent) => Promise<void> | void;
    signal?: AbortSignal;
  }): Promise<void>;

  cancel(input: {
    handle: AcpRuntimeHandle;
    runtimeRunId?: string;
    reason?: string;
    idempotencyKey: string;
  }): Promise<void>;

  close(input: { handle: AcpRuntimeHandle; reason: string; idempotencyKey: string }): Promise<void>;

  health?(): Promise<{ ok: boolean; details?: string }>;
}
実装の詳細:
  • 最初のバックエンド: プラグインサービスとして提供される AcpxRuntime
  • コア側はレジストリを介してランタイムを解決し、利用可能なバックエンドがない場合はオペレーター向けに明確なエラーを表示します。

コントロールプレーンのデータモデルと永続化

長期的な「真実のソース」は、トランザクション更新とクラッシュ耐性のある復旧のために、専用の ACP SQLite データベース(WAL モード)になります:
  • acp_sessions (セッション管理)
  • acp_runs (実行管理)
  • acp_bindings (スレッドとの紐付け)
  • acp_events (イベントログ)
  • acp_delivery_checkpoint (配信チェックポイント)
  • acp_idempotency (べき等性管理)
保存ルール:
  • 移行中は SessionEntry.acp への投影を維持。
  • プロセス ID やソケット情報はメモリ内のみに保持。
  • ライフサイクルと実行ステータスは ACP DB で管理し、汎用のセッション JSON には含めない。
  • ランタイムが停止した場合、ゲートウェイは ACP DB から状態を復元し、チェックポイントから再開する。

ルーティングと配信

インバウンド:
  • 最初のステップとして、現在のスレッドバインディングのルックアップを維持。
  • バインド先が ACP セッションであれば、通常の処理ではなく ACP ランタイムブランチへルーティング。
  • 明示的な /acp steer コマンドには mode: "steer" を使用。
アウトバウンド:
  • ACP イベントストリームを OpenClaw の返信チャンクに正規化。
  • 配信先は既存のバインディングパスから解決。
  • 紐付けられたスレッドがアクティブな場合、親チャネルへの完了通知は抑制する。
ストリーミングポリシー:
  • 部分的な出力をバッファリング(結合)して送信。
  • Discord のレート制限を守るため、最小間隔と最大チャンクサイズを設定可能にする。
  • 完了または失敗時には、必ず最終メッセージを送信する。

ステートマシンとトランザクション境界

セッションおよび実行の状態遷移を厳格に管理します。 必要なトランザクション境界:
  • Spawn トランザクション: セッション行の作成、紐付け行の作成/更新、最初の実行行のエンキュー。
  • Close トランザクション: セッションを closed にマーク、紐付け行の削除/期限切れ処理、最終クローズイベントの書き込み。
  • Cancel トランザクション: べき等性キーを用いて対象の実行を cancelling/cancelled にマーク。
これらの境界を跨いだ部分的な成功は許可されません。

セッションごとのアクターモデル

AcpSessionManager は、ACP セッションキーごとに 1 つのアクターを実行します:
  • アクターのメールボックスが submit, cancel, close, stream などの副作用をシリアル化(順序化)する。
  • アクターが、そのセッションのランタイムハンドルとプロセスライフサイクルを管理する。
  • Discord への配信前に、アクターが実行イベントをシーケンス番号 (seq) 通りに書き込む。
  • アウトバウンド送信の成功後、アクターが配信チェックポイントを更新する。
これにより、ターンを跨いだ競合を排除し、スレッド出力の重複や順序の逆転を防ぎます。

べき等性と配信投影

外部からのすべての ACP アクションにはべき等性キー(idempotency keys)が必要です。 配信ルール:
  • Discord メッセージは、acp_eventsacp_delivery_checkpoint から導出される。
  • 再試行時は、既に配信済みのチャンクを送ることなくチェックポイントから再開する。
  • 最終的な返信は、投影ロジックによって「実行ごとに正確に 1 回」送信される。

復旧と自己修復

ゲートウェイ起動時:
  • 終了していない(non-terminal)ACP セッションをロード。
  • 最初のインバウンドイベント時にアクターを遅延作成、または上限設定に従って先行作成。
  • ハートビートが途切れている running 状態の実行を調整し、失敗としてマークするかアダプター経由で復旧。
受信 Discord スレッドメッセージ:
  • バインディングはあるが ACP セッションが見つからない場合、明確なエラーメッセージと共に処理を拒否(fail-closed)。
  • 安全性が確認された場合のみ、古いバインディングを自動解除。
  • 切れたバインディングを黙って通常の LLM パスへ流すことは決してしない。

ライフサイクルと安全性

  • サポートされる操作: キャンセル (/acp cancel)、解除 (/unfocus)、クローズ (/acp close)。
  • アイドルセッションの自動クローズ(実効 TTL に基づく)。
  • 安全制御: エージェントの許可リスト、ワークスペースルートの制限、環境変数のパススルー制限、最大同時実行数、再起動時のバックオフ。

構成設定(Config)

コア設定:
  • acp.enabled
  • acp.dispatch.enabled (ルーティングの個別スイッチ)
  • acp.backend (既定は acpx)
  • acp.allowedAgents[]
  • acp.controlPlane.storePath
  • channels.discord.threadBindings.spawnAcpSessions
プラグイン/バックエンド設定:
  • コマンド/パスの上書き、環境変数許可リスト、エージェントごとのプリセット、タイムアウト設定など。

段階的なリリース計画

フェーズ 1: コア側でのコントロールプレーン基盤の実装

  • AcpSessionManager とアクターランタイムの実装。
  • SQLite ストア、べき等性管理、イベント保存、チェックポイントモジュールの実装。

フェーズ 2: コアルーティングとライフサイクルの統合

  • ディスパッチパイプラインから ACP マネージャーへの接続。
  • fail-closed なルーティングの強制。
  • リセット/削除/アーカイブ等のライフサイクルとの統合。

フェーズ 3: acpx バックエンドアダプター/プラグイン

  • ランタイムコントラクトに従った acpx アダプターの実装。
  • イベントの正規化とプロセス監視、バックオフポリシーの実装。

フェーズ 4: 配信投影とチャネル UX (まずは Discord)

  • イベント駆動型のチャネル投影とチェックポイントからの再開機能の実装。
  • /acp 各種コマンドの提供。

フェーズ 5: 移行と切り替え

  • 投影用データの二重書き込みの導入。
  • レガシーデータの移行ユーティリティの提供。
  • 読み取りパスを ACP SQLite 優先に切り替え。

テスト計画

  • ユニットテスト: トランザクション境界、ステートマシン遷移、べき等性、アクターの順序制御、イベントパースなど。
  • 結合テスト: 擬似アダプターを用いたストリーミング/キャンセル、インバウンドルーティング、二重出力の抑制、チェックポイントからの復旧など。
  • E2E テスト: スレッドを用いた spawn から一連の対話、解除、ゲートウェイ再起動後の継続確認、複数スレッドでの同時実行など。

リスクと緩和策

  • 移行中の二重配信: 単一の配信リゾルバーとべき等なチェックポイントで防ぐ。
  • 負荷時のプロセス過多: 長寿命な所有者モデルと同時実行数制限、バックオフで制御。
  • プラグインの欠落/誤設定: オペレーター向けに明確なエラーを表示し、不用意なフォールバックを避ける。