ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-03-18Intermediate

Agent Memory Design Patterns in Antigravity — Persisting Context Across Sessions

Learn memory design patterns for Antigravity agents to retain context across sessions. Practical implementation examples combining vector databases, episodic memory, and semantic memory.

antigravity404agents111memory4RAG5context-management3

What Does "Memory" Mean for an Agent?

One of the first walls developers hit when building AI agents is maintaining context across sessions. Conversational LLMs can only hold information within their context window — when a user returns the next day, the previous conversation is gone.

Antigravity is a flexible platform for building agent architectures, but without proper memory design, even the most sophisticated agent will give users a frustrating "starting from zero" experience every time they open it.


The Three Layers of Agent Memory

Organizing agent memory into three distinct layers makes the design much cleaner:

1. Working Memory (Short-term) The conversation history within the current session. This is what the context window provides. Antigravity's standard conversation history handles this.

2. Episodic Memory (Medium-term) Specific past interactions like "this user mentioned X last week" or "they chose option Y in this project." These need to persist across sessions.

3. Semantic Memory (Long-term) Abstracted facts and knowledge like "this user prefers React" or "this project is TypeScript." Referenced over the longest timeframes.


Pattern 1: Episodic Memory — Structured Storage of Past Sessions

The simplest approach is to save the key points of each session as structured data in a database, and load them at the start of the next session.

Implementation: Episodic Memory with 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;
}
 
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 || [];
}

Agent Skill: Summarize and Save at Session End

// skills/summarize-and-save.ts
export async function summarizeAndSave(
  conversationHistory: Message[],
  userId: string,
  sessionId: string
): Promise<void> {
  const summary = await callClaude(`
    Summarize the following conversation in under 200 words and extract
    key facts as a JSON array.
 
    Conversation:
    ${conversationHistory.map(m => `${m.role}: ${m.content}`).join('\n')}
 
    Response format:
    {
      "summary": "...",
      "keyFacts": ["...", "..."]
    }
  `);
 
  const parsed = JSON.parse(summary);
 
  await saveEpisode({
    userId,
    sessionId,
    summary: parsed.summary,
    keyFacts: parsed.keyFacts,
    timestamp: new Date().toISOString(),
  });
}

Pattern 2: Semantic Memory — Meaning-based Search with Vector DB

While episodic memory captures "what happened when," semantic memory stores "what we know about the user." Text is embedded into vectors and stored for semantic similarity search.

Implementation with 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> {
  const embedding = await getEmbedding(fact); // OpenAI Embeddings API
 
  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);
 
  // Cosine similarity search using pgvector's <=> operator
  const { data } = await supabase.rpc('match_memories', {
    query_embedding: queryEmbedding,
    match_user_id: userId,
    match_count: topK,
  });
 
  return data || [];
}

Supabase Custom Function (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;
$$;

Pattern 3: Memory-Augmented Prompt Construction

Having memory means nothing if you don't inject it into the agent's prompt effectively. Here's how to do it in Antigravity:

// agent/memory-augmented-agent.ts
export async function buildSystemPrompt(
  userId: string,
  currentQuery: string
): Promise<string> {
  // Fetch both memory types in parallel
  const [recentEpisodes, relevantMemories] = await Promise.all([
    recallRecentEpisodes(userId, 3),
    recallRelevant(userId, currentQuery, 5),
  ]);
 
  const episodeContext = recentEpisodes.length > 0
    ? `## Recent Session History\n${recentEpisodes.map(e => `- ${e.summary}`).join('\n')}`
    : '';
 
  const memoryContext = relevantMemories.length > 0
    ? `## Relevant Memories\n${relevantMemories.map(m => `- ${m.content}`).join('\n')}`
    : '';
 
  return `You are the user's personal AI assistant.
 
${episodeContext}
 
${memoryContext}
 
Use the memories above to provide consistent, personalized assistance.
If new important facts emerge, suggest adding them to memory.`;
}

Memory Maintenance Strategies

Memory needs more than just writing — it also requires updating, pruning, and weighting. Here are three strategies to prevent stale information from causing problems:

1. TTL (Time-to-Live) for Episodes

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 Before Storage

Before saving new memories, check whether a similar memory already exists:

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('Skipping duplicate memory:', fact);
    return false;
  }
 
  await embedAndStore(userId, fact);
  return true;
}

3. Importance Scoring

interface MemoryWithScore extends SemanticMemory {
  importanceScore: number;
  accessCount: number;
  lastAccessedAt: string;
}
 
// Boost importance score every time a memory is accessed
export async function accessMemory(memoryId: string): Promise<void> {
  await supabase.rpc('increment_importance', { memory_id: memoryId });
}

Antigravity Integration: Defining a Memory Skill in SKILL.md

Using Antigravity's skill system, you can let the agent handle its own memory autonomously:

# Memory Manager Skill
 
## Description
Extract important information from user interactions and store/retrieve
it from persistent memory.
 
## Triggers
- When the user says "remember this" or "make a note"
- When a new user preference is revealed in conversation
- At session start and end
 
## Actions
1. **recall**: Retrieve memories relevant to the current query
2. **store**: Save a new fact to semantic memory
3. **summarize**: Save an episode summary at session end
4. **forget**: Delete a memory when the user says "forget that"

Where to Start

You do not need all three layers at once. In my experience as an indie developer building operations agents for my own sites since 2014, the order of impact is: episodic memory → prompt augmentation → semantic memory. Saving a session summary on exit (Pattern 1) and loading it on start (Pattern 3) removes most of the "starting from zero" friction by itself. Vector-based semantic recall earns its keep only once memories number in the hundreds and "the latest five" stops being enough.

One operational item worth designing up front: the deletion path — letting a user say "forget that" — is hard to retrofit, so put it in the first schema rather than the second. I hope this saves you a refactor.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Agents & Manager2026-06-29
When Parallel Sub-Agents Fight Over One API's Rate Limit: A Shared Token Bucket That Caps the Aggregate
Run Antigravity 2.0 dynamic sub-agents in parallel and each one hits the same external API independently, pushing the aggregate rate over the limit and triggering cascades of 429s. Here is a shared token bucket that caps the aggregate proactively, with working code through a Redis version.
Agents & Manager2026-06-28
It Did Things I Never Asked For — Binding an Agent's Task Scope With a Contract
Ask it to fix a button color and you get a refactor, renames, and a dependency bump too. This is a scope problem, not a permission one. Here is a contract that stops at the scope boundary and asks.
Agents & Manager2026-06-28
The Day the Article I Asked It to Format Became the Agent's Instructions
When you run an unattended content-formatting pipeline with Antigravity CLI, instruction-like text buried in the file you are processing can hijack the agent. Here is how I separate the instruction channel from the data channel and add an output-scope acceptance gate to reject anything out of bounds.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →