はじめに

ACP ライフサイクルのリファクタリング

ACP のライフサイクルは現在機能していますが、その多くが事後的な推測に依存しています。 プロセスのクリーンアップでは、PID、コマンド文字列、ラッパーの パス、実行中のプロセステーブルから所有関係を再構築します。セッションの可視性では、 セッションキー文字列と二次的な sessions.list({ spawnedBy }) ルックアップから所有関係を再構築します。 そのため限定的な修正は可能ですが、エッジケースも見落としやすくなります。 PID の再利用、引用符付きコマンド、アダプターの孫プロセス、複数 Gateway の状態ルート、 cancelclose、および treeall の可視性がすべて、同じ所有権ルールを 個別に再発見する箇所になります。

このリファクタリングでは、所有権を第一級の概念にします。目的は新しい ACP 製品 サーフェスではなく、既存の ACP および ACPX の動作に対する、より安全な内部契約です。

目標

  • 現在の実プロセスの証拠が OpenClaw 所有のリースと一致しない限り、クリーンアップはプロセスにシグナルを送信しません。
  • cancelclose、起動時の刈り取りは、それぞれ異なるライフサイクル上の意図を持ちます。
  • sessions_listsessions_historysessions_send、ステータス確認では、 同じリクエスター所有セッションモデルを使用します。
  • 複数 Gateway のインストール環境で、互いの ACPX ラッパーを刈り取ることはできません。
  • 移行中も古い ACPX セッションレコードは引き続き動作します。
  • ランタイムは引き続き Plugin が所有し、コアは ACPX パッケージの詳細を認識しません。

目標外

  • ACPX の置き換え、または公開 /acp コマンドサーフェスの変更。
  • ベンダー固有の ACP アダプター動作をコアへ移動すること。
  • アップグレード前にユーザーへ状態の手動クリーンアップを要求すること。
  • cancel に再利用可能な ACP セッションを閉じさせること。

目標モデル

Gateway インスタンス ID

各 Gateway プロセスは、安定したランタイムインスタンス ID を持つ必要があります。

ts
type GatewayInstanceId = string;

これは Gateway の起動時に生成し、そのインストール環境の存続期間中は状態に 永続化できます。セキュリティ上のシークレットではありません。ある Gateway の ACP プロセスを別の Gateway のプロセスと 混同しないために使用する所有権の識別子です。

ACP セッションの所有権

生成されたすべての ACP セッションは、正規化された所有権メタデータを持つ必要があります。

ts
type AcpSessionOwner = {  sessionKey: string;  spawnedBy?: string;  parentSessionKey?: string;  ownerSessionKey: string;  agentId: string;  backend: "acpx";  gatewayInstanceId: GatewayInstanceId;  createdAt: number;};

Gateway は、既知のフィールドをセッション行で返す必要があります。 可視性フィルタリングは、行メタデータに対する純粋なチェックにする必要があります。

ts
canSeeSessionRow({  row,  requesterSessionKey,  visibility,  a2aPolicy,});

これにより、可視性チェックから隠れた二次的な sessions.list({ spawnedBy }) 呼び出しが なくなります。生成されたクロスエージェント ACP 子セッションがリクエスター所有であるのは、 二度目のクエリで偶然見つかるからではなく、そのように行に記録されているからです。

ACPX プロセスリース

生成されたラッパーを起動するたびに、リースレコードを作成する必要があります。

ts
type AcpxProcessLease = {  leaseId: string;  gatewayInstanceId: GatewayInstanceId;  sessionKey: string;  wrapperRoot: string;  wrapperPath: string;  rootPid: number;  processGroupId?: number;  commandHash: string;  startedAt: number;  state: "open" | "closing" | "closed" | "lost";};

ラッパープロセスは、移植可能な引数としてリース ID と Gateway インスタンス ID を受け取ります。

sh
--openclaw-acpx-lease-id ... --openclaw-gateway-instance-id ...

