yoi/docs/pod-protocol.md
2026-04-09 05:23:57 +09:00

6.3 KiB
Raw Blame History

Pod Protocol 仕様

概要

Pod の制御・監視に使う JSONL ベースのメッセージプロトコル。

トランスポートに依存しない。CLI は Pod を直接制御し、daemon は Unix socket 上でこのプロトコルを中継する。

  • フレーミング: 1行 = 1 JSON オブジェクト(\n 区切り)
  • 方向: 双方向。クライアントはメソッドを送信し、Pod はイベントを emit する
CLI        → Pod Protocol (直接呼び出し)
Native App → Pod Protocol (直接呼び出し)
Web        → 中央バックエンド → daemon (Unix socket) → Pod Protocol

設計原則

  • リクエストとレスポンスの紐付けはしない。Pod は1つであり、Pod の状態遷移(イベント)を見れば何が起きているか分かる
  • イベントは全リスナーに broadcast される。読み取り専用の監視も、操作側も同じストリームを受け取る
  • 操作の競合は先勝ち。run 中に別の run が来たらエラーイベントを返す

メッセージ形式

クライアント → Podメソッド

{"method": "<name>", "params": {<...>}}

params はメソッドごとに異なる。省略可能な場合は params フィールド自体を省略できる。

Pod → クライアント(イベント)

{"event": "<name>", "data": {<...>}}

全リスナーに broadcast される。

メソッド一覧

run

ユーザー入力を送信し、LLM ターンを開始する。

{"method": "run", "params": {"input": "What is the capital of France?"}}

Pod が既に実行中の場合、エラーイベントが返る。

resume

Paused 状態から再開する。

{"method": "resume"}

cancel

実行中のターンをキャンセルする。

{"method": "cancel"}

get_status

Pod の現在の状態を要求する。応答は status イベントとして返る。

{"method": "get_status"}

get_history

会話履歴を要求する。応答は history イベントとして返る。

{"method": "get_history"}

イベント一覧

ターン制御

turn_start

LLM ターンの開始。

{"event": "turn_start", "data": {"turn": 1}}

turn_end

LLM ターンの完了。

{"event": "turn_end", "data": {"turn": 1, "result": "finished"}}

result: "finished" | "paused"

ストリーミング

text_delta

テキスト応答の差分。

{"event": "text_delta", "data": {"text": "The capital"}}

text_done

テキストブロックの完了。全文を含む。

{"event": "text_done", "data": {"text": "The capital of France is Paris."}}

thinking_delta

思考プロセスの差分extended thinking 対応モデル)。

{"event": "thinking_delta", "data": {"text": "Let me consider..."}}

thinking_done

思考ブロックの完了。

{"event": "thinking_done", "data": {"text": "..."}}

ツール

tool_call_start

ツール呼び出しの開始。

{"event": "tool_call_start", "data": {"id": "call_123", "name": "search"}}

tool_call_args_delta

ツール引数の JSON 差分(ストリーミング中)。

{"event": "tool_call_args_delta", "data": {"id": "call_123", "json": "{\"query\":"}}

tool_call_done

ツール呼び出しの引数確定。

{"event": "tool_call_done", "data": {"id": "call_123", "name": "search", "arguments": "{\"query\": \"Paris\"}"}}

tool_result

ツール実行結果。

{"event": "tool_result", "data": {"id": "call_123", "output": "Paris is the capital...", "is_error": false}}

状態

status

get_status への応答、または状態変化時に送信。

{"event": "status", "data": {"state": "idle", "session_id": "019d6e91-...", "pod_name": "hello-pod"}}

state: "idle" | "running" | "paused"

history

get_history への応答。

{"event": "history", "data": {"items": [...]}}

items は llm-worker の Item 配列をそのまま JSON シリアライズしたもの。

メタ

usage

トークン使用量。

{"event": "usage", "data": {"input_tokens": 25, "output_tokens": 150}}

error

エラー通知。

{"event": "error", "data": {"code": "already_running", "message": "Pod is already executing a turn"}}

エラーコード:

  • already_running — run 中に run が来た
  • not_running — run していないのに resume/cancel が来た
  • not_paused — paused でないのに resume が来た
  • provider_error — LLM プロバイダからのエラー
  • tool_error — ツール実行エラー
  • internal — 内部エラー

リスナーのライフサイクル

  1. リスナーが登録される直接呼び出しなら関数登録、daemon 経由なら socket 接続)
  2. 登録直後から Pod のイベントが流れ始める(購読手続き不要)
  3. クライアントはメソッドを任意のタイミングで送信できる
  4. リスナーの解除は登録解除または接続切断で行う

トランスポート: daemon (Unix socket)

daemon は Pod Protocol を Unix domain socket 上で中継する薄い層。

  • クライアントが socket に接続するとリスナーとして登録される
  • メソッドは socket 経由で Pod に転送される
  • 切断時にリスナーリストから除外するだけでクリーンアップ完了

イベントと llm-worker の対応

イベント llm-worker ソース
turn_start Subscriber on_turn_start
turn_end Subscriber on_turn_end + WorkerResult
text_delta TextBlockEvent::Delta
text_done Subscriber on_text_complete
thinking_delta ThinkingBlockEvent::Delta
thinking_done ThinkingBlockEvent::Stop
tool_call_start ToolUseBlockEvent::Start
tool_call_args_delta ToolUseBlockEvent::InputJsonDelta
tool_call_done Subscriber on_tool_call_complete
tool_result PostToolCall hook
usage UsageEvent
error ErrorEvent / WorkerError
status Pod 状態Pod 層が管理)
history Worker::history()