Retrieval Strategy Selector

By Arc Labs Research8 min read

The Problem: Which Retrieval Strategy?

Agent memory is only useful if you retrieve the right data. Asking the wrong way loses critical context:

  • Semantic only: You search for "user felt anxious" but memory stored "feeling of dread" as a keyword phrase. Miss it.
  • Keyword only: You search for "trading strategy" but memory stored similar concepts under different words. Semantic search would find them; keyword won't.
  • No time filtering: You retrieve "agent decision" and get a decision from 6 months ago, irrelevant to today's context.
  • No type filtering: You retrieve and get 100 results when you only want error outcomes, wasting tokens.

Brain offers 5 retrieval strategies, each optimized for a query type. This guide shows when to use each.

Decision Flowchart

START: What are you trying to retrieve?
├─ "Recent context (last 24 hours)"? → TEMPORAL
├─ "Exact phrase or keyword"? → LEXICAL
├─ "Category or type (errors, decisions)"? → TYPE-FILTERED
├─ "Relationships or causal chains"? → GRAPH
├─ "Conceptual match (similar ideas)"? → SEMANTIC
└─ "Everything above"? → HYBRID (Semantic + Lexical)

Retrieval Strategies

1. Semantic Retrieval

Use when: Concept matching, idea similarity, paraphrasing.

Example queries:

  • "How did the user feel about the last meeting?"
  • "What trading strategies worked well?"
  • "Similar customer complaints we've seen?"

How it works: Converts query to embedding, finds memories with similar embeddings.

Strengths:

  • Paraphrase-tolerant (query "felt sad" matches memory "depressed mood")
  • Multi-concept matching ("trading risk management" matches various risk concepts)
  • Domain-agnostic (works across any domain)

Weaknesses:

  • Ignores exact phrases ("find the word 'error'")
  • Slow for huge indexes (>100M vectors)

TypeScript example:

import { Brain } from "brain-sdk";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

// Semantic: find conceptually similar memories
const results = await brain.retrieve({
  agentId: "support_agent_1",
  query: "How did the customer feel?",
  topK: 5,
  mode: "semantic",
});

console.log(results); // Finds memories with sentiment concepts, even if worded differently

2. Temporal Retrieval

Use when: Time-based filtering, recency, session boundaries.

Example queries:

  • "All decisions made in the last 24 hours"
  • "Events from this month"
  • "Trading outcomes between 9am–5pm"

How it works: Filters memories by timestamp, then ranks by recency or relevance.

Strengths:

  • Precise time windows (no guessing)
  • Combines time + relevance (can add keyword/semantic on top)
  • Efficient even for large memory stores

Weaknesses:

  • Requires timestamp metadata
  • Not for fuzzy time ("around last week")

TypeScript example:

import { Brain } from "brain-sdk";
import { subHours } from "date-fns";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

// Temporal: memories from the last 24 hours
const oneDayAgo = subHours(new Date(), 24).toISOString();

const results = await brain.retrieve({
  agentId: "trader_1",
  query: "trading outcomes", // Optional: combine with keyword
  filters: {
    timestamp: { $gte: oneDayAgo },
  },
  topK: 10,
  mode: "temporal",
});

console.log(results); // All trading outcomes in last 24 hours, ranked by recency

3. Lexical Retrieval

Use when: Exact phrases, keywords, boolean logic.

Example queries:

  • "Find the word 'error' in memories"
  • "All memories tagged 'critical'"
  • "Customer name contains 'Smith'"

How it works: Full-text search (BM25), tokenizes and matches terms.

Strengths:

  • Exact phrase matching (no approximation)
  • Fast for keyword searches
  • Boolean logic support (AND, OR, NOT)

Weaknesses:

  • Requires exact words or known terminology
  • Misses paraphrases ("felt sad" ≠ "depressed")

TypeScript example:

import { Brain } from "brain-sdk";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

// Lexical: exact keyword matching
const results = await brain.retrieve({
  agentId: "support_agent_1",
  query: "error AND timeout", // Boolean logic supported
  topK: 10,
  mode: "lexical",
});

console.log(results); // Only memories containing both "error" and "timeout"

4. Type-Filtered Retrieval

Use when: Category filtering, memory types, outcome filtering.

Example queries:

  • "All failed trades only"
  • "Decisions made by the user (exclude observations)"
  • "Error events, not warnings"

How it works: Filters by type metadata field, then retrieves within that type.

Strengths:

  • Clean categorical filtering
  • Reduces noise (exclude irrelevant memory types)
  • Efficient metadata queries

Weaknesses:

  • Requires structured type metadata
  • Can't filter on undefined types

TypeScript example:

import { Brain } from "brain-sdk";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

// Type-filtered: retrieve only decision memories
const results = await brain.retrieve({
  agentId: "trader_1",
  query: "strategies", // Optional semantic/keyword query
  filters: {
    type: "decision", // Structured type filter
  },
  topK: 5,
});

console.log(results); // Only memories where type="decision"

5. Graph Retrieval

Use when: Relationships, causal chains, entailment.

Example queries:

  • "What caused this error?"
  • "What did the agent decide as a result of this observation?"
  • "Trace the causal chain leading to this outcome"

How it works: Traverses relationships in graph (edge: "causedBy", "resultedIn", "leadsTo").

Strengths:

  • Captures causality (not just similarity)
  • Multi-hop reasoning (A → B → C)
  • Avoids spurious correlations

