All posts

Devlog

Grep finds what changed — the reasoning chain tells you why

Two weeks ago a routine `just ctx-browse` blew up on me.

the failing command
Loading context docs from "docs/context"...
Failed to read index.json: The system cannot find the file specified. (os error 2)
error: ctx-browse exited with code 1
error: Recipe `ctx-browse` failed

Named file. Named binary. Named recipe. Looks like a broken script. My first instinct was to debug it as one — open the justfile, find the recipe, grep for `index.json`, trace what produces it.

That instinct cost me five tool calls and was heading for another five before I caught myself. The answer was sitting in my own context docs the whole time — captured as a reasoning chain, not as code — and would have been a two-call retrieval. This is a post-mortem of that detour, and of what a reasoning-chain knowledge base gives you that grep structurally can't.

What I actually ran

Before I caught the mistake, here's what my agent ran, in order:

  • 1. — `ls docs/context/ | head -30` — confirm the directory, check for `index.json`. Missing.
  • 2. — `ls justfile Justfile` — locate the recipe file.
  • 3. — `Grep ctx-browse` — find how it's wired. It builds the context engine's visual browser.
  • 4. — `Grep index\.json|load_index|read_index` in the browser's source — find where it expects the file. It reads a graph-data file from disk.
  • 5. — `ls meta-index.json && git log -- docs/context/index.json` — check whether the file was once tracked.

Five calls in, I had the local mechanics but none of the story. If I had continued — and I was about to — the minimum remaining calls to reach the same architectural picture:

  • 6. — `git log --diff-filter=D -- docs/context/index.json` to find the deletion commit.
  • 7. — `git show <sha>` to read the deletion commit message.
  • 8. — `Grep "index.json"` repo-wide to find any remaining producer.
  • 9. — Search for the now-deleted binary that used to generate the file. Grep + `git log` again.
  • 10. — Read whatever README or CLAUDE.md fragment finally explained the architectural shift.

Call ten, best case. And I would still only know **what changed** — not **what replaced it**, or **why the new system is shaped the way it is**. That context lives somewhere no `git log` line has ever lived: in human-written docs that explain intent, not delta.

What the reasoning chain gave me instead

After catching the mistake I went back to the retrieval path:

two calls, ~8K tokens
context_browse "context browser index.json generation docs/context"15 ranked sections with trigger phrases (~2K tokens)

context_fetch "context browser binary index.json visual graph D3 ctx-browse, JSON docs compile to a search index, context engine key files"
  → ~12 sections of full content + 1-hop graph expansion (~6K tokens)

That's it. Two calls — plus a one-time schema warmup for the MCP tools. ~8K tokens total. What came back:

  • The editorial fact I could not have grepped. — *"The old standalone build binary has been removed."* Written by a human, explaining intent, not derivable from a diff without the surrounding architectural narrative.
  • The replacement architecture in one shot. — A persistent local context server fronted by a thin HTTP/MCP client, with the cluster-assignment file as the surviving source of truth — the role of each component spelled out.
  • A third remediation option I would not have proposed. — Repoint the D3 browser at the running context engine's HTTP API instead of expecting a file on disk. From a bash-only investigation I'd have offered only "regenerate `index.json`" or "delete the recipe." The docs knew there was a better architecture hiding in the question.
  • Adjacent knowledge for free. — The rebuild command, where the compiled search index lives, and the fact that 200+ curated JSON docs and thousands of hand-authored trigger phrases back the retrieval.

Grep could have told me what `index.json` **was**. It could not have told me what the system **is now**. That distinction is the whole argument.

The numbers, side by side

tool-call cost comparison
path                                    calls       tokens     reasoning hops to diagnosis
--------------------------------------  ----------  ---------  ---------------------------
Bash / Grep / Read (first instinct)     ~812       5–15K      ~610
context_browse + context_fetch          2 (+1 warm) ~8K        2

