docs(tickets): memory-extract-occupancy-cap 完了

This commit is contained in:
Keisuke Hirata 2026-05-11 01:32:45 +09:00
parent dbc96ee075
commit a8a6e049bc
3 changed files with 0 additions and 90 deletions

View File

@ -29,7 +29,6 @@
- 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md)
- Phase 1/2 呼称を extract/consolidation に統一 → [tickets/memory-phase-naming.md](tickets/memory-phase-naming.md)
- extract / consolidation 監査ログ → [tickets/memory-audit-log.md](tickets/memory-audit-log.md)
- extract worker サーキットブレーカーを占有量ベースに統一 → [tickets/memory-extract-occupancy-cap.md](tickets/memory-extract-occupancy-cap.md)
- セッション内 Task ツールの注意機構(無アクティビティで `<system-reminder>` ナッジ) → [tickets/session-todo-reminder.md](tickets/session-todo-reminder.md)
- ワークスペースのメモリーをLintするヘッドレスCLI
- system-reminder 注入機構の汎用化2件目の利用者が出た時に検討。タグ形式 `<system-reminder>...</system-reminder>` の規約は session-todo-reminder で先行確立。注入された Item は worker.history に append する方針)

View File

@ -1,60 +0,0 @@
# Memory: extract worker のサーキットブレーカーを占有量ベースに統一
## 背景
`compact-worker-occupancy-cap` で compact worker のサーキットブレーカーを `UsageTracker` + `llm_worker::token_counter::total_tokens` ベースに切り替えたが、同じ設計上のバグを抱えた `MemoryExtractWorkerInterceptor``crates/pod/src/pod.rs:2180-2199` に残っている。実装コメントには `Mirror of compact::worker::CompactWorkerInterceptor;` と書かれており、compact 側が直った今、コメントも事実と乖離している。
具体的には:
- `MemoryExtractWorkerInterceptor``input_so_far: AtomicU64``UsageEvent.input_tokens``fetch_add` し、`pre_llm_request` で累積値が `max_input_tokens` を超えたら `Cancel` する (`pod.rs:1930-1942` で配線、`pod.rs:2186-2198` で判定)。
- `UsageEvent.input_tokens` は cache hit を含む prompt prefix 全体の占有量 (`crates/llm-worker/src/llm_client/event.rs:76-94`)。同じ prefix を毎ターンフルカウントするため、cache が 99% ヒットしても累積値は context size × ターン数で増える。
- 結果は compact と同じcap が「現在占有量」ではなく「累積(≒ context × turns」を測っており、設計と整合しない。
extract worker は compact ほどツールループが深くない(`extract.write_extracted_tool` のみ、ファイル探索なし)ため誤発火頻度は compact より低いものの、構造的な誤りは同じであり、放置すると今後ツールが増えた瞬間に再発する。
メイン Pod のしきい値群と compact worker (修正済み) はどちらも `Pod::total_tokens()` (`pod.rs:146-149`) 経由で「現在の占有量」を見ており、extract worker だけ別ロジックという歪みも残っている。
## 方針
`MemoryExtractWorkerInterceptor` を、compact 側と同じ `UsageTracker` + `llm_worker::token_counter::total_tokens` 機構に乗せ替える。累積メトリックは廃止する。compact と対称に `extract_worker_max_turns` を新設し、`Worker::set_max_turns` 経由で extract worker に伝える。
## 要件
- `MemoryExtractWorkerInterceptor``pre_llm_request``llm_worker::token_counter::total_tokens(context, &records).tokens > max_input_tokens` ベースに置き換える。`input_so_far: AtomicU64` の累積パスは廃止。
- Extract worker にも `UsageTracker` を持たせ、`pre_llm_request` で `note_request(context.len())`、`on_usage` で `record_usage(event)` する。compact worker (`pod.rs:1535-1547` 周辺) と同じ配線パターン。
- `extract_worker_max_input_tokens` の意味を「extract worker 側の現在占有量しきい値」に変更し、ドキュメント (`crates/manifest/src/lib.rs:111-115`, `crates/manifest/src/defaults.rs:54-56`, 関連 `docs/`) を更新する。デフォルト値 30000 は新セマンティクスで再評価可能だが、変更必須ではない。
- `MemoryConfig``extract_worker_max_turns: Option<u32>` を追加し、cascade (`crates/manifest/src/config.rs`) に通したうえで extract worker 構築箇所 (`pod.rs:1924-1942`) で `extract_worker.set_max_turns` に渡す。デフォルトは要検討(仮: `Some(8)` — extract は本来 1-2 ターンで終わる軽量 worker のため compact より小さく)。
- compact 側で消した「Mirror of `CompactWorkerInterceptor`」というコメントを削除し、独立した interceptor として doc を整える(実体はほぼ同じだが、ロジック共有のための共通化はやらない: subsystem ごとにメッセージとしきい値が異なる前提を維持)。
- 後方互換 shim は入れない。`extract_worker_max_input_tokens` はフィールド名を維持しつつセマンティクスだけ差し替える。
## 完了条件
- 通常の extract 実行で、cache hit 込みの prefix がフルカウントされなくなり、現実的な extract 入力 (compaction 区間の skeleton) で cap に当たらない。
- extract worker のループが暴走するケースで、`extract_worker_max_turns` により打ち切られる。
- `Pod::total_tokens()` / `CompactWorkerInterceptor` / `MemoryExtractWorkerInterceptor` の 3 者が同じ算出経路 (`llm_worker::token_counter::total_tokens`) を使っている。
- `MemoryExtractWorkerInterceptor` のユニットテストで「累積では落ちる量でも occupancy では通る」「占有量が cap を超えたら cancel」の 2 パターンが covered されている。
## 範囲外
- `extract_threshold`extract 発火条件)のセマンティクス変更。これは「ポインタ以降の cumulative input tokens」で別概念。
- Consolidation 側の同等変更consolidation worker は別の lock + iteration 機構で動いており、interceptor 設計が異なる。必要なら別チケット)。
- `compact_threshold` / `compact_request_threshold` 自体のセマンティクス変更。
- 共通 `OccupancyInterceptor` を新設して compact / extract で共有する抽象化。サブシステム個別のメッセージ・cap・将来の挙動分岐を残せるよう、現状の重複構造を維持。
## 影響範囲
- `crates/pod/src/pod.rs`:
- `MemoryExtractWorkerInterceptor` 定義 (`pod.rs:2180-2199`) を occupancy ベースに書き換え、`input_so_far` フィールドを `usage_tracker: Arc<UsageTracker>` に置換。
- extract worker 構築箇所 (`pod.rs:1924-1942`) で `UsageTracker` 配線、`set_max_turns` 呼び出し追加。
- `crates/manifest/src/lib.rs`:
- `MemoryConfig::extract_worker_max_input_tokens` の doc を新セマンティクスに更新。
- `extract_worker_max_turns: Option<u32>` フィールド追加 + デフォルト値関数。
- `crates/manifest/src/config.rs`: `MemoryConfigPartial` への追加と cascade 解決。
- `crates/manifest/src/defaults.rs`: `MEMORY_EXTRACT_WORKER_MAX_INPUT_TOKENS` の doc 更新、`MEMORY_EXTRACT_WORKER_MAX_TURNS` 定数の追加。
- `docs/manifest.toml` 等の memory 設定例: 新フィールドと意味の更新。
- `crates/pod/src/pod.rs` 周辺もしくは新規モジュールでのユニットテストcompact 側で追加した 2 パターンを extract 用に)。
## Review
- 状態: Approve
- レビュー詳細: [./memory-extract-occupancy-cap.review.md](./memory-extract-occupancy-cap.review.md)
- 日付: 2026-05-11

