技術 約13分で読めます

OCR-Memoryはエージェントの履歴を画像として思い出す

いけさん目次

arXivに出ていた OCR-Memory: Optical Context Retrieval for Long-Horizon Agent Memory を読んだ。
ACL 2026 Main採択の論文で、エージェントの長い作業履歴をテキストとして要約・検索するのではなく、画像として保存して検索する。

最初は「OCRでメモリ?」と思ったが、これは文字認識ツールの話ではない。
DeepSeek-OCRの記事で触れた Contexts Optical Compression を、長期エージェントメモリに持ち込んだものだ。
テキストをそのままコンテキストへ戻すと高い。要約すると細部が消える。
なら、履歴を一度画像へ押し込んで、必要な箇所だけ元ログから戻す、という発想になる。

履歴を読むモデルと考えるモデルを分ける

OCR-Memoryの肝は、メモリ検索モデルに答えを生成させないところだ。
履歴の各セグメントを画像へレンダリングし、赤枠と番号を付ける。
検索時にはVLMが「どの番号が今のクエリに関係するか」だけを選ぶ。
そのあと、選ばれた番号に対応する元テキストをデータベースから逐語的に取り出す。

flowchart TD
    A[過去のエージェント履歴] --> B[セグメントへ分割]
    B --> C[赤枠と番号つき画像へ変換]
    C --> D[Visual Memory Bank]
    E[新しいタスク] --> F[Optical Retriever]
    D --> F
    F --> G[関連番号だけ選択]
    G --> H[元ログから逐語テキスト取得]
    H --> I[推論エージェントのコンテキストへ注入]

この分離はかなり大事だ。
VLMに「画像内の該当テキストを答えて」とやらせると、低解像度やぼけで読めない箇所をそれっぽく補完する可能性がある。
OCR-MemoryではVLMの仕事を「0/1の関連判定」に寄せ、証拠本文は元ログから決定的に取得する。
論文中では、この方式を Locate-and-Transcribe と呼んでいる。

名前にTranscribeとあるが、実際にはモデルが自由に文字起こしするわけではない。
「位置を当てる」と「原文を戻す」を分けるから、生成由来のハルシネーションを避けやすい。

画像化は圧縮というより索引になる

DeepSeek-OCRの文脈では、テキストを画像にして視覚トークンへ圧縮する話が中心だった。
DeepSeek-OCR論文では、テキストトークン数が視覚トークン数の10倍未満なら約97%のOCR精度、20倍では約60%まで落ちるとされていた。
OCR-Memoryはこの発想をそのまま「全文復元」に使うのではなく、関連箇所を探す索引として使う。

論文の実装では、DeepSeek-OCR 3Bをベースにする。
視覚エンコーダは凍結し、言語デコーダだけをLoRAで調整する。
HotpotQAのsupporting factsを使って、質問に関係する段落番号を当てるタスクへ作り替えている。

部品役割
DeepSeek-OCR 3B画像化された履歴を読むベースモデル
Set-of-Mark赤枠と番号でセグメントを指定可能にする
LoRA fine-tuning質問に関連する番号を選ぶ能力を足す
元ログDB選ばれた番号から逐語テキストを返す
推論エージェント取り出された証拠を使ってタスクを解く

この設計だと、メモリ画像は検索用の視覚インデックスとして働く。
最終的にエージェントへ渡るのは画像ではなく、選ばれた元テキストだ。
画像の役割は、巨大な履歴の中からどこを見るかを安く決めることにある。

古い履歴はぼかして、当たったら戻す

面白いのは、古い履歴ほど低解像度にするところだ。
論文では直近5ステップを1024×1024で保持し、それ以前を512×512へ落とす二段階ポリシーを使っている。
DeepSeek-OCRの視覚トークン予算としては、1024×1024が256 tokens、512×512が64 tokensになる。

低解像度にした履歴が検索で引っかかったら、元ログから高解像度画像を再レンダリングして、そのエピソード中は高解像度のまま扱う。
論文はこれを Active Recall Upscaling と呼ぶ。
「古い記憶はぼんやりしているが、重要だと分かったら鮮明に思い出す」という比喩になっている。

この点は YourMemoryの記事 で見た忘却曲線の話と近い。
YourMemoryは検索スコアを時間で減衰させる。
OCR-Memoryは、検索対象から消すのではなく、画像の解像度を落として視覚トークンを節約する。
どちらも「全部を同じ鮮度で持ち続けない」という問題意識は同じだが、捨て方が違う。

