🧠

Goで作るセキュリティ分析LLMエージェント(21): エージェントの記憶システム概論

に公開

この記事はアドベントカレンダー「Goで作るセキュリティ分析LLMエージェント」の21日目です。

これまでも解説してきた通り、LLMはステートレスであり、かつコンテキストウィンドウに制限があります。コンテキストウィンドウとは与えられるトークンサイズのことであり、この制約をうまく扱うためのアプローチの一つが「記憶」です。

今回は記憶に関する解説をしますが、実は筆者自身もいろいろ試したものの、セキュリティ分析における記憶の使い方の解がまだ見えていないというのが正直なところです。

そのため本記事では、2025年12月執筆時点での技術とこれまで試した記憶に関する手法の現在地について解説します。記憶システムの分野は今後も発展していくと考えられ、より良い手法が確立されていくことが期待されます。

LLMエージェントにおける「記憶」の整理

2025年現在、LLM実行においてユーザ入力以外で外部から取り込むデータの総称として「記憶(Memory)」という言葉が使われることが多い印象です。システムプロンプトや会話履歴、RAGで取得するドキュメントなど、LLMに与える情報は多岐にわたりますが、これらも十把一絡げに「記憶」として扱われています。

明確な定義があるわけではありませんが、LLMが利用する記憶は短期記憶と長期記憶に分けて考えられることが多くなっています。これは人間の記憶モデルを参考にしたもので、人間がワーキングメモリで保持する短期記憶と、長期的に蓄積される長期記憶に対応します。人間の記憶モデルを参考にしたアプローチは他にもいくつか提案されていますが、今のところ実用的なのはまずこの分類と思われます。例えばLlamaIndexにおけるメモリモデル[1]では、短期記憶(Short-term Memory、あるいはActive Context)と長期記憶(Long-term Memory、あるいはExternal Archive)に分類されています。短期記憶は主にセッションの履歴や状態の管理を指し、現在進行中の会話で必要な情報を一時的に保持します。短期記憶は主に会話履歴をメモリに保持することで実装されます。一方で長期記憶は、セッションを跨いで長期的に保存され必要に応じて呼び出される記憶を指します。RAGが代表的な長期記憶の実装方法です。

また、処理の仕方によって事前定義(static)と都度更新(dynamic)のような分類もできます。事前定義はRAGのように事前に文章を突っ込んでおくケースや、実装上のワークフローで定義するプロンプトのようなものを指します。一方で都度更新は、生成AIの行動によって生成されるもの、つまり会話から発生するユーザのプロファイル、エピソード、知見などを指します。これらの組み合わせで整理すると次のようになります。

Static(事前定義) Dynamic(都度更新)
Short(短期) (1) システムプロンプト、Reasoning Strategies (2) セッション状態管理[2]、履歴、CoT
Long(長期) (3) RAG、固定ポリシー、GraphRAG[3] (4) 過去の知見、プロファイル、エピソード

(1)から(3)に関してはこれまでも説明してきましたし、現時点で成長の余地はあるものの、かなり方法論が確立してきた印象があります。それぞれ具体的には次のようなものです。

  • (1) システムプロンプト: 「あなたはセキュリティアナリストです」といった役割定義や、タスクに応じた推論戦略
  • (2) 会話履歴: ユーザとLLMの過去のやり取り、Chain-of-Thoughtの推論過程、セッション状態のチェックポイント
  • (3) RAG: 事前に用意されたドキュメントやポリシー、過去のアラート対応記録

一方で(4)の長期動的記憶は課題が多い領域です。これはエージェントが動作する中で得た知見や経験を次回以降のセッションで活用するというものですが、ユースケースにかなり依存するため、まだ各プロダクト・サービスなどが試行錯誤している段階のように感じます。

長期動的記憶の特性と課題

前節で整理した4つの記憶タイプのうち、(4)の長期動的記憶は基本的にセッション(一連の会話)を跨ぐことで効果を発揮します。一度のセッション内で完結する一問一答形式のChatだとあまり問題になりませんが、実際にはメモリ機能がChatGPT、Claude、Geminiでそれぞれ導入済みもしくは導入中であり、注目されている機能です。

セッション間で記憶を引き継ぐということについて、ChatGPTで過去に話題になった4oがわかりやすいかと思います。チャットのセッションをまたいで記憶を引き継ぐ機能が導入された際、ユーザーがChatGPTに「人格」を記憶機能に実質的に設定することができました(その後、その機能がなくなり問題となりました)。

単なるチャットであれば「その人とのエピソード」を散発的に記憶すればよいのですが、業務によっては記憶を細かく制御する必要があります。具体的には次のような分類が考えられます。

  • 覚えてほしい記憶: 組織固有のルール、過去のインシデント対応で得た教訓
  • 覚えてほしくない記憶: 一時的なテストデータ、個人的な雑談
  • 思い出してほしい記憶: 現在のタスクに直接関連する過去の知見
  • 思い出してほしくない記憶: 異なる文脈での類似事例(混乱を招く可能性)