View File

@ -1,29 +0,0 @@
# Review: Memory: extract worker のサーキットブレーカーを占有量ベースに統一
## 前提・要件の確認
- 要件1「`MemoryExtractWorkerInterceptor::pre_llm_request` を `total_tokens(context, &records).tokens > max_input_tokens` ベースに置換、`input_so_far` 廃止」: 達成。`crates/pod/src/pod.rs:2192-2208` で `usage_tracker.records()``llm_worker::token_counter::total_tokens(context, &records)` の経路に切り替わり、`fetch_add` ベースの累積パスは消滅。リポジトリ全体を `grep` しても `input_so_far` の残滓なし。
- 要件2「extract worker にも `UsageTracker` を持たせ、`note_request` / `record_usage` で配線」: 達成。`pod.rs:1934-1945` の wiring が compact 側 (`crates/pod/src/pod.rs:1537-1548`) と完全に対称。`extract_usage_tracker` というローカル独立 trackermain Pod の persist 用 `UsageTracker` とは別インスタンスになっており、compact 側の `summary_usage_tracker` と同じ分離方針。`pre_llm_request` 末尾で `usage_tracker.note_request(context.len())` も入っている (`pod.rs:2206`)。
- 要件3「`extract_worker_max_input_tokens` の doc を新セマンティクスに更新」: 達成。`crates/manifest/src/lib.rs:111-115`、`crates/manifest/src/defaults.rs:53-56`、`docs/manifest.toml:260-262` がいずれも「現在占有 token cap」表現に書き換わっている。
- 要件4「`MemoryConfig::extract_worker_max_turns: Option<u32>` を新設、cascade と `set_max_turns` 連動」: 達成。`MemoryConfig` (`lib.rs:116-121`)、`MemoryConfig::merge` (`config.rs:255-257`)、`MEMORY_EXTRACT_WORKER_MAX_TURNS` 定数 (`defaults.rs:58-60`) が揃い、`pod.rs:1918-1920` で cascade、`pod.rs:1945` で `extract_worker.set_max_turns(extract_worker_max_turns)``Option<u32>` ごと無条件に渡している。compact 側レビューで撤去された `if .is_some()` 分岐の轍は踏んでいない。
- 要件5「`Mirror of CompactWorkerInterceptor` コメントを独立 doc に書き換え」: 達成。`pod.rs:2178-2185` の doc は「Kept separate from `compact::worker::CompactWorkerInterceptor` so each subsystem can tune its own cancel message and budget」と独立性を明示する形に書き換え済み。リポジトリ全体に `Mirror of CompactWorkerInterceptor` の残骸なし。
- 要件6「後方互換 shim 無し」: 達成。`extract_worker_max_input_tokens` はフィールド名そのままで意味だけ差し替え。新フィールド `extract_worker_max_turns``#[serde(default)]` の Option で導入され、過渡互換用のエイリアスや旧名フォールバックは入っていない。
- 完了条件「3 者が同じ算出経路 (`llm_worker::token_counter::total_tokens`) を使う」: 達成。`Pod::total_tokens()` (`compact/token_counter.rs:146-149`)、`CompactWorkerInterceptor::pre_llm_request` (`compact/worker.rs:261-273`)、`MemoryExtractWorkerInterceptor::pre_llm_request` (`pod.rs:2192-2208`) の 3 者がいずれも `llm_worker::token_counter::total_tokens(history/context, &records)` 一本に揃っている。
- 完了条件「ユニットテスト 2 パターン」: 達成。`crates/pod/src/pod.rs:3093-3158` の `memory_extract_interceptor_tests` モジュールに `extract_interceptor_uses_occupancy_not_cumulative_usage``extract_interceptor_cancels_when_occupancy_exceeds_cap` の 2 本があり、compact 側 `compact/worker.rs:301-349` の 2 本と入力値cap=150, 100×3 / cap=99, 100×1まで対称。
## アーキテクチャ・スコープ
- 占有量計算は main Pod / compact / extract の 3 系統で一本化された。設計上の歪み(要約: 「extract worker だけ別ロジック」)は解消。
- 共通 `OccupancyInterceptor` 抽象化に踏み込まず、サブシステム個別の cancel メッセージと cap を別構造体として保持。チケットの範囲外指定通り。
- `extract_threshold` (`docs/manifest.toml:256`) の累積セマンティクスは触らずに残されており、これは「ポインタ→now の差分」という別概念なので正解。
- 新規依存追加なし、新規クレートなし、`llm-worker` の責務範囲は変えていない。クレート命名や cargo add ポリシーに抵触する変更なし。
- `extract_worker_max_turns` のデフォルト `Some(8)` は、extract が write_extracted_tool 1 本で 1-2 ターンで終わる軽量 worker であることを踏まえると compact (20) より小さく合理的。runaway リカバリのみを担う上限として妥当。
- cascade パターンは `MemoryConfig::merge` 内の他フィールドと同じ `upper.x.or(self.x)` 形。`CompactionConfig` 側が serde-time デフォルト関数で埋めている方式とは流儀が異なるが、これは既存 `MemoryConfig` 内部の慣習(他フィールドも `#[serde(default)]` Option + 呼び出し側 `unwrap_or(defaults::...)`)と一致しており、内部一貫性の観点で問題なし。
## 指摘事項
特になし。compact 側で確立した方式の対称適用が、コメント・テスト・cascade・wiring まで含めて綺麗に完遂されている。docs 側の「累積」表現も新規導入箇所には残っていない。
## 判断
Approve (完了可) — チケットの全要件と完了条件が明示的な根拠付きで満たされており、compact 側との対称性が wiring・doc・テストの 3 階層で揃っている。コードベースの歪みを増やす要素もなし。