Choosing Your Memory Schema

By Arc Labs Research8 min read

The Problem: Choosing the Wrong Memory Type

Agents fail quietly when memory types don't match use cases:

  • Storing temporal events as facts: "User browsed shoes on 2026-05-01" stored as a fact, but you lose the timestamp. Later, you can't answer "What did user do last week?"
  • Storing relationships as facts: "User owns product X" stored as { content: "owns product X" }, but queries miss it because it's worded differently elsewhere ("User purchased X", "owns item X").
  • Mixing types: Some code treats memory as immutable facts, other code updates it. Consistency breaks.
  • No filtering strategy: All queries are semantic search. You pay for expensive embeddings instead of using metadata filters.

Production agents need a schema that matches query patterns and minimizes retrieval cost.

Memory Type Decision Tree

START: What are your primary queries?

1. "What are the user's static facts?"
   ├─ Yes → USE FACT
   │  (Example: "User prefers Nike", "Has budget <$500")

   └─ No, next question

2. "Do you need temporal sequence or causality?"
   ├─ Yes → USE EVENT
   │  (Example: "User browsed shoes at 10am, bought at 2pm")

   └─ No, next question

3. "Do you model the world as interconnected entities + their attributes?"
   ├─ Yes → USE ENTITY
   │  (Example: Customer + Orders + Products + Preferences, interconnected)

   └─ No, next question

4. "Do you need to query relationships between pairs of things?"
   ├─ Yes → USE RELATION
   │  (Example: "What is the relationship between user-123 and product-456?")

   └─ You don't fit the schema yet—consult docs

Four Memory Types: Comparison Table

TypeContentExampleRetrieval PatternCostWhen to Use
FactStateless, single-valued or categorical"Customer prefers Nike", "Has budget 100100–500"Keyword (metadata) + semanticLowStatic user preferences, category tags, configuration
EventTemporal, causally linked, sequence matters"Clicked product at 10:00am", "Purchased boots at 2pm"Temporal (sort by date) + semanticMediumUser actions, conversation history, time-series events
EntityRich object with typed attributes + relationshipsCustomer { name, email, preferences[], orders[], location }Graph traversal + semanticHighComplex domains (customer + orders + products, all interconnected)
RelationBinary relationship between two entities(user-123) --"purchased"--> (product-456), with metadataRelationship query ("What connects X and Y?")MediumKnowledge graphs, recommendation (who likes what?)

Type 1: Fact (Static Attributes)

Use facts for information that doesn't change or changes rarely.

Schema

interface FactMemory {
  agentId: string;
  namespace: string;
  
  content: string; // "Customer prefers Nike"
  type: "fact";
  
  metadata: {
    category: string; // "preference", "configuration", "setting"
    value?: string; // "Nike" (optional, for filtering)
    strength?: "strong" | "weak" | "neutral"; // Inferred from frequency
    timestamp: string; // When this fact was observed
  };
}

Retrieval Pattern

import { Brain } from "brain-ai";

const memory = new Brain({ namespace: "customer-service" });

// Query 1: Get all facts for a customer (keyword)
const facts = await memory.retrieve({
  agentId: "customer-12345",
  filters: { type: "fact" }, // Metadata filter (cheap)
  topK: 20,
});

// Query 2: Get preference facts only (metadata filter)
const preferences = await memory.retrieve({
  agentId: "customer-12345",
  filters: { type: "fact", "metadata.category": "preference" },
  topK: 10,
});

// Query 3: Strong preferences (filtered)
const strongPrefs = await memory.retrieve({
  agentId: "customer-12345",
  filters: { type: "fact", "metadata.strength": "strong" },
  topK: 5,
});

// Use the results
const preferredBrands = preferences
  .map(f => f.metadata.value)
  .filter(Boolean);
// ["Nike", "Adidas"] (brands with strong preference)

When to Use

  • Customer preferences (brand, price range, size, color)
  • Configuration (notification settings, language, timezone)
  • Tags or categories (VIP customer, at-risk churn, high-value)

When NOT to Use

  • Temporal sequences (use Event instead)
  • Relationships between entities (use Relation instead)

Type 2: Event (Temporal Sequence)

Use events for ordered actions with timestamps.

Schema

interface EventMemory {
  agentId: string;
  namespace: string;
  
  content: string; // "User clicked product page"
  type: "event";
  
  metadata: {
    timestamp: string; // ISO 8601, required for sorting
    action: string; // "click", "purchase", "view", "message"
    entityId?: string; // Product ID, order ID, etc.
    entityType?: string; // "product", "order", "page"
    outcome?: string; // "success", "failed", "abandoned"
    context?: {
      sessionId?: string;
      source?: string; // "web", "mobile", "api"
    };
  };
}

Retrieval Pattern

