Skip to main content
ブログに戻る
ブログ

舞台裏で: JSONL セッション解決がエージェントルーティングを支える仕組み

MadoHub が Claude Code のセッションログを読み取り、インテリジェントなルーティングのためにクリーンで構造化された応答を抽出する仕組みを詳しく解説します。

エージェントルーティングで最も難しい問題は、ターゲットエージェント向けのプロンプトを生成することではありません。ソースエージェントからクリーンな応答を取り出すことです。

ターミナル出力は雑然としています。ANSI エスケープコード、進捗スピナー、折り返し、色のシーケンス。生のターミナルスクレイピングでは、ノイズの壁しか得られません。制御コードを取り除いても、書式の崩れ、途中で切れた行、混ざった内容が残ります。

MadoHub は別の方法を取ります。ターミナル出力をスクレイプするのではなく、Claude Code の構造化されたセッションログを読みます。これは、各会話ターンのクリーンで意味的な表現を含む JSONL ファイルです。

この記事では、その仕組みを説明します。

ターミナルスクレイピングの問題点

Claude Code がターミナルで動くと、その出力には次のようなものが含まれます。

  • ANSI エスケープシーケンス - 色、カーソル位置、書式用。
  • Unicode マーカー - ❯(プロンプト)、✽(思考)、⏺(ツール使用)。
  • 進捗インジケータ - 以前の行を上書きするもの。
  • 対話要素を含む許可ダイアログ。
  • 複数行のコードブロック - シンタックスハイライトのエスケープコード付き。

ANSI コードを剥がすとクリーンなテキストに近づきますが、まだ完全ではありません。進捗表示が途中行を残し、コードブロックは構造を失います。「エージェント出力」と「シェルノイズ」の境界も曖昧です。

単純なルーティング(たとえば「最後の 10 行を送る」)なら、ターミナルスクレイピングでも十分です。しかし、インテリジェントルーティング - AI モデルがエージェントの実際の行動を理解し、文脈付きのプロンプトを生成する必要がある場面 - では、クリーンで構造化されたデータが必要です。

Claude Code のセッション保存方法

Claude Code は、構造化されたセッションログを JSONL 形式(1 行 1 JSON オブジェクト)で書き込みます。解決パスは次の通りです。