Weaknesses:

  • Requires graph relationships (edges) in schema
  • Slower for deep graphs

TypeScript example:

import { Brain } from "brain-sdk";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

// Graph: retrieve causal chain
const results = await brain.retrieve({
  agentId: "trader_1",
  query: "root cause of loss on 2026-05-12",
  topK: 5,
  mode: "graph",
  graphFilters: {
    // Traverse "causedBy" relationships
    relationshipType: "causedBy",
    maxHops: 2, // Up to 2 hops away
  },
});

console.log(results); // Causal chain: Trade Loss ← Strategy A ← Market Regime ← Observation

When to Use Each: Decision Table

ScenarioStrategyExample
"What did the user like?" (paraphrasing ok)SemanticQuery "prefer", Memory "enjoy" → Match ✓
"Last 10 decisions" (time-bounded)TemporalFilter by timestamp > now - 24h, rank by recency
"Find error logs" (exact keyword)LexicalQuery "error", Memory "error: 500" → Match ✓
"Only failed trades" (category filter)Type-FilteredFilter by type = "trade_outcome" AND outcome = "loss"
"Why did this fail?" (causality)GraphStart at "Trade Loss", follow "causedBy" edges
"Everything: recent + semantic + keyword"HybridTemporal window + semantic + lexical in one query

Hybrid Retrieval (Most Common)

For most agent loops, use hybrid: combines semantic + lexical + temporal. It's the safest default.

TypeScript:

const results = await brain.retrieve({
  agentId: "agent_1",
  query: "user preference coffee", // Semantic + lexical
  filters: {
    timestamp: { $gte: subDays(new Date(), 7) }, // Temporal
    type: "preference", // Type-filtered
  },
  topK: 5,
  mode: "hybrid",
});

This retrieves memories that are:

  • Semantically similar to "user preference coffee"
  • Contain keywords related to your query
  • Created in the last 7 days
  • Tagged as type "preference"

Implementation Examples

Trading Agent: Market Context

import { Brain } from "brain-sdk";

const brain = new Brain({ apiKey: process.env.BRAIN_API_KEY });

async function getMarketContext(
  agentId: string,
  symbol: string,
  timeWindowHours: number = 24
) {
  // 1. Temporal + Type: Recent market observations
  const oneDayAgo = new Date(
    Date.now() - timeWindowHours * 60 * 60 * 1000
  ).toISOString();

  const marketFacts = await brain.retrieve({
    agentId,
    query: `Market condition for ${symbol}`,
    filters: {
      timestamp: { $gte: oneDayAgo },
      type: "market_observation",
    },
    topK: 5,
    mode: "temporal",
  });

  // 2. Semantic: Similar past market conditions
  const similarConditions = await brain.retrieve({
    agentId,
    query: `${symbol} bull market resistance level`,
    filters: { type: "market_fact" },
    topK: 3,
    mode: "semantic",
  });

  // 3. Lexical + Type: Recent error events
  const recentErrors = await brain.retrieve({
    agentId,
    query: "error OR warning",
    filters: {
      timestamp: { $gte: oneDayAgo },
      type: "error_event",
    },
    topK: 5,
    mode: "lexical",
  });

  return {
    recentConditions: marketFacts,
    similarPastConditions: similarConditions,
    recentErrors: recentErrors,
  };
}

Support Agent: Customer Sentiment

async function getCustomerContext(agentId: string, customerId: string) {
  // 1. Semantic: Similar past interactions
  const similarInteractions = await brain.retrieve({
    agentId,
    query: "customer complaint frustration",
    filters: {
      customerId,
      type: "customer_interaction",
    },
    topK: 5,
    mode: "semantic",
  });

  // 2. Temporal: Recent sentiment
  const recentWeek = new Date(
    Date.now() - 7 * 24 * 60 * 60 * 1000
  ).toISOString();

  const recentSentiment = await brain.retrieve({
    agentId,
    query: "sentiment positive negative",
    filters: {
      customerId,
      timestamp: { $gte: recentWeek },
      type: "sentiment_signal",
    },
    topK: 3,
    mode: "hybrid",
  });

  // 3. Graph: Root cause of last complaint
  const rootCause = await brain.retrieve({
    agentId,
    query: customerId,
    topK: 1,
    mode: "graph",
    graphFilters: {
      relationshipType: "rootCauseOf",
      maxHops: 2,
    },
  });

  return {
    similarPastComplaints: similarInteractions,
    recentSentiment: recentSentiment,
    rootCauses: rootCause,
  };
}

Performance & Latency

StrategyLatency (P99)Best Index Size
Semantic100–200ms<10M vectors
Temporal50–100ms<100M
Lexical30–50ms<100M
Type-Filtered10–50msUnlimited
Graph50–200ms (depends on depth)<5M nodes
Hybrid100–200ms<10M

Implementation Checklist

  • Define memory types you'll use (decision, observation, error, preference, etc.)
  • Choose primary retrieval strategy based on agent loop (most: hybrid)
  • Implement temporal filtering if you care about recency
  • Add type metadata to all stored memories
  • Test: retrieve queries and verify results include expected memories
  • Monitor latency: ensure P99 <200ms for agent loop queries
  • Iterate: A/B test strategies, measure retrieval quality
  • Document: which memory types use which retrieval strategy

Next Steps

Related reading

Updates from the lab.

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