Why We Built Chronicle: Code Search Designed for AI Agents

Every AI coding agent has the same dirty secret: most of its context window is wasted on search results.

Ask an agent to find where PlayerHealth is used and it runs grep -rn "PlayerHealth" src/. Back comes 47 matches with 5 lines of context each. That’s 2,000+ tokens consumed before the agent even starts thinking. Half those matches are comments. A quarter are import statements. Maybe 3 are what you actually wanted.

This is the problem we kept hitting while building with Claude Code. Not that the AI wasn’t smart enough - it was drowning in noise before it could be smart.

The Search Landscape Today

There are three dominant approaches to code search, and none of them were designed for AI agents.

IntelliJ: Deep Semantic Understanding

IntelliJ (and the JetBrains family) builds a full Program Structure Interface - a deep semantic model of your codebase. It resolves types, traces call graphs, understands inheritance hierarchies. When you search for render, it doesn’t just find the string - it knows which render() on which class, called from which 7 locations.

This is powerful stuff. It’s also heavy. JVM startup, gigabytes of RAM, minutes of initial indexing. And none of this rich semantic data is exposed to AI agents. It lives inside the IDE’s process, optimized for GUI rendering, not for feeding into an LLM context window.

Cursor: Ripgrep + Embeddings

Cursor takes a dual approach. For instant search, it uses ripgrep - blazing fast text matching, same as you’d get from the terminal. For AI-assisted features, it chunks your codebase into embeddings stored in a vector database, enabling semantic similarity search (“find code that does something like this”).

The text search is fast but noisy - same grep problem. The semantic search is clever but opaque - you can’t filter by “only show me method declarations” or “only things changed in the last 2 hours.” And the embedding generation adds overhead you don’t control.

Grep/Ripgrep: Universal but Dumb

The fallback for every AI agent. Fast, available everywhere, zero setup. But pure text matching knows nothing about code structure. It can’t distinguish an identifier from a comment from a string literal. Every search returns noise that the AI has to parse through, burning tokens on context that adds nothing.

What We Actually Needed

Working with Claude Code daily, the pattern became clear. An AI agent navigating code needs three things:

  1. Find identifiers, not text. When searching for render, return the function declarations and usages - not every comment that mentions rendering.
  2. Understand structure without reading files. “What methods does Engine.ts have?” shouldn’t require reading 500 lines of implementation.
  3. Track what changed. “What’s different since I last looked?” is a constant question during iterative development.

None of the existing tools optimized for all three. So we built Chronicle.

How Chronicle Works

Chronicle is an MCP server that creates a persistent structural index of your codebase. Here’s what sets it apart.

Tree-sitter Parsing, Not Text Matching

Chronicle doesn’t search text. It parses your code into an AST using Tree-sitter (supporting 11 languages), extracts every identifier, and stores them in a normalized SQLite database.

Each identifier is classified by context: is it in a method declaration? A struct definition? A comment? A property? This means you can search for render and filter to only method declarations - something grep literally cannot do.

Language-specific keyword lists filter out noise. Chronicle won’t return matches on function, const, class, or 140+ other TypeScript reserved words that tell you nothing useful.

~50 Tokens Per Search, Not 2,000

A Chronicle query returns:

[{ file: "engine.ts", line: 45, type: "method" },
 { file: "player.ts", line: 23, type: "code" }]

File path, line number, structural context. No surrounding code. No 5-line context windows. The AI gets precise coordinates and can Read only the specific lines it needs.

Compare this to grep returning 47 matches with context:

ChronicleGrep
Tokens per search~502,000+
False positivesNear zero (parsed identifiers)High (comments, strings, imports)
Structural contextMethod vs comment vs propertyNone
PersistenceSurvives sessionsRuns fresh every time

Structural Signatures

chronicle_signature returns the complete structural overview of a file: all types, all methods with prototypes, visibility modifiers, async/static flags, header comments. An AI can understand what a 500-line file does without reading a single line of implementation.

Think of it as IntelliJ’s Structure panel, but exposed as an MCP tool that feeds directly into an AI’s context.

Every indexed line carries a modification timestamp. Search with modified_since: "2h" and get only what changed recently. This kills the “grep the whole project and hope for the best” pattern that wastes tokens on ancient, stable code.

The timestamps are hash-based: if a line’s content hash hasn’t changed, its timestamp is preserved even if the file was re-saved or the line moved. Chronicle tracks content changes, not file touches.

Persistent Across Sessions

The index lives in .chronicle/index.db - a SQLite database using WAL mode. It survives sessions, IDE restarts, and agent context resets. When a new session starts, chronicle_session detects what changed externally and re-indexes only those files.

This is a big difference from both grep (stateless, runs fresh every time) and IDE indexes (tied to the IDE process, lost when it exits).

