Skip to content
Published on

コンテキストエンジニアリング — AIエージェントに記憶を設計する方法

Authors

はじめに

2026年上半期のAIエンジニアリングコミュニティで最も頻繁に登場する言葉を一つ挙げるなら、間違いなく「コンテキストエンジニアリング(context engineering)」です。最近GeekNewsではLLMメモリレイヤーのオープンソースであるSupermemoryが話題になり、Hacker Newsでもエージェントのメモリ設計に関する記事が連日フロントページに上がっています。Claude CodeやCodexのようなコーディングエージェントが数時間単位の自律作業をこなす時代になり、「モデルに何をどう見せるか」という問題が、プロンプトの文言を磨くことよりはるかに重要になったからです。

プロンプトエンジニアリングが「一度のリクエストを上手に書く技術」だったとすれば、コンテキストエンジニアリングは「モデルが各瞬間に見る情報の全体構成を設計する技術」です。そしてその中心には、メモリ、つまりエージェントに記憶を設計してあげる問題があります。本記事ではパラダイム転換の背景から、メモリ階層の設計、事実抽出パイプライン、compaction戦略、評価手法、そしてメモリ汚染のような落とし穴まで順に見ていきます。

プロンプトエンジニアリングからコンテキストエンジニアリングへ

何が変わったのか

2023年のLLM活用は概ね単発的でした。ユーザーが質問を入力し、モデルが答え、会話が終わればすべてが消えました。この時代の最適化対象は「プロンプト一枚」であり、few-shot例を入れるか、ロールプレイをさせるか、chain-of-thoughtを誘導するかが主な関心事でした。

2026年のエージェントは違います。ツールを呼び出し、ファイルを読み、数十ターンにわたって作業を続け、昨日のセッションで決めた内容を今日のセッションで参照しなければなりません。このときモデルに渡される入力はもはや「プロンプト一枚」ではなく、次の要素の合成物です。

  • システムプロンプトとツール定義
  • 過去の会話履歴またはその要約
  • 検索で取得したドキュメント断片
  • 長期メモリから読み込んだユーザープロフィールと事実
  • 現在の作業の中間成果物(ファイル内容、コマンド実行結果)

この合成物の品質こそがエージェントの品質です。Anthropicのエンジニアリングブログが整理したように、コンテキストエンジニアリングは「限られたコンテキストウィンドウという予算の中で、各推論時点に最も有効な最小限のトークン集合を選んで配置する仕事」と定義できます。

二つのパラダイムの比較

区分プロンプトエンジニアリングコンテキストエンジニアリング
最適化対象単一リクエストの文言入力全体の構成と流れ
時間範囲1ターンマルチターン、マルチセッション
中核技術few-shot、ロール指定、CoT誘導メモリ階層、検索、圧縮、分離
失敗の様相ぎこちない回答文脈忘却、一貫性の崩壊、コスト爆発
担当成果物プロンプトテンプレートメモリスキーマ、パイプライン、評価セット

プロンプトエンジニアリングが消えたわけではありません。システムプロンプトの文言は依然として重要です。ただし、それは今や全体設計の一構成要素に過ぎず、ボトルネックは「何を記憶させ、何を忘れさせるか」に移動しました。

コンテキストウィンドウの経済学

トークンはタダではない

frontierモデルのコンテキストウィンドウは20万から100万トークンまで大きくなりましたが、「入れられる」と「入れるべき」は全く別の問題です。まずコスト面から見ましょう。入力トークン単価を100万トークンあたり3ドルと仮定すると、毎ターン15万トークンのコンテキストを引きずるエージェントは、ターンあたり約0.45ドルを入力コストだけで消費します。50ターンのセッションなら22ドルを超え、1日1000セッションを処理するサービスなら入力コストだけで月数十万ドル規模になります。prompt cachingで削減できますが、キャッシュミスが発生する構造なら効果は限定的です。

性能もタダではない — lost in the middle

コストより深刻なのは性能低下です。Liuらの「Lost in the Middle」研究(2023)は、長いコンテキストの中間に位置する情報の活用率が前後に比べて著しく低下する事実を示し、この傾向は2026年の最新モデルでも程度が減っただけで依然として観察されます。よく「context rot」と呼ばれる現象もあります。関連性の低い情報が蓄積するほど、モデルは指示を見落とし、古い情報と新しい情報を混同し、ハルシネーション率が上がります。

