Engineering
Calendar Icon Light V2 - TechVR X Webflow Template
May 27, 2026

sessiongrep: A Local-First Memory Layer for CLI Agents

SQLite + FTS5 turns raw Claude Code and Codex CLI transcripts into something humans and agents can actually search.

Nisarg Patel

Member of Technical Staff @ Brain Co

You solved that bug last week. Your next agent session has no idea.

If you use Claude Code, Codex CLI, or Cursor every day, you know the failure mode. The prompt that finally worked, the migration path you ruled out, and the mental model you built over two hours still exist on disk, but they are buried inside provider-specific JSONL files with opaque names. Humans don't want to read them. Agents don't know how to retrieve them. So both end up repeating work.

We built sessiongrep to fix that: a small, fast Rust CLI that turns your scattered local session history into something you — and your agent — can actually search.

The problem

Two frictions compound.

The first is human recall. Claude Code writes sessions under ~/.claude/projects/**/*.jsonl. Codex CLI writes them under ~/.codex/sessions/**/*.jsonl, with extra metadata in ~/.codex/session_index.jsonl and ~/.codex/state_5.sqlite. Cursor keeps its own transcripts under ~/.cursor/projects/**. The filenames are opaque. The formats differ. A simple question like "where was that Redis migration?" turns into a slow walk through hashed directories and noisy JSONL. The built-in resume pickers don't save you either — Claude's often shows /exit as the last prompt, which is the one line that tells you nothing.

The second is agent recall. Each new session starts cold. The context you earned yesterday, the file layout you mapped, the dead-end you ruled out, the user preference you finally discovered are invisible to today's agent. It asks the same questions, rereads the same files, and proposes the same failed approach.

The information is not missing. It is stranded.

Why the obvious fixes fail

  • grep over JSONL is noisy. Matches land inside tool payloads, bookkeeping messages, and long blobs of machine output.
  • Shell history is not session history. It tells you what you ran, not what you learned or what the agent reasoned through.
  • A vector DB is the wrong default. Most queries are not semantic search problems. They are recall problems: "the Datadog one," "the Redis thing," "the session where we fixed ArgoCD."
  • Cloud sync creates the wrong risk surface. Session transcripts often contain internal URLs, secrets, and half-written credentials. Keeping them local is a feature, not a limitation.

The right primitive here is retrieval, not reinvention.

The design

Session files are already structured enough to index well. They are append-only JSONL with stable provider IDs, timestamps, and a predictable shape. That is enough to build a derived index that is:

  • Local-first so the raw transcripts never leave the machine that produced them
  • Cheap to refresh because unchanged files can be skipped using mtime and file size
  • Expressive enough to query because SQLite handles structured metadata and FTS5 handles full-text search
  • Disposable by design because the index is a cache, not a new source of truth

This matters because it keeps the system boring. There is no daemon to babysit, no server to deploy, and no synchronization story to debug. The index lives at ~/.local/share/sessiongrep/index.db. Delete it and the next reindex --full rebuilds it.

Architecture

sessiongrep ships two binaries: sessiongrep and sessiongrep-mcp. Both read from the same SQLite index.

Providers: Each source system gets its own adapter.

  • The Claude adapter walks ~/.claude/projects/**/*.jsonl.
  • The Codex adapter walks ~/.codex/sessions/**/*.jsonl and enriches those records with metadata from session_index.jsonl and state_5.sqlite.
  • The Cursor adapter walks ~/.cursor/projects/** and normalizes Cursor's thread transcripts into the same shape.

Adding a new provider is just another adapter over a new session format.

Normalization: Claude, Codex, and Cursor store different kinds of session artifacts, so sessiongrep collapses all three into one provider-agnostic Session model: ID, provider, title, summary, cwd, repo root, timestamps, preview text, and flattened transcript. Claude and Cursor subagent transcripts are intentionally excluded to avoid duplicate data.

Index: SQLite runs in WAL mode and stores structured session metadata in regular tables plus an FTS5 virtual table over title, summary, preview_text, and transcript_text. Search starts with FTS candidate retrieval, then rescoring combines fuzzy matching and metadata-aware ranking across title, summary, cwd, repo, preview, and transcript. The result is simple: good recall without extra infrastructure.

Incremental refresh: Every read command runs an incremental reindex first. Files whose mtime and size have not changed are skipped. Changed files are reparsed and upserted. In steady state, the refresh cost is small enough to disappear into normal terminal use.

Surfaces: The CLI exposes search, list, show, resume, export, doctor, and paths. The TUI, built on ratatui, gives live search, a preview pane, and one-key resume. The MCP server exposes the same index to agents.

sessiongrep never mutates provider state. sessiongrep resume <id> resolves the session and prints or executes the provider's own command: claude --resume <id> or codex resume <id> (Cursor transcripts are indexed and searchable, but resume isn't currently supported there). That constraint is intentional. sessiongrep is a recall layer, not a replacement runtime.

Why Rust? Because this kind of tool has to disappear into the workflow. A single static binary, fast startup, and low enough latency that searching old work feels cheaper than re-explaining it.

MCP: letting agents recover their own work

The architectural payoff is not just that humans can search session history. Agents can too.

sessiongrep-mcp exposes four tools over MCP:

  • search_sessions: Keyword search with optional provider filter and limit
  • get_session: Full transcript by ID, with max_lines to bound context
  • list_sessions: Recent sessions, filterable by provider and path prefix
  • get_resume_command: The exact native CLI command needed to resume a session

Installation is one line:

claude mcp add --scope user --transport stdio sessiongrep -- sessiongrep-mcp
# or
codex mcp add sessiongrep -- sessiongrep-mcp

After that, a prompt like: "find my previous session where I was setting up Datadog metrics and pull in the part where we got the agent to emit histograms" becomes self-serve. The agent can call search_sessions, inspect the candidates, then pull only the relevant slice with get_session.

✨ One accidental discovery was that retrieval quickly turns into review. Once an agent can pull a previous session into context, you can ask it to critique that earlier work with fresh eyes. Claude can review a plan drafted in Codex. Codex can poke holes in a migration written up in Claude. A prompt like "find the session where we outlined the Azure identity rollout and review the plan like a skeptical staff engineer; what assumptions are weak, what sequencing is risky, and what production questions did we miss?" is now a single prompt away. sessiongrep does not just help an agent remember what it said before; it gives the old reasoning a second pass.

Performance

The goal for sessiongrep was never benchmark theater. The goal was simpler: make recall cheaper than repetition.

On a local test corpus of 149 sessions across Claude Code and Codex CLI, the release binary rebuilt the full index in about 59 ms and returned a Datadog query in about 18 ms. The exact numbers will vary by machine and corpus size, but the important point is that the tool is fast enough to use by default instead of only when you are desperate.

What sessiongrep is not

sessiongrep is not a knowledge base. It does not deduplicate concepts across sessions, replace documentation, or decide what mattered for you. It is a recall layer over raw session history.

It is also only as private as the machine it runs on. Session transcripts contain whatever you typed and whatever the agent saw, including API keys, internal hostnames, and partially configured secrets. Keep the index local. Do not check it in. Do not sync it to anything you would not trust with raw shell history.

And do not treat searchable history as a substitute for writing things down. Agents can recover what happened. Humans still need to decide what it means.

What's next

There are a few obvious directions from here:

  • Related-session suggestions. Open one session and immediately surface the three most similar ones.
  • Richer MCP tools. diff_sessions, summarize_session, and timeline_for_repo would let agents reason across history instead of only retrieving it.
  • More providers. Aider, Cline, and whatever the next agent runtime turns out to be.
  • Optional encrypted sync. Not the default, but useful for teams that explicitly want shared memory.
  • Push-based updates. A watch mode for workflows where new sessions should become searchable immediately.

We would rather grow slowly and keep the tool boring. The best compliment sessiongrep can get is that you stop thinking about it.

A safety note

Treat retrieved sessions as untrusted input. If a past session ingested a poisoned page or tool output, surfacing it into a new agent context can carry that prompt injection forward. Same risk surface as pasting old transcripts in by hand, but worth naming.

Key takeaways

  • Session history already contains useful memory. The missing piece is an index.
  • SQLite + FTS5 is enough for this class of problem. You do not need more infrastructure by default.
  • A derived, read-only index is the right boundary. Do not create a second source of truth.
  • The biggest leverage is not just human search. It is giving agents access to their own prior work through MCP.
  • When recall becomes cheap, agents stop asking you to re-explain last week.

sessiongrep is open source under the Apache-2.0 license. Code, issues, and PRs welcome: github.com/braincompany/sessiongrep.