tmux pane → ps process-tree BFS
  → agent PID (first descendant whose command matches claude/codex/amp/opencode)
    → ~/.claude/sessions/{PID}.json
      → sessionId field
        → ~/.claude/projects/*/{sessionId}.jsonl
          → conversation turns (user/assistant messages)

各会話ターンには、メッセージの役割、内容、メタデータが入っており、ターミナル書式は含まれません。これは Claude Code が内部的に会話コンテキストを維持するために使っているのと同じデータです。

解決チェーン

MadoHub は、複数段階のチェーンで JSONL ファイルを解決します。

ステップ 1: PTY プロセス ID を取得する

各ターミナルタイルの裏側には tmux ペインがあり、MadoHub はターミナルが作成された瞬間からそのペインの PID を追跡します。この PID は、実際のエージェントプロセス(通常はペインのシェルの子孫として動作します)を特定するための出発点です。

ステップ 2: エージェント PID とそのセッションファイルを見つける

Claude Code は ~/.claude/sessions/ にセッションディレクトリを持っていますが、各ファイルは エージェントのプロセス ID にちなんで名付けられており、セッション ID ではありません。この PID を見つけるため、MadoHub は tmux ペインからプロセスツリーをたどります(子孫プロセスに対する ps -eo pid=,ppid=,comm= の幅優先探索)。既知のエージェント(claude、codex、amp、opencode)にコマンド名が一致する最初のプロセスに到達するまで探索します。そして MadoHub はその PID の ~/.claude/sessions/{PID}.json を読み取ります。このファイルには、実際の会話ログを指す sessionId フィールドが含まれています。

ステップ 3: JSONL を特定して読む

MadoHub はセッションファイルから sessionId を取得し、~/.claude/projects/ のすべてのサブディレクトリを走査して {sessionId}.jsonl という名前のファイルを探します - それがトランスクリプトです。MadoHub はそれをパースし、最後の assistant メッセージ - Claude Code の最新応答 - を取り出します。

このメッセージはクリーンなテキストです。ANSI コードもターミナルの残骸も、進捗表示もありません。Claude Code が意図した形式そのままに、「何を言ったか」が残っています。

ステップ 4: 解決に失敗した場合

プロセスツリーの探索で一致するエージェントプロセスが見つからない場合、またはセッションファイルがすでに消えている場合(たとえばプロセスが終了した場合)、MadoHub はそのターンで使える JSONL パスを持てません。その場合はターミナル出力抽出にフォールバックします - セッションログを一切生成しないエージェントに使うのと同じ ANSI 除去方式です。

クリーンな応答で何が可能になるか

クリーンで構造化された応答があると、ルーティング変換は次のことができます。

文脈を理解する: AI ルーティングモデルは、実際の意味的内容 - たとえば「認証ミドルウェアをセッション Cookie から JWT トークンにリファクタリングしました。変更した 3 ファイルは src/auth/middleware.ts、src/auth/tokens.ts、src/config/auth.ts です」 - を受け取れます。色付きターミナルのノイズではありません。

正確なプロンプトを生成する: 「src/auth/middleware.ts、src/auth/tokens.ts、src/config/auth.ts における JWT トークン移行をレビューしてください。トークン更新フローが有効期限切れを正しく処理し、ミドルウェアがトークン署名を適切に検証しているか確認してください。」

コードブロックを保持する: エージェントの応答にコードスニペットが含まれていても、ターミナルの折り返しや ANSI 色コードで壊れず、そのままの書式で保たれます。

キャッシュと性能

長いセッションでは JSONL ファイルが大きくなります。MadoHub は、タイル ID ごとに最後に解決した応答をキャッシュし、ルーティングのたびにファイル全体を再パースしないようにします。キャッシュは新しい完了が検出されたときに無効化されます。

解決チェーンは一般的なケース向けにも最適化されています。プロセスツリーの探索とセッションファイルの読み取りは高速で、大半のケースで解決に成功します。~/.claude/projects/ を走査して一致する {sessionId}.jsonl ファイルを探す部分は、すべてのプロジェクトサブディレクトリを検索する必要があるため、より コストの高いステップです。

制限

JSONL ベースの状態検出は、もはや Claude Code だけのものではありません - Codex にも独自の JSONL ベースのステータスパイプラインがあります(task_complete → completed、turn_aborted/error → failed、user_message/task_started/agent_message → working、ri_message → stopped)。エージェント間でまだ違いがあるのは応答テキストの抽出です。この記事で説明した、トランスクリプトから最後の assistant メッセージをそのまま読み取るクリーンで構造化されたセッションログ形式は、Claude Code に固有のものです。Codex とシェルコマンドについては、MadoHub は実際の応答テキストを得るために、引き続き ANSI 除去付きのターミナル出力抽出にフォールバックします。

raw と full-output の transform タイプは、任意のエージェントで動きます。ターミナル出力を直接使うからです。ai-routing と summary の transform は、クリーンな JSONL 内容を受け取れる Claude Code で最も良い結果を出します。

これが重要な理由

ルーティング出力の品質は、抽出された応答の品質に直接比例します。入力が汚ければ、出力も汚い。生のターミナル出力をスクレイプするのではなく、構造化されたセッションログを読むことで、MadoHub はソースエージェントが実際に生成した内容を最も忠実に再現できます。

これは、連鎖的に効く設計判断です。より良い抽出 → より良い変換プロンプト → より正確なターゲットエージェント指示 → 無駄なラウンドの削減 → ワークフロー収束の高速化。クリーンな抽出への投資は、ルーティングパイプラインの後続すべての段階で報われます。