プラットフォームで可能な場合、検証ではコマンドの引用方法によって混同されない 実プロセスのメタデータを優先する必要があります。

  • ルート PID がまだ存在する
  • 実行中のラッパーパスが wrapperRoot 配下にある
  • 利用可能な場合、プロセスグループがリースと一致する
  • 引数に期待されるリース ID が含まれる
  • コマンドハッシュまたは実行可能ファイルのパスがリースと一致する

実プロセスを検証できない場合、クリーンアップは安全側に倒して失敗します。

ライフサイクルコントローラー

プロセスリースとクリーンアップポリシーを所有する単一の ACPX ライフサイクルコントローラーを 導入します。

ts
interface AcpxLifecycleController {  ensureSession(input: AcpRuntimeEnsureInput): Promise&lt;AcpRuntimeHandle&gt;;  cancelTurn(handle: AcpRuntimeHandle): Promise<void>;  closeSession(input: {    handle: AcpRuntimeHandle;    discardPersistentState?: boolean;    reason?: string;  }): Promise<void>;  reapStartupOrphans(): Promise<void>;  verifyOwnedTree(lease: AcpxProcessLease): Promise&lt;OwnedProcessTree | null&gt;;}

cancelTurn はターンのキャンセルのみを要求します。再利用可能なラッパー またはアダプタープロセスを刈り取ってはなりません。

closeSession は刈り取りを実行できますが、セッションレコードを読み込み、 リースを読み込み、実行中のプロセスツリーが引き続きそのリースに属することを検証した後に限ります。

reapStartupOrphans は状態内のオープンなリースから開始します。子孫を見つけるためにプロセス テーブルを使用してもかまいませんが、最初に ACP らしい任意のコマンドをスキャンし、 後からおそらく自分たちのものだと判断すべきではありません。

ラッパー契約

生成されるラッパーは小さく保つ必要があります。以下を行います。

  • サポートされている場合、プロセスグループ内でアダプターを起動する
  • 通常の終了シグナルをプロセスグループへ転送する
  • 親プロセスの終了を検出する
  • 親プロセスの終了時に SIGTERM を送信し、SIGKILL フォールバックが実行されるまでラッパーを生存させる
  • 利用可能な場合、ルート PID とプロセスグループ ID をライフサイクルコントローラーへ 報告する

ラッパーがセッションポリシーを決定してはなりません。自身のアダプターグループに対する ローカルなプロセスツリーのクリーンアップのみを実施します。

セッション可視性の契約

可視性では正規化された行の所有権を使用する必要があります。

ts
type SessionVisibilityInput = {  requesterSessionKey: string;  row: {    key: string;    agentId: string;    ownerSessionKey?: string;    spawnedBy?: string;    parentSessionKey?: string;  };  visibility: "self" | "tree" | "agent" | "all";  a2aPolicy: AgentToAgentPolicy;};

ルール:

  • self: リクエスターのセッションのみ。
  • tree: リクエスターのセッションに加え、リクエスターが所有する行、またはリクエスターから生成された行。
  • all: 同一エージェントのすべての行、a2a で許可されたクロスエージェント行、および一般的な a2a が無効でもリクエスター所有の 生成されたクロスエージェント行。
  • agent: 明示的な所有関係により行がリクエスターに属すると示されない限り、同一エージェントのみ。

これにより、treeall は単調になります。tree で表示される所有済みの子を all で非表示にしてはなりません。

移行計画

フェーズ 1: ID とリースの追加

  • Gateway の状態に gatewayInstanceId を追加する。
  • ACPX の状態ディレクトリ配下に ACPX リースストアを追加する。
  • 生成されたラッパーを起動する前にリースを書き込む。
  • 新しい ACPX セッションレコードに leaseId を保存する。
  • 古いレコードのために既存の PID およびコマンドフィールドを維持する。

フェーズ 2: リース優先のクリーンアップ

  • 終了時のクリーンアップで、最初に leaseId を読み込むよう変更する。
  • シグナル送信前に、実プロセスの所有権をリースに照らして検証する。
  • 現在のルート PID とラッパールートのフォールバックは、レガシーレコードに対してのみ維持する。
  • 検証済みのクリーンアップ後、リースを closed としてマークする。
  • クリーンアップ前にプロセスが消失していた場合、リースを lost としてマークする。

