AIエージェントを長く運用すると、「賢いのに会話をまたぐと前提が毎回リセットされる」という壁に当たります。原因は記憶を実装していないこと。この記事は、永続記憶を最小構成で実装する手順と、運用でハマる落とし穴・チェックリストを、コード中心でまとめます。
設計思想の背景は別記事に分けています。ここは実装の手を動かす側に寄せます。
全体像:3層 × 3段
- 記憶を確度で3層に分ける:
tentative(仮) /confirmed(確定) /working(実働) - 流れは3段:保存 → 昇格/降格 → 想起
これだけ押さえれば、素朴な「全部ためて全部流し込む」実装の破綻(トークン爆発・古い前提の混入・文脈の取り違え)を避けられます。
Step 1. スキーマを決める
1記憶=1事実。scope(用途境界) と tier(確度) を必ず持たせます。
type MemoryTier = "tentative" | "confirmed" | "working";
interface MemoryRecord {
id: string;
scope: string; // プロジェクト/用途。混ぜない単位
type: "episodic" | "semantic" | "procedural";
tier: MemoryTier;
content: string;
evidenceCount: number; // 昇格判定
createdAt: string;
updatedAt: string;
embedding?: number[]; // 想起の関連検索
}
DBはPostgres + pgvector でも、SQLite + 自前コサイン類似度でも構いません。最初はscope/tier/embeddingの3つが引ければ十分です。
Step 2. 保存(capture)
会話の終わりに要点を抽出し、既定 tentative で書き込みます。ここで confirmed にしないのが事故防止の肝。
async function capture(turn: ConversationTurn) {
const facts = await extractSalientFacts(turn); // LLMで要点抽出
for (const f of facts) {
await db.insert({
...f, tier: "tentative", evidenceCount: 1,
embedding: await embed(f.content),
});
}
}
Step 3. 昇格・降格(reconcile)
繰り返し確認された記憶だけ confirmed へ昇格。矛盾したものは降格。降ろす処理を入れないと古い地図で走り続けます。
async function reconcile(memo: MemoryRecord, incoming: Fact) {
if (semanticallyEqual(memo, incoming)) {
memo.evidenceCount++;
if (memo.evidenceCount >= 2) memo.tier = "confirmed"; // 昇格
} else if (contradicts(memo, incoming)) {
memo.tier = "tentative"; // 降格
}
memo.updatedAt = new Date().toISOString();
await db.update(memo);
}
会話のレイテンシに乗せず、低頻度のバッチ(cron等)で回すのがコツ。
Step 4. 想起(recall)
次の会話で必要な分だけ注入。全件入れない。
async function recall(query: string, scope: string, tokenBudget: number) {
const hits = await db.search({
scope, // 境界を越えない
tierPriority: ["working", "confirmed"], // 仮は原則入れない
vector: await embed(query),
limit: 20,
});
return packWithinBudget(hits, tokenBudget); // 予算で打ち切る
}
想起の3原則:scopeを越えない / tierで絞る / トークン予算で打ち切る。
Step 5. MCPサーバーとして公開する(任意だが推奨)
記憶をモデルの外に出し、memory_save / memory_recall を MCP ツールとして公開すると、どのエージェント・どのモデルからも同じ記憶を使えます。
{
"tools": [
{ "name": "memory_save", "description": "事実を保存(既定tier=tentative)" },
{ "name": "memory_recall", "description": "queryとscopeで関連記憶を取得" }
]
}
外に置く利点:可搬性(モデル乗り換えで記憶を失わない)/ 共有(複数エージェント・チーム)/ 所有(見える・直せる・持ち出せる)。
ハマりどころチェックリスト
-
1記憶=1事実。
scopeとtierを必須にしたか -
保存は既定
tentative。いきなりconfirmedにしていないか -
evidenceCountで昇格、矛盾検知で降格を実装したか - recall は scope限定 + tier優先 + トークン予算で打ち切りか
- 想起は関連検索(埋め込み)で必要分だけか
- 昇格・圧縮は低頻度バッチに逃がしたか
- 別用途の記憶が混ざっていないか(scopeの切り方を点検)
まとめ
記憶は「ためる」ではなく「整理して取り出す」。3層で確度を分け、保存→昇格/降格→想起で流し、scopeとトークン予算で絞る。MCPで外に出せば可搬・共有・所有が手に入ります。
モデルは使い捨て、記憶は育つ資産。実装の主戦場は保存ではなく**想起(取り出し方)**です。
記憶レイヤーの設計と実録は、エージェントメモリーズで継続的に公開しています。