File System as Claude Code's Memory


I’m bearish on any sort of memory system that isn’t integrated into the model or a fronteir capability for continual learning. The same is true for integrated auxiliary context systems; things that may naively/blindly pollute the context window. I don’t think the overhead & NLP overlap is worth it in the early days of agentic systems like Claude Code.

With a simple Agent Skill called rg_history, we can give Claude Code the ability to search its own memory.

I think we’re still figuring out what it really means to have an agent on a computer and to be able to access things like the file system. A benefit of Claude Code is its entire memory already exists on the file-system.

Claude Code stores every conversation as JSONL files on disk. Each message, tool call, file edit, and decision lives in ~/.claude/projects/{project}/. All rationale and iteration over time right here, perfectly preserved with extra context that wasn’t even visible in chat. This is already a memory system. We just need to read it.

What’s On Disk

~/.claude/projects/-Users-ramos-my-project/
├── abc123-def4-5678-....jsonl    # Main session (UUID)
├── agent-a1b2c3d.jsonl           # Sub-agent spawned by Task tool
└── ...

Each line is a JSON object:

  • "type":"user" with "userType":"external" — human input
  • "type":"assistant" — Claude’s responses
  • "name":"Edit" — file modifications with old_string and new_string
  • "name":"Bash" — commands that were run

Everything is there. Chronological. Structured. Greppable.

ripgrep

ripgrep (rg) is a line-oriented search tool. It’s fast enough that searching megabytes of session history feels instant. More importantly, it’s precise: regex patterns, file filtering, context lines, match limiting.

The key insight for JSONL: each event is one long line. Raw output is unreadable. But ripgrep can extract snippets:

# Count matches first
rg -c 'authentication' session.jsonl

# Extract ~60 chars around each match
rg -o '.{0,60}authentication.{0,60}' session.jsonl | head -20

# Chain filters to narrow
rg -o '.{0,60}authentication.{0,60}' session.jsonl | rg 'Edit'

This is the entire technique. Count, snippet, narrow, then full context only when you know what you want.

What We Tried

We built a Rust CLI called lookback with boolean query parsing (AND/OR operators) and snippet extraction. It works. It’s also unnecessary complexity.

When we compared it against raw ripgrep for finding specific information in session history, ripgrep won. Fewer queries, clearer results. The structure of the raw JSON actually helped — seeing "name":"Edit" next to "file_path" immediately shows what happened.

The boolean query parser was clever. But rg 'pattern' | rg 'filter' does the same thing.

Why This Works

Session files are append-only logs. New events add lines; nothing mutates. This means:

  • No index invalidation
  • No sync issues
  • No cold start — files are already there
  • Debugging is trivial: basically cat the file

Implementation (Agent Skill)

https://github.com/backnotprop/rg_history

A skill file teaches Claude Code how to search its own history:

  1. Find session files for the current project
  2. Use ripgrep with output limiting (snippets, not full lines)
  3. Iterate: count → sample → narrow → extract

The skill is ~100 lines of markdown. No database. No embedding service. No API keys.

The file system was the memory system all along.