Skip to content

recall: One Search Bar Across Every Agent Transcript I've Ever Had

How I built a local SQLite FTS5 index over every Claude Code and Cursor conversation, and a prompt you can hand your own agent to build the same thing.

Matt Dennis

I have 607 agent sessions across three different tools and no way to search any of them. Claude Code writes JSONL to ~/.claude/projects. Cursor’s local IDE writes its own JSONL to ~/.cursor/projects. Cursor Cloud keeps its history behind an API. Every one of those transcripts holds a decision, a command, a root cause I already found once. None of it was searchable in one place.


recall fixes that. It is a Python CLI that walks all three sources, normalizes every message into one schema, and indexes it into a local SQLite database with FTS5. One command, recall "commit everything", searches everything I have ever typed to an agent or had an agent type back to me.


Why this exists


  • One index, three sources. Claude Code local, Cursor IDE local, and Cursor Cloud all land in the same table with the same schema.
  • Full-text, not grep. FTS5 ranks by relevance, not just presence, and it’s fast at 240MB and 37,000+ messages.
  • Incremental by default. Re-indexing only touches files that changed size or mtime, and cloud agents that changed fingerprint.
  • Runs itself. A launchd agent reindexes every 30 minutes with no intervention.
  • Context on demand. Every hit has a stable message ID. recall context <id> pulls the surrounding lines from that exact session without opening the file.
  • Cloud history survives deletion. If a Cursor Cloud agent disappears remotely, its transcript stays in my local index because it was already synced.

What’s actually in the index


Right now, on my machine:


Database:       ~/.claude/recall/recall.db
Schema:         2
DB size:        242.5 MB
Projects:       57
Sessions:       607
Messages:       37,485
  claude/local: 71 sessions, 9,128 messages
  cursor/cloud: 270 sessions, 1,184 messages
  cursor/local: 266 sessions, 27,173 messages

That’s every project I’ve touched with an agent this year, in one place, searchable by keyword, project, date range, role, or message kind — tool call, tool result, thinking block, plain message.


Using it


Bare arguments are shorthand for a search:


recall "commit everything"
recall search "sidecar" --provider cursor --runtime cloud
recall search "pytest" --source thought --limit 5
recall search "ReadFile" --kind tool --project salesforce

Filters compose: --provider claude|cursor, --runtime local|cloud, --project NAME, --since / --until dates, --role user|assistant|system, --source message|thought, --kind message|thought|tool|tool_result, --sort relevance|date, and --json for anything scripted.


Every result carries a stable message ID, so I never have to grep the raw JSONL by hand:


recall context MESSAGE_ID
recall context MESSAGE_ID --lines 10 --json

How the indexing works


recall index is incremental on purpose. Local files are tracked by size and modification time — no re-parsing a 500-message session because one byte at the end changed. Cursor Cloud agents don’t have a filesystem to stat, so they get a transcript fingerprint instead, and a failed API call during sync never rolls back the local indexing that already succeeded or deletes history already pulled down.


The database lives at ~/.claude/recall/recall.db. The first run against an existing Claude-only index upgrades the schema transactionally and labels the old rows claude/local, so nothing gets re-indexed just because the schema grew.


Authentication for Cursor Cloud goes through recall auth cursor, which writes the API key to macOS Keychain. The key is never written to the index, the logs, the script, or the launchd plist. CURSOR_API_KEY works too and takes precedence if set. Without a key at all, local indexing for both Claude and Cursor still runs fine — cloud sync just reports itself as skipped.


Set it up yourself


If you already run Claude Code or Cursor and want this exact tool, here’s the shape of it:


# dedicated runtime so it never touches your system Python
python3 -m venv ~/.local/share/recall/venv
~/.local/share/recall/venv/bin/pip install -r ~/recall/requirements.txt

# optional: only needed for Cursor Cloud sync
recall auth cursor

# first index
recall index
recall status

