# How Claude-Mem Context Injection Selects Observations for AI Prompts

> Discover how Claude-Mem context injection selects observations for AI prompts by filtering SQLite records with whitelists, recency, and count for efficient prompt generation.

- Repository: [Alex Newman/claude-mem](https://github.com/thedotmack/claude-mem)
- Tags: deep-dive
- Published: 2026-02-19

---

**Claude-Mem's context injection system selects observations by filtering SQLite records against user-defined type and concept whitelists, then limits results by recency and count before rendering them into the prompt.**

Claude-Mem, an open-source context management system for AI coding assistants, automatically prepends relevant historical observations to every prompt through its context injection pipeline. Understanding how this system determines which observations to include helps developers optimize their context windows and ensure critical code history surfaces at the right time. The selection process follows a rigorous three-stage pipeline implemented in TypeScript, querying a local SQLite store to retrieve only the most relevant records.

## The Three-Stage Selection Pipeline

The context injection mechanism operates through three distinct phases: configuration loading, database querying, and context assembly. Each phase is implemented in specific service files within the `src/services/context/` directory.

### Stage 1: Loading Configuration with ContextConfigLoader

The pipeline begins in [`src/services/context/ContextConfigLoader.ts`](https://github.com/thedotmack/claude-mem/blob/main/src/services/context/ContextConfigLoader.ts), where the `ContextConfigLoader` class reads user-level settings from `~/.claude-mem/settings.json`. For non-code modes, it falls back to the active mode's definition to determine which observation types and concepts are relevant.

The configuration exposes three critical parameters that drive selection:

- `observationTypes` – A set of allowed observation types (e.g., *code-change*, *research*)
- `observationConcepts` – A set of allowed concepts (e.g., *bug-fix*, *refactor*)
- `totalObservationCount` – The maximum number of observations to fetch

These parameters can also be overridden via environment variables: `CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES`, `CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS`, and `CLAUDE_MEM_CONTEXT_OBSERVATIONS`.

### Stage 2: Querying the SQLite Store

Once configuration is loaded, [`src/services/context/ObservationCompiler.ts`](https://github.com/thedotmack/claude-mem/blob/main/src/services/context/ObservationCompiler.ts) executes the selection logic through its `queryObservations` method (or `queryObservationsMulti` when working with multiple projects in a worktree). This method translates the configuration into a parameterized SQL query against the SQLite observations table.

The query implements a precise filtering hierarchy:

```sql
SELECT … FROM observations
WHERE project = ?
  AND type IN ( …place‑holders… )
  AND EXISTS (
    SELECT 1 FROM json_each(concepts)
    WHERE value IN ( …place‑holders… )
  )
ORDER BY created_at_epoch DESC
LIMIT ?

```

The selection criteria operate as follows:

- **Project filter** guarantees only observations from the current repository (or all worktree projects) are considered
- **Type filter** (`type IN (…)`) restricts rows to the `observationTypes` set from configuration
- **Concept filter** (`EXISTS (…)`) keeps only rows whose `concepts` JSON array contains at least one entry from `observationConcepts`
- **Ordering** by `created_at_epoch DESC` prioritizes the newest observations
- **Limit** enforces the `totalObservationCount` maximum

### Stage 3: Assembling the Final Context String

The final stage occurs in [`src/services/context/ContextBuilder.ts`](https://github.com/thedotmack/claude-mem/blob/main/src/services/context/ContextBuilder.ts), where the `generateContext` method orchestrates the process. It calls the query methods, receives the filtered observation list, and passes it through a renderer pipeline (header → timeline → summary → prior-message → footer).

Crucially, the renderer does not add or drop observations; it simply formats the list that the SQL query already limited. This ensures deterministic context injection based strictly on the configuration parameters and recency.

## Practical Implementation Examples

### Basic Context Generation

To generate context programmatically (which is what the Cursor hook ultimately runs):

```typescript
import { generateContext } from './src/services/context/ContextBuilder.js';

// Automatically reads settings, picks current project, returns markdown context
const ctx = await generateContext();
console.log(ctx);

```

### Customizing Observation Filters via Environment Variables

Restrict context injection to specific observation types and concepts while limiting the count:

```bash
export CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES=bug-fix,refactor
export CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS=critical,performance
export CLAUDE_MEM_CONTEXT_OBSERVATIONS=5

```

When `claude-mem cursor install` is active, the `beforeSubmitPrompt` hook calls the same `/api/context/inject` endpoint, ensuring these filters apply automatically before every prompt.

### Worktree Support: Multi-Project Context Injection

To include observations from both a parent repository and a child worktree:

```typescript
await generateContext({
  projects: ['parent-repo', 'child-worktree'],
  cwd: process.cwd()
});

```

This internally uses `queryObservationsMulti` to pull data from both SQLite stores and merges results chronologically.

### Debugging the SQL Query

To inspect the exact SQL generated for your configuration:

```typescript
import { queryObservations } from './src/services/context/ObservationCompiler.js';
import { SessionStore } from './src/services/sqlite/SessionStore.js';
import { loadContextConfig } from './src/services/context/ContextConfigLoader.js';
import { getProjectName } from './src/utils/project-name.js';

const db = new SessionStore();
const cfg = loadContextConfig();
const project = getProjectName(process.cwd());

const sql = db.db.prepare(`
  SELECT id, type, concepts FROM observations
  WHERE project = ?
    AND type IN (${Array.from(cfg.observationTypes).map(() => '?').join(',')})
    AND EXISTS (SELECT 1 FROM json_each(concepts) WHERE value IN (${Array.from(cfg.observationConcepts).map(() => '?').join(',')}))
  ORDER BY created_at_epoch DESC
  LIMIT ?
`).source;

console.log(sql);

```

## Summary

- **Claude-Mem context injection** selects observations through a three-stage pipeline: configuration loading, SQL filtering, and context assembly.
- **Configuration** in [`ContextConfigLoader.ts`](https://github.com/thedotmack/claude-mem/blob/main/ContextConfigLoader.ts) defines allowed `observationTypes`, `observationConcepts`, and `totalObservationCount` via settings files or environment variables.
- **Filtering** in [`ObservationCompiler.ts`](https://github.com/thedotmack/claude-mem/blob/main/ObservationCompiler.ts) executes parameterized SQL that filters by project, type whitelist, JSON concept array intersection, and recency before applying a hard limit.
- **Rendering** in [`ContextBuilder.ts`](https://github.com/thedotmack/claude-mem/blob/main/ContextBuilder.ts) formats the pre-filtered list without modification, ensuring deterministic context windows.
- **Worktree support** enables multi-project context injection by merging results from multiple SQLite stores chronologically.

## Frequently Asked Questions

### How does Claude-Mem prioritize which observations to include when the limit is reached?

Claude-Mem prioritizes observations strictly by recency. The SQL query in [`ObservationCompiler.ts`](https://github.com/thedotmack/claude-mem/blob/main/ObservationCompiler.ts) orders results by `created_at_epoch DESC` before applying the `LIMIT` clause defined by `totalObservationCount`. This ensures the newest observations matching your type and concept filters appear in the context window, while older entries are excluded once the maximum count is reached.

### Can I include observations from multiple repositories in a single prompt?

Yes, Claude-Mem supports worktree and multi-project contexts through the `queryObservationsMulti` method. When calling `generateContext()` with a `projects` array containing multiple project identifiers, the system queries each project's SQLite store separately and merges the results chronologically by `created_at_epoch`. This allows parent repositories and child worktrees to contribute relevant historical context to a single prompt.

### What happens if no observations match the configured filters?

If the SQL query returns zero rows matching the specified `observationTypes`, `observationConcepts`, and project filters, the context injection pipeline continues with an empty observation list. The [`ContextBuilder.ts`](https://github.com/thedotmack/claude-mem/blob/main/ContextBuilder.ts) renderer formats this empty list into the context string, resulting in a context block that contains only the header, footer, and structural elements without specific historical observations. The prompt still functions normally, simply without injected historical context.

### How do environment variables interact with the settings.json configuration?

Environment variables act as overrides to the `~/.claude-mem/settings.json` configuration. The [`ContextConfigLoader.ts`](https://github.com/thedotmack/claude-mem/blob/main/ContextConfigLoader.ts) service checks for `CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES`, `CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS`, and `CLAUDE_MEM_CONTEXT_OBSERVATIONS` at runtime. If present, these values supersede the corresponding entries in the JSON settings file. For non-code modes, the active mode's definition may also supply default type and concept sets that serve as fallbacks when neither environment variables nor explicit settings are defined.