From be22c65af3954809efc3fbe3dfda2e5b28b03a72 Mon Sep 17 00:00:00 2001 From: Hare Date: Sun, 3 May 2026 01:01:09 +0900 Subject: [PATCH] =?UTF-8?q?docs(tickets):=20tui=E3=81=A7PodEvent=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=83=BB=E3=82=BB=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E4=B8=AD=E3=81=A7=E3=83=A1=E3=83=88?= =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=82=B9=E3=82=92=E5=8F=96=E3=82=8B=E3=83=81?= =?UTF-8?q?=E3=82=B1=E3=83=83=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 2 +- tickets/session-metrics.md | 74 +++++++++++++++++++++++++++++++++ tickets/tui-pod-event-render.md | 20 +++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 tickets/session-metrics.md create mode 100644 tickets/tui-pod-event-render.md diff --git a/TODO.md b/TODO.md index 138d4ed5..9a694295 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ - [ ] Run 中の入力キューイング → [tickets/tui-input-queue.md](tickets/tui-input-queue.md) - [ ] ユーザーマニフェストのモデル設定 wizard → [tickets/tui-user-model-setup.md](tickets/tui-user-model-setup.md) - [ ] auto-kick 由来ターンが描画されない → [tickets/tui-pod-event-render.md](tickets/tui-pod-event-render.md) -- [ ] 子 Pod の PodEvent::TurnEnded が親に届かない → [tickets/pod-event-delivery.md](tickets/pod-event-delivery.md) - [ ] サブミット入力 - [ ] FileRef リゾルバ → [tickets/submit-file-ref-resolver.md](tickets/submit-file-ref-resolver.md) - [ ] Manifest: Tool Output / File Upload 上限の分離とデフォルト緩和 → [tickets/manifest-output-upload-limits.md](tickets/manifest-output-upload-limits.md) @@ -23,5 +22,6 @@ - [ ] 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md) - [ ] Phase 2 累積入力トークン上限の撤去 → [tickets/memory-consolidation-drop-input-cap.md](tickets/memory-consolidation-drop-input-cap.md) - [ ] セッション内 TODO ツール(注意機構付き) → [tickets/session-todo.md](tickets/session-todo.md) +- [ ] セッションメトリクス: Extension 経由の汎用計測レーン(最初の利用者は Prune) → [tickets/session-metrics.md](tickets/session-metrics.md) - ワークスペースのメモリーをLintするヘッドレスCLI - system-reminder 注入機構の汎用化(2件目の利用者が出た時に検討。タグ形式と「履歴を汚さない」原則は session-todo で先行確立) diff --git a/tickets/session-metrics.md b/tickets/session-metrics.md new file mode 100644 index 00000000..f412c875 --- /dev/null +++ b/tickets/session-metrics.md @@ -0,0 +1,74 @@ +# セッションメトリクス: Extension 経由の汎用計測レーン + +## 背景 + +セッション中の挙動を後から定量評価したい需要が増えている。直近の動機は Prune projection の効果測定(どこでどれくらいの頻度で発火したか、KV キャッシュ無効化のコストに対して回収トークンがどれだけあったか)だが、Compact 起動条件、Hook の実行時間、ツール呼び出しのリトライ回数など、同種の「セッション中に積み上げて後で引きたい」値は今後も発生する。 + +現状の session-log は `UserInput` / `AssistantItems` / `LlmUsage` といった状態遷移を典型化した variant のみで、ad hoc な計測値の置き場所がない。一方 `LogEntry::Extension { domain, payload }` という汎用エスケープハッチが既に用意されており、ここに乗せれば hash chain・replay 経路を流用できる。 + +## 方針 + +- `LogEntry::Extension` の `domain = "metrics"` 名前空間として実装する。session-store の型は触らない +- メトリクス型は `name + dimensions(sparse map) + value(Option) + correlation_id(Option)` 程度の最小スキーマ。値が測れない次元は `None` で明示し、Prometheus 的な厳格な label set は持たない +- 「後から埋まる値」(例: prune 発火直後の LLM 呼び出しで観測される `cache_read_tokens`)は前 entry に書き戻さず、`correlation_id` を共有する別 metric として流す。集計は読み手側で join +- 集計 / 可視化 API は本チケットでは作らない。session-log を読めば取り出せる、までを到達点とする + +## 要件 + +### メトリクス型 + +- 専用 crate(または既存の適切な配置)に定義。`serde` で JSON ラウンドトリップ可能 +- 必須: `name`(`namespace.metric` 形式の文字列、例: `prune.fire`)、`ts`(u64 epoch ms) +- 任意: `dimensions: BTreeMap`、`value: Option`、`correlation_id: Option` +- 「unknown」は対応フィールドを `None` にすることで表現。schema レベルで dimension の網羅性は要求しない + +### 書き込み経路 + +- `Pod` から呼べる薄いヘルパー(例: `Pod::record_metric(&self, metric)`) を session-store に追加し、`LogEntry::Extension { domain: "metrics", payload: serde_json::to_value(metric) }` として append する +- 既存の `append_entry` フローを踏襲し、hash chain に乗る +- 書き込み失敗(store IO エラー)はメトリクス側で握りつぶす。本体処理を阻害しない + +### 読み出し経路 + +- replay 時、`RestoredState.extensions` に `("metrics", payload)` として既に積まれる(既存挙動) +- メトリクスドメイン側で payload を `Vec` に fold するヘルパーを提供 +- session-store のテストハーネスから「特定セッションの metric 列を取り出す」サンプルが書ける状態にする + +### 最初の利用者: Prune projection + +本チケットの完了は、最低 1 つの実利用者が乗っていることを条件とする。Prune を最初の利用者として組み込む: + +- `pod::compact::prune` の `attach_prune` 経路で、projection 評価のたびに以下を発行 + - 発火時: `name = "prune.fire"`, `dimensions = { border_turn, candidate_count }`, `value = estimated_savings`, `correlation_id = <次の LLM 呼び出しと紐付ける ID>` + - スキップ時: `name = "prune.skip"`, `dimensions = { reason }`(`below_min_savings` / `no_candidates` 等) +- 直後の LLM リクエストで `LlmUsage` が記録される際、同じ `correlation_id` を持つ補助 metric `prune.post_request` を併発し、`cache_read_tokens` / `cache_write_tokens` を value/dimension として記録 +- `correlation_id` の生成・伝搬経路は実装側で決定。既存の request-id 系があれば再利用 + +### Resume 互換 + +- 旧セッション(metric entry を持たないログ)の replay は何も変えない。`extensions` に metrics domain が無いだけ +- payload schema が将来変わった場合、deserialize 失敗した metric は無視してよい(fold ヘルパー側で `serde_json::from_value` の Err を skip) + +## 完了条件 + +- メトリクス型と書き込み / 読み出しヘルパーが定義され、unit test がある +- Prune projection から `prune.fire` / `prune.skip` / `prune.post_request` が session-log に乗る +- 既存セッションログの replay が壊れない(後方互換) +- セッションログから prune metric 列を取り出すテストが通る +- correlation_id で prune 発火と直後の LLM 呼び出しの cache 値が join できることを test で示す + +## 範囲外 + +- 集計 / 可視化ツール(CLI / TUI)。後続で別途 +- ワークスペースまたぎのメトリクス集約(複数セッション横断分析) +- リアルタイム購読 API(Watcher 経由の stream 配信) +- session-log 以外の sink(jsonl 別系統、外部時系列 DB 等) +- Prune 以外のメトリクス利用者の追加(Compact / Hook 等は別チケット) +- メトリクス保存量の自動圧縮 / 退避 + +## 参照 + +- 設計指針: `CLAUDE.md`(最小の構造化 / 概念の追加は不在が問題になってから) +- `crates/session-store/src/session_log.rs`(`LogEntry::Extension` と `RestoredState.extensions` の既存仕様) +- `crates/llm-worker/src/usage_record.rs`、`crates/llm-worker/src/llm_client/event.rs`(cache_read / cache_write の取得経路) +- `crates/pod/src/compact/prune.rs`、`crates/llm-worker/src/prune.rs`(最初の利用者の挿入点) diff --git a/tickets/tui-pod-event-render.md b/tickets/tui-pod-event-render.md new file mode 100644 index 00000000..c598dd1f --- /dev/null +++ b/tickets/tui-pod-event-render.md @@ -0,0 +1,20 @@ +# TUI で auto-kick 由来のターンが表示されない + +## 背景 + +Pod が `Method::PodEvent::TurnEnded` などを socket 経由で受信すると、controller は notification を notify buffer に積み、Idle なら `pod.run_for_notification()` で新しいターンを起動する(`crates/pod/src/controller.rs:611-687`)。このターンの assistant 出力 (`Event::TurnStart` / `TextDelta` / `TurnEnd` 等) は通常通り broadcast Event として全クライアント(TUI 含む)に配信されるはず。 + +## 問題 + +socat で稼働中の codex-oauth pod の socket に `Method::PodEvent::TurnEnded` を 1 行流したところ、socat 側の subscribe には turn が完全に流れてきた(thinking_delta / text_done / turn_end 取得済み)が、同じ pod を起動している TUI 画面には新ターンが描画されなかった。 + +`Method::Run` 経由の通常ターンは TUI に表示されるので、broadcast 配信そのものは生きている。auto-kick 由来のターン(user_message を伴わない turn)に固有の表示パスで落ちている可能性が高い。 + +## 要件 + +- auto-kick で起動したターン(user 入力を伴わないターン)も、user 由来ターンと同様に TUI 履歴に表示される。 +- turn header 等の見た目で「通知由来である」ことを示す表記を入れるかは別議論。 + +## 完了条件 + +- 親 pod が PodEvent を受信して auto-kick した際、TUI 上で thinking / assistant text / turn_end が user 由来ターンと同様に表示される。