What I actually did: 5 grep/bash calls, then 2 MCP calls after I caught the mistake, then synthesis. ~7 calls instead of ~4, plus the cost of reconstructing my mental model midway through.

What I got wrong, diagnosed

The symptom looked **local and concrete** — a missing file, a known recipe, a known binary. I pattern-matched to "debug a broken script" rather than "understand a subsystem." My own project's CLAUDE.md says browse-first for any codebase question. I implicitly excused myself because the error message looked self-explanatory.

It wasn't. The reason `index.json` is missing is an **architectural fact** — the file's producer was deliberately removed during a system migration — not a local bug. Any task whose root cause is *"the system around this code changed"* is a context-query task, even when it presents as a one-line error.

There's a conditioning trap here that I suspect applies to most engineers who got fluent on grep before they got fluent on retrieval: an error that names a specific file and a specific binary feels like it has a local fix. But the question *"why was this file expected and why is it gone"* has no local answer. It lives in the architecture story.

Honest cons of context-query

Before this reads as a sales pitch, the limits matter.

  • Docs lag disk state. — The context engine describes the world as documented, not as currently in the filesystem. If someone committed an hour ago and hasn't updated the relevant context doc, the engine will confidently tell you the old truth.
  • Coverage is uneven. — Newly added or undocumented systems won't show up. Trigger phrases are authored by hand; if nobody wrote one, queries miss. The engine is only as good as the docs behind it.
  • It can't read a specific file for you. — It gives you pointers, not current contents. For "what does this exact line do right now," grep and read are still correct.

The right play is a **hybrid in the opposite order from the one I ran**:

  • 1. — `context_browse` + `context_fetch` first — learn the system. (~8K tokens, 2 calls.)
  • 2. — One targeted Grep + Read afterwards — confirm the specific failing call site is what the architecture story says it should be. (~1-2K tokens, 1-2 calls.)

Total: ~3-4 calls, ~10K tokens, complete diagnosis with three informed remediation options. Half what I actually spent, with the overarching "what moved and why" context that grep-first would have missed entirely.

What the reasoning chain brings to the table

Hybrid keyword + semantic search is just the routing layer — plenty of tools have that. What makes the difference is what it routes you to: a reasoning chain — a human-distilled record of a piece of work, not a slice of source text.

  • The decision, not the diff — Why the build binary was removed and what replaced it, written down as intent. A git log shows the deletion; the reasoning chain explains the migration.
  • The alternatives that lost — Each rejected option and why it was rejected — so the next agent doesn't re-propose the dead end I was about to.
  • The systems it touched — Every chain links to the neighbouring systems it affected, so one answer pulls in the surrounding architecture instead of a single isolated fact.
  • Written for the next reader — Captured at the moment the work was done, for whoever hits the same question next — human or agent. None of it is derivable from the code alone.

Grep reconstructs what changed from the delta. A reasoning chain hands you the why someone already wrote down. On an architectural question, that is the difference between ten calls of detective work and a two-call answer.

The heuristic

If the plausible answer to *"why is this broken?"* is *"because something else moved,"* browse first.

Local-only errors — typo, off-by-one, null deref in code I'm currently editing — stay grep-first.

The tell: **would the explanation make sense to someone who only sees this one file?** If no, browse. If yes, grep.

In my failing `ctx-browse` case, no explanation of `Failed to read index.json` makes sense without the context-engine migration context. That was the signal I missed.

Why this is worth writing down

I suspect the grep-first instinct is common and under-examined. We spend so long training those reflexes that even an error message pointing at a file in a directory called `docs` feels like it has a local fix. The framing I needed was different: every debugging question has a disk layer and a design layer. The disk layer is in the code; the design layer lives in reasoning chains — and most sessions start on the wrong one.

The context engine described here runs locally as part of the Stardust Engine project — a 136K-line Rust game engine where autonomous agents plan, implement, review, and merge changes against production code, and every finished task is distilled into a new reasoning chain for the next one.