検索精度 (概念的な曲線)

 100% |■■■
      |■■■■■
  80% |■■■■■■■           ■■■■
      |■■■■■■■■        ■■■■■■
  60% |■■■■■■■■■     ■■■■■■■■
      |■■■■■■■■■■■■■■■■■■■■■■
      +--------------------------
       位置:   前方    中間    後方

重要な情報がコンテキストの「中間」にあるとき
回収率が最も低い (lost in the middle)

結論: コンテキストは希少資源である

整理すると、コンテキストウィンドウは三つの意味で希少資源です。

  1. 金銭コスト — トークン単価とキャッシュ効率
  2. レイテンシ — 入力が長いほど最初のトークンまでの時間が伸びる
  3. 注意力予算 — トークンが多いほど個々の情報へのモデルの注意が希釈される

コンテキストエンジニアリングの出発点は、この三つの制約を認め、「全部入れる」の代わりに「選んで入れる」を設計することです。そして選んで入れるには、どこかに候補を保管する場所が必要です。それがメモリです。

メモリ階層の設計

認知科学の記憶分類を借用すると、エージェントメモリを三階層に分けるのが標準パターンとして定着しました。

+--------------------------------------------------+
|              エージェントメモリ階層                |
+--------------------------------------------------+
|  ワーキングメモリ (working memory)                |
|  - 現在のコンテキストウィンドウそのもの            |
|  - 寿命: 現在のセッション、容量: トークン予算      |
+--------------------------------------------------+
|  エピソード記憶 (episodic memory)                 |
|  - 「いつ何があったか」事件単位の記録              |
|  - セッション要約、作業ログ、決定履歴              |
+--------------------------------------------------+
|  意味記憶 (semantic memory)                       |
|  - 「何が事実か」時間に依存しない知識              |
|  - ユーザープロフィール、好み、ドメイン事実        |
+--------------------------------------------------+

ワーキングメモリ — コンテキストウィンドウそのもの

ワーキングメモリは別の保存場所ではなく、今モデルが見ているコンテキストです。設計ポイントは配置と優先順位です。システムプロンプトと中核指示は先頭に、最新の会話と当面の課題は末尾に置き、中間には回収率が低くても構わない補助資料を置きます。ツール実行結果のように容量の大きい項目は寿命ポリシーを定め、一定ターンが過ぎたら切り捨てます。

エピソード記憶 — 事件の記録

エピソード記憶は「先週火曜のセッションで決済モジュールのリファクタリングを行い、トランザクション分離レベルの問題で二度失敗した」のような事件単位の記録です。実装は通常、セッション終了時点(またはcompaction時点)にLLMで要約を生成し、タイムスタンプとともに保存する方式です。次のセッション開始時に直近のエピソード数件をコンテキストに注入すれば、「昨日の続きから」が可能になります。

意味記憶 — 事実の保管庫

意味記憶は時間と無関係に真である命題の集まりです。「ユーザーはTypeScriptを好む」「このプロジェクトのDBはPostgreSQL 16だ」のようなものです。中核の設計課題は、抽出(会話から事実をどう取り出すか)、更新(矛盾する新事実が来たらどう上書きするか)、回収(現在のクエリに関連する事実だけをどう選び出すか)の三つです。

階層別の特性比較

階層寿命保存場所回収方式更新頻度
ワーキングメモリセッション内コンテキストウィンドウ常に見える毎ターン
エピソード数週間から数ヶ月DB、ベクトルストア時系列 + 類似度セッション単位
意味記憶半永久DB、グラフキー照会 + 類似度事実変更時

会話から事実を抽出する — ユーザープロフィール構築パターン

GeekNewsで話題になったSupermemoryをはじめ、mem0、Letta(旧MemGPT)のようなメモリレイヤーのオープンソースが共通して提供する中核機能がまさにこの部分です。会話が流れる間、バックグラウンドで「記憶する価値のある事実」を抽出し、既存メモリと照合して追加・更新・破棄を決定するパイプラインです。

抽出パイプラインの構造

会話ターン --> [抽出器 LLM] --> 候補事実リスト
                                   |
                                   v
              既存メモリ検索 (類似度 top-k)
                                   |
                                   v
            [判定器 LLM] --> ADD / UPDATE / DELETE / NOOP
                                   |
                                   v
                        メモリストアへ反映

実装例

以下は抽出と判定を分離した最小実装です。実サービスでは抽出器を小型モデルに、判定器を中型モデルにしてコストを下げるのが一般的です。

import json
from anthropic import Anthropic

client = Anthropic()