ベンチ結果はトークン制限下で強い

評価はMind2WebとAppWorld。
メモリモジュール側のコンテキストは4096 tokensに固定されている。
OCR-MemoryはMind2WebでElement Accuracy 53.8%、Step SR 46.1%、Task SR 4.8%。
AppWorldの平均Success Rateは58.1%だった。

方法Mind2Web Ele AccMind2Web Step SRMind2Web Task SRAppWorld Avg
Retrieval41.338.92.746.2
MemoryBank43.839.23.352.1
AWM49.142.64.355.0
ACON48.241.44.156.2
OCR-Memory53.846.14.858.1

派手に見えるのは、トークンがきつい条件での差だ。
1024、2048、4096、8192 tokensの各制限でText-RAGより一貫して高く、1024 tokensではElement Accuracyで+17.0、Action F1で+14.8、Step SRで+14.8の差が出ている。

Needle-in-a-Haystack系の長文検索でも、4kでRecall@1 98.5%、32kで94.1%。
圧縮率はおおむね10倍台に収まっている。
全文を読むより、視覚的に圧縮した履歴から場所を当てるほうが、限られたコンテキストでは有利になりやすいという結果だ。

安くなるリソースと高くなるリソース

OCR-Memoryは万能に安いわけではない。
論文の効率プロファイルでは、Mind2Webの連続ログでText-RAGが1エピソード18KB、OCR-Memoryが1.47MB。
検索レイテンシもText-RAGの0.3秒に対し、OCR-Memoryは1.7秒。
一方、推論LLMへ戻すテキストトークンは3,980から596へ減っている。

項目Text-RAGOCR-Memory
ディスク/エピソード18KB1.47MB
推論へ戻すテキスト/ステップ3,980 tokens596 tokens
検索レイテンシ/ステップ0.3秒1.7秒

つまり、節約しているのは主に推論コンテキストだ。
代わりに、画像レンダリング、追加のVLM、ディスク容量、検索遅延を払う。
ローカルの軽いエージェントメモリにそのまま入れるには重い。
長時間走るWeb操作エージェントやAPI操作エージェントのように、履歴が膨らみ続け、かつ正確な過去ログ参照が成功率に直結する場面で意味が出る。

ここは GLM-5.1のLong-Horizonエージェント記事 と読み合わせると分かりやすい。
モデル本体が600回以上の反復に耐えても、履歴の渡し方が雑なら古いエラーや成功手順を拾えない。
Long-Horizonはモデルの持久力だけでなく、履歴の保存・検索・再注入の設計でも決まる。

まだ研究システムとして読む段階

論文自身も限界を明記している。
OCR-Memoryはtraining-freeではなく、専用の光学検索モデルをfine-tuneする。
履歴を画像へレンダリングする処理も、テキスト保存より重い。
さらに、推論エージェントとは別に視覚エンコーダをメモリへ載せる必要がある。

評価も、Mind2WebとAppWorldというベンチ上の話だ。
本番のエージェントログには、スクリーンショット、HTML、ツール出力、エラー、ユーザー指示、途中推論の扱いなどが混ざる。
何をセグメント化し、どこまで画像へ入れ、どの粒度で元ログへ戻すかは実装の設計問題として残る。

手元で組むなら何がいるか

論文のコードは公開されていないが、部品は個別に揃っている。

ベースのVLMはDeepSeek-OCR 3Bで、GitHubリポジトリから取れる。
3Bなのでfp16で約6GB、M1 MacやRTX 3060クラスで推論が回る。
視覚エンコーダは凍結してLoRAだけ当てるから、学習もVRAM 8GBあれば足りる。

学習データはHotpotQAのsupporting factsを流用する。
元の質問応答ペアを「どのセグメント番号が関係するか」に作り替える手順はこうだ。
テキストをセグメント分割してSet-of-Markで番号つき画像にレンダリングし、正解のsupporting factが含まれるセグメントの番号をラベルにする。
PEFTのLoRAConfigでrank 8〜16、alpha 16〜32あたりから始めればいい。

画像レンダリングはPillowで十分だ。
テキストをフォント指定で描画して、セグメント境界に赤い矩形と番号を重ねるだけ。
論文ではグリッドレイアウトを使って1枚の画像に複数セグメントを詰めている。
解像度は直近が1024×1024、古いものが512×512の二段で、エピソード管理側で切り替える。

ログDBはSQLiteで済む。
セグメントIDをキーにして元テキストとタイムスタンプ、現在の解像度フラグを持たせる。
検索ヒット時のActive Recall Upscalingは、元テキストから1024×1024で再レンダリングしてメモリバンク上の画像を差し替えるだけだ。

エージェントループへの組み込みは、各ステップの後にログ保存→レンダリング→メモリバンク更新を挟み、各ステップの前にクエリ→検索→元テキスト取得→コンテキスト注入を入れる。

fine-tuneを飛ばしてプロトタイプを組むなら、DeepSeek-OCR 3BのLoRA学習を省いて、GPT-4oやClaude Sonnetにfew-shotでSet-of-Mark画像を渡す手もある。
「この画像のうち、質問Xに関連するセグメント番号をすべて挙げよ」という指示を投げるだけだ。
API呼び出しのコストは上がるが、学習データの用意やfine-tuneのイテレーションを丸ごと省ける。
検索精度がどこまで出るかは試さないと分からないが、OCR-Memoryの設計が回るかの検証としてはこれで十分だろう。

逆に厄介なのはセグメント分割の粒度だ。
論文ではエージェントの1ステップを1セグメントにしている。
実際のエージェントログは、ステップの長さが数行から数百行までばらつく。
短すぎると画像1枚に大量のセグメントが並んで視覚的に潰れ、長すぎるとVLMが必要な情報を拾えない。
トークン数で上限を設けてスプリットする、ツール呼び出し単位で切る、など試行錯誤が要る部分だ。

1Mコンテキストがあっても全履歴は入れられない

Claude 1Mコンテキストの正式GADeepSeek V4の1M対応、Geminiの2Mと、ウィンドウ自体は急速に広がっている。
履歴を全部入れればOCR-Memoryのような仕掛けは要らないのかというと、そうはならない。

ウィンドウが広くても無関係なテキストが大量に入ると精度は下がる。
Chroma Context-1はモデル自身が不要パッセージを削除するself-editing機構でこの問題に対処していたが、OCR-Memoryは検索の段階で視覚的に絞り込むことで同じ問題を回避する。
1Mウィンドウに全履歴を入れたところで、200ステップ前のエラー回避手順をモデルが正確に拾える保証はない。
OCR-MemoryがNeedle-in-a-Haystack実験を入れているのは、「広いウィンドウに入れるだけでは検索精度が保てない」ことを示す意図がある。

コスト面もきつい。
AnthropicのプロンプトキャッシュTTL問題で調べたように、Prompt Cachingが効くのはシステムプロンプトや変化しない会話の先頭部分だけで、エージェントの実行履歴は毎ステップ伸びていく。
新規入力トークンはフルレートで課金される。
200ステップのタスクで毎回全履歴を送れば、APIコストが線形以上に膨れる。
CLAUDE.mdが肥大化して困ってる人のためのトークン管理ガイドで「トークン管理がエージェント設計の中核」と書いたのはこの事情だ。

既存のクラウド向けアプローチと並べるとOCR-Memoryの位置が分かりやすい。

Compresr Context GatewayはエージェントとLLM APIの間にプロキシを挟んで、ツール出力を要約し先読み圧縮でトークンを減らす。
テキスト→テキストの圧縮で、既存のAPIコールをそのまま使える手軽さがあるが、要約した時点で元の詳細は消える。

Cloudflare Agent Memoryはプラットフォーム側で会話から記憶を自動抽出し、Durable Objects/Vectorizeに格納するマネージドサービスだ。
自分でメモリ基盤を作らなくていいが、抽出ロジックはCloudflare側の実装に依存する。

OCR-Memoryはどちらとも出発点が違う。
元テキストを全部保持したまま、「どこを見るか」の判断だけを視覚ベースの安い検索で済ませる。
圧縮ベースだと潰れる情報が、元ログ復元なら残る。
代償として、テキスト保存なら18KBで済むところが画像化で1.47MBになり、検索レイテンシも0.3秒から1.7秒に増える。

光学検索器にDeepSeek-OCR以外のVLMを使う

前のセクションでDeepSeek-OCR 3BのLoRA学習とGPT-4o/Claudeのfew-shot検証を書いたが、ローカルで動かすVLMの選択肢はもう少しある。

ローカルVision LLMでキャラ画像からRPGパラメータを抽出した実験では、Qwen2.5-VL 7BをOllamaで動かして画像からの構造化抽出を試している。
OCR-Memoryの光学検索器が要求するタスクも「画像を見て、関連する番号を返す」という構造化抽出で、自由文の生成ではない。同系統の能力で動く。

