はじめに
本記事は「Context Engineering 完全入門」シリーズの第 6 回です。今回は メモリーと自己更新 を取り上げます。
第2〜5回でランタイムの制御パターンを学んだところで、本記事からはエージェント自身の状態管理に移ります。セッションをまたいで学習内容や作業状態を保持する仕組みについて、VS Code 1.109 の実装を参考にしながら Microsoft Agent Framework (MAF) での実装方法をご紹介します。
前回:第 5 回 サブエージェント分離
次回:第 7 回 TODO ツール🎉 エージェントの自己状態管理開始:エージェントが セッションを跨いで自分の状態を持つ 仕組みを扱います。
🗺️ 単独でも読めます:VS Code 1.109 の メモリー機能を MAF で再現したい方は、本記事単独で実装可能です。
📝 本記事は、生成 AI で作成した草案をベースに、筆者が加筆・修正し、技術的な正確性を確認した上で公開しています。
📝 本記事の要点
- 問題: セッション終了でエージェントの学習が消える。AGENTS.md を直接書き換えさせるとチーム全員に悪影響
- 解決: 3スコープ Memory(User / Repository / Session)+ 提案ファイル方式による安全な自己更新
- 実装の要点:
ScopedMemoryProviderで 3 スコープを context に注入、AGENTS.md 更新は.agents-md-proposals/に提案ファイルを作成しユーザーレビューを実施業務系開発者は §2 の Offload Context の業務系翻訳 と §4-4 AuditedOffloadStore を参照ください。
🎯 本記事の対象者
- VS Code 1.109 の メモリー機能の中身を知りたい
- AGENTS.md を「人が書く指示書」だと思っている
- プロジェクト進捗をエージェント自身に管理させたい
- 業務系で「ファイルシステムを context として使う」をどう実装すべきか知りたい
- マルチテナント SaaS で メモリーを顧客ごとに分離したい
1. ⭐ 原則
メモリー機能設計の方針を整理すると以下の通りです。#1・#2 は本節、#3 は §2 で詳しく解説します。
| # | 原則 | 概要 | 参照項目 |
|---|---|---|---|
| 1 | スコープを分離する | User / Repository / Session の 3 スコープで、共有性と永続性を使い分ける | §1 |
| 2 | エージェントの自己更新は「提案ファイル方式」で統制する | 直接書き込みは Session のみ、AGENTS.md 等は必ずユーザーレビューを経る | §1 |
| 3 | 業務系では監査・PII・テナント分離を最初から組み込む | Manus 流の「ファイル自由作成」は業務系では破綻するため、統制付き Offload Store で置き換える | §2 |
空間的分離との違い:自己状態管理へ
まず、第 5 回のサブエージェント分離との違いを整理します。第 5 回では、独立性の高い作業を 別のエージェント に任せることで、メインエージェントのコンテキストを汚さないアプローチをご紹介しました。これは 同一セッション内での空間的分離 でした。
一方、本記事で扱う メモリー は、セッションをまたいでエージェント自身が状態を持つ 仕組みです。つまり、第 2〜5 回のランタイム制御(セッション内で完結する仕組み)から、エージェントの自己状態管理(セッションを跨いで状態を持つ仕組み)へと関心事が移ります。
こちらも要約・サブエージェントとの関係と同様に補完関係にあり、組み合わせることで長時間・複数セッションにわたるタスクでも、エージェントが学習と作業を継続できる設計が可能になります。
よくある誤解: AGENTS.md は人だけが書くもの
VS Code の instructions.md(または .github/copilot-instructions.md)と同様に「AGENTS.md は人が書くプロジェクト指示書」と捉えられがちですが、これはあくまで 初期状態 の話です。実際には以下のようなサイクルが想定されています。
人が書いた初期版をエージェントが利用し、新パターンなどがあれば AGENTS.md の更新版を生成 → レビューしてマージ、というサイクルを繰り返すことで、エージェントを継続して学習することを可能にします。本番運用では、これを如何に実現するかが今後の差別化ポイントになると考えられます。
メモリーと TODO(第 7 回)との異なるポイント
メモリーには複数の種類があり、AGENTS.md・PROGRESS.md は ファイルで管理される恒久的/中期的な情報、Session Memory は破棄前提の一時情報です。一方、TODO はセッション内でのタスク進捗管理に特化しており、更新頻度・保存場所・目的が異なります。
以下の表で違いを整理します。
| 観点 | AGENTS.md (メモリー・恒久) |
PROGRESS.md (メモリー・中期) |
Session Memory (メモリー・一時) |
TODO (第 7 回) |
|---|---|---|---|---|
| 何を保存 | プロジェクト規約・ルール | フェーズ単位の進捗 | 進行中のプラン・メモ | タスク状態・進捗 |
| 更新頻度 | 低頻度 (規約変更時のみ) |
中頻度 (フェーズ完了時) |
中〜高頻度 (作業中随時) |
高頻度 (各ステップ完了時) |
| 保存場所 | Git 管理 | Git 管理 | ローカル(セッション破棄) | セッション内(または永続化) |
| 構造 | 自由形式テキスト | Markdown チェックリスト | 自由形式テキスト | 構造化(status, priority, id 等) |
| 目的 | 「規約を守る」 | 「フェーズ進捗を追う」 | 「作業中の思考を残す」 | 「ドリフトしない」 |
| 書き込み権限 | 提案 + ユーザーレビュー | 提案 + ユーザーレビュー | 完全自動 | 完全自動 |
📌 業界共通語彙との対応:Microsoft ai-agents-for-beginners では、Session Memory 相当の仕組みを 「Agent Scratchpad」 と呼びます。本シリーズの Session Memory + 第 7 回の TODO ツールが、この概念に対応します。
つまり、「メモリー」と一括りに言っても、恒久・中期・一時の 3 種類があり、それぞれ TODO とは目的・頻度・権限が異なります。特に PROGRESS.md と TODO は「進捗管理」という点で近いですが、 PROGRESS.md は ファイルで管理される中期的な進捗の可視化、TODO は セッション内でのドリフト防止 という異なる目的で使い分けます。
3 スコープの 3 軸評価
VS Code 1.109 の メモリー機能が採用している User / Repository / Session の 3 スコープを、共有性 × 永続性 × 分離性の 3 軸 で評価すると、それぞれの用途が明確になります。
- 共有性:どこまで範囲を跨いで参照できるか(全 workspace / workspace 内 / セッション内)
- 永続性:セッション終了後も保持されるか
- 分離性:他の作業から隔離されているか(タスク固有情報が混ざらないか)
| スコープ | 共有性 | 永続性 | 分離性 | 用途 |
|---|---|---|---|---|
| User Memory | 全 workspace 共有 ◎ | ✅ 永続 | × | 個人の好み、よく使うコマンド |
| Repository Memory | workspace 内 ○ | ✅ 永続 | ○ | コードベース規約、ビルドコマンド |
| Session Memory | セッション内 × | ✗ 破棄 | ◎ | タスク固有の進行中プラン |
3 スコープはいずれかが優れているのではなく、用途に応じて使い分ける ものです。永続的な情報を Session Memory に入れると消えてしまい、タスク固有の一時情報を User Memory に入れると他 workspace を汚染します。詳細は §2 で解説します。
2. 📐 パターン
本節では、メモリー機能を設計するための実装ベースのパターンを扱います。まず §2-1 で VS Code 1.109 の メモリー機能(本記事の実装参考元)を確認し、§2-2 で AGENTS.md の自己更新パターン、§2-3 で業務系システムへの適用時の制約についての整理を行います。
2-1. VS Code 1.109 の Memory システム(実装参考元)
本記事の Microsoft Agent Framework (MAF) 実装(§4)は、VS Code Copilot Chat 1.109(2026/02 正式 preview)のメモリー機能を参考にしています。ここでは、その仕様を説明します。
システム全体像
3 スコープの実装仕様
§1 の 3 軸評価(共有性・永続性・分離性)に加えて、VS Code 1.109 の具体的な実装仕様は以下の通りです。
| スコープ | パス | セッション間 | workspace 間 | 用途 |
|---|---|---|---|---|
| User | ~/memories/ |
✅ | ✅ | 個人の好み、よく使うコマンド |
| Repository | ~/memories/repo/ |
✅ | ✗ | コードベース規約、ビルドコマンド |
| Session | ~/memories/session/ |
✗ | ✗ | タスク固有の進行中プラン |
設定キー:github.copilot.chat.tools.memory.enabled(デフォルト有効)
User Memory:先頭 200 行が自動ロード
User Memory の読み込みは「先頭 200 行」に制限されています。肥大化対策とロードコスト抑制を両立するための設計で、ユーザーは優先度の高い情報をファイルの上部に書く運用が推奨されます。
2-2. AGENTS.md の自己更新パターン(4 種)
VS Code 1.109 のメモリー機能を踏まえて、AGENTS.md に対してエージェントが自己更新するための代表的な 4 パターンを紹介します。
パターン 1:/init コマンドによる初期生成
VS Code Copilot Chat や Codex CLI には /init スラッシュコマンド があり、リポジトリを解析して ** AGENTS.mdの初期版を自動生成** します。既存コードをスキャンして、プロジェクト構造・よく使うコマンド・テスト方法などを抽出するため、人が一から書く必要はありません。
パターン 2: PROGRESS.md による進捗管理
PROGRESS.md は、プロジェクトの実装フェーズごとの進捗を Markdown ファイルで永続化 するものです。AGENTS.md(プロジェクト規約)とは独立しており、フェーズ分割やマイルストーンなどの 初期構造はユーザーが設計、進捗の更新はエージェントが担う という運用が一般的です。
PROGRESS.md の例は下記の通りで、GitHub標準のタスクリスト記法を用いてフェーズごとのタスクを列挙します。
# PROGRESS.md
## Phase 1: Setup
- [x] Initialize project structure
- [x] Install dependencies
- [-] Configure CI/CD ← in-progress
- [ ] Write README
| 記号 | 状態 |
|---|---|
[ ] |
Not started |
[-] |
In progress |
[x] |
Completed |
PROGRESS.md はファイルで管理される 中期的な進捗の可視化 が目的です。エージェントの詳細なタスク管理(セッション内で完結する高頻度更新)には向かず、そのユースケースには第 7 回の TODO ツールを使い分けます。詳細は §1「メモリーと TODO との異なるポイント」をご参照ください。
パターン 3:GitHub Actionsなどによる週次自動更新
「先週から AGENTS.md を見直すべき変更がありましたか?」を週次で問う運用。GitHub Actions、GitLab CI/CD、Jenkins などの CI/CD ツールで Copilot Coding Agent を Issue 経由で起動し、PR → ユーザーレビュー → マージのサイクルで安全に更新します。
パターン 4:エージェント自身による境界線の更新
## Boundaries
### Always
- Read existing patterns before introducing new ones
- 🤖 Added 2026-03-15: Run `pnpm typecheck` after editing TS files
🤖 マークは「エージェント追加」を示す慣例。後でユーザーがレビューしやすくなります。
2-3. Offload Context の業務系システムでの制約
原則 3 で挙げた通り、業界研究の「File System as Context」(Manus 原則 3)は 業務系ではそのまま適用できません。
| Manus の前提 | 業務系の制約 |
|---|---|
| クラウド上にエージェント専用のサンドボックス(Linux実行環境)が提供される | 顧客テナント内の既存インフラに隔離環境を用意するのが困難 |
| ファイル作成・削除自由 | 監査ログ保持義務、PII 制約 |
| エージェント所有のデータ | データは顧客所有 |
| 使い捨て前提 | 永続データ、GDPR・個人情報保護法 |
業務系での 3 つの代替策
下記に代替策について整理します。これらの代替策は、「動的性(自由書き込み)」を維持しつつ「統制性(監査・PII・テナント分離)」を担保する という第 5 回で整理した業務系システムへの適用の考え方に沿っています。実装例は §4-4 で詳しく解説します。
| 代替策 | 内容 | 適用場面 |
|---|---|---|
| Repository Memory + ファイル制限 | 拡張子・サイズ・TTL を厳格に設定 | コードベース固有のメモ |
| Resource Store + Reference | データ本体は外部、agent には URI のみ | 大量データの参照 |
| Audit-log 駆動の Append-only | 何が書かれたかを全て監査ログへ | 規制業界・PII データ |
3. 💡 実装の方針と設計判断
サンプルコードの実装方針は以下の通りです。
設計判断 1:3 スコープを AIContextProvider で実装する
メモリーは「LLM 呼び出し前に context に注入する情報」のため、AIContextProvider.InvokingAsync を使ってファイルの中身を Messages に変換して返すのが自然な責務分離です。ツールとして公開せず Provider にすることで、LLM が「メモリの存在を意識せず使える」ようになります。
設計判断 2:User Memory に「先頭 200 行」制限を入れる
VS Code 公式の仕様と一致させ、物理的な上限を「先頭 200 行」で切ります。ユーザーが優先度高い情報を上に書く運用を促すことができます。
設計判断 3: AGENTS.md は「直接編集」せず「提案ファイル」を作る(統制性の担保)
エージェントに AGENTS.md を直接書き換えさせると、誤った内容がチーム全員に影響します。.agents-md-proposals/YYYYMMDD_HHMMSS.md に提案ファイルを作り、ユーザーによるレビュー・マージを必須にすることで、動的性(エージェントの学習内容を反映)と統制性(誤った内容の混入防止)を両立します。
設計判断 4:業務系 Offload Store に「PII マスキング層」を組み込む
DI で外部からマスキング関数を注入できる設計にすることで、業務によって異なるマスキング戦略(電話番号、メールアドレス、住所など)に柔軟に対応できます。また、テナント ID を必須引数化することで、テナント分離を実装レベルで強制 できます。
4. 💻 実装:C# / Microsoft Agent Framework (MAF)
4-1. ScopedMemoryProvider — 3 スコープ実装
実装の要点:
- User Memory は先頭 200 行のみロード(VS Code 1.109 仕様準拠)
- Repository / Session は全文ロード
- system role で末尾に注入し、
AIContextProviderの標準パターンに沿う -
tenantIdを必須にし、マルチテナント SaaS でもスコープが混在しないようにする - パストラバーサル対策として
Path.GetFileName()で sanitize
💻 IMemoryStorage — Memory永続化バックエンドのサンプル実装(C#・約10行、クリックで展開)
永続化の抽象化(インターフェース定義):
// 🔑 サンプルではファイルシステム実装、本番では Cosmos DB / Blob Storage 等に置換
public interface IMemoryStorage
{
Task<string?> ReadAsync(string tenantId, string scope, string key, CancellationToken ct = default);
Task WriteAsync(string tenantId, string scope, string key, string content, CancellationToken ct = default);
Task AppendAsync(string tenantId, string scope, string key, string content, CancellationToken ct = default);
Task<IReadOnlyList<string>> ListKeysAsync(string tenantId, string scope, CancellationToken ct = default);
}
💻 ScopedMemoryProvider — 3スコープMemory Providerのサンプル実装(C#・約90行、クリックで展開)
using System.ComponentModel;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace ContextEngineering.Memory;
/// <summary>
/// 3 スコープ(User / Repository / Session)の Memory を context に注入する Provider。
/// VS Code Copilot Chat 1.109 の Memory tool 仕様を MAF で再現。
/// </summary>
public class ScopedMemoryProvider : AIContextProvider
{
private const string MemoryKey = "memory.md";
private const string UserScope = "user";
private const string RepoScope = "repo";
private readonly IMemoryStorage _storage;
private readonly string _tenantId;
private readonly string _sessionScope;
private readonly int _userHeadLines;
public ScopedMemoryProvider(
IMemoryStorage storage,
string tenantId,
string? sessionId = null,
int userMemoryHeadLines = 200)
: base(null, null)
{
_storage = storage;
_tenantId = tenantId;
// 🔑 Session Memory: session_id 単位で隔離
_sessionScope = $"session:{sessionId ?? "default"}";
// 🔑 User Memory の物理的上限(肥大化対策)
_userHeadLines = userMemoryHeadLines;
}
// 🔑 ProvideAIContextAsync は「LLM 呼び出し直前」のフック(protected override)
protected override async ValueTask<AIContext> ProvideAIContextAsync(
InvokingContext context, CancellationToken cancellationToken = default)
{
var sections = new List<string>();
// 🔑 User Memory: 先頭 200 行のみ(VS Code 1.109 流)
var userContent = await _storage.ReadAsync(_tenantId, UserScope, MemoryKey, cancellationToken);
if (!string.IsNullOrEmpty(userContent))
{
var truncated = string.Join("\n", userContent.Split('\n').Take(_userHeadLines));
sections.Add($"## User Memory (personal preferences)\n{truncated}");
}
// 🔑 Repository Memory: 全文
var repoContent = await _storage.ReadAsync(_tenantId, RepoScope, MemoryKey, cancellationToken);
if (!string.IsNullOrEmpty(repoContent))
sections.Add($"## Repository Memory (codebase facts)\n{repoContent}");
// 🔑 Session Memory: 全ファイル走査(plan.md など)
var sessionKeys = await _storage.ListKeysAsync(_tenantId, _sessionScope, cancellationToken);
foreach (var key in sessionKeys)
{
var content = await _storage.ReadAsync(_tenantId, _sessionScope, key, cancellationToken);
if (!string.IsNullOrEmpty(content))
sections.Add($"## Session Memory: {key}\n{content}");
}
// 🔑 何もなければ空の context を返す(無駄な system message を避ける)
if (sections.Count == 0)
return new AIContext();
var combined = string.Join("\n\n", sections);
// 🔑 system role で注入
// → LLM は「これは指示文ではなく文脈情報」と区別できる
return new AIContext
{
Messages = [new ChatMessage(ChatRole.System, $"# Memory\n{combined}")],
};
}
public Task AppendUserMemoryAsync(string memo, CancellationToken cancellationToken = default)
// 🔑 append-only で記録(過去の memo を上書きしない)
=> _storage.AppendAsync(_tenantId, UserScope, MemoryKey, $"\n- [{DateTime.Now:yyyy-MM-dd}] {memo}\n", cancellationToken);
public Task WriteSessionNoteAsync(string filename, string content, CancellationToken cancellationToken = default)
{
// 🔑 ファイル名を sanitize(パストラバーサル対策)
var safeName = Path.GetFileName(filename);
if (!safeName.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
safeName += ".md";
return _storage.WriteAsync(_tenantId, _sessionScope, safeName, content, cancellationToken);
}
}
重要メソッドの抜粋
実装の核心は、3 スコープを順に読み込んで sections に追加し、system role の単一メッセージとして注入する流れです:
💻 User Memoryの行数制限とSystem Roleへの注入ロジック(C#・約15行、クリックで展開)
// User Memory のみ 200 行制限を適用(VS Code 1.109 仕様)
var userContent = await _storage.ReadAsync(_tenantId, UserScope, MemoryKey, cancellationToken);
if (!string.IsNullOrEmpty(userContent))
{
var truncated = string.Join("\n", userContent.Split('\n').Take(_userHeadLines));
sections.Add($"## User Memory (personal preferences)\n{truncated}");
}
// system role で注入
return new AIContext
{
Messages = [new ChatMessage(ChatRole.System, $"# Memory\n{combined}")],
};
4-2. メモリ操作ツール
💻 MemoryTools — User・Session Memory操作ツールのサンプル実装(C#・約35行、クリックで展開)
public class MemoryTools
{
private readonly ScopedMemoryProvider _provider;
public MemoryTools(ScopedMemoryProvider provider) => _provider = provider;
[Description(
// 🔑 「全 workspace で共有される個人の好み」を明示
// → LLM が「これは User スコープに保存すべき」と判断
"Save a personal preference remembered across all workspaces."
)]
public async Task<string> RememberUserPreferenceAsync(
[Description("Preference to remember (e.g., 'I prefer tabs over spaces')")] string memo)
{
await _provider.AppendUserMemoryAsync(memo);
return $"Saved to user memory: {memo}";
}
[Description(
"Write a temporary working note (plan, todo) that lasts only this conversation. " +
"Overwrites the file if it exists."
)]
public async Task<string> WriteSessionNoteAsync(
[Description("File name (e.g., 'plan')")] string filename,
[Description("Content")] string content)
{
await _provider.WriteSessionNoteAsync(filename, content);
return $"Wrote session note: {filename}";
}
}
4-3. AGENTS.md 自己更新(提案ファイル方式)
以下の実装は、サンプル用の PoC 実装です。本番運用では、IProposalStorage を実装した永続化バックエンドへの置き換えが推奨されます。
💻 IProposalStorage — 提案ファイル永続化バックエンドのサンプル実装(C#・約7行、クリックで展開)
永続化の抽象化(インターフェース定義):
// 🔑 サンプルではファイルシステム実装、本番では DB / Blob Storage 等に置換
public interface IProposalStorage
{
Task SaveAsync(string proposalId, string content, CancellationToken ct = default);
Task<IReadOnlyList<(string ProposalId, string Content)>> ListAsync(CancellationToken ct = default);
}
💻 AgentsMdProposalTool — AGENTS.md更新提案ツールのサンプル実装(C#・約45行、クリックで展開)
本体実装:
/// <summary>
/// エージェントによる AGENTS.md 更新を「提案ファイル」に閉じ込める。
/// 直接編集させず、レビュー → マージを必須にする。
/// </summary>
public class AgentsMdProposalTool
{
private readonly IProposalStorage _storage;
public AgentsMdProposalTool(IProposalStorage storage) => _storage = storage;
[Description(
"Propose an update to AGENTS.md based on learnings. Creates a markdown proposal that " +
// 🔑 「Does NOT directly modify AGENTS.md」を明示
// → LLM が「これは安全な操作」と認識
"a human can review. Does NOT directly modify AGENTS.md."
)]
public async Task<string> ProposeAgentsMdUpdateAsync(
[Description("Section: Always, Ask First, Never, Code Style, or Commands")] string section,
[Description("Proposed addition")] string proposedAddition,
[Description("Reasoning")] string reasoning)
{
// 🔑 ファイル名(ID)にタイムスタンプを含めて順序を明確化
var proposalId = $"{DateTime.Now:yyyyMMdd_HHmmss}";
// 🔑 提案フォーマット:人が読みやすい構造
var content =
"# AGENTS.md Update Proposal\n\n" +
$"**Date**: {DateTime.Now:O}\n" +
$"**Section**: {section}\n\n" +
"## Proposed Addition\n\n" +
// 🔑 🤖 マークで「エージェント追加」と明示(コミット履歴で追跡可能)
$"```markdown\n🤖 {proposedAddition}\n```\n\n" +
"## Reasoning\n\n" + reasoning + "\n\n" +
"## Approve\n\n" +
$"To approve, add the line above to AGENTS.md under '{section}'. " +
"To reject, discard this proposal.\n";
await _storage.SaveAsync(proposalId, content);
return $"Created proposal: {proposalId}. Human review required.";
}
}
4-4. Audit-log 駆動 Offload Store
実装の要点:
-
tenantIdを必須引数化してテナント分離を強制(空文字は実行時にも拒否) - PII マスカーは DI で注入(業務ごとに戦略を切り替え可能)
- 全操作を
IAuditLogger経由で append-only 記録 - ストレージ領域を
IOffloadStorageでテナント別に分離
💻 IOffloadStorage / IAuditLogger — Offload保存・監査ログ抽象化のサンプル実装(C#・約15行、クリックで展開)
永続化の抽象化(インターフェース定義):
// 🔑 サンプルではファイルシステム実装、本番では Blob Storage 等に置換
public interface IOffloadStorage
{
Task WriteAsync(string tenantId, string resourceId, string content, CancellationToken ct = default);
Task<string?> ReadAsync(string tenantId, string resourceId, CancellationToken ct = default);
}
// 🔑 offload/retrieve 操作を append-only で記録する監査ログの抽象化
public interface IAuditLogger
{
Task LogAsync(object auditEntry, CancellationToken ct = default);
Task<IReadOnlyList<string>> ReadAllAsync(CancellationToken ct = default);
}
💻 AuditedOffloadStore — 監査・PIIマスキング・テナント分離のサンプル実装(C#・約85行、クリックで展開)
/// <summary>
/// 業務系向け:監査ログ + PII マスキング + テナント分離を組み込んだ Offload Store。
/// </summary>
public class AuditedOffloadStore
{
private readonly IOffloadStorage _storage;
private readonly IAuditLogger _auditLogger;
// 🔑 PII マスカーは DI で注入できる
// → 業務によってマスキング戦略を切り替え可能
private readonly Func<string, string> _piiMasker;
public AuditedOffloadStore(
IOffloadStorage storage,
IAuditLogger auditLogger,
Func<string, string>? piiMasker = null)
{
_storage = storage;
_auditLogger = auditLogger;
// 🔑 マスカー未指定なら identity 関数(マスキングしない)
_piiMasker = piiMasker ?? (s => s);
}
public async Task<string> OffloadAsync(
string content,
string tenantId, // 🔑 tenantId は必須引数:テナント分離を強制
string subject,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
// 🔑 空文字のテナント ID を実行時にも拒否し、テナント分離を徹底する
if (string.IsNullOrWhiteSpace(tenantId))
throw new ArgumentException("tenantId is required for tenant isolation.", nameof(tenantId));
var resourceId = Guid.NewGuid().ToString("N")[..12];
// 🔑 保存前に PII マスキング
// → PII が漏れない保証を「保存時点」で取る
var maskedContent = _piiMasker(content);
// 🔑 テナント分離:ストレージ領域をテナント ID で分ける
// → 物理的に分離されるので、別テナント間の漏洩を防ぐ
await _storage.WriteAsync(tenantId, resourceId, maskedContent, cancellationToken);
// 🔑 監査ログに必ず記録
// → 業務系では「いつ、誰が、何を offload したか」を遡及的に確認できる必要がある
await _auditLogger.LogAsync(new
{
timestamp = DateTime.UtcNow,
action = "offload",
resource_id = resourceId,
tenant_id = tenantId,
subject,
size = maskedContent.Length,
ttl = ttl?.TotalSeconds,
}, cancellationToken);
return resourceId;
}
public async Task<string?> RetrieveAsync(
string resourceId, string tenantId, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(tenantId))
throw new ArgumentException("tenantId is required for tenant isolation.", nameof(tenantId));
// 🔑 テナント分離の検証
// → 別テナントの ID を渡しても、別ストレージ領域なので見つからない
var content = await _storage.ReadAsync(tenantId, resourceId, cancellationToken);
if (content is null) return null;
// 🔑 取得操作も監査ログに記録
await _auditLogger.LogAsync(new
{
timestamp = DateTime.UtcNow,
action = "retrieve",
resource_id = resourceId,
tenant_id = tenantId,
}, cancellationToken);
return content;
}
}
重要メソッドの抜粋
業務系での Offload Store の核心は、テナント分離を実装レベルで強制 することです。tenantId を必須引数化し(空文字は例外)、ストレージ領域をテナント別に分離することで、別テナント間の情報漏洩を実装レベルで防止します:
💻 テナント分離・PIIマスキング・監査記録の中核ロジック(C#・約20行、クリックで展開)
// tenantId を必須引数化してテナント分離を強制(実行時にも空文字を拒否)
public async Task<string> OffloadAsync(string content, string tenantId, ...)
{
if (string.IsNullOrWhiteSpace(tenantId))
throw new ArgumentException("tenantId is required for tenant isolation.", nameof(tenantId));
// 保存前に PII マスキング
var maskedContent = _piiMasker(content);
// ストレージ領域をテナント別に分離
await _storage.WriteAsync(tenantId, resourceId, maskedContent, cancellationToken);
// 監査ログに必ず記録(append-only)
await _auditLogger.LogAsync(auditEntry, cancellationToken);
}
4-5. エージェント組み立て
💻 Example.BuildAgent — 3スコープMemory対応エージェントのサンプル実装(C#・約40行、クリックで展開)
public static class Example
{
public static AIAgent BuildAgent(
IChatClient chatClient, IMemoryStorage memoryStorage, IProposalStorage proposalStorage, string tenantId)
{
// 🔑 3 スコープの Memory Provider(テナント単位)
var memoryProvider = new ScopedMemoryProvider(memoryStorage, tenantId);
var memoryTools = new MemoryTools(memoryProvider);
var agentsMdTool = new AgentsMdProposalTool(proposalStorage);
return chatClient.AsAIAgent(new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Instructions =
"Agent with persistent memory. " +
"Use RememberUserPreference for personal preferences (cross-workspace). " +
"Use WriteSessionNote for current task notes (deleted after session). " +
"Use ProposeAgentsMdUpdate to propose AGENTS.md changes (human will review).",
// 🔑 Tools は ChatOptions に登録する
Tools = new List<AITool>
{
AIFunctionFactory.Create(memoryTools.RememberUserPreferenceAsync),
AIFunctionFactory.Create(memoryTools.WriteSessionNoteAsync),
AIFunctionFactory.Create(agentsMdTool.ProposeAgentsMdUpdateAsync),
},
},
// 🔑 ContextProvider として Memory を組み込む
// → 毎 LLM 呼び出しで Memory が自動的に context に注入される
AIContextProviders = [memoryProvider],
});
}
}
5. 📊 ケーススタディ
主要ツールのメモリー機能 実装比較
主要ツールのメモリー機能の実装を、§1 で定義した 共有性・永続性・分離性の 3 軸 の観点で比較すると、ツールごとの明確な差が現れます。
| ツール | メモリー機能実装 | スコープ数 | 業務系適性 | 公式リリース |
|---|---|---|---|---|
| VS Code Copilot Chat | Memory tool(ローカル)+ Copilot Memory(クラウド) | 3 スコープ | ○ | 2026/02 preview |
| Codex CLI | なし | 0 | × | - |
| Claude Code |
--continue で履歴復元 |
1(セッション単位) | △ | 2025/12 |
| MAF Memory Provider(本記事) |
ScopedMemoryProvider + AuditedOffloadStore
|
3 スコープ + テナント分離 | ◎ | — |
教訓:
- VS Code Copilot Chat は 3 スコープ(User / Repository / Session)を明確に分離しており、個人利用のコーディング支援では業界最先端の設計です。ただし PII マスキングやテナント分離の仕組みは提供されていないため、業務系 SaaS に直接転用するには追加実装が必要です
- Codex CLI は メモリー機能を持たないため、セッション間の学習継続はできません。長時間タスクや複数プロジェクト横断の作業には不向きです
-
Claude Code は
--continueによる履歴復元があるものの、実質的にはセッション単位の記憶のみで、User / Repository スコープの分離はありません - MAF Memory Provider(本記事) は VS Code の 3 スコープ設計を踏襲しつつ、業務系必須要件(PII マスキング・テナント分離・監査ログ)を最初から組み込んでいる 点が特徴です
つまり、「個人利用のメモリーと業務系のメモリーは別物」 であり、業務系では VS Code 流の 3 スコープ設計をベースに、Offload Store 側で監査・PII・テナント分離を担保する 二層構造 が必要です。
自己更新ポリシーの 3 段階
エージェントによる自己更新は、書き込み対象の重要度 に応じて 3 段階の権限を設計します。この 3 段階は、第 5 回で扱った「動的性と統制性の両立」と同じ設計思想に基づいています。
| レベル | エージェントの権限 | 適用場面 | 動的性 | 統制性 |
|---|---|---|---|---|
| 完全自動更新 | 直接書き込み | Session Memory(plan.md、todo) | ◎ | × |
| 提案 + ユーザーレビュー | 提案ファイル作成、ユーザーが承認 | AGENTS.md、PROGRESS.md | ○ | ◎ |
| 読み取り専用 | 参照のみ | Repository コア設定、監査ログ | × | ◎ |
教訓:
- Session Memory は動的性最優先(統制性 ×):タスク固有の一時情報のため、失われても業務全体には影響しないよう設計します
- AGENTS.md / PROGRESS.md は動的性と統制性の両立:チーム全員に影響する情報のため、提案ファイル方式でユーザーレビューを経る
- Repository コア設定 / 監査ログ は統制性最優先(動的性 ×):改変されると業務や監査が破綻する
「レベルが上がるほど良い」のではなく、書き込み対象の重要度に応じて適切なレベルを選ぶ ことが重要です。第 5 回のサブエージェント設計と同じく、業務系では 動的性と統制性のバランス が設計の核心となります。
6. ⚠️ アンチパターン
本記事で扱った メモリー機能設計の要点を、実装時のセルフレビュー用チェックリスト(アンチパターン)としてまとめました。
実装時は本表を「やってはいけないことリスト」としてご参照ください。特に #3・#6・#7 は業務系での本番運用、コンプライアンス、マルチテナント SaaS の契約に直結する重要項目です。
| # | アンチパターン | 問題点 |
|---|---|---|
| 1 | Session Memory に重要情報を入れる | セッション終了で消えるため、永続的な情報には使えない |
| 2 | User Memory が肥大化したまま放置 | 先頭 200 行制限を意識せず、重要情報が読まれなくなる |
| 3 | AGENTS.md をエージェントに自動 push 権限で更新 | 誤った内容がチーム全員に影響する |
| 4 | PROGRESS.md と plan.md を混同 | PROGRESS.md は Git 管理(永続)、plan.md は Session Memory(破棄) |
| 5 | Memory tool と AGENTS.md に同じ情報を書く | 矛盾が発生し、どちらを信じればいいか分からなくなる |
| 6 | Manus 流をそのまま業務系で実装 | ファイル自由作成は監査・PII で破綻する |
| 7 | テナント分離なしの共有ストア | 業務 SaaS では契約レベルの違反になりかねない |
おわりに
本記事では、エージェントがセッションを跨いで状態を持つための メモリー機能の設計パターンをご紹介しました。特に 共有性・永続性・分離性の 3 軸 で 3 スコープ(User / Repository / Session)を評価し、VS Code 1.109 の設計を Microsoft Agent Framework (MAF) で再現する実装と、業務系での注意点(PII マスキング、テナント分離、監査ログ)についても解説しました。
第 5 回のサブエージェント設計と同じく、業務系では 動的性と統制性のバランス が メモリー機能設計の核心となります。Session Memory は動的性最優先で、AGENTS.md は提案ファイル方式で両立、Repository コア設定は統制性最優先で、といったように、書き込み対象の重要度に応じて適切な権限レベルを選ぶことが重要です。
次回は自己状態管理の 2 本目として「TODO ツール」を解説します。長いタスクでエージェントが脱線しないための「ドリフト防止」の仕組みについてご紹介します。
参考文献
VS Code 公式ドキュメント
- VS Code Docs「Memory in VS Code agents」
- VS Code Docs「Planning with agents in VS Code」
- VS Code Updates「January 2026 (version 1.109)」
- Visual Studio Blog「Copilot Memories」(2026/1/15)
コミュニティ実装・解説
- Portfolio Notes (flowzenn)「AI エージェント向け AGENTS.md を自動更新する仕組み — monorepo 対応 & Issue 自動生成」
-
theaiautomators/claude-code-agentic-rag-masterclass — GitHub (PROGRESS.md パターン)
- 補足: PROGRESS.md の設計詳細は DeepWiki: Progress Tracking with PROGRESS.md も参照。
業界研究
Microsoft Learn / Microsoft Agent Framework
- Microsoft Learn「Microsoft Agent Framework — Context Providers」
- microsoft/agent-framework — GitHub repository