この制御方法はまだいろいろ模索中であるように感じます。

難しいポイントとして、ある程度使い込んでみないとその記憶システムの良し悪しが判断できないという点があります。人間の体感による良し悪しがあるうえ、記憶による影響が自明ではないことが多いため、その記憶が本当に良かったのかどうか判断しづらいのです。例えばLLMがある判断を下したとき、その原因を特定するのは困難です。それが記憶の影響なのか、プロンプトの影響なのか、あるいはLLM自体の推論能力によるものなのかを切り分けられません。ただし明らかに行動は変容するため、記憶の有無による影響を後から認識できることもあります。このように評価が難しい=ベンチマークしづらい=方法が確立しづらい、という構造があると感じています。

記憶機能の設計

ここでは長期動的記憶(Long-term Dynamic Memory)のシステムを構築するに当たってのポイントについて解説します。LLMエージェントにおける記憶は保存、取得、管理という3つの要素から考えることができます。

記憶の保存:何をいつ記憶すべきか

記憶すべき内容の選定

記憶すべき内容は、その業務の目的によって異なります。セッション中で明らかになった事実、ユーザーから教えられた知見、ツール実行時にうまくいったこと、ツール実行で失敗したこと、あるいは改善されたことなど、様々な候補が考えられます。

これらの情報を別セッションに持ち越すことで利益になるかを考える必要があります。例えば単なるおしゃべり会話だとしたらツール実行の振り返りは役に立ちません。またシンプルなAPI実行代行のようなものであれば、殆どの場合これも振り返りは不要です。それぞれのセッションが独立しており、前回の経験が次回に活きる場面が少ないためです。

しかしツール実行が複雑な業務、例えばBigQueryにクエリ発行してログを分析するような場合はどうでしょうか。分析を進める中で「このテーブルのこのフィールドには特定形式のデータが入っている」「このフィールドは空の場合がある」「このフィールドが取りうる値はこのぐらいの範囲だ」「このフィールドはキーとして使える」といった知見が蓄積されていきます。こうした情報は次回のセッションでも役立ちます。また、組織固有の文化、文脈、体制や最近の状況、活発な事業などの変わりゆく情報も記憶の対象となります。これらは静的なドキュメントには書かれていない、実際の運用を通じて得られる知見です。

もちろんこれらは事前にプロンプトやツール説明に入れておくべきという考え方もあります。しかし実際には分析している最中に気づくことが多く、その都度設定ファイルをいじって編集するのは手間がかかります。特に運用中のシステムでは、このような知見が日々蓄積されていくため、手動でメンテナンスし続けるのは現実的ではありません。エージェント自身が学習して覚えてくれるなら便利です。これが長期動的記憶の醍醐味といえます。

ただしこれはプロンプトの管理などに近しい部分もあり、システム上これらを統合して管理する仕組みがあってもよいと考えられます。例えば記憶として蓄積した知見のうち、十分に安定したものは静的なプロンプトやツール説明に昇格させるといった運用も考えられます。むしろそれらをどう統合管理するのが最適なのか、模索が必要です。

LLMに判断させるか、人間が判断するか

さらに「何」を記憶するかをLLMに判断させるか、それとも人間が判断するかという点も検討ポイントです。

LLMに判断させることももちろんできます。たとえばセッションの終わりで、「このセッションで有効だったことを最大3つあげろ」といった指示をして記憶させる方法があります。自動化できるため運用負荷が低いのが利点です。しかし本当に記憶してほしいことをちゃんと記憶したかどうかは判断が難しく、LLMが適切に判断してくれるとは限りません。重要な知見を見逃したり、逆に些細なことを記憶してしまったりする可能性があります。プロンプトをチューニングしたり与えるデータを調整したりなど、かなり工夫が必要になります。

一方で人間が指示する方法もあります。指示自体は自然言語で行えばよいため、「この情報は今後も使うから覚えておいて」といった形で明示的に記憶を指示できます。重要度の判定がLLMに難しかったり、あるいはまだプロンプトが熟れてなくて自動判定が難しいときはこちらのほうが適しているかもしれません。LLMベースのコードレビューサービス(devinCodeRabbitなど)がこういった機能を持っています。

記憶の取得:何をいつ思い出すべきか

記憶システムは「覚える」と「思い出す」の両方を設計する必要があります。前節では何を記憶するかを解説しましたが、記憶する方法だけでなく、当然ながら思い出す方法も考えないといけません。

