7.8 KiB
7.8 KiB
Review: セッションログの Segment 保持
前提・要件の確認
LogEntry::UserInputからitem: Itemを取り除きsegments: Vec<Segment>のみ持つ 満たされている。session_log.rs:120で置換済み。collect_stateはSegment::flatten_to_text経由でItem::user_message(text)を派生(session_log.rs:254-258)。- セッションログに Segment を persist し、
Podの入口で submit-time に直書き、save_deltaは user_message を skip 満たされている。session.rs:148-164にsave_user_input関数追加。session.rs:188-190でis_user_message()を skip。pod.rs:608-615でPod::runの入口(flatten 直前)にsave_user_inputを埋め込み、in-memoryuser_segmentsに push。 - 復元経路で client が segments を取り戻す(TUI で paste チップが復元)
実装済み。
ipc/server.rs:91-127でMethod::GetHistory応答時に worker history JSON の user_message にsegmentsフィールドを embed、tui/src/app.rs:461-473のrestore_historyでsegmentsを読み出してBlock::UserMessageを組み立てる。手動 UI 確認は未消化(report 申告通り)。 - worker / llm-client 層は変更しない
守られている。
Segment::flatten_to_textをprotocolに追加した以外、worker / llm-client API に変更なし。 - 後方互換は持たない 既存 jsonl の読み込みフォールバックは入れていない(適切)。
- Round-trip テスト
replay_user_input_segments_round_trip追加(session_log.rs:682-751)。Text/Paste/FileRef を含む混合 segments の JSON 往復+flatten_to_text派生+user_segments保持を一気に検証。テスト粒度は要件十分。 - 既存ビルド・テストが新スキーマで合格
cargo test --workspace全合格を確認。
アーキテクチャ・スコープ
Segment::flatten_to_textをprotocol側に純粋関数として抜き、Pod 側のflatten_segmentsを「アラート発火 + flatten_to_text 呼び出し」へ整理した分離(pod.rs:636-682)は正しい。replay 経路がアラートを再発火させない設計が両立している。- session-store が
protocol::Segmentに依存する構造は妥当。Cargo.tomlにはcargo addで追加されており(コミットの差分通り)、依存方向(session-store → protocol)も既存の依存グラフ的に問題ない。 RestoredState.user_segmentsを別フィールドとして並走させ、replay 経路では Item と Segment を二重管理する設計は、worker history の責務(LLM への渡し物)と client 復元の責務(typed atom 描画)の分離として理にかなっている。- IPC server で
is_user_message()数とuser_segments数の差分から「seed history は最初に詰まれる」という性質を使った右寄せ整列(ipc/server.rs:101-126)は、ticket 記載の前提("seed history は segments を持たない、live submission のみ持つ")の素直な実装。
指摘事項
Blocking
Pod::compact()後にself.user_segmentsがクリアされない(pod.rs:1165-1224)compact()は worker history を[summary, ...retained_items]に置換するが、self.user_segmentsは触られていない。プロセス継続中に compaction が走った場合、次のMethod::GetHistoryでipc/server.rs:103のtotal_user_msgs.saturating_sub(segments_per_user.len())が想定外の値になり、segments と worker user_message が錯誤マッチする。 例: 5 user_msg を投げた後 compaction で 1 だけ retained のとき、total=1, segs=5 → skip=0となり、retained の user_msg に 1 番目の古い segments が貼られる。 対策案:compact()内でretained_items中のis_user_message()件数 K を数え、self.user_segments = self.user_segments.split_off(self.user_segments.len() - K)相当に切り詰める(K=0 なら clear)。controller 側shared_stateも同期するため、compact 後にshared_state.set_user_segments(pod.user_segments().to_vec())を呼び直す経路が要る。 なお post-compaction 後にプロセスを再起動して restore する場合は、新セッションの jsonl に UserInput が無いのでstate.user_segmentsは空になり、自動的に整列が直る。問題はプロセス継続中の compaction → 次の GetHistory の窓に限定されるが、TUI の通常運用フローで踏みうる。
Non-blocking / Follow-up
save_user_input失敗時の shared_state ghost segment(controller.rs:331とpod.rs:608-615の順序) Controller はMethod::Run受信直後にshared_state.push_user_segments(input.clone())してから run_future を await する。save_user_inputが I/O エラーで失敗すると、pod.user_segmentsには push されないがshared_state.user_segmentsには残り、worker.history にも user_msg は積まれない(save_user_inputの失敗でpod.runが早期 return するため)。次回 GetHistory で 1 段ずれる可能性。実害は store I/O 障害のレアケースに限定されるが、run_future完了後にshared_state.set_user_segments(pod.user_segments().to_vec())で同期し直すのが安全。- seed history の segments 喪失(
pod.rs:294-295,session_log.rs:217-223のコメント参照) ticket の範囲外として明示済み・コメントでも記述されている設計判断。compaction を境に paste チップの典型 magenta 復元が単純テキストにフォールバックする挙動はユーザー視点で受容可能だが、UX として把握しておく価値あり。後続で必要ならSessionStart.history側にも segments を持たせる拡張余地あり。 - chain hash の確定性
Segmentの serde 実装は HashMap 不使用・field 順序固定で、session_log.rs:466-499のhash_chain_is_deterministic/different_content_produces_different_hashで確認済み。スキーマ変更で hash 値は別物になるが、新スキーマ内での安定性は壊れていない(要件通り)。 - 手動 UI 確認(paste chip の magenta 復元)の自動テスト化は未消化。session-store + flatten 経路は単体テストでカバーされているが、TUI の
restore_history経路は serde_json::Value からの復元アサーションが望ましい follow-up。
Nits
pod.rs:615のself.user_segments.push(input.clone())は直前でsave_user_inputにinput.clone()を渡しているためinputを 2 回 clone している。Vec<Segment>の clone はそこそこ重い(特に Paste の content)ので、save_user_input(... segments)の引数を所有Vec<Segment>のまま受けて push 後に消費するか、save_user_inputを&[Segment]に変えてから push 一回にする選択肢。マイクロ最適化なので必須ではない。ipc/server.rs:105-114のコメント「seed user_messages always come first」は、ensure_head_or_forkの auto-fork 経路で発生しうる "seed + 一部 segments のあとさらに live segments" のケースまで明示してくれると将来の保守者に親切(実際には auto-fork 後も pod.user_segments は live 分のみ累積する形で右寄せが成立しているが、コメントを読むと一瞬不安になる)。
判断
Request changes — Pod::compact() 後に self.user_segments を切り詰めない件(Blocking)が、ticket の完了条件「現行の compaction / fork / restore のフローを壊さない」に直接抵触する。修正は数行+shared_state 再同期で済む規模。それ以外の要件は満たされており、修正後は Approve 想定。