yoi/tickets/protocol-design.md
2026-04-21 09:27:58 +09:00

7.7 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 再取得などに使う)。

5. wire 型 — protocoluuid::Uuid を直接扱う

  • SessionId のパースは protocol クレートの責務。protocol/Cargo.tomluuid = { workspace = true, features = ["serde"] } を追加し、 Event::CompactDone { new_session_id: uuid::Uuid } として型付けする。
  • session-store::SessionIduuid::Uuid のエイリアスなので、Pod 側は変換なしで渡せる。
  • 文字列経由にはしないwire を弱く型付けして嬉しいことがない)。

6. broadcast 手段 — Pod が event_tx を直接保持

  • NotifierEvent::Notification の replay バッファ専用のまま残し、compact 系イベントは 通さない(意味が噛み合わない)。
  • Podevent_tx: Option<broadcast::Sender<Event>> を持たせ、 attach_notifier と同じタイミングで Controller 側から渡す。
  • compact 発火点では self.event_tx.as_ref().map(|tx| tx.send(...)) で直接流す。

7. late subscriber への再配信 — しない

  • compact イベントはバッファせず、broadcast 時点で購読していないクライアントには届かない。
  • TUI は現状 session_id を使っていないため、接続後に直近の compact を知る必要がない。
  • 必要になった時点fork や複数クライアント同時接続が現実になった時)で別チケットで buffer 化。

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. protocol クレートに uuid = { workspace = true, features = ["serde"] } を追加し、 Event::CompactStart / Event::CompactDone { new_session_id: uuid::Uuid } / Event::CompactFailed { error: String } を追加する。
  2. Podevent_tx: Option<broadcast::Sender<Event>> を持たせ、Controller 側から Notifier と同じタイミングで渡す。
  3. crates/pod/src/pod.rs の compact 発火 2 箇所(do_compact_and_resume / try_post_run_compact)で start / 成功CompactDone / 失敗CompactFailedを broadcast する。
  4. crates/tui/src/app.rshandle_pod_event に 3 分岐を追加し、 既存の NoticeWarn / NoticeError と同じ枠で最低限のテキストを表示する。
    • [compact] starting
    • [compact] done (new session <uuid先頭8字>)
    • [compact error] <message>
  5. テスト:
    • Event の JSON roundtripprotocol クレート): 3 バリアント + uuid の shape
    • 成功/失敗/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 への置き換えを検討する。

Review