記憶を全部プロンプトに乗せていいかというと、まったくそういうわけにはいきません。再び立ちふさがるのがコンテキストウィンドウ制限の壁です。そのため記憶を大量に持っていたとしても、それを適切なタイミングで適切な記憶だけを注入するというテクニックが必要になります。

記憶取得の3つのアプローチ

これについてはケースバイケースすぎて一概には言えませんが、大きく分類すると3つのアプローチが考えられます。以下の図は、3つのアプローチとその処理フローを示しています。

(1) システムプロンプトに注入するアプローチ

まず初期プロンプト(特にシステムプロンプト)に記憶を注入してしまうパターンです。エージェント起動時に関連する記憶を検索してプロンプトに含めることで、LLMが最初から必要な知識を持った状態で動作できます。ユーザ入力をEmbeddingして検索するのがいわゆるRAGで、このアプローチに位置します。

もし他の入力データもセッションの文脈で持つシステムなら、それを利用して記憶を取得することもできますし、そちらのほうが良い場合もあります。例えばセキュリティ分析の場合、分析対象となる「アラート」というわかりやすい構造データが存在します。アラートにはソースIPアドレス、宛先URL、検知ルール名などの属性があり、これらを活用できます。その中の一部のデータをEmbeddingして検索したり、属性値と一致するものがないか検索したり、アラートの種類が特定できればその種類に関する記憶を引っ張ってこれます。

また、記憶となるデータをそのまま全部プロンプトに書くのではなく、ID+要約だけ記載しておくという手もあります。これはIDによって記憶を検索するツールを用意しておくことで全文の掲載を回避し、コンテキスト消費を抑えるテクニックです。ただしLLMが正しく検索してくれる必要があり、プロンプトを工夫する必要があります。このように単に記憶をプロンプトに入れ込むだけでも様々なアプローチがあります。

(2) 処理フロー内で取得するアプローチ

(1)に似ていますが、処理フローの中で何か条件にマッチした場合にワークフロー内で記憶を取得し、プロンプトなどに付与する方法です。初期プロンプトではなく、処理の途中で動的に記憶を追加します。例えばPlan & Executeのような処理で列挙させたタスク名をEmbeddingし、似たような過去のタスクの実行結果振り返りの記憶を探すといったことが考えられます。

具体的には「BigQueryでログを検索する」というタスクが出てきたら、過去の同様のタスクで得られた知見(「このフィールドはタイムアウトしやすいのでLIMIT句を付けるべき」など)を検索して注入する、といった使い方です。タスクの種類に応じて関連する記憶を動的に取得することで、必要な知識だけを効率的に活用できます。

このアプローチは(1)以上に処理が複雑になりますし、ちゃんと噛み合うケースは結構少ないかもしれません。適切なタイミングで適切な記憶を取得する条件設計が難しいためです。

(3) エージェントに検索させるアプローチ

LLMが使えるツールに記憶検索機能を実装しておく方法です。これによってLLMが必要だと判断したタイミングで任意に記憶を取得できます。利点としてはID+要約をいれておく(1)の場合と同じく、コンテキスト消費を大幅に抑えられます。LLMが本当に必要なときだけ記憶を取得するため、無駄なコンテキスト消費を避けられます。

問題はLLMが必要なときに必要な記憶を検索してくれるかどうかです。「記憶を検索できますよ」という情報だけだとなかなか検索しなかったり、検索しようとしても期待した検索条件を入れてくれないことがあります。例えば「BigQueryに関する知見を検索して」と指示しても、具体的にどのような検索キーワードを使うべきか、LLMが適切に判断できない場合があります。

そのため、必要に応じて記憶を検索せよ、といったことをかなり強めにプロンプトに書き込んでおく必要があります。また、ツールの説明には、格納されている知識の種類(過去のBigQueryクエリ実行時の知見など)と、利用すべきタイミング(分析開始前や問題発生時など)を明記する必要があります。漠然と記憶がありますとだけ伝えるのでは不十分です。あとはツールの検索方法についてもいろいろなアプローチがありえます。

記憶の管理:何をいつ更新する・忘れるべきか

なぜ記憶を忘れる必要があるのか

記憶は蓄積するだけでなく、忘れる必要もあります。すべての記憶が有効というわけではなく、たまたまうまくいった、もしくはうまくいかなかったケースがあとから有害になることがあります。例えば特定の条件下でのみ有効だった回避策を一般的な知見として記憶してしまうと、本来適用すべきでない場面でその回避策が使われてしまう可能性があります。あるいは状況が変わったことでその情報はもう価値がなかったり、間違った情報になっているということもありえます。システムの仕様変更やデータスキーマの更新などにより、過去の知見が陳腐化することは珍しくありません。そのため記憶を更新したり忘れる(つまり記憶の削除)をする必要があります。

更新・削除のタイミング

