AI Agent向け長期記憶ツールを作った話

はじめに

今回は、AI Agent向けの長期記憶機能を担うツールを作成してみたので、それについてまとめていきたいと思います。

本記事の内容は2026/8時点のものです。各サービスの機能・名称は、今後変更される可能性があります。

作成理由

昨今のAgentは、contextに入った情報をもとにタスクを遂行する構成が基本です。そのため、contextをいかに効率的に使うかという点が重要になっています。その中で、前のタスクでやったことを覚えていてほしいとか、前回やった修正が巻き戻ったといった経験は一度はあるかと思います。こうした課題に対し、OpenAIやAnthropicなどのプロバイダはMemory機能を提供しています。Memory機能について大まかに話すと、Agentがセッション内で有用だと思った情報をMemory(ファイル)に保存していき、別セッションでMemoryを読むことで、過去のセッションの知識を再利用できるというものです。最初はこの機能を使ってAgentを利用していたのですが、使っているうちにいくつか不満がありました。

今回はCodex(ChatGPT)を前提に書いています。Claudeだといくつか解決できているものもあります。

  1. AgentがMemoryを読むため、contextが持っていかれる
  2. プロジェクトの区別がないため、別プロジェクトのMemoryが混入する可能性がある
  3. 1つのファイルに積み重なっていくため、使えば使うほどコストが上がっていってしまう
  4. Memoryの細かい調整はファイル編集を行うしかない

そこで、これらの不満を解消するべく新しくツールを作成しました。

全体の構成

まず全体像を見ていきます。

名称内容
Application Serviceメインシステム
Document Store実ファイルの保存領域
Background Worker保存済みMemoryをチャンク化・ベクトル化し、Qdrantへ非同期登録する処理
Embedding Provider意味的なインデックスを貼るための埋め込みモデル
QdrantベクトルDB

この構成は全てローカル環境で動作するものになっています。長期記憶や外部記憶はObsidianやGemini Notebookを使う方がシンプルに済みますが、今回は外部依存をできるだけ削るために全てローカルで動作させるようにしています。ローカルで全て動作させる場合、複数端末からの利用やCloud環境で使えないのですが、色々面倒だったので全てローカルで動作させるようにしました。

記憶部分の主要機能

主要な記憶機能についてはCLIで実装し、コマンドで利用できるようにしています。
実装されている主要機能としては、

| 名称 | 内容 |
| – | – |
| remember | MemoryをDBに保存 |
| recall | DBからMemoryを検索 |
| dedupe | Memoryの重複チェック |
| 管理機能 | 全文参照、一覧、論理削除、インデックス再構築など |

が実装されています。
これらの機能を細かく見ていきましょう。

Remember

Rememberは、受け取ったMemoryをファイルとして保存します。その後、WorkerがMemoryを分割・ベクトル化し、検索用インデックスとしてQdrantへ非同期で登録します。Qdrantは検索専用であり、Memoryの実データとしては扱っていません。Memoryの更新もRememberで行っています。

Memoryとして、再利用可能なアイデアや傾向などをAgentが独自に判断し保存するようになっています。

プロジェクトの判別はAgentに任せていません。CLIが実行時の作業ディレクトリから保存先のプロジェクトを決定し、Rememberへ引き継ぎます。そのため、Agentは保存先を意識せず、現在のプロジェクトに対応するMemoryを保存できます。これは、後述するDedupeでも同様です。

Recall

Recallは、任意の検索文から関連するMemoryを検索します。今回のCodex向け連携では、UserPromptSubmit Hookからユーザープロンプトを検索文として渡しています。検索結果にはMemory全文ではなく、関連するチャンクの本文、Memory ID、検索スコアなどを含めます。その情報だけでは不足する場合に、AgentがReadを使ってMemory全体を取得します。

検索時には検索文を埋め込みモデルでベクトル化し、あらかじめチャンク単位でベクトル化してあるMemoryとQdrant上で比較します。そのため、単語が完全に一致していなくても意味が近い内容を検索でき、類似度の高いチャンクから順に候補として返せます。

