From 79842b212ac82ebaecdac86171fdbc944279eb59 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 23 May 2026 03:29:01 +0900 Subject: [PATCH] docs: add pod event callback delivery ticket --- TODO.md | 1 + .../2026-05-22-pod-event-callback-delivery.md | 23 +++++ tickets/pod-event-callback-delivery.md | 92 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 docs/report/2026-05-22-pod-event-callback-delivery.md create mode 100644 tickets/pod-event-callback-delivery.md diff --git a/TODO.md b/TODO.md index 2c5a09fb..2492fdc6 100644 --- a/TODO.md +++ b/TODO.md @@ -7,6 +7,7 @@ - Pod: 空応答ターン (Submit 後 AI 応答ゼロで Pause/Cancel) を自動巻き戻し → [tickets/pod-empty-turn-rollback.md](tickets/pod-empty-turn-rollback.md) - Pod: 任意ターンからの Fork(複数ターン巻き戻しを汎用化) → [tickets/pod-session-fork.md](tickets/pod-session-fork.md) - Pod: Inbound PodEvent ハンドリングの重複を統合 → [tickets/pod-inbound-pod-event-dedup.md](tickets/pod-inbound-pod-event-dedup.md) +- Pod: PodEvent callback delivery を確実化 → [tickets/pod-event-callback-delivery.md](tickets/pod-event-callback-delivery.md) - Pod: 過去 Pod の探索と restore ツール → [tickets/pod-discovery-restore-tools.md](tickets/pod-discovery-restore-tools.md) - Pod: Spawned child 終了時に delegated scope を parent へ reclaim → [tickets/spawned-delegation-scope-reclaim.md](tickets/spawned-delegation-scope-reclaim.md) - llm-worker のエラー耐性 diff --git a/docs/report/2026-05-22-pod-event-callback-delivery.md b/docs/report/2026-05-22-pod-event-callback-delivery.md new file mode 100644 index 00000000..3c9eae05 --- /dev/null +++ b/docs/report/2026-05-22-pod-event-callback-delivery.md @@ -0,0 +1,23 @@ +# Spawned Pod 完了通知が来ない経路の疑い + +## 観測 + +Spawned Pod が完了しているにもかかわらず、parent 側に完了通知が来ないことがある。`ReadPodOutput` の assistant text 抽出バグは修正済みだが、完了通知そのものの `PodEvent` delivery path に別の構造的リスクが見つかった。 + +## 現行の通知経路 + +child の parent-originated turn が `Finished` になると、child controller の `drive_turn` が parent socket へ `PodEvent::TurnEnded` を fire-and-forget する。 + +parent 側は `Method::PodEvent` を受けると side effect を適用し、`NotifyBuffer` に typed event を積む。parent が idle なら `RunForNotification(PodEvent)` が auto-kick され、interceptor が `SystemItem::PodEvent` として history に commit し、LLM request に通知が乗る。 + +## 疑い + +送信 helper `connect_and_send` は socket に接続して Method を 1 行 write し、応答を読まずに close する。一方、受信側 `SocketServer::handle_connection` は Method を読む前に alert snapshot と `Event::Snapshot` を client に write する。 + +この組み合わせでは、send-only client が読まない / read half を保持しないため、server 側が snapshot write で失敗または詰まり、Method を読む前に connection handler が終わる可能性がある。これが起きると child の `PodEvent::TurnEnded` は parent controller に到達せず、NotifyBuffer にも入らない。 + +影響は `PodEvent` だけでなく、`StopPod` が child に送る `Method::Shutdown` など `connect_and_send` 利用箇所全般に及ぶ可能性がある。 + +## 対応 + +`tickets/pod-event-callback-delivery.md` を作成した。callback / fire-and-forget Method delivery を server の initial snapshot write に阻害されない形へ修正し、大きな snapshot を持つ parent に対しても `PodEvent::TurnEnded` が届く regression test を追加する。 diff --git a/tickets/pod-event-callback-delivery.md b/tickets/pod-event-callback-delivery.md new file mode 100644 index 00000000..75563155 --- /dev/null +++ b/tickets/pod-event-callback-delivery.md @@ -0,0 +1,92 @@ +# PodEvent callback delivery を確実化する + +## 背景 + +Spawned Pod の完了通知は、child が parent の Unix socket へ `Method::PodEvent(PodEvent::TurnEnded)` を送ることで成立する。parent は受信した `PodEvent` を `NotifyBuffer` に積み、idle なら `RunForNotification(PodEvent)` を auto-kick して LLM に通知する。 + +しかし実運用で child が完了しているのに parent 側に完了通知が来ない事象が複数回発生した。`ReadPodOutput` の singular `LogEntry` 追従漏れは別途解消済みだが、完了通知自体の delivery path にも構造的な不安がある。 + +現行の callback 送信は `ipc::event::fire_and_forget` → `send_pod_event` → `connect_and_send` で、`connect_and_send` は socket に接続して Method を 1 行 write し、応答を読まずに close する。一方、受信側 `SocketServer::handle_connection` は新規接続に対して、Method を読む前に alert snapshot と `Event::Snapshot` を先に write する。このため、send-only client が read half を持たない / 読まない状態で接続した場合、受信側が snapshot write で失敗または詰まり、肝心の Method を読む前に接続処理が終わる可能性がある。 + +この経路は `PodEvent` だけでなく、`StopPod` が child に送る `Method::Shutdown` など `connect_and_send` を使う fire-and-forget Method 全般に影響しうる。 + +## 現行経路 + +### child completion + +```text +child controller drive_turn + -> Worker turn が Finished + -> parent_originated なら PodEvent::TurnEnded を fire_and_forget + -> child は実行継続、socket は alive +``` + +### transport + +```text +fire_and_forget(parent_socket, PodEvent::TurnEnded) + -> tokio::spawn + -> send_pod_event + -> connect_and_send(parent_socket, Method::PodEvent(...)) + -> Method を write して close(応答は読まない) +``` + +### parent receive + +```text +SocketServer::handle_connection + -> subscribe snapshot + -> Event::Snapshot を client に write + -> その後 select! で Method を read + -> controller Method::PodEvent + -> apply_event_side_effects + -> NotifyBuffer.push_pod_event + -> parent が Idle なら RunForNotification(PodEvent) + -> interceptor が SystemItem::PodEvent として history に commit + -> LLM request に通知が乗る +``` + +## 要件 + +- fire-and-forget Method 送信が、受信側の initial `Event::Snapshot` write に阻害されないようにする。 + - `Method::PodEvent` / `Method::Shutdown` など send-only 用 helper が確実に controller へ Method を届けること。 + - 解決策は実装時に選ぶが、少なくとも以下のどちらかを満たす。 + - send-only client 側が read half を保持し、必要なら snapshot を drain しつつ Method write を完了する。 + - server 側で method-only / callback 接続が initial snapshot を要求しない protocol にする。 +- `PodEvent::TurnEnded` の delivery failure を観測可能にする。 + - fire-and-forget の warn log だけに閉じず、テスト可能な失敗経路を持つ。 + - 必要なら callback sender を fire-and-forget と awaited variant に分ける。 +- parent 受信後の処理が期待通り行われることを test する。 + - `Method::PodEvent(TurnEnded)` 受信で `NotifyBuffer` に typed event が積まれる。 + - parent が Idle の場合、`RunForNotification(PodEvent)` が staged / 実行される。 + - parent が Running の場合、in-flight turn の次回 `pre_llm_request` で drain される。 +- `connect_and_send` を使う既存経路への影響を確認する。 + - `StopPod` の `Method::Shutdown` + - child の `PodEvent::ShutDown` + - `ScopeSubDelegated` re-emit +- 大きな snapshot を持つ parent に対しても callback Method が届くことを regression test にする。 + +## 完了条件 + +- child completion 相当の `PodEvent::TurnEnded` が parent controller に確実に届く test を追加する。 +- initial snapshot が大きい parent socket に対しても `connect_and_send` / callback delivery が詰まらない test を追加する。 +- `StopPod` が child に `Method::Shutdown` を届けられることを test する。 +- 通知が parent history に `SystemItem::PodEvent` として commit されることを test する。 +- `cargo fmt --check` +- `cargo check --workspace` +- `cargo test -p pod` + +## 範囲外 + +- PodEvent の永続 retry queue +- delivery ack を LLM-visible tool result として返す UX 変更 +- 過去 Pod 探索 / restore tool +- delegated scope reclaim の実装 + +## 関連 + +- `crates/pod/src/controller.rs`: `drive_turn`, `Method::PodEvent` handling +- `crates/pod/src/ipc/event.rs`: `fire_and_forget`, `send_pod_event`, side effects +- `crates/pod/src/spawn/comm_tools.rs`: `connect_and_send` +- `crates/pod/src/ipc/server.rs`: connection start時の `Event::Snapshot` 送信 +- `crates/pod/src/ipc/notify_buffer.rs`: parent LLM context への通知注入