どのタイミングでどう更新・削除するかは極めて難しい問題です。一つのタイミングとして考えられるのは、それが間違い・無価値と判明した瞬間です。殆どの場合それは何か処理をする過程で発覚するため、タスク完了時などにその記憶の有用性を評価させるアプローチはありえます。

更新・削除する記憶の選定

どの記憶を更新・削除するのかという点もまた難しい問題です。「役に立った」記憶は評価しやすいのですが、一方で「役に立たなかった」記憶をどう評価するかが問題になります。ある記憶が参照されなかったからといって、それが無価値とは限りません。後で役に立つかもしれませんし、その時はたまたま何か噛み合わなかっただけかもしれません。逆に頻繁に参照される記憶でも、内容が古くなっていれば有害です。

現実的には記憶にスコアを付けて徐々に評価を落としていくパターンになると思われます。例えばスコアリング方式の一例として、記憶作成時に初期スコアを設定し、参照時に加点、一定期間未参照で減点、スコアが閾値を下回ったら削除といったルールが考えられます。しかし実際にそこまで長期運用されたケースはまだまだ世の中に多くないのではないでしょうか。このあたりについては今後ベストプラクティスが定まっていくことに期待しています。

また場合によっては人間が直接記憶をいじることができるインターフェースを用意するというのも手段のひとつです。例えばWebUIに記憶編集・削除画面を作るといった方法があります。記憶データ(文章など)なら人間にも理解しやすく管理できます。ただし「このワークフローで悪さしたのはどれだ」といった特定作業はやや大変かもしれません。

セキュリティ分析における記憶のユースケース

今回は実装を載せるに至りませんが、記憶に関するセキュリティ分析のユースケースをまとめておきます。

(1) 類似アラートの対応記録の呼び出し

これはすでに実装したものです。過去の対応記録を元に脅威度の判定をさせる一種の記憶管理で、どちらかといえばfalse positiveであることの根拠に使います。同じようなアラートが過去に誤検知と判定されていれば、今回も誤検知である可能性が高いと推論できます。これは長期静的記憶(表の(3))に近い実装ですが、対応記録が増えていくという意味では動的な側面もあります。

(2) ツールの使い方に関する技術的知見

ツール呼び出しに関する実践的なノウハウの記憶が有用です。先述した通り、例えばログを蓄積したDWHへのアクセスでは、どのフィールドに何が入っていて、それがどう活用できるのかといった知識が必要になります。もちろんこれらはスキーマ情報として与えられているのが望ましいのですが、説明不足であったり行間の補足が必要なものもあります。「このクエリはタイムアウトしやすいのでLIMIT句を付けるべき」「このフィールドとこのフィールドをJOINすると効率的」といった実践的なノウハウは、実際に使ってみないとわかりません。それゆえに行動の記録を取得しておくことには効果があります。

(3) ビジネス文脈や組織ルールに関する知識

組織固有の情報、監視対象環境のアーキテクチャ、システム運用方法、組織のポリシー、独特なルールといった情報も記憶の対象になります。「この部署は毎週金曜日に定期メンテナンスを行う」「このIPアドレス帯は開発環境なので多少の異常は許容される」「このアプリケーションは既知の脆弱性があるが業務上の理由でパッチ適用を延期している」といった文脈情報です。これも事前にプロンプトに入れておけばよいのですが、管理するのが面倒ですし、実際に分析させてみてわかることもあります。それをいちいちLLMで要約してコピーして設定ファイルに投入する、といった作業もできなくはありませんが、今どきのやり方でやるならもうエージェント内で完結していてほしいところです。

まとめ

本記事では、LLMエージェントにおける記憶システムを短期/長期と事前定義/都度更新という2軸で整理し、特に長期動的記憶の課題と可能性について解説しました。記憶システムの設計では、何を記憶し、いつ思い出し、どう管理するかという3つの観点を総合的に考える必要があります。長期動的記憶は静的なプロンプトやRAGでは対応しきれない「実運用を通じて得られる知見」を扱う仕組みですが、その制御は複雑で評価も難しく、ベストプラクティスはまだ確立していません。

本記事では4つの記憶タイプと3つの取得アプローチという設計の枠組みを整理しました。次回は、これらの概念を実際のコードに落とし込み、セキュリティ分析エージェントに記憶機能を組み込んでいきます。

脚注
  1. Memory in LlamaIndex https://developers.llamaindex.ai/python/examples/memory/memory/ ↩︎

  2. LangChain Short-term memory https://docs.langchain.com/oss/python/langchain/short-term-memory?utm_source=chatgpt.com。セッション状態のチェックポイントとして、会話の途中経過を保存し復元する仕組みを指します。 ↩︎

  3. From Local to Global: A Graph RAG Approach to Query-Focused Summarization https://arxiv.org/pdf/2404.16130 ↩︎

Discussion