フェーズ 3: リース優先の起動時刈り取り

  • 起動時の刈り取りでは、オープンなリースをスキャンする。
  • 各リースについて、ルートプロセスを検証し、子孫を収集する。
  • 検証済みのツリーを子から先に刈り取る。
  • 有界の保持期間を設け、古い closed および lost リースを期限切れにする。
  • コマンドマーカーのスキャンは、一時的なレガシーフォールバックとしてのみ維持し、 可能な場合はラッパールートと Gateway インスタンスで制限する。

フェーズ 4: セッション所有権行

  • Gateway のセッション行に所有権メタデータを追加する。
  • ACPX、サブエージェント、バックグラウンドタスク、セッションストアの各ライターが ownerSessionKey または spawnedBy を設定するようにする。
  • セッション可視性チェックを行メタデータの使用へ移行する。
  • 可視性判定時の二次的な sessions.list({ spawnedBy }) ルックアップを削除する。

フェーズ 5: レガシーヒューリスティックの削除

1 リリース期間後:

  • 非レガシー ACPX のクリーンアップで、保存されたルートコマンド文字列への依存を停止する
  • コマンドマーカーによる起動時スキャンを削除する
  • 可視性のフォールバックリスト検索を削除する
  • 欠落または検証不能なリースに対する防御的なフェイルクローズ動作を維持する

テスト

テーブル駆動のスイートを 2 つ追加します。

プロセスライフサイクルシミュレーター:

  • 無関係なプロセスによって PID が再利用される
  • 別の Gateway のラッパールートによって PID が再利用される
  • 保存されたラッパーコマンドにはシェル引用符があり、実行中の ps コマンドにはない
  • アダプターの子プロセスが終了し、孫プロセスがプロセスグループ内に残る
  • 親プロセス終了時の SIGTERM フォールバックが SIGKILL に到達する
  • プロセス一覧を取得できない
  • プロセスが存在しない古いリース
  • ラッパー、アダプターの子プロセス、孫プロセスを含む起動時の孤児プロセス

セッション可視性マトリックス:

  • selftreeagentall
  • a2a が有効な場合と無効な場合
  • 同一エージェントの行
  • クロスエージェント行
  • リクエスター所有の生成されたクロスエージェント ACP 行
  • サンドボックス化されたリクエスターが tree に制限される
  • 一覧、履歴、送信、ステータスの各アクション

重要な不変条件: リクエスター所有の生成された子は、設定された可視性に リクエスターのセッションツリーが含まれるすべての箇所で表示され、all の能力が tree を下回ることはありません。

互換性に関する注意事項

古いセッションレコードには leaseId が存在しない場合があります。その場合は、レガシーの フェイルクローズ型クリーンアップパスを使用する必要があります。

  • 実行中のルートプロセスを必須とする
  • 生成されたラッパーが想定される場合、ラッパールートの所有権を必須とする
  • ラッパー以外のルートではコマンドの一致を必須とする
  • 古い保存済み PID メタデータだけを根拠にシグナルを送信しない

レガシーレコードを検証できない場合は、そのままにします。起動時のリースクリーンアップと 次のリリース期間によって、最終的にフォールバックを廃止する必要があります。

成功基準

  • 古い、または期限切れの ACPX セッションを閉じても、別の Gateway のプロセスを終了させることはできない。
  • 親プロセスが終了しても、終了しにくいアダプターの孫プロセスが実行中のまま残らない。
  • cancel は再利用可能なセッションを閉じることなく、アクティブなターンを中止する。
  • sessions_list は、treeall の両方で、リクエスター所有のクロスエージェント ACP 子セッションを表示できる。
  • 起動時のクリーンアップは、広範なコマンド文字列スキャンではなくリースによって駆動される。
  • 対象を絞ったプロセスおよび可視性マトリックスのテストで、以前は個別のレビュー修正が必要だったすべてのエッジケースを網羅する。
Was this useful?
On this page

On this page