EXTRACT_PROMPT = """次の会話から、長期的に記憶する価値のある
ユーザーに関する事実のみ抽出せよ。一時的状態(今お腹が空いた等)は除外。
JSON配列のみで答えよ。各要素は fact, category, confidence フィールドを持つ。
category は preference, identity, project, constraint のいずれか。"""

def extract_facts(conversation_text: str) -> list[dict]:
    resp = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=1024,
        system=EXTRACT_PROMPT,
        messages=[{"role": "user", "content": conversation_text}],
    )
    return json.loads(resp.content[0].text)

RECONCILE_PROMPT = """新しい事実と既存メモリのリストを比較し、
各新事実について ADD、UPDATE(対象id含む)、NOOP のいずれかを判定せよ。
矛盾する既存メモリがあれば UPDATE を選べ。JSON配列のみで答えよ。"""

def reconcile(new_facts: list[dict], existing: list[dict]) -> list[dict]:
    payload = json.dumps(
        {"new_facts": new_facts, "existing": existing},
        ensure_ascii=False,
    )
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=2048,
        system=RECONCILE_PROMPT,
        messages=[{"role": "user", "content": payload}],
    )
    return json.loads(resp.content[0].text)

抽出時の判断基準

何でもかんでも保存すれば、メモリはすぐにゴミ捨て場になります。実務で検証されたフィルタ基準は次のとおりです。

  • 再利用可能性: 将来の会話で再び使われる確率があるか
  • 安定性: 来週も真である可能性が高いか(一時的な感情、今日の天気は除外)
  • 出所の明確性: ユーザーが直接言ったのか、モデルが推測したのか(推測はconfidenceを低く)
  • 機微度: 健康、政治的傾向など機微情報は明示的同意なしに保存しない

RAG vs メモリ — 何が違うのか

どちらも「外部ストレージからテキストを取得してコンテキストに入れる」という点で機械的には似ていますが、設計目的が異なります。混同すると誤ったツールを選ぶことになります。

区分RAGメモリ
データの出所外部ドキュメントコーパスエージェント自身の相互作用
データの性質静的、大量、共有動的、少量、ユーザー別
更新主体バッチインデックスパイプラインエージェントランタイム
書き込み演算ほぼなし (read-heavy)頻繁 (read-write)
衝突処理不要中核課題 (事実の更新)
代表的な問いこの文書に何と書いてあるかこのユーザーは誰か

実戦では両者を組み合わせます。ドメイン知識はRAGで、ユーザーと作業の履歴はメモリで取得し、コンテキストに注入する際は出所を明示した別セクションに区分することで、モデルの混乱を減らせます。

CompactionとSummarizationの戦略

長いセッションでコンテキストが上限に近づくと、何かを捨てなければなりません。Claude Codeのauto-compactが代表的な実装ですが、一般化すると戦略は三つです。

戦略1 — スライディングウィンドウ + 要約

古いターンを丸ごと捨てる代わりに要約で置き換えます。要約時に必ず保存すべき項目を明示することが品質を左右します。

COMPACT_PROMPT = """次の会話履歴を要約せよ。必ず保存すべきもの:
1. ユーザーの元の目標と制約条件
2. 下された決定とその理由
3. 試みて失敗したアプローチ (再試行防止用)
4. 現在進行中の作業の正確な状態
5. ファイルパス、関数名、設定値などの具体的識別子
雑談と中間の推論過程は捨ててよい。"""

def compact(history: list[dict], keep_recent: int = 10) -> list[dict]:
    old, recent = history[:-keep_recent], history[-keep_recent:]
    summary = summarize_with_llm(COMPACT_PROMPT, old)
    return [{"role": "user", "content": f"[これまでの会話の要約]\n{summary}"}] + recent

戦略2 — 構造化ノート (note-taking)

要約の代わりに、エージェント自身が作業中に構造化されたノートファイルを維持する方式です。長時間自律作業が可能なモデル世代で特に効果的です。コンテキストがリセットされても、ノートファイルを読み直せば状態が復元されます。

戦略3 — サブエージェント分離

容量の大きい探索作業(コードベース検索、ドキュメント調査)をサブエージェントに委任し、本体エージェントは圧縮された結果だけを受け取る方式です。コンテキスト汚染を根本から遮断する効果があります。

戦略長所短所適するケース
要約置換実装が簡単詳細情報の損失一般的な対話エージェント
構造化ノート損失最小、監査可能ノート管理のオーバーヘッド長時間コーディングエージェント
サブエージェント汚染遮断遅延とコストの増加大規模探索作業

