ACP の内部
目次
ACP アダプタは、Hermes の同期で動く AIAgent を、非同期の JSON-RPC stdio サーバーで包んだものです。
実装の中心となるファイルは次のとおりです。
acp_adapter/entry.pyacp_adapter/server.pyacp_adapter/session.pyacp_adapter/events.pyacp_adapter/permissions.pyacp_adapter/tools.pyacp_adapter/auth.py
起動の流れ
hermes acp / hermes-acp / python -m acp_adapter
-> acp_adapter.entry.main()
-> parse --version / --check / --setup before server startup
-> load ~/.hermes/.env
-> configure stderr logging
-> construct HermesACPAgent
-> acp.run_agent(agent, use_unstable_protocol=True)標準出力は ACP の JSON-RPC のやり取り専用です。人が読むログは標準エラー出力へ送られます。
ゲートウェイの終了結果の反映
acp_adapter/gateway_server.py は、送信したその受付(admission)そのものの完了を待ちます。受付がキャンセルされた場合は stopReason: cancelled を返し、失敗した場合は end_turn を報告せずに ACP のリクエストエラーを発生させます。通常どおり 完了したときは、これまでどおり end_turn を返します。モデルの応答を待っている間にキャンセルされると、 Operation interrupted: waiting for model response というローカルの状態メッセージは表示されません。ただし、実際に 途中まで出力されたアシスタントのテキストはそのまま見えます。終了結果はゲートウェイが返す構造化された実行結果から決まり、 アシスタントの文面やセッション全体のキャンセルフラグからは決まりません。
主な構成要素
HermesACPAgent
acp_adapter/server.py が ACP のエージェントプロトコルを実装しています。
役割は次のとおりです。
- 初期化と認証
- セッションの新規作成・読み込み・再開・分岐・一覧・中止の各メソッド
- プロンプトの実行
- セッションのモデル切り替え
- 同期で動く AIAgent のコールバックを、ACP の非同期の通知につなぐこと
SessionManager
acp_adapter/session.py が、動いている ACP のセッションを管理します。
セッションごとに次を持ちます。
session_idagentcwdmodelhistorycancel_event
このマネージャはスレッドセーフで、次の操作に対応します。
- 作成
- 取得
- 削除
- 分岐
- 一覧
- 片づけ
- 作業ディレクトリの更新
イベントの橋渡し
acp_adapter/events.py が、AIAgent のコールバックを ACP の session_update イベントに変換します。
橋渡しされるコールバックは次のとおりです。
tool_progress_callbackthinking_callback(ACP の橋渡しでは今のところNoneに設定されていて、推論の内容はstep_callbackを通して送られます)step_callback
ACP のツール呼び出しは、必ずどれかの終端の状態にたどり着きます。tool.completed が その呼び出し自身の結果(completed / failed)で決着を付け、step_callback の prev_tools を見る処理は、完了を出さないランタイムのための予備でしかありません。 ターンが終わった時点でまだ開いているもの(拒否された呼び出し、遮断された呼び出し、 途中で中断されたもの)は、応答を返す前に failed として記録されます。
AIAgent はワーカースレッドで動く一方、ACP の入出力はメインのイベントループにあるため、橋渡しには次を使います。
asyncio.run_coroutine_threadsafe(...)承認の橋渡し
acp_adapter/permissions.py が、危険なターミナル操作の承認の問いかけを、ACP の許可の要求に合わせます。
対応は次のとおりです。
allow_once-> Hermes のonceallow_always-> Hermes のalways- 拒否の選択肢 -> Hermes の
deny
待ち時間切れと橋渡しの失敗は、既定で拒否になります。
ツールの表示を助けるしくみ
acp_adapter/tools.py が、Hermes のツールを ACP のツールの種類に対応づけ、エディタに見せる内容を組み立てます。
例を挙げます。
patch/write_file-> ファイルの差分terminal-> シェルのコマンドの文字列read_file/search_files-> テキストの下読み- 大きな結果 -> 画面が壊れないよう切り詰めたテキストの塊
セッションの一生
new_session(cwd)
-> create SessionState
-> create AIAgent(platform="acp", enabled_toolsets=<_get_platform_tools(config, "acp"), as on the
gateway: platform_toolsets.acp (default hermes-acp) plus the resolver's extras
such as plugin toolsets, with its admitted MCP servers keyed mcp-<server>>,
disabled_toolsets=<agent.disabled_toolsets>)
-> bind task_id/session_id to cwd override
prompt(..., session_id)
-> extract text from ACP content blocks
-> reset cancel event
-> install callbacks + approval bridge
-> run AIAgent in ThreadPoolExecutor
-> update session history
-> emit final agent message chunk終端の失敗で終わったターン(プロバイダーによる拒否、やり直しのきかないエラー、 やり直しを使い切った場合、返答が 1 つも出る前の中断)は、中核のループが Hermes 自身の 書いたアシスタントの行(「Your request was not processed…」「This turn did not complete…」)で 締めくくります。こうして、保存される会話の記録が user の行を開いたまま終わらないように します。これがないと、次のプロンプトが失敗した要求に混ぜられて、そのまま繰り返されて しまいます。文脈があふれて失敗した場合だけは例外で、その立て直しはセッションの 入れ替えであって、行を足すことではありません。
中止
cancel(session_id) は次のように動きます。
- セッションの中止イベントを立てる
- 使えるときは
agent.interrupt()を呼ぶ - プロンプトの応答が
stop_reason="cancelled"を返すようにする
分岐
fork_session() は、メッセージの履歴を深くコピーして新しいセッションを作ります。会話の状態はそのまま引き継ぎつつ、分岐した側には独自のセッション ID と作業ディレクトリを与えます。
プロバイダと認証の扱い
ACP は独自の認証情報の保管場所を持ちません。
代わりに、Hermes の実行時の解決のしくみをそのまま使います。
acp_adapter/auth.pyhermes_cli/runtime_provider.py
そのため ACP は、今 Hermes に設定されているプロバイダと認証情報を、そのまま告知して使います。加えて、ターミナルでのセットアップという認証方法(hermes-setup、引数は --setup)を常に告知するので、初めて使う ACP のクライアントは、通常の ACP セッションを始める前に Hermes の対話式のモデル・プロバイダ設定を開けます。
作業ディレクトリの結びつけ
ACP のセッションは、エディタ側の作業ディレクトリを持ちます。
セッションマネージャは、その作業ディレクトリを、タスク単位のターミナル・ファイルの上書き設定を通して ACP のセッション ID に結びつけます。これにより、ファイルとターミナルのツールはエディタの作業場所を基準に動きます。
同じ名前のツールが重なって呼ばれるとき
イベントの橋渡しは、ツール ID をツール名ごとに先入れ先出しで管理します。名前ごとに 1 つだけ持つのではありません。これは次の場合に効いてきます。
- 同じ名前のツールが並行して呼ばれるとき
- 1 つのステップで同じ名前のツールが繰り返し呼ばれるとき
先入れ先出しの待ち行列がないと、完了のイベントが別の呼び出しに結び付いてしまいます。
承認コールバックの復元
ACP は、プロンプトの実行中だけターミナルのツールに承認のコールバックを差し込み、終わったら元のコールバックに戻します。これにより、ACP のセッション固有の承認の処理が、いつまでも全体に居座ることを防ぎます。
今のところの制限
- ACP のセッションは共有の
~/.hermes/state.db(SessionDB)に保存され、プロセスを再起動しても自動で戻ります。session_searchにも出てきます - テキスト以外のプロンプトの塊は、今のところ本文の取り出しでは無視されます
- エディタごとの使い勝手は、ACP クライアントの実装によって変わります
関連するファイル
tests/acp_adapter/— ACP のテスト一式toolsets.py—hermes-acpツールセットの定義hermes_cli/main.py—hermes acpのサブコマンドpyproject.toml—[acp]の追加依存とhermes-acpのスクリプト