BLOG.HIRO UENO.ART
← INDEX

4段階ライフサイクル

前の記事で、会話にラベルを貼る話を書いた。パネルの開閉をそのままカテゴリにして、しまう時点で仕分けておく。

同じ日、もう一つ考えていたことがある。毎回APIに送る会話の配列から、いつ何を外すか。

最初の案は会話履歴を3段階に分けるというものだった。

Hot — 1つ目のホット(熱い)ゾーン。直近のやり取り。そのまま配列に入れる。

Warm — 2つ目のワーム(温かい)ゾーン。ツールの実行結果だけをプレースホルダーに置き換える。「Handler(ハンドラー。タスクとスケジュール管理アプリ)がタスク一覧を返した」という事実は残るけど、一覧の中身は消える。Codeのリサーチで出てきたJetBrainsの研究(Observation Maskingと呼ばれている)では、これだけでAPIコストが半分近く減って、AIに要約を書かせるより精度も良かった。

Cold — 3つ目のコールド(冷たい)ゾーン。会話履歴の配列から外す。原文はDBに残してあって、必要になったらラベルとIDで引き出す(リヒート=再加熱)。

急に消すんじゃなくて、段階的に冷ましていく。ここまでが最初の設計メモ。

でも実装に入る前に、もう一段階増えた。WarmがAとBに割れた。


そのセッションは深夜に始まった。

正確に言うと、始まったの遅い時間ではなかったんだけど、途中で止まれなくて気づいたら朝の3時を過ぎていた。加えてその日、僕は風邪を引いていた。翌朝になって結局インフルエンザだったとわかるんだけど、その時点では「ちょっと熱っぽいな」くらいの感覚で、頭の回転が落ちる前にメモを残したくて話し続けていた。


3段階のライフサイクルの設計メモを、もう一度見直していた時だった。Warmの扱いに少し引っかかるところがあった。

Warmは、ツールの結果を [tool result hidden] みたいなプレースホルダーに置き換える。「そのツールを呼んだ」という事実と、Claude自身の推論は残る。中間の巨大なJSONの塊が抜けるので、かなり軽くなる。

ただ、空のマスクが大量に並ぶのを想像してみると、なんとなく気持ち悪かった。

「5時間前にHandlerを呼んだ。結果は消えている」「4時間前にもHandlerを呼んだ。結果は消えている」——こういうメッセージがずらずら並んでいる状態は、情報としてあまりにも無駄だ。Claudeがその履歴を眺めて得られる情報はほとんどない。

マスクしたまま置いておくより、もう一段階圧縮できるんじゃないか、と思った。


そこで思いついたのが「歴史年表」だった。

ツール結果を空のマスクにする代わりに、一行のタイムラインエントリに変換する。

例えばこういう形。

[14:30] Handler (seg-047): #42 → done, #43 → done, #45 → urgent

時刻、どのアプリを操作したか、セグメント(会話のひと区切り)のID、そして何が起きたかの要約。JSONの塊でも推論のテキストでもなく、ただのログ的な1行。後から「この時#42をdoneにしたな」と振り返れる。詳細が必要になればIDでDBを引ける。

まさに歴史の教科書にあるような歴史年表の会話履歴版。

Observation Maskingのマスクよりも、読みやすくて、トークン(AIが文章を数える単位)の効率が良くて、意味がある。

ブレインストームの途中で、あ、もう一段作れる、となった瞬間だった。


これでWarmが2段階に割れた。

Warm-A はこれまで通りのObservation Masking。ツール結果はプレースホルダー、でもClaudeの推論テキストはそのまま残す。直近のやり取りに対しては推論の流れが残っていた方がいい。

Warm-B は歴史年表エントリ。推論テキストもろとも、1行のタイムラインに畳む。古くなってきたメッセージは、もう文脈としてより、ログとして持っていた方が役に立つ。

そして最終的にCold — これはmessages配列から完全に消えて、SQLiteに眠るだけになる。必要ならIDで取り出せる。

まとめると、こうなった。

段階 中身 役割
Hot 完全なメッセージ 現在の文脈
Warm-A ツール結果をマスク、推論は保持 直近の思考の流れ
Warm-B 1行のタイムラインエントリに圧縮 歴史的な記録
Cold 配列から除外、DBに保存 オンデマンド取得

3段階が4段階になった。急に消えるんじゃなくて、段階的にクーリングしていく。


もう一つ、この夜に最初のメモを一箇所直している。

最初のメモには、パネルが閉じたらそのカテゴリの会話が冷め始める、と書いていた。ラベルを貼るパネルが、冷ます単位も決めてくれる想定だった。

でも自分の使い方を思い返すと、僕はHandlerとTypewriter(タイプライター。代筆アプリ)を行き来しながら、同じ話題を続けることがある。タスクを見ながら、その報告の投稿を書く、みたいな時。パネル単位で冷ますと、まだ生きている話題が途中で切れる。

だから、Hotに何を残すかは「今の話題に関係があるか」で決める。パネルが決めるのは、しまう時のラベルだけ。前の記事で書いた役目に絞った。


この夜はもう一つ、別の話題が挟まっていた。

リサーチ中に、Anthropicの clear_tool_uses_20250919 というベータAPIが見つかった。Context Editing APIと呼ばれるもので、要はサーバー側でObservation Maskingをやってくれる機能らしい。自前で組む前に、これをまずテストする必要がある。公式が用意しているなら、重複実装は無駄だ。

このAPIの発見自体はこの記事の主題じゃない。ただ、設計の最中に外部情報が入ってきて、自分の設計を相対化できたのはありがたかった。


セッションの後半、別のClaude(別のスレッドで、sontaku(忖度)がかかっていない素の英語Claude)にシステム全体をレビューさせた。これは前の記事で書いた、言語バイアスへの対処の一環。

そのClaudeが5つの弱点を指摘してきた。

  1. Hot → Warm-Aの遷移閾値が未定義(いつ遷移する?)
  2. Coldから戻すフロー(リヒート=再加熱)が曖昧
  3. 複数パネル同時開きの処理
  4. エンジニアじゃない人間が複雑なシステムを一人で組む難易度
  5. Hotエリアの中でもLost in the middleは起きる

この中で、すぐに答えられるものと、オープンに認めるしかないものがあった。3番は解消済み(UIが同時に1パネルしか開けない設計になっている)。2番もツールベースの取得パターンで自然に処理できる。1番はこの後の実装で決める。

4番と5番に関しては、正直に「そうですね」としか言えなかった。一人で組む難易度は高い。Hotの中でも中間は読みにくくなる。逃げ道のない事実。

でも「オープンに認める」というのは割と大事な姿勢だと思っていて、設計のうまく行っていない部分を取り繕わずに残しておくほうが、後で自分が助かる。


そろそろ朝の4時が見えてきたあたりで、最後に決めたのは実装の順番だった。

  1. まずはObservation Masking(Warm-A)— 一番シンプルで、すでにJetBrainsの数字で効果が実証されている
  2. 次にUIシグナルのカテゴリ付け
  3. それからカテゴリフィルタリング
  4. Cold遷移
  5. 最後に再加熱

ROIが高くて、失敗しても戻しやすい順。一度に全部組まない。

これで朝になった。


この日の議事録を読み返すと、技術的にはかなりたくさんのことが決まっている。4段階ライフサイクル、ツールコールでのインデックス取得、MONET(僕が普段話しかけている、中心のAI)が自分でパネルを開けるようにする双方向UIシグナル、記憶の索引(Memory Index)をmessages配列の先頭に注入する、バックグラウンド事前圧縮、エージェント間会話はPunk Memory Systemに直接保存する——。

このままインフルでしばらく寝込みそうだけど、復帰したら、設計はかなり先に進んだところから再開できる。無理した価値はあったと思う。

実装は復帰後。遷移閾値の数値を決めて、Observation Maskingのプロトタイプを組む。