メモリスキーマの例

メモリを自由テキストで積み上げると、更新と衝突処理が不可能になります。スキーマを定義すべきです。以下は実務で出発点として使えるJSONスキーマの例です。

{
  "memory_id": "mem_01HXYZ",
  "subject": "user:fjvbn2003",
  "category": "preference",
  "fact": "コードレビューのコメントは韓国語で、コードのコメントは英語で書いてほしい",
  "confidence": 0.92,
  "source": {
    "type": "explicit_statement",
    "session_id": "sess_20260610_a",
    "turn": 14
  },
  "created_at": "2026-06-10T09:32:00Z",
  "updated_at": "2026-06-10T09:32:00Z",
  "expires_at": null,
  "supersedes": null,
  "embedding_ref": "vec_8f3a",
  "access_count": 7,
  "last_accessed_at": "2026-06-12T01:10:00Z"
}

設計ポイントを挙げると次のとおりです。

  • sourceフィールド: 明示的発言か推論かを区別します。メモリ汚染事故の調査時に必須です。
  • supersedesフィールド: 更新時に旧メモリのidを指して履歴を保存します。削除はsoft deleteで。
  • access_countとlast_accessed_at: 回収ランキングと忘却(decay)ポリシーの入力になります。
  • expires_at: 「来月引っ越し予定」のような期限付き事実に使います。

セッションをまたぐ状態維持 — 実装パターン

最後のピースは、これらすべてを束ねてセッション境界を越えるエージェントを作ることです。核心は、セッション開始時にメモリからコンテキストを組み立て、セッション終了時に新しい記憶を書き込む二つのフックです。

class MemoryAwareAgent:
    def __init__(self, store, user_id: str):
        self.store = store
        self.user_id = user_id

    def start_session(self, first_message: str) -> str:
        profile = self.store.get_semantic(self.user_id, limit=20)
        episodes = self.store.get_recent_episodes(self.user_id, limit=3)
        relevant = self.store.search(self.user_id, first_message, k=5)

        memory_block = render_memory_block(profile, episodes, relevant)
        return (
            "以下はこのユーザーについて記憶している内容である。\n"
            "古い場合や現在の会話と矛盾する場合は会話内容を優先せよ。\n\n"
            + memory_block
        )

    def end_session(self, history: list[dict]) -> None:
        episode = summarize_episode(history)
        self.store.add_episode(self.user_id, episode)

        facts = extract_facts(render_history(history))
        existing = self.store.get_semantic(self.user_id, limit=100)
        for op in reconcile(facts, existing):
            self.store.apply(self.user_id, op)

ここで見落としやすいディテールを二つ強調したいと思います。

第一に、メモリブロック注入時に「矛盾する場合は現在の会話を優先せよ」という指示を一緒に入れる必要があります。この一行がないと、モデルが古いメモリを現在のユーザー発言より信頼する事故が発生します。

第二に、end_sessionフックは非同期で処理すべきです。事実抽出と判定にLLM呼び出しが二回入るため、同期処理するとユーザーがセッション終了時に数秒待たされることになります。

メモリシステムの評価手法

「付けてみたら良くなった気がする」は評価ではありません。メモリシステムは次の四軸で測定します。

1. 抽出品質

手動ラベリングした会話セットで抽出器の適合率と再現率を測定します。「記憶すべきだったのに見逃した事実」(再現率)と「記憶すべきでなかったのに保存したノイズ」(適合率)の両方を見る必要があります。

2. 回収品質

クエリごとに関連メモリがtop-kに入るかを測定します。一般的な検索評価と同じ方法論(recall at k、MRR)を使いつつ、時間減衰と更新履歴が反映されるかを別のケースとして検証します。

実務では類似度単独ではなく、最近性と使用頻度を合算したスコアでランキングするのが一般的です。重み自体が評価対象になります。

import math

def memory_score(m, query_sim: float, now) -> float:
    days = (now - m.last_accessed_at).days
    recency = math.exp(-days / 30)       # 30日半減を仮定
    frequency = math.log(1 + m.access_count)
    return 0.6 * query_sim + 0.25 * recency + 0.15 * frequency

3. エンドツーエンドのタスク成功率

最も重要な指標です。「3セッション前に伝えた好みを、新しいセッションで聞き直さずに反映するか」のようなシナリオをスクリプト化し、通過率を測定します。LongMemEvalのような公開ベンチマークは、時間推論、マルチセッション推論、更新された事実の優先適用などを体系的に扱っているので、自前の評価セット設計の参考になります。

