diff --git a/TODO.md b/TODO.md index 2461e4d9..2d96bdd5 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,7 @@ - Pod: scope 永続化 authority の整理 → [tickets/pod-scope-persistence-authority.md](tickets/pod-scope-persistence-authority.md) - SpawnPod 初回 task delivery の受理確認 → [tickets/spawnpod-initial-run-confirmation.md](tickets/spawnpod-initial-run-confirmation.md) - E2E テストハーネス(`tests/e2e/`、opt-in) → [tickets/e2e-harness.md](tickets/e2e-harness.md) +- Compact: retained split が pruning 後 usage record に引きずられて巨大 tail を残す → [tickets/compact-retained-split-usage-records.md](tickets/compact-retained-split-usage-records.md) - メモリ機構 - consolidation skip 表示と invalid staging の観測性 → [tickets/memory-consolidation-skip-observability.md](tickets/memory-consolidation-skip-observability.md) - memory / knowledge tool 利用タイミングのプロンプトガイダンス → [tickets/memory-tool-guidance-prompt.md](tickets/memory-tool-guidance-prompt.md) diff --git a/tickets/compact-retained-split-usage-records.md b/tickets/compact-retained-split-usage-records.md new file mode 100644 index 00000000..064ce4d7 --- /dev/null +++ b/tickets/compact-retained-split-usage-records.md @@ -0,0 +1,100 @@ +# Compact: retained split が pruning 後 usage record に引きずられて巨大 tail を残す + +## 背景 + +2026-05-26、起動中の `insomnia` Pod で compact 後の継続 turn が実質失敗した。compact 自体は summary を生成して新 segment を作成しており、処理としては成功扱いだった。しかし post-compact `SegmentStart.history` が 1957 items / 約 3.7MB と巨大なまま残り、その後の新 segment 上の turn は assistant output / `llm_usage` を残さず `run_completed finished` になった。 + +関連する過去事例として `docs/report/2026-05-24-compact-retained-tail-oversize.md` がある。今回の調査では、前回疑っていた「retained split が request-time pruning / projection 後の usage record と persisted history の実サイズのズレに引きずられる」問題が、より具体的に再現した。 + +対象観測: + +```text +session: 019e5d30-f7ad-7fb1-bdec-c592e888e290 +old segment: 019e5d30-f7ad-7fb1-bdec-c5a41394e6b1 +new segment: 019e62d3-5cbf-7020-b696-8d661b5e1026 +``` + +pre-compact history は 2192 items / 約 4.5MB。compact 後の `SegmentStart.history` は 1957 items / 約 3.7MB だった。 + +## 原因の見立て + +`llm_usage.input_total_tokens` は「その LLM request に実際に投げた prompt の占有量」としては正しいが、`split_for_retained` はそれを persisted/raw history の prefix token 数のように扱っている。 + +現在の retained split は概ね以下の前提で動く。 + +```text +current = tokens_at(history.len()) +target = current - retained_tokens +先頭から tokens_at(idx) >= target になる最初の idx を cut にする +``` + +この探索は `tokens_at(idx)` が prefix 長に対して単調増加することを前提にしている。しかし実際の usage record は pruning / projection 後の request 実測であり、raw persisted history の累積 token 数ではないため、大きく上下する。 + +今回の直接原因になった周辺 record: + +```text +history_len=237 input_total_tokens=93358 +history_len=240 input_total_tokens=20532 +history_len=242 input_total_tokens=166263 # target を超えて cut に選ばれた +history_len=245 input_total_tokens=27680 +history_len=247 input_total_tokens=85797 +``` + +compact 直前の推定は以下だった。 + +```text +current estimate: 173310 tokens +retained target: 8000 tokens +split target: 165310 tokens +initial cut: 242 +balanced cut: 242 +retained items: 1950 +``` + +`balance_to_pair_boundary` による引き戻しは今回発生しておらず、問題は tool call/result boundary 保護ではない。最初から usage record の外れ値によって cut が早すぎる位置に決まっている。 + +## 要件 + +- Compact の retained split が request-time pruning / projection 後の `llm_usage.input_total_tokens` を raw persisted history prefix として誤用しない。 + - `llm_usage.input_total_tokens` は request occupancy としては維持してよい。 + - retained split 用の推定は、persisted/raw history の実サイズに近い基準で行う。 + - 少なくとも usage record が非単調な場合に、古い外れ値で cut が極端に早くならない。 +- Compact 成功後に post-compact context の検証を行う。 + - retained item count + - retained byte size / serialized history size + - post-compact estimated tokens + - threshold / retained budget に対する比率 +- post-compact history が明らかに過大な場合、単純な成功扱いにしない。 + - `CompactFailed` 相当として扱う、または `just_compacted` を立てず次の safety net を有効にする。 + - infinite compact loop / thrash を避けるため、失敗理由は明示する。 +- compact 後の次 turn が assistant output / `llm_usage` 無しに `run_completed finished` になる経路を潰す。 + - context length / request build / upstream error が発生した場合は `run_errored` または rollback として観測できる。 + - 少なくとも空 finished turn を正常完了として扱わない。 +- compact metrics を session log か metrics extension で追えるようにする。 + - source: manual / pre_run / between_requests + - old_segment_id / new_segment_id + - old_history_len / new_history_len + - retained_from index + - retained_items / retained bytes + - estimate source / post-condition result + +## 完了条件 + +- 非単調な usage record が存在しても、retained split が巨大 tail を残さないことを確認する test がある。 +- pruning / projection 後 usage と raw persisted history size が乖離するケースの regression test がある。 +- post-compact context 検証が実装され、過大 tail の成功扱いを防ぐ test がある。 +- compact 後の空 turn が `run_completed finished` にならないことを確認する test がある。 +- `cargo fmt --check` と関連 crate の test が通る。 + +## 範囲外 + +- ローカルトークナイザの導入。 +- 既存 session log の migration。 +- compact summary prompt の大幅な再設計。 +- TUI 表示だけで問題を隠す対応。 + +## 参考 + +- `docs/report/2026-05-24-compact-retained-tail-oversize.md` +- `crates/pod/src/compact/token_counter.rs` +- `crates/llm-worker/src/token_counter.rs`