Then schedule it with a launchd agent so it stays current without you thinking about it:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.yourname.recall</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/you/.local/share/recall/venv/bin/python</string>
        <string>/Users/you/bin/recall</string>
        <string>index</string>
    </array>
    <key>StartInterval</key>
    <integer>1800</integer>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/you/.claude/recall/recall.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/you/.claude/recall/recall.log</string>
</dict>
</plist>

Save that as ~/Library/LaunchAgents/com.yourname.recall.plist and load it:


launchctl load ~/Library/LaunchAgents/com.yourname.recall.plist

Requirements are minimal: Python 3.10+, SQLite with FTS5 (standard on macOS), and cursor-sdk if you want cloud sync.


Build your own


I’m not shipping the source here — it’s a personal tool tuned to my own directory layout. What I can give you is the prompt I’d hand a coding agent to build the equivalent for whatever mix of tools you actually use.


Open the complete prompt
You are building a local, full-text search tool called recall over my AI coding agent transcripts.

## Goal

Normalize every agent conversation I have into one local SQLite database with an FTS5 virtual table, so I can run one search command across every tool instead of grepping raw JSONL by hand.

## Sources to index

Ask me which of these apply, then only build support for the ones I confirm:

- Local transcript files on disk (find their real path and format first — don't assume a schema, read a real file and infer it)
- A cloud/hosted history behind a REST API or SDK, if the tool has one
- Any secondary metadata files (chat titles, timestamps) stored separately from the main transcript

## Schema requirements

- One normalized table (or a small set of joined tables) covering: session id, project/workspace name, provider (which tool), runtime (local vs cloud), role (user/assistant/system), message kind (plain message, tool call, tool result, thinking/reasoning block), timestamp, and full text body.
- A companion FTS5 virtual table over the text body, kept in sync with triggers or an explicit rebuild step.
- Every row gets a stable, deterministic message ID so a "show me context around this hit" command can find it again later without re-parsing.
- Design for incremental re-indexing from day one: local files should be tracked by size + mtime so unchanged files are skipped; anything without a filesystem (cloud API results) should be tracked by a content fingerprint (hash of the transcript) instead.
- Write a schema version number into the database and a migration path, so future schema changes upgrade existing rows in a transaction instead of forcing a full re-index.

## CLI requirements

- Bare positional argument is shorthand for a search: `toolname "some text"`.
- `search` with filters: provider, runtime, project, date range (since/until), role, message kind, sort order (relevance vs date), and a `--json` flag for scripting.
- `context ` prints N lines of surrounding conversation around a given message, for both local and cloud-backed sessions.
- `index` runs the incremental sync; support a `--force` full rebuild and per-provider/per-runtime scoping.
- `status` reports database path, size, schema version, and per-source session/message counts.
- `auth` (if any source needs credentials) stores secrets in the OS keychain, never in the database, logs, script, or scheduler config. Support an environment variable override that takes precedence when set.

## Reliability rules

- A failed remote sync must never roll back or delete already-successful local indexing.
- Deleted-remotely content that was already synced locally should remain searchable.
- Log clearly when a source is skipped (e.g., no credentials configured) rather than silently indexing nothing.

## Automation

- Once the CLI works, generate a scheduler config for my OS (launchd on macOS, systemd timer on Linux, Task Scheduler on Windows — ask which) that runs `index` on a fixed interval, with stdout/stderr routed to a log file.

## Process

1. Read a handful of real transcript files from each confirmed source first. Do not guess the JSON shape.
2. Propose the normalized schema and get my confirmation before writing migration code.
3. Build indexing before search — I need to see row counts before I trust a query.
4. Build search filters incrementally, starting with plain keyword search, adding filters one at a time.
5. Write tests against small synthetic fixtures, not my real transcript text.

Point that at whatever tools you actually run — Claude Code, Cursor, Aider, Codex, a homegrown wrapper — and the agent will read your real transcript files, propose a schema, and build the same shape of tool: one database, one query language, every session you’ve ever had with it.


The interesting part was never the search bar. It was admitting that every agent conversation is disposable by default, and refusing to let that be true.