yoi/tickets/protocol-design.md

5.8 KiB
Raw Blame History

Protocol の設計

背景

現状の Protocol (Method / Event) は最低限のストリーミングイベントのみ。 機能が増えるにつれ、以下が不足している:

  • Compact 発生時のクライアント通知
  • Permission の ask/reply フローpermission-extension-point の段階 3
  • セッション切り替えcompact 後の新 session_id 通知)
  • クライアント→Pod の制御拡張(設定変更等)

現状(調査結果)

Method (Client → Pod)

crates/protocol/src/lib.rs:11-32

Run { input } | Notify { message } | PodEvent(PodEvent)
Resume | Cancel | Pause | Shutdown | GetHistory

Event (Pod → Client)

crates/protocol/src/lib.rs:85-134

TurnStart/TurnEnd, TextDelta/TextDone,
ToolCallStart/ArgsDelta/Done, ToolResult,
Usage, RunEnd, Error, History, Notification, Shutdown

既存の request-response の扱い

crates/pod/src/socket_server.rs:96-113GetHistory だけが 「socket 層で即応答」。他の Method は handle.send(method) で Controller に fire-and-forget。

Client (crates/tui/src/client.rs) は reader を単一の mpsc に流すだけで、 応答と broadcast を区別しない。GetHistory は 1 回きりで競合しないため 現状は動く。

Compact の現状

crates/pod/src/pod.rs:825compact() は新しい SessionId を戻すが、 ネットワークに一切乗らない:

  • mid-turn: do_compact_and_resume (685行) — 失敗時のみ Notification::Error
  • post-turn: try_post_run_compact (715行) — 失敗時のみ Notification::Warn

成功時は完全にサイレント。Event::History / Greetingsession_id は入っていないため、 TUI は現在の session_id を認識できない。

決定事項

1. request-response の扱い — 今は足さない

  • 現状 GetHistory が 1 回きりで競合しないため、新しい wire フィールドなしで動く。
  • 将来 Permission ask/reply などが入る時点で、Method/Event 双方に request_id: Option<String> を追加する方針を採る。既存実装は無視するだけで互換を保てる。
  • Response を別 enum にする案 (C) は採用しない:
    • 先例として PodEventMethod::PodEvent(PodEvent) で Method 側に包まれており、第 3 の wire 型を増やす理由がない。
    • Reader の分岐が増え、PodClient の mpsc 設計を崩す。

今回は ドキュメントとしての宣言のみ。フィールドは足さない。

2. Compact イベント — 追加する

Event::CompactStart
Event::CompactDone { new_session_id: SessionId }
Event::CompactFailed { error: String }
  • 発火点は do_compact_and_resumetry_post_run_compact の 2 箇所。 event_tx.send(...) を start / 成功 / 失敗に挟むだけ。
  • 既存の Notification::Error/Warncompactor sourceはイベント側に移行してよいが、 初期移行では重複して出しても害はない。先に Event を足し、Notification は後続チケットで整理。
  • Broadcast で全クライアントに通知compact は Pod 自律発火で、特定リクエストへの応答ではない)。

3. session_id 通知 — CompactDone に含める

  • Event::CompactDone { new_session_id } に載せる。
  • 汎用 SessionChanged は作らないYAGNI — fork 等が実装される時まで判断を保留)。
  • TUI 側は現状 session_id を利用していないので、イベントを受け取るだけでよい(将来 GetHistory 再取得などに使う)。

4. Permission ask/reply — 別チケットで実装

  • permission-extension-point の段階 3 で追加する。
  • Pod → Client は Event::PermissionRequest { id, tool, args }、 Client → Pod は Method::PermissionReply { id, allow } を想定。
  • ここで使う id は 1. の request_id: Option<String> パターンに従う。 protocol-design 本チケットでは足さず、permission-extension-point 側で導入する。

本チケットで実装するもの

  1. Event::CompactStart / Event::CompactDone { new_session_id: SessionId } / Event::CompactFailed { error: String } を追加する。
    • SessionId の wire 表現は既存 session_store::SessionId をそのまま Serialize できれば使う。外部型に依存するなら String 化する。
  2. crates/pod/src/pod.rs の compact 発火 2 箇所で対応する Event を broadcast する。
  3. crates/tui/src/app.rshandle_pod_event に 3 分岐を追加し、最低限の表示を行う(アイコン + メッセージ、詳細 UI は別チケット)。
  4. テスト:
    • Event の JSON roundtripprotocol クレート)
    • 成功/失敗/mid-turn の各パスで Event が発行されることを確認する統合テスト(pod クレート)

本チケットで実装しないもの

  • request_id: Option<String> フィールドの追加(将来、必要になったチケットで足す)
  • Event::SessionChangedfork 実装時に再検討)
  • Notification と CompactFailed の重複整理(後続チケット)
  • Permission ask/reply の wire 定義permission-extension-point 段階 3

検討メモ(将来向け)

  • Event の肥大化が気になってきたら、Event::Stream(StreamEvent) のようにカテゴリ分けしてネストする余地はある。ただし現状 15 バリアントで破綻していないので先送り。
  • Protocol のバージョニングは未着手。クライアントの互換性問題が顕在化した時点で Event::Hello { protocol_version } のような握手を追加する。
  • Broadcast channel の特性上、遅い client では drop が起き得る。現状はログのみだが、compact 等の重要イベントを落とすとまずいので将来は per-client queue への置き換えを検討する。