// Query 1: Get recent events (temporal, sorted by timestamp)
const recentEvents = await memory.retrieve({
  agentId: "customer-12345",
  filters: { type: "event" },
  topK: 10,
  sortBy: "timestamp", // Retrieve most recent first
  direction: "desc",
});

// Query 2: Get events in time window
const lastWeekEvents = await memory.retrieve({
  agentId: "customer-12345",
  filters: {
    type: "event",
    "metadata.timestamp": { $gte: "2026-05-06T00:00:00Z" },
  },
  topK: 50,
});

// Query 3: Get events of specific action
const purchases = await memory.retrieve({
  agentId: "customer-12345",
  filters: { type: "event", "metadata.action": "purchase" },
  topK: 20,
});

// Query 4: Semantic search on events
const shoeBrowsing = await memory.retrieve({
  agentId: "customer-12345",
  query: "browsing shoes or boots",
  filters: { type: "event" },
  topK: 5,
});

// Use the results
const timeline = recentEvents.map(e => ({
  time: e.metadata.timestamp,
  action: e.metadata.action,
  entity: e.metadata.entityId,
}));
// Reconstruct sequence: clicked product -> added to cart -> purchased

When to Use

  • Conversation history (messages, turn sequence)
  • User actions (clicks, purchases, logins, errors)
  • Time-series data (sensor readings with timestamps)
  • Audit logs (who did what when)

When NOT to Use

  • Static attributes (use Fact instead)
  • Complex object relationships (use Entity instead)

Type 3: Entity (Rich Objects with Relationships)

Use entities when you model a domain as interconnected objects.

Schema

interface EntityMemory {
  agentId: string;
  namespace: string;
  
  content: string; // JSON serialized or prose description
  type: "entity";
  
  metadata: {
    entityId: string; // Unique ID (customer-123, product-456)
    entityType: string; // "customer", "order", "product", "location"
    
    // Entity attributes (use for filtering and updates)
    attributes?: {
      name?: string;
      category?: string;
      status?: string;
      [key: string]: any;
    };
    
    // Links to related entities
    relations?: {
      customer?: string[]; // Related customer IDs
      products?: string[]; // Related product IDs
      orders?: string[]; // Related order IDs
    };
    
    timestamp: string;
  };
}

// Example: Customer entity
const customerEntity = {
  agentId: "agent-ecommerce",
  content: `
    Customer Sarah Chen (sarah@example.com)
    - Account created 2026-01-15
    - Tier: Gold (lifetime spend >$5000)
    - Preferences: Nike shoes, budget $100–$300, size 8
    - Recent orders: [order-789, order-790]
    - Associated reviews: [review-123, review-124]
  `,
  type: "entity",
  metadata: {
    entityId: "customer-sarah-123",
    entityType: "customer",
    attributes: {
      name: "Sarah Chen",
      email: "sarah@example.com",
      accountCreated: "2026-01-15",
      tier: "Gold",
      lifetimeSpend: 5500,
    },
    relations: {
      orders: ["order-789", "order-790", "order-656"],
      reviews: ["review-123", "review-124"],
      referrals: ["customer-alex-456"],
    },
  },
};

Retrieval Pattern

// Query 1: Get entity by ID
const customer = await memory.retrieve({
  agentId: "agent-ecommerce",
  filters: { type: "entity", "metadata.entityId": "customer-sarah-123" },
  topK: 1,
});

// Query 2: Expand relationships (get related entities)
if (customer.length > 0) {
  const relatedOrderIds = customer[0].metadata.relations?.orders || [];
  const orders = await memory.retrieve({
    agentId: "agent-ecommerce",
    filters: {
      type: "entity",
      "metadata.entityId": { $in: relatedOrderIds },
    },
  });
}

// Query 3: Search by attribute
const goldTierCustomers = await memory.retrieve({
  agentId: "agent-ecommerce",
  filters: {
    type: "entity",
    "metadata.entityType": "customer",
    "metadata.attributes.tier": "Gold",
  },
  topK: 50,
});

// Query 4: Semantic search + attribute filter
const nikeFans = await memory.retrieve({
  agentId: "agent-ecommerce",
  query: "customers who prefer Nike shoes",
  filters: {
    type: "entity",
    "metadata.entityType": "customer",
  },
  topK: 20,
});

When to Use

  • Recommendation systems (customers + products + reviews, all interconnected)
  • Knowledge bases (entities + attributes + relationships modeled together)
  • CRM systems (customers, accounts, contacts, interactions)
  • Complex domains where you traverse relationships frequently

When NOT to Use

  • Simple, flat data (use Fact instead)
  • Temporal sequences (use Event instead)
  • You only have 1–2 entity types (use simpler types)

Type 4: Relation (Binary Relationships)

Use relations for first-order relationships between pairs of things.

Schema

interface RelationMemory {
  agentId: string;
  namespace: string;
  
  content: string; // "Sarah purchased a Nike Air Jordan 1"
  type: "relation";
  
  metadata: {
    subject: string; // "customer-sarah-123"
    subjectType: string; // "customer"
    
    predicate: string; // "purchased", "reviewed", "recommended", "follows"
    
    object: string; // "product-nike-aj1-456"
    objectType: string; // "product"
    
    strength?: number; // 0.0–1.0, confidence/weight of relationship
    timestamp: string; // When relationship was formed
    
    // Optional: relationship details
    context?: {
      reviewScore?: 5; // If predicate is "reviewed"
      quantity?: 1; // If predicate is "purchased"
      reason?: "recommendation_engine"; // Why this relationship?
    };
  };
}

Retrieval Pattern

// Query 1: What did Sarah purchase?
const sarahPurchases = await memory.retrieve({
  agentId: "agent-ecommerce",
  filters: {
    type: "relation",
    "metadata.subject": "customer-sarah-123",
    "metadata.predicate": "purchased",
  },
  topK: 20,
});

// Query 2: Who purchased this product?
const aj1Buyers = await memory.retrieve({
  agentId: "agent-ecommerce",
  filters: {
    type: "relation",
    "metadata.object": "product-nike-aj1-456",
    "metadata.predicate": "purchased",
  },
  topK: 100,
});

// Query 3: High-confidence relationships only
const strongRelations = await memory.retrieve({
  agentId: "agent-ecommerce",
  filters: {
    type: "relation",
    "metadata.subject": "customer-sarah-123",
    "metadata.strength": { $gte: 0.8 }, // Only strong relationships
  },
  topK: 10,
});

// Query 4: Semantic search for implicit relationships
const similarTaste = await memory.retrieve({
  agentId: "agent-ecommerce",
  query: "customers with similar purchase history",
  filters: {
    type: "relation",
    "metadata.predicate": "purchased",
  },
  topK: 50,
});

When to Use

  • Recommendations (who-bought-what, who-reviewed-what)
  • Social networks (follows, friends, collaborates-with)
  • Knowledge graphs (simple facts about connections)
  • Audit trails (user-performed-action, system-generated-event)

When NOT to Use

  • Static attributes (use Fact instead)
  • Many-to-many relationships with complex logic (use Entity instead)

Schema Design Decision Checklist

  • Identify query patterns: What are your 5 most common queries? (e.g., "Get customer preferences", "What did user do last week?")
  • Map to memory types: Does each query fit fact, event, entity, or relation?
  • Test schema compatibility: Can you answer all 5 queries efficiently with your chosen schema?
  • Design for filtering: Which fields should be indexed for fast metadata filters? (These are cheaper than semantic search.)
  • Define relationships: If using Entity or Relation, define how entities/subjects/objects link.
  • Prototype retrieval: Write retrieve() calls for each query pattern. Do filters return results in <\100ms?
  • Measure costs: Estimate semantic vs. metadata retrieval breakdown. Aim for >70% metadata, <\10% semantic.

TypeScript Interface Examples

Ecommerce Customer Support

// Facts: preferences, settings
interface CustomerPreferenceFact {
  type: "fact";
  metadata: {
    category: "preference" | "setting";
    value: string;
    strength: "strong" | "weak";
    timestamp: string;
  };
}

// Events: browsing, purchases, messages
interface CustomerActionEvent {
  type: "event";
  metadata: {
    action: "browse" | "purchase" | "review" | "inquire";
    entityId: string;
    timestamp: string;
    outcome?: "success" | "abandoned";
  };
}

// Relations: who-bought-what, who-reviewed-what
interface PurchaseRelation {
  type: "relation";
  metadata: {
    subject: string; // customer-id
    predicate: "purchased" | "reviewed" | "refunded";
    object: string; // product-id
    strength: number; // confidence
    timestamp: string;
  };
}

Conversational Agent

// Facts: user properties
interface UserPropertyFact {
  type: "fact";
  metadata: {
    category: "name" | "role" | "location" | "preference";
    value: string;
    timestamp: string;
  };
}

// Events: conversation turns
interface ConversationEvent {
  type: "event";
  metadata: {
    action: "user_message" | "assistant_message" | "function_call";
    role: "user" | "assistant";
    turnNumber: number;
    timestamp: string;
  };
}

// Relations: user-mentions-entity, user-asks-about
interface MentionRelation {
  type: "relation";
  metadata: {
    subject: string; // user-id
    predicate: "mentions" | "asks_about" | "clarifies";
    object: string; // topic, entity
    turnNumber: number;
    timestamp: string;
  };
}

Next Steps

Related reading

Updates from the lab.

Engineering notes, research drops, occasional product updates. Roughly monthly.