Qwen2.5-VL 7Bは汎用VLMだが画像内テキストの読解能力が高く、Ollamaで即座に動く手軽さがある。
VRAM 8GB程度で推論が回るからM1/M4 MacやRTX 3060クラスで十分だ。

InternVL2-8BはOCR・ドキュメント理解のベンチマークで上位に来ているモデルで、テキスト密度の高い画像に強い。
Set-of-Markの赤枠付き画像はまさにテキスト密度の高い入力なので、相性は良さそうだ。

Moondream 2Bは2GBクラスで動く最軽量VLMだが、テキスト密度の高い画像での精度は未検証の部分が多い。

論文のAblation StudyではLoRAなしのDeepSeek-OCR 3Bだと、LoRA適用版よりElement Accuracyが10ポイント近く落ちる。
3Bクラスでfew-shotだけでは精度が足りないことを示唆しているが、7B〜8Bの汎用VLMなら元の視覚理解能力が高いぶん、fine-tuneなしでもDeepSeek-OCR 3B+LoRAに近い精度が出る余地はある。
LoRA学習を省きたいならDeepSeek-OCR 3Bより大きい汎用VLMを使う方向が現実的だ。

HypurAのNVMeストリーミングのようなSSD最適化は、OCR-Memoryの文脈では画像ストレージの読み込み高速化として効く。
VLMの推論自体は3B〜7Bで軽いが、検索のたびに複数枚の画像をディスクから読み込む。
高解像度1024×1024の画像が数百枚たまったメモリバンクを毎ステップスキャンするなら、NVMeの読み込み速度がレイテンシに直結する。

かなチャットのHeartbeatメモリとの距離

かなチャット v1ではタスクごとにtmuxセッションを立てて、あるタスクのコンテキストが別タスクに漏れない設計にした。
v2で追加したHeartbeatメモリは、Haikuが直近60件のユーザー発言からプロファイル・日々の活動・タスクシグナルを抽出し、構造化JSONとして蓄積する。
次のジョブの起動時にこのJSONをプロンプトに差し込むことで、セッション間の文脈引き継ぎを実現している。

Heartbeatが捨てているのは元の会話テキストだ。Haikuが「この人はセキュリティ記事に詳しい」と抽出したら、その根拠になった50件の会話は構造化JSONに圧縮されて消える。
OCR-Memoryは逆で、全履歴をロスレスに画像化して保持し、検索時にVLMが関連箇所を当てて原文を戻す。
Heartbeatは「だいたい何があったか」を覚えるのに向いていて、OCR-Memoryは「正確に何が書いてあったか」を戻すのに向いている。
排他ではなく、Heartbeatでざっくり文脈を持ち、詳細が必要なときにOCR-Memory的な検索で原文を戻す二段構えはありえる。

かなチャットが防いでいるのはタスク間の「横の汚染」で、OCR-Memoryが解いているのは1つのタスク内の「縦の膨張」だ。
かなチャットのタスクは長くても数十ステップで終わることが多いから、OCR-Memoryが効くのはもっと長い数百ステップ以上のLong-Horizonタスクで、守備範囲がずれている。

ただし、マルチエージェント構成では両方の問題が同時に出る。

Claude Codeのマルチエージェントレビューでは複数のエージェントが並列でPRをレビューし、発見を持ち寄って検証フェーズに回す。
かなチャットでもClaude Codeワーカーの結果をCodexレビュアーに渡す構成を作っている。
どちらも、あるエージェントの作業履歴を別のエージェントに効率よく渡す問題を抱えていて、何を渡し何を捨てるかは今のところ設計者が決めている。

OCR-Memoryの仕組みをマルチエージェントに転用するなら、各エージェントの全履歴を画像化して共有メモリバンクに入れ、コーディネーターが「他のエージェントはこのファイルについて何を見つけた?」と視覚検索して該当箇所だけ渡す、という構成が考えられる。

YourMemoryはEbbinghaus忘却曲線で古い記憶を減衰させるローカルMCPだが、こちらは1エージェントの長期記憶が対象で、マルチエージェント間の履歴共有は想定していない。
信頼度スコアで文書抽出の人手確認を絞る記事で書いた「安い方法で大部分をさばき、精度が必要な箇所だけコストをかける」パターンは、OCR-Memoryの「安い視覚検索で場所を特定し、元テキストで正確に戻す」構造と同じだ。

参考