検索対象は、現在のプロジェクトとそれより上位の階層です。上位階層のMemoryには、階層の距離に応じたペナルティを検索スコアへ与えます。プロジェクト固有のMemoryに加えて、上位階層へ保存した横断的なMemoryも検索できます。一方、同列や下位の階層は現在の検索対象に含めていません。

たとえば、プロジェクトが次のような階層構造になっているとします。

user
└── projects
    ├── main-project
    └── other-project

user/projects/main-projectから検索すると、現在の階層に加えてuser/projectsuserも検索します。同列にあたるuser/projects/other-projectは検索しません。下位や同列の階層を検索対象に含めることには検討の余地がありますが、下位や同列はどの程度関連させるかなどが上位に比べて複雑化すると思ったため現在は実装していません。

Dedupe

Dedupeは入力全体の一致を確認し、一致しない場合は同じプロジェクト内から意味的に近いMemoryを探します。Recallとは異なり、上位階層は検索しません。

結果は「完全一致」「既存Memoryの可能性あり」「新規候補」「判定不能」に分けて返します。「判定不能」は、インデックスの準備が完了していない場合や検索処理を利用できない場合に返します。この結果をもとに、Agentが既存Memoryを更新するか、新規作成するかを選べるようにしています。

ファイルとQdrantの役割分担

このシステムでは、Memoryの実データはファイルに保存し、Qdrantを再構築可能な検索用インデックスとして扱っています。最初はMemory全文を1つのベクトルに詰め込んでいましたが、内容が大きくなるほど目的の箇所を検索しにくくなりました。そこで、WorkerがMemoryをチャンクへ分割してQdrantへ登録し、検索結果だけでは情報が足りない場合にAgentがファイルからMemory全文を読める設計にしています。

Memory全文を1つのベクトルにする構成と比べて、関連する箇所を検索しやすくなりました。現在のContent TypeはPlain TextとMarkdownに対応しています。将来的には、複雑な情報を表現するためにHTMLのような形式を扱う余地もあると考えています。

ファイルと検索用インデックスを分けることで管理コストは上がりますが、Qdrantを実データから切り離せる利点があります。Qdrant上のインデックスに問題が起きても、ファイルにあるMemoryから再構築できます。

論理削除されたMemoryは、保持期間の経過後にWorkerが自動で削除するため、削除済みデータがファイルとして残り続けることはありません。

管理画面

Memoryやファイルなどを簡単に管理するために管理画面とAPIの作成も行いました。

管理画面についてはツールの本質ではないため、特にこだわったポイントはないので、簡単な説明にとどめたいと思います。

管理画面では、Memoryの検索や削除、グラフビューなどを利用できます。

Memory

プロジェクトごとのMemoryの一覧を見ることが可能で、ここから内容の編集だったり削除だったりを行うことができます。

Graph View

Memoryやプロジェクトの構造をグラフとして可視化するページです。実用性はほぼないです。

CLIはAgent向け、GUIは人間向けと、それぞれが効率よく作業できる形に分離しました。

Agentへの組み込み

私がメインで使っているAgent toolがCodexだったので、Codex基準の話になります。

まずrecall処理ですが、これはUserPromptSubmit Hookでrecallスクリプトを実行するだけなのでそこまで難しくはなかったです。しかし、この方式の場合ユーザプロンプトに含まれない情報のMemoryは検索対象外になるため、より柔軟なMemoryを実現するためには、recallを自由にやらせるか、保存するMemoryを工夫するなどしないといけないです。

続いてRemember処理ですが、これが非常に難航しているというか、未だ綺麗に解決できていません。

まずRememberに関するSkillを定義して、それを元にAgentが処理できる形を整えました。Skillそのものはいたってシンプルなのでここでは詳しく言及しませんが、Memoryの記憶基準であったり、Memoryの粒度だったりを定義しています。