Once we had a persistent, structured index living alongside the codebase, it became the natural place to attach other things an AI agent needs during a session.

Session notes. chronicle_note lets an agent leave reminders for the next session, stored in the same SQLite database, surviving context resets. “Test the glob fix after restart” is more useful than hoping the agent will remember.

Task backlog. chronicle_task and chronicle_tasks give agents a lightweight task manager that lives with the code index. Priorities, statuses, tags, auto-logging on state changes. No external project management tool needed. The agent creates, updates, and filters tasks as part of its workflow.

Interactive viewer. chronicle_viewer (or chronicle viewer from the CLI) opens a browser-based project explorer at localhost:3333 with live reload. File tree navigation, collapsible method signatures with counts, syntax highlighting with line numbers, git status, and a full task management UI with filtering, inline editing, and drag-and-drop reordering. It’s the human-readable window into what Chronicle knows about your project.

Screenshots. chronicle_screenshot captures fullscreen, active window, specific window, or region screenshots. Cross-platform (macOS, Windows, Linux). The agent takes a screenshot, gets a file path back, and can immediately read it for visual verification. No browser automation framework required.

Cross-project links. chronicle_link connects multiple indexed projects so a single query can search across your entire workspace. Monorepo-style navigation without monorepo structure.

One-command setup. chronicle setup auto-detects your installed AI clients and registers Chronicle as an MCP server in all of them (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI, VS Code Copilot). No manual config editing.

Full CLI. Chronicle ships with a complete command-line interface: chronicle init, chronicle viewer, chronicle scan, chronicle setup, chronicle --version, and chronicle --help. Running chronicle bare in a terminal shows a branded quick-start guide; running it from an MCP client starts the server silently. Smart TTY detection means the same binary works everywhere.

None of these features are the reason Chronicle exists. The core value is still structural code search at minimal token cost. But they make it more useful to keep Chronicle running as a persistent project companion rather than just a search tool you invoke occasionally.

What Chronicle Doesn’t Do

We’d rather be upfront about limitations:

  • No semantic/vector search. Chronicle can’t find “code that does something similar to X.” It’s purely lexical - identifier matching, not meaning matching.
  • No reference resolution. IntelliJ knows that player.render() calls Player.render() at line 45. Chronicle just knows “render” appears in both files independently.
  • No type inference. It doesn’t know that variable x is a Player and therefore can’t trace method calls through types.
  • No refactoring. It’s a read-only index. It tells you where things are, not how to safely change them.
  • 11 languages. Not everything.

These are deliberate trade-offs. Chronicle optimizes for one thing: giving AI agents the minimum tokens needed to navigate code accurately. Everything else is out of scope, for now.

The Design Principle

AI agents and human developers have very different search needs.

Humans need rich, visual results with surrounding context, syntax highlighting, and interactive navigation. They process information visually and can scan 50 grep results to find the 3 that matter in seconds.

AI agents need precise, minimal, structured results. Every extra token of context is a token not spent on reasoning. The agent can’t “glance” at results - it processes every token sequentially. Noise doesn’t just slow it down, it actively degrades performance (see: context rot research).

Chronicle is search designed for the second case. Not better than IntelliJ for humans. Not smarter than Cursor’s embeddings. Just more efficient at the specific job of feeding structural code information to an AI agent’s context window.

Performance at Scale

We recently tested Chronicle on a 148,000-file monorepo. The original implementation using npm’s glob library hung for 25+ minutes. Glob doesn’t prune excluded directories - it walks into node_modules and checks every file against ignore patterns.

The fix: replace glob entirely with a single-pass readdirSync walk using O(1) Set lookups for excluded directory names. The same monorepo now indexes in 16 seconds. Incremental re-indexing (only changed files) completes in under 2 seconds.

The viewer had a similar problem. Chokidar’s glob-based ignore patterns don’t prevent directory traversal, causing EMFILE: too many open files on large projects. Switching to a function-based ignore with the same O(1) Set lookup solved it completely.

What’s Next

The biggest remaining gap is reference resolution - moving from “this identifier appears here” to “this symbol is defined here and used there.” That’s the bridge between lexical search and semantic understanding. We’re exploring how to add lightweight reference tracking without going full Language Server Protocol.

Try It

npm install -g @tensakulabs/chronicle
chronicle setup

Two commands. chronicle setup auto-registers with your installed AI clients (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI, VS Code Copilot). Then ask your AI to run chronicle_init on your project, or do it yourself:

chronicle init .           # Index current project
chronicle viewer           # Open interactive explorer

Chronicle is open source: github.com/tensakulabs/chronicle

It’s an MCP server, which means it works with any AI agent that speaks MCP. Index your project, and your AI gets structural code understanding at a fraction of the token cost.


Chronicle is built by Tensaku Labs. We build the tools we wish our AI agents had.

← All posts