session-store-llm-worker-type-ownership完了
This commit is contained in:
parent
eed3f13e51
commit
cfd1879f7e
1
TODO.md
1
TODO.md
|
|
@ -19,6 +19,5 @@
|
|||
- [ ] Phase 2 consolidation → [tickets/memory-phase2-consolidation.md](tickets/memory-phase2-consolidation.md)
|
||||
- [ ] 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md)
|
||||
- [ ] GC(定期再評価) → [tickets/memory-gc.md](tickets/memory-gc.md)
|
||||
- [ ] session-store / llm-worker 型責務の整理 → [tickets/session-store-llm-worker-type-ownership.md](tickets/session-store-llm-worker-type-ownership.md)
|
||||
- ワークスペースのメモリーをLintするヘッドレスCLI
|
||||
- Thinking中のTUI上での表示: 内容の公開/非公開両対応
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ mod tests {
|
|||
}
|
||||
|
||||
/// Build a usage_history handle with a single record pinned at the
|
||||
/// current `context_len` so that `total_tokens_impl` returns exactly
|
||||
/// current `context_len` so that `total_tokens` returns exactly
|
||||
/// `tokens` (Measured, no interpolation or byte-based fallback).
|
||||
fn usage_handle_with(context_len: usize, tokens: u64) -> Arc<Mutex<Vec<UsageRecord>>> {
|
||||
Arc::new(Mutex::new(vec![UsageRecord {
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
# session-store / llm-worker 型責務の整理
|
||||
|
||||
## 背景
|
||||
|
||||
session-store の `LogEntry` 周りで「llm-worker の概念を session-store 内に二重定義 / inline flatten している」「未使用の variants が残っている」状態が複数ある。`memory-phase1-extract` の作業中に整理対象として浮上したが、本筋とは別軸なので独立チケットに切り出す。
|
||||
|
||||
依存方向は崩さない: pod → llm-worker、pod → session-store、session-store → llm-worker は維持。
|
||||
|
||||
## 要件
|
||||
|
||||
### 1. `UsageRecord` を llm-worker に移動
|
||||
|
||||
- 現状: `crates/session-store/src/session_log.rs` 内で `pub struct UsageRecord { history_len, input_total_tokens, cache_read_tokens, cache_write_tokens, output_tokens }` を定義
|
||||
- 本性: 「ある history prefix 長で 1 リクエスト送ったときの計測スナップショット」 = LLM call に紐づく per-call measurement。永続化が本質ではない
|
||||
- 移動先: `crates/llm-worker/src/usage_record.rs` (or `llm_client/usage.rs`)。`UsageEvent` (provider stream イベント) と隣接させる
|
||||
- session-store 側: `pub use llm_worker::UsageRecord` で互換 re-export。`LogEntry::LlmUsage` は inline fields のままで良い (中身が `UsageRecord` 1 個分の field 列に対応している)
|
||||
- pod 側: import 経路だけ更新
|
||||
|
||||
### 2. `token_counter` を llm-worker に移動
|
||||
|
||||
- 現状: `crates/pod/src/compact/token_counter.rs` に `prefix_bytes`, `tokens_at`, `total_tokens_impl`, `total_tokens_at_impl`, `split_for_retained_impl`, `tool_result_content_bytes` が同居
|
||||
- consumer も増えている: 当初は compact だけだったが、memory phase 1 trigger (`Pod::tokens_added_since` → `total_tokens_at(now) - total_tokens_at(pointer)` の差分計算) でも同じ accounting を使うようになった。`compact::` 名前空間下にあるのが事実とそぐわない
|
||||
- 移動方針:
|
||||
- **汎用部分** (`prefix_bytes`, `tokens_at`, `total_tokens_impl`, `total_tokens_at_impl`) を `crates/llm-worker/src/token_counter.rs` に移す。`Item` も llm-worker、`UsageRecord` も llm-worker に来るので素材が揃う
|
||||
- **compact 専用部分** (`split_for_retained_impl`, `tool_result_content_bytes`) は pod 側に残す (compact / prune だけが consumer)
|
||||
- pod 側の `Pod::total_tokens()` / `Pod::total_tokens_at()` / `Pod::split_for_retained()` メソッドは llm-worker の関数を呼ぶ薄ラッパーに (現在は `compact::token_counter::*_impl` を呼んでいる、import 経路だけが変わる)
|
||||
- これにより phase 1 trigger と将来の usage metrics が `use llm_worker::token_counter::...` で参照できるようになり、`compact::` 経由の不自然な依存が解消される
|
||||
|
||||
### 3. `Outcome` 廃止 + `LogEntry::RunCompleted` / `RunErrored` に flat 展開
|
||||
|
||||
- 現状: `crates/session-store/src/session_log.rs` の `Outcome` enum が `WorkerResult` の 4 variants (Finished / Paused / LimitReached / Yielded) を再定義した上に `Error { message: String }` を追加した形。`LogEntry::RunOutcome { outcome: Outcome, interrupted: bool }` で wrap されてる
|
||||
- 当初設計の意図 (`docs/persistence.md` の元コミット 2026-04-05): `RunOutcome` は **「audit-only metadata、replay 分岐には使わない」** と明記されていた。後から log viewer 等の consumer ができる前提で「書く側だけ整えた」状態。現在も replay は `interrupted: bool` しか参照しない (`session_log.rs:294`)
|
||||
- 問題点: WorkerResult の 4 variants が session-store 側で二重定義されている / `Outcome` 中間層が JSON / Rust 両方で余分なネストを生む / variant 名 (`RunOutcome`) と enum 名 (`Outcome`) が重複
|
||||
- 動機: pod の `handle_worker_result` で `Result<WorkerResult, WorkerError>` を 1 record に永続化する必要がある。`WorkerError` は `ClientError` (reqwest 等) を wrap していて `Serialize` 不可能なので、エラー側は `message: String` に lossy 変換するしかない (この事情は変わらない)
|
||||
- 改修方針:
|
||||
- `llm_worker::WorkerResult` に `#[derive(Serialize, Deserialize)]` + `#[serde(rename_all = "snake_case")]` を追加
|
||||
- session-store 側の `Outcome` enum を **完全削除**
|
||||
- `LogEntry::RunOutcome` を 2 variants に分解 (audit metadata の意図は保持):
|
||||
```rust
|
||||
pub enum LogEntry {
|
||||
// ...
|
||||
|
||||
/// run() / resume() が WorkerResult で正常終了した。
|
||||
/// 当初設計どおり audit-only: replay は `interrupted` のみ反映。
|
||||
RunCompleted {
|
||||
ts: u64,
|
||||
interrupted: bool,
|
||||
result: llm_worker::WorkerResult,
|
||||
},
|
||||
|
||||
/// run() / resume() が WorkerError で終了した。
|
||||
/// WorkerError は Serialize 不可なので message のみ lossy 保持。
|
||||
/// audit-only: replay は `interrupted` のみ反映。
|
||||
RunErrored {
|
||||
ts: u64,
|
||||
interrupted: bool,
|
||||
message: String,
|
||||
},
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
- `save_outcome` を `save_run_completed` / `save_run_errored` の 2 関数に分割 (or `save_run_outcome(result: &Result<WorkerResult, WorkerError>)` の helper を 1 つだけ持って内部で振り分け、どちらでも)
|
||||
- pod の `match` (`pod.rs:967` 付近) を `Ok(r) => save_run_completed(.., r) / Err(e) => save_run_errored(.., e.to_string())` に
|
||||
- `collect_state` の対応 match arm 2 つに分けるが、どちらも `state.last_run_interrupted = *interrupted` だけ
|
||||
- 既存ログ互換: variant tag が変わる (`run_outcome` → `run_completed` / `run_errored`) ので JSON 形式が変わる。v1 ログを読む経路があるなら custom deserializer か migration が必要 (実運用ログがほぼ無い前提なら破壊的変更で OK、判断はチケット着手時)
|
||||
|
||||
### 4. `LogEntry::Locked` / `LogEntry::CacheUnlocked` および周辺 API を削除
|
||||
|
||||
- 現状: variants 自体は残っているが、**書き手 (caller) が存在しない**
|
||||
- `save_cache_locked` / `save_cache_unlocked` は `pub` 公開だが session-store 外の呼び出しゼロ
|
||||
- Pod は `worker.set_cache_anchor(...)` を in-memory で操作するだけで永続化していない
|
||||
- `RestoredState.locked_prefix_len` も誰も読んでいない
|
||||
- 削除対象:
|
||||
- `LogEntry::Locked` / `LogEntry::CacheUnlocked` variants
|
||||
- `save_cache_locked` / `save_cache_unlocked` 関数 (lib.rs の re-export 含む)
|
||||
- `RestoredState.locked_prefix_len` field
|
||||
- `collect_state` の対応 match arm
|
||||
- 関連 unit test
|
||||
- 既存ログ互換: 上述の通り書き手不在なので既存ログにエントリは入っていないはず。念のため `serde(other)` 等で未知 variant を skip する救済層を入れるかは判断
|
||||
|
||||
## 範囲外
|
||||
|
||||
- `LogEntry::TurnEnd` の `usize` flatten (Worker.turn_count() の永続化) — 重複というほどではないので触らない
|
||||
- pod の cache anchor 永続化を実装する話 — 必要性が出てから別途
|
||||
- session-store の独立した一般化 (memory ドメイン以外の Extension 用途展開) — 必要が出てから別途
|
||||
|
||||
## 完了条件
|
||||
|
||||
- `UsageRecord` が llm-worker から `pub` され、session-store / pod の参照経路が更新されて workspace 全テスト pass
|
||||
- token_counter の汎用部分が llm-worker 配下にあり、pod / 将来の memory phase 1 から `use llm_worker::token_counter::...` で参照できる
|
||||
- `Outcome` enum が削除され、`LogEntry::RunCompleted { result: WorkerResult }` / `LogEntry::RunErrored { message }` の 2 variants で表現される。`WorkerResult` の 4 variants は llm-worker 単一情報源
|
||||
- `Locked` / `CacheUnlocked` 関連 variants / 関数 / fields が削除されてビルド & テストが通る
|
||||
- 既存 compact / prune / phase 1 trigger の挙動に回帰がない (token accounting の数値が変わらない、Outcome serialization の往復が成立する)
|
||||
|
||||
## 参照
|
||||
|
||||
- `crates/session-store/src/session_log.rs` (LogEntry, RestoredState, UsageRecord, Outcome)
|
||||
- `crates/session-store/src/session.rs` (save_outcome, save_cache_*, save_usage)
|
||||
- `crates/llm-worker/src/worker.rs` (WorkerResult, WorkerError, set_cache_anchor)
|
||||
- `crates/pod/src/compact/token_counter.rs` (移動元)
|
||||
- `crates/pod/src/pod.rs` (handle_worker_result の Outcome 構築箇所、Pod::total_tokens 経路)
|
||||
- `docs/persistence.md` (元設計の意図: RunOutcome は audit-only)
|
||||
|
||||
## Review
|
||||
- 状態: Approve
|
||||
- レビュー詳細: [./session-store-llm-worker-type-ownership.review.md](./session-store-llm-worker-type-ownership.review.md)
|
||||
- 日付: 2026-04-28
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
# Review: session-store / llm-worker 型責務の整理
|
||||
|
||||
## 前提・要件の確認
|
||||
|
||||
### 1. `UsageRecord` を llm-worker に移動
|
||||
- 移動先: `crates/llm-worker/src/usage_record.rs:9` に `UsageRecord` を新規定義、`crates/llm-worker/src/lib.rs:54,61` で `pub mod` + `pub use` 済み
|
||||
- session-store 側: `crates/session-store/src/lib.rs:42` で `pub use llm_worker::UsageRecord;` の互換 re-export
|
||||
- session-store 内部: `crates/session-store/src/session_log.rs:12` で `use llm_worker::{UsageRecord, WorkerResult};` に切替済み(旧定義は削除)
|
||||
- pod の参照経路更新: `crates/pod/src/pod.rs:9`, `crates/pod/src/compact/usage_tracker.rs:19`, `crates/pod/src/ipc/interceptor.rs:19` がいずれも `llm_worker::UsageRecord` 経由
|
||||
- `LogEntry::LlmUsage` は inline fields のまま(`session_log.rs:160`)— 方針通り
|
||||
- 充足
|
||||
|
||||
### 2. `token_counter` を llm-worker に移動
|
||||
- 汎用部分: `crates/llm-worker/src/token_counter.rs` に `prefix_bytes`, `tokens_at`, `total_tokens`, `total_tokens_at`, `item_bytes` 移動済み。`EstimateSource` / `TokenEstimate` も llm-worker に集約
|
||||
- compact 専用部分: `crates/pod/src/compact/token_counter.rs` に `SplitPoint`, `split_for_retained_impl`, `tool_result_content_bytes`, `savings_for_prune_impl` 残置
|
||||
- Pod の薄ラッパー: `total_tokens` (`compact/token_counter.rs:146`), `total_tokens_at` (`:157`), `split_for_retained` (`:165`) はいずれも `llm_worker::token_counter::*` 呼び出しの薄ラッパーに
|
||||
- 外部呼出経路: `pod/src/ipc/interceptor.rs:24,85` で `use llm_worker::token_counter::total_tokens;` を直接利用(pod 経由を回避できる)
|
||||
- pod の lib re-export (`pod/src/lib.rs:14`) は維持されており、外部 API の互換が崩れていない
|
||||
- 充足
|
||||
|
||||
### 3. `Outcome` 廃止 + `RunCompleted` / `RunErrored` への分解
|
||||
- `WorkerResult` の derive: `crates/llm-worker/src/worker.rs:69-70` に `#[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]` + `#[serde(rename_all = "snake_case")]` 追加済み
|
||||
- `Outcome` enum は完全削除(`session_log.rs` 内に痕跡なし、grep 確認済み)
|
||||
- `LogEntry::RunCompleted` / `LogEntry::RunErrored` 2 variants で表現(`session_log.rs:128-142`)。両方とも audit-only metadata のまま
|
||||
- `collect_state` の対応 arm(`session_log.rs:254-259`)はチケット指示通り `state.last_run_interrupted = *interrupted` のみ
|
||||
- 関数分割: `save_run_completed` (`session.rs:242`) / `save_run_errored` (`session.rs:266`) の 2 関数。lib.rs の `pub use` (`lib.rs:40`) も追従
|
||||
- pod 側 match: `pod/src/pod.rs:964-985` で `Ok(r) => save_run_completed / Err(e) => save_run_errored` 化
|
||||
- テスト更新: `session_test.rs:118-138`, `fs_store_test.rs:34`, `session_log.rs` 内の `replay_full_turn` 等が `RunCompleted` 参照に更新
|
||||
- 既存ログ互換: variant tag 破壊的変更(`run_outcome` → `run_completed` / `run_errored`)。チケットの判断「実運用ログがほぼ無い前提なら破壊的変更で OK」に沿う
|
||||
- 充足
|
||||
|
||||
### 4. `Locked` / `CacheUnlocked` 関連 API 削除
|
||||
- `LogEntry::Locked` / `LogEntry::CacheUnlocked` variants 削除、grep で痕跡なし(session-store 配下)
|
||||
- `save_cache_locked` / `save_cache_unlocked` 関数削除、`lib.rs` の re-export からも削除
|
||||
- `RestoredState.locked_prefix_len` field 削除、`collect_state` の match arm 削除
|
||||
- 関連 unit test 削除(`replay_cache_lock_unlock`, `session_cache_lock_unlock_logged` 等)
|
||||
- `replay_empty` の `assert_eq!(state.locked_prefix_len, 0)` も削除済み
|
||||
- Worker 内部の `locked_prefix_len: usize` は session-store と無関係 — 残置で問題なし(チケット注記と一致)
|
||||
- 充足
|
||||
|
||||
## アーキテクチャ・スコープ
|
||||
- 依存方向: pod → llm-worker、pod → session-store、session-store → llm-worker を維持。Cargo.toml で確認、循環なし
|
||||
- llm-worker の階層性: `usage_record` / `token_counter` の追加は LLM call の per-call measurement と pure な token accounting のみで、上位ドメイン(compact 等)を持ち込んでおらず低レベル基盤の方針に沿う
|
||||
- crate 名前付け: 新規ファイル追加のみで、新規 crate なし
|
||||
- ScopedFs 等のスクリプティング計画への影響なし
|
||||
- LLM provider policy への影響なし
|
||||
- 変更範囲: チケット記載の 4 項目に正確に対応、範囲外の改変は見当たらない
|
||||
- ビルド & テスト: `cargo check --workspace --all-targets` 新規 warning なし。session-store 8+7+13 件、llm-worker token_counter 4 件、pod 全テスト pass を再確認
|
||||
|
||||
## 指摘事項
|
||||
|
||||
### Non-blocking / Follow-up
|
||||
- なし
|
||||
|
||||
### Nits
|
||||
- `crates/pod/src/ipc/interceptor.rs:274` のテスト用ヘルパー doc comment が `total_tokens_impl` を参照したまま。実際の関数は `llm_worker::token_counter::total_tokens` にリネーム済みなので追従が望ましい
|
||||
- `crates/pod/src/compact/token_counter.rs:34` の `split_for_retained_impl` は pod ローカルなヘルパー(公開 API は `Pod::split_for_retained`)なので `_impl` サフィックスは慣習的に許容範囲。ただし `total_tokens_impl` → `total_tokens` のリネーム方針と揃えるなら `split_for_retained_inner` 等に揃える余地あり(現状のままで実害なし)
|
||||
|
||||
## 判断
|
||||
Approve — チケットに記載された 4 項目すべてが過不足なく実装され、既存テストはパス、依存方向と層責務も維持されている。残課題は doc comment 1 行のみで、ブロッキングではない。
|
||||
Loading…
Reference in New Issue
Block a user