次にこのSkillをどうやって使うかですが、最初はStop Hookで起動してAgentにRemember処理をさせていたところ、適当なプロンプトでも発火してしまうのと、メインセッションを一時的に占有してしまうので微妙という結論で却下しました。(async hookかagent hookください)

また、Hookの中身を工夫してSubagentにやらせたり、新規タスクを起動させてやらせるということも考えました。しかし、Subagentは結局メインのセッションが一時的にロックされてしまう、新規タスクはHook単位でタスクが増えて管理が面倒になるのに加え、チャットに新規タスクの起動ログが残るということで、これらも採用しませんでした。

Agent自身が、サイドチャットを起動できればそれでもよかったのですが、OpenAIはその機能を提供していないようで技術的にそちらは実現不可能でした。

そのため、結局サイドチャットを起動して手動でRememberを実行させるという手間がかかるフローに落ち着きましたが、これが一番安定しています。ただ、並列で何個も起動していると認知負荷がかなり高まり管理が大変になるので、いずれかのHookで実現するなりで手間がかからない方法を考えたいです。

余談ですが、私はCodexではない別Harnessも使っているのですが、そっちだとMemory機能をHarnessとして簡単に実装できており、不満なく使用できているためかなり満足度が高いです。そのせいで、Codex側の対応が疎かになっているふしがあります。

使用感

1ヶ月ほど使ってみた感想ですが、悪くはないといったところで、明らかな出戻りなどは減った印象です。この記事でも、Gemini Notebookが正式名称に変更されている中で、NotebookLMが間違いと何回もAgentに指摘しなくて良かったので、最低限は動作してます。トークン消費量については、目に見えて変わるほどではないですが、横断的なファイル読み込みがなくなった分、検索時間は減ったように感じています。

定量評価をしていないため、ぼやけた評価ですが実際にAgentに組み込んだ状態で使用を続けて問題ないくらいの品質にはなったと思っています。

ただ、Rememberが手動になっているのでこれはどうにかした方がいいという気持ちがかなり強くあります。また、プロンプトに含まれない情報の検索が行われない部分も絶妙に不便なので、これも何らかの形で解決したいです。

作成しての感想

システムそのものの実装はAgentに任せたので、特に面倒なところはなかったですが、Agent組み込みの部分に関してはどうしてもこちらで考える要素が多いため大変だと思います。

こういったツールはハーネスによって組み込める範囲が大きく異なってくるので、汎用的に作っていくのは厳しいと感じました。また、Memoryの保存基準などは自分自身が曖昧な部分があり品質を上げるのに苦戦しました。

これからやりたいこと

現状のシステムはまだまだ改善の余地があるので、今後は次のような機能を追加できたらと思っています。

  • 下位・同列階層への検索有効化
  • Memoryの上位階層への昇格・Skillへの昇華
  • 汎用的なRememberの仕組み
  • 類似Memoryを新規保存・統合・更新のどれにするか自動判断する仕組み
  • プロンプト外検索
  • 意味検索の類似度計算改善

おわりに

作成した長期記憶ツールについて簡単にまとめた話でした。Memory関連については、そのうちプロバイダ側が提供してくるか、そもそも不要になるツールになるとは思いますが、ベクトルDBなどにすぐアクセスできる領域に置いているのは、大きな利点だと思います。また、こういうシステムを作成すると曖昧な部分が明瞭になってくるので、非常によかったです。

こういった外部記憶装置を作りはしましたが、Agentの発達は著しく、こういったツールが1ヶ月後には不要になっていても不思議ではありません。Memoryだけでなく、SkillやMCPなんかもなくなっていくものだとは思います。

それでも、Agentをワークフローに組み込んでいくうえでは、車輪の再発明に見えるようなものにも価値があると思います。ある意味では今しかできないことなので、しばらくはこういったツールの開発を続けたいです。

コメントを残す

メールアドレスが公開されることはありません。*がついている欄は必須項目です。

日本語が含まれない投稿は無視されますのでご注意ください。

求ム!人財!意欲ある方、お問い合わせください!募集要項はコチラ