Skip to content

CLI / TUI / Web

源码版本v2026.7.20

職務

UI 層はユーザーが agent に触れるもう一組の入口(ゲートウェイ (gateway) のメッセージプラットフォーム以外):CLI(hermes_cli/、コマンドラインのサブコマンド体系)、TUI(ui-tui/、TypeScript 端末 UI + tui_gateway/ Python 橋渡し)、Web(web/、ブラウザ UI)。三者とも最終的にユーザー入力を agent の run_conversation に送り、出力を描画し戻す。ゲートウェイ層とは並列——ゲートウェイが外部プラットフォームを、UI 層がローカルユーザーを受け持つ。

主要ファイル

CLI

TUI

Web

CLI サブコマンドの表層

hermes_cli/_parser.py はトップレベルの argparse を独立して切り出し、relaunch.py などのモジュールが main を走らせずにどんな flag があるか内省できるようにしている。パーサーは chat サブコマンドとグローバルオプションだけを管り、他のサブコマンドは main.py でその場で構築する(cmd_* 関数と強結合)。_EPILOGUE の例がユーザーが向き合うサブコマンドの全景だ。

python
hermes                        Start interactive chat
hermes --tui                  Launch the modern TUI
hermes auth add <provider>    Add a pooled credential
hermes gateway                Run messaging gateway
hermes dashboard              Start web UI dashboard (port 9119)

CLI は「対話入口」であると同時に「運用パネル」——auth/model/config/gateway/dashboard/backup がすべて同じサブコマンドツリーにぶら下がる。chat とその派生(-c/--resume)だけが run_conversation に入り、他は各自のモジュールに直行する。

TUI ゲートウェイの stdin/JSON-RPC 主ループ

TUI は端末描画を TypeScript 側(ui-tui/)に置き、Python 側は軽量な tui_gateway 橋だけを走らせる。両者は stdin/stdout 上の JSON-RPC で通信する。tui_gateway/entry.pymain() の核心は readline + dispatch ループだ(tui_gateway/entry.py)。

python
while True:
    raw = sys.stdin.readline()
    if not raw:
        # Stdin fell through — spurious EOF (child flipped O_NONBLOCK)
        # or genuine close.  handle_spurious_eof が継続判定を行う。
        if not handle_spurious_eof(_recovery_times, _log_exit):
            break
        continue
    try:
        req = json.loads(raw.strip())
    except json.JSONDecodeError:
        write_json({"jsonrpc": "2.0", "error": {"code": -32700, ...}, "id": None})
        continue
    resp = dispatch(req)

いくつかのエンジニアリング詳細に注意:handle_spurious_eof は子プロセスが O_NONBLOCK を反転させたことによる「偽の EOF」を処理する。_log_exit は stdout への書き込み失敗時にクラッシュログを残す。SIGPIPE は明示的に無視し(signal.signal(signal.SIGPIPE, signal.SIG_IGN))、バックグラウンドの TTS/beep スレッドが既に閉じたパイプに書き込み、プロセス全体を道連れにするのを防ぐ。

三者は最終的に run_conversation に合流

どの入口から入っても、対話系のリクエストは最終的に AIAgent.run_conversation に至る——これは単なる転送器で、本当の主ループは agent/conversation_loop.py にある(run_agent.py:6350)。

python
def run_conversation(self, user_message, system_message=None,
                     conversation_history=None, ..., moa_config=None) -> Dict[str, Any]:
    """Forwarder — see ``agent.conversation_loop.run_conversation``."""
    from agent.conversation_loop import run_conversation
    from agent.portal_tags import set_conversation_context
    token = set_conversation_context(self._conversation_root_id())
    with scoped_runtime_main({}):
        return run_conversation(self, user_message, ...)

会話 ID は ContextVar で押し下げられる——主ループ内のすべての LLM 呼び出し(圧縮 (compression)、ビジョン、MoA スロット、バックグラウンド審視の fork)が自動的に同じタグを持ち、各呼び出し点で明示的に渡す必要がない。Agent インスタンスは init_agent(agent/agent_init.py:276) で組み立てられ、シグネチャの長い callback 引数群(step_callback/stream_delta_callback/event_callback/...)が三つの殻のフックだ——同じ agent で殻ごとに異なるコールバックを掛け、描画形態が完全に変わる。

データフロー

  1. CLI:hermes <subcommand>hermes_cli/_parser.py が解析 → サブコマンドモジュール(auth/backup/blueprint/...)が実行。対話系コマンドは最終的に AIAgent.run_conversation:6350 を呼ぶ。
  2. TUI:ui-tui/(TS フロントエンド)が WebSocket で tui_gateway/ws.py に接続、tui_gateway が端末イベントを agent に橋渡し(tui_gateway/server.py が agent を保持)。出力は tui_gateway/event_publisher.py でフロントに送られる。
  3. Web:web/(Vite フロントエンド)も同様にバックエンド経由で agent に接続し、対話とツール結果を描画する。
  4. 三者の agent インスタンスはすべて init_agent:276 で組み立てられ、同じ機能層を共有する。

設計動機

なぜ三つの殻がそれぞれ独自のループを走らせず run_conversation を共用するのか? 核心は「描画の差異 vs 業務の一致」だ。ストリーミング token、ツール進度、イベント形式は殻の役目で、セッション (session) 状態、ツール呼び出し (tool dispatch)、予算 (budget) 減算、記憶注入は agent の役目だ。殻をコールバック層に隔離すれば、殻を替えても業務を変えず、業務を追加しても三箇所を直す必要がない。tui_gateway が単独で存在するのは、TS 側が Python agent を直接保持できないからだ——これは言語橋であって、業務層ではない。

境界と失敗

  • TUI の stdin/stdout は JSON-RPC パイプであってログではないprint デバッグで stdout に書くと TUI のパースを壊す。Python 側のログは stderr に流れ、TUI はそれを gateway.stderr イベントとして Activity パネルに届ける。
  • tui_gateway 子プロセスが落ちても TUI は自動再接続しない_CRASH_LOG は事後取证用であって、回復機構ではない——ユーザーが手動で hermes --tui を再起動しなければならない。
  • Web は TUI の代替ではないweb/hermes dashboard(ポート 9119)を走り、位置付けはローカルコンソール + 長会話表示で、リモート多ユーザーアクセスのウェブ製品ではない。

まとめ

UI 層 = 三つの殻(CLI/TUI/Web)+ 一つの tui_gateway 橋渡し。業務を持たず、入出力の形だけを担う。ゲートウェイ層と並列:ゲートウェイは外部メッセージプラットフォーム、UI 層はローカル端末/ブラウザを受け持つ。ACP(参照)は IDE を受け持ち、完全な「触達面」を構成する。

非公式コミュニティ学習サイト。MIT ライセンスの NousResearch/hermes-agent ソースに基づく。