4. コストとレイテンシ

メモリパイプライン自体のLLM呼び出しコスト、そしてメモリ注入で増えた入力トークンが、節約されたトークン(再説明不要)より大きいか小さいかを測定します。メモリがコスト削減装置なのかコスト追加装置なのかは、測定する前にはわかりません。

評価シナリオの例 (multi-session probe)

セッション1: ユーザーが「うちのチームはpnpmしか使わない」と言及
セッション2: (無関係な会話)
セッション3: 「依存関係を追加して」と依頼
  合格: pnpm add を使用
  不合格: npm install を使用、またはどちらを使うか聞き返す

落とし穴と批判的視点

メモリ汚染 (memory poisoning)

最も危険な失敗モードです。誤った事実が一度保存されると、以後すべてのセッションに注入されてエラーを再生産します。さらに深刻なのは悪意ある汚染です。ユーザーではない第三者のコンテンツ(ウェブページ、メール、文書)をエージェントが読んでいる際に、その中の指示文を「記憶」してしまうと、持続性を持つプロンプトインジェクションになります。防御策は次のとおりです。

  • 信頼境界の区分: ユーザーの直接発言と外部コンテンツ由来の情報を異なる信頼等級で保存
  • 書き込み検証: メモリ書き込み前にインジェクションパターンを検査
  • 定期監査: メモリストアを周期的にLLM監査(矛盾、異常な指示文の検出)
  • ユーザー可視性: 保存されたメモリをユーザーが閲覧・削除できるUI

プライバシーと規制

メモリは本質的にユーザープロファイリングです。GDPRの消去権(right to erasure)に対応するには、メモリストアの設計段階からユーザー単位の完全削除が可能でなければならず、埋め込みとバックアップも削除範囲に含める必要があります。機微カテゴリ(健康、信条、性的指向)は抽出段階でデフォルト遮断し、明示的なオプトインがあるときのみ保存するのが安全です。

過剰記憶のパラドックス

メモリを多く注入するほど良いという仮定は間違いです。関連性の低いメモリ注入は先に見たcontext rotをそのまま再現し、ユーザーの立場でも「昔言ったことを脈絡なく持ち出される」不気味な体験になります。回収段階の関連性しきい値を保守的に設定し、注入量に上限を設けるのが実務の推奨値です。

「コンテキストを大きくすればいいのでは」という反論

100万トークンコンテキストの時代にメモリシステムが必要なのかという反論があります。一理ありますが、三つの理由でメモリは依然として必要です。第一に、マルチセッションの累積履歴は100万トークンもすぐに超えます。第二に、コストとレイテンシはコンテキスト長に比例します。第三に、lost in the middleはウィンドウが大きくなっても消えませんでした。長いコンテキストとメモリは代替材ではなく補完材です。

実務導入ガイド

最初から3階層メモリをすべて作る必要はありません。段階的ロードマップをお勧めします。

  1. ステージ1 — compactionから: セッション内の要約置換だけ実装しても、長いセッションの品質が目に見えて改善します。
  2. ステージ2 — エピソード記憶: セッション終了時に要約を保存し、開始時に直近3件を注入。実装難度に対して効果が最も大きい段階です。
  3. ステージ3 — 意味記憶: 事実抽出と更新のパイプライン。Supermemoryやmem0のような既存オープンソースをまず検討し、要件が特殊な場合のみ自前構築します。
  4. ステージ4 — 評価とガバナンス: multi-session probe評価セット、メモリ閲覧/削除UI、監査パイプライン。

各段階で「メモリがない場合と比べてタスク成功率がどれだけ上がったか」を測定し、次の段階への進行可否を決めれば、過剰エンジニアリングを避けられます。

おわりに

プロンプトエンジニアリングが「上手に話しかける方法」だったとすれば、コンテキストエンジニアリングは「記憶と注意を設計する方法」です。そしてこれは一回限りのトリックではなく、スキーマ設計、パイプライン、評価、ガバナンスを備えたソフトウェアエンジニアリングの領域です。Supermemoryのようなオープンソースが話題になる理由も、この問題がすべてのエージェントビルダー共通の宿題になったからでしょう。

エージェントの知能はモデルの重みから生まれますが、エージェントの有用さの大部分は記憶設計から生まれます。次のエージェントを作るとき、モデル選択よりも先にメモリスキーマを描いてみることをお勧めします。

参考資料