ANTIGRAVITY LABEN
記事一覧/Agents & Manager
Agents & Manager/2026-03-18中級

Antigravity エージェントのメモリ設計パターン — セッションを超えてコンテキストを保持する実践ガイド

Antigravity のエージェントがセッションをまたいでコンテキストを保持するためのメモリ設計パターンを解説。ベクトルDB、要約記憶、エピソード記憶を組み合わせた実装例を紹介します。

antigravity435agents90memory2RAG5コンテキスト管理5

エージェントにとって「記憶」とはなにか

AI エージェントを開発するとき、最初の壁になりやすいのが**「セッションを超えた記憶の維持」**です。会話型の LLM はコンテキストウィンドウの範囲内でしか情報を保持できません。ユーザーが翌日また起動したとき、前の会話内容は失われてしまいます。

Antigravity はエージェントアーキテクチャを柔軟に組めるプラットフォームですが、メモリ設計を正しく行わないと、せっかく高度なエージェントを作っても「毎回ゼロから始まる」という体験をユーザーに与えてしまいます。

3つのメモリパターンと、それを Antigravity で実装する具体的な方法を順番にご紹介します。私自身、2014年から個人開発を続ける中で複数サイトの運用補助エージェントに「前回までの判断」を引き継がせる仕組みを育ててきました。記憶の持たせ方を1段階整理するだけで、エージェントの使い勝手は見違えます。


メモリの3つのレイヤー

エージェントのメモリは用途に応じて3つのレイヤーに分けて考えると整理しやすくなります:

1. ワーキングメモリ(短期) 現在のセッション内での会話履歴。コンテキストウィンドウがこれに相当します。Antigravity の標準的な会話履歴管理で対応できます。

2. エピソード記憶(中期) 「先週このユーザーが〇〇と言った」「このプロジェクトでは△△を選択した」という具体的な過去のやりとり。セッションをまたいで保持する必要があります。

3. セマンティック記憶(長期) 「このユーザーはReactを好む」「このプロジェクトはTypeScript製」といった抽象化された事実・知識。最も長期に渡って参照されます。


パターン 1: エピソード記憶 — 過去の会話を構造化保存

最もシンプルなアプローチは、各セッションの要点を構造化されたデータとしてDBに保存し、次回起動時に読み込む方式です。

実装例:Supabase を使ったエピソード記憶

// memory/episode-store.ts
import { createClient } from '@supabase/supabase-js';
 
interface Episode {
  id: string;
  userId: string;
  sessionId: string;
  summary: string;
  keyFacts: string[];
  timestamp: string;
  embedding?: number[];
}
 
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_KEY!
);
 
export async function saveEpisode(episode: Omit<Episode, 'id'>): Promise<void> {
  await supabase.from('episodes').insert(episode);
}
 
export async function recallRecentEpisodes(
  userId: string,
  limit = 5
): Promise<Episode[]> {
  const { data } = await supabase
    .from('episodes')
    .select('*')
    .eq('userId', userId)
    .order('timestamp', { ascending: false })
    .limit(limit);
 
  return data || [];
}

セッション終了時に要約を保存するエージェントスキル

// skills/summarize-and-save.ts
export async function summarizeAndSave(
  conversationHistory: Message[],
  userId: string,
  sessionId: string
): Promise<void> {
  // Claude に要約させる
  const summary = await callClaude(`
    以下の会話を200字以内で要約し、重要な事実をJSON配列として抽出してください。
 
    会話履歴:
    ${conversationHistory.map(m => `${m.role}: ${m.content}`).join('\n')}
 
    レスポンス形式:
    {
      "summary": "...",
      "keyFacts": ["...", "..."]
    }
  `);
 
  const parsed = JSON.parse(summary);
 
  await saveEpisode({
    userId,
    sessionId,
    summary: parsed.summary,
    keyFacts: parsed.keyFacts,
    timestamp: new Date().toISOString(),
  });
}

パターン 2: セマンティック記憶 — ベクトルDBで意味検索

エピソード記憶が「いつ何があったか」を保存するのに対し、セマンティック記憶は**「ユーザーについて何を知っているか」**を保存します。テキストをベクトル化して保存し、関連する記憶を意味的に検索します。

pgvector を使った実装

// memory/semantic-store.ts
 
interface SemanticMemory {
  id: string;
  userId: string;
  content: string;
  embedding: number[];
  createdAt: string;
}
 
export async function embedAndStore(
  userId: string,
  fact: string
): Promise<void> {
  // OpenAI Embeddings API でベクトル化
  const embedding = await getEmbedding(fact);
 
  await supabase.from('semantic_memories').insert({
    userId,
    content: fact,
    embedding,
    createdAt: new Date().toISOString(),
  });
}
 
export async function recallRelevant(
  userId: string,
  query: string,
  topK = 5
): Promise<SemanticMemory[]> {
  const queryEmbedding = await getEmbedding(query);
 
  // pgvector の <=> 演算子でコサイン類似度検索
  const { data } = await supabase.rpc('match_memories', {
    query_embedding: queryEmbedding,
    match_user_id: userId,
    match_count: topK,
  });
 
  return data || [];
}

Supabase のカスタム関数(SQL)

CREATE OR REPLACE FUNCTION match_memories(
  query_embedding vector(1536),
  match_user_id text,
  match_count int DEFAULT 5
)
RETURNS TABLE(
  id uuid,
  content text,
  similarity float
)
LANGUAGE sql
AS $$
  SELECT
    id,
    content,
    1 - (embedding <=> query_embedding) AS similarity
  FROM semantic_memories
  WHERE user_id = match_user_id
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
$$;

パターン 3: メモリを使ったプロンプト拡張

記憶を持っていても、エージェントのプロンプトに適切に組み込まなければ意味がありません。Antigravity のエージェント定義でこれを実現する方法:

// agent/memory-augmented-agent.ts
export async function buildSystemPrompt(userId: string, currentQuery: string): Promise<string> {
  // 並列でメモリを取得
  const [recentEpisodes, relevantMemories] = await Promise.all([
    recallRecentEpisodes(userId, 3),
    recallRelevant(userId, currentQuery, 5),
  ]);
 
  const episodeContext = recentEpisodes.length > 0
    ? `## 最近のセッション記録\n${recentEpisodes.map(e => `- ${e.summary}`).join('\n')}`
    : '';
 
  const memoryContext = relevantMemories.length > 0
    ? `## 関連する記憶\n${relevantMemories.map(m => `- ${m.content}`).join('\n')}`
    : '';
 
  return `あなたはユーザーのパーソナルAIアシスタントです。
 
${episodeContext}
 
${memoryContext}
 
上記の記憶を参考にしながら、ユーザーを一貫して支援してください。
新しい重要な事実が出てきた場合は、それをメモとして記録することを提案してください。`;
}

メモリの自動更新戦略

記憶は「書き込む」だけでなく「更新」「削除」「重みづけ」も必要です。古い情報が邪魔になることを防ぐための戦略を3つ紹介します:

1. TTL(有効期限)付きエピソード

// 30日経過したエピソードは自動削除
const EPISODE_TTL_DAYS = 30;
 
export async function cleanOldEpisodes(userId: string): Promise<void> {
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - EPISODE_TTL_DAYS);
 
  await supabase
    .from('episodes')
    .delete()
    .eq('userId', userId)
    .lt('timestamp', cutoff.toISOString());
}

2. 重複排除(Deduplication)

新しい記憶を保存する前に、既存の類似した記憶がないかチェックします:

export async function storeIfNovelFact(userId: string, fact: string): Promise<boolean> {
  const similar = await recallRelevant(userId, fact, 1);
 
  if (similar.length > 0 && similar[0].similarity > 0.92) {
    console.log('重複する記憶をスキップ:', fact);
    return false;
  }
 
  await embedAndStore(userId, fact);
  return true;
}

3. 重要度スコアによる優先度管理

interface MemoryWithScore extends SemanticMemory {
  importanceScore: number;
  accessCount: number;
  lastAccessedAt: string;
}
 
// アクセスされるたびに重要度を更新
export async function accessMemory(memoryId: string): Promise<void> {
  await supabase.rpc('increment_importance', { memory_id: memoryId });
}

Antigravity での統合: SKILL.md によるメモリスキル定義

Antigravity のスキルシステムを使えば、メモリ管理をエージェントに自律的に行わせることができます:

# Memory Manager Skill
 
## Description
ユーザーとのやりとりから重要な情報を抽出し、永続的なメモリに保存・参照するスキル。
 
## Triggers
- ユーザーが「覚えておいて」「メモして」と言ったとき
- 会話の中で新しいユーザープリファレンスが判明したとき
- セッション開始時・終了時
 
## Actions
1. **recall**: 現在のクエリに関連する記憶を取得
2. **store**: 新しい事実をセマンティック記憶に保存
3. **summarize**: セッション終了時にエピソード記憶を保存
4. **forget**: ユーザーが「忘れて」と言ったときに記憶を削除

どこから実装を始めるか

3層を一度に作る必要はありません。体感的に効果が大きい順は「エピソード記憶 → プロンプト拡張 → セマンティック記憶」です。セッション終了時の要約保存(パターン1)と、その読み込み(パターン3)だけで「毎回ゼロから」の摩擦はほぼ消えます。ベクトルDBによる意味検索は、記憶が数百件を超えて「直近5件では足りない」と感じてからで遅くありません。

運用面で一つだけ先回りしておくと、記憶の「削除」導線(ユーザーが「忘れて」と言える仕組み)は後付けしづらいので、最初のスキーマ設計時に入れておくことをお勧めします。実装の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

Agents & Manager2026-07-15
ls には見えるのに、エージェントが開けない — 日本語ファイル名が macOS と Linux で別物になるとき
エージェントが「そのファイルは存在しません」と言い、ターミナルの ls には確かに写っている。三日かけて同期を疑った末に辿り着いたのは、濁点が一文字ではなかったという事実でした。検出スクリプトと三層ゲートまで残します。
Agents & Manager2026-07-08
エージェントに任せた仕事の「やり直し率」を測る — 委譲境界を勘ではなく数字で引く
どこまでをエージェントに任せるか。この線引きを私は長らく勘で引いていました。タスク種別ごとの「やり直し率」を git 履歴から算出し、委譲境界を数字で引き直す実践を、動くコードとともに整理します。
Agents & Manager2026-07-05
エージェント開発スタックの『既知の正常』を1枚のロックファイルで守る — 可動部が同時に動く時代の変更予算設計
IDEビルド・CLI・モデル・依存が同時に動くと、回帰の原因が特定できなくなります。既知の正常を1枚のロックファイルに固定し、一度に動かす軸を絞る変更予算の設計を、実装コードと運用ログで整理しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →