Meet ctx: a local quality authority for AI-written code
I build with AI coding agents every day, and at some point I had to admit something uncomfortable about how they work. They’re fast. They’re often right. And left unsupervised, they quietly make my codebase worse.
Not in dramatic ways. In small, compounding ones. An agent re-implements a helper that already exists three folders over, because it never saw the original. It edits a function and misses the caller two hops away that it just broke, because it grepped two files and moved on. It finishes, tells me it’s done, and it is done in the sense that the code runs, but nobody checked whether it fits the architecture I’ve been trying to hold together. Multiply that across a few hundred turns and you don’t have a codebase anymore, you have a pile of locally-plausible edits.
I got tired of catching this in review, so I built a tool to catch it earlier. That tool is ctx, and this post is me introducing it properly.
The actual problem
If you strip it down, agents fail at code in two directions, and they’re opposites.
They read too much. To feel safe, an agent dumps whole directories into the prompt. The ctx codebase as it stands is about 545,000 tokens wrapped as a single dump: past most context windows, and almost entirely irrelevant to any one task. You pay for those tokens, the model gets slower, and the signal drowns in noise. Call this a grounding failure.
Or they read too little. The agent greps a couple of files, makes its edit, and never learns about the dependency it just violated. Nothing flags the blast radius before the change lands. That’s a grounding failure and a governance failure at once: the agent didn’t know enough going in, and nothing checked what came out.
The tools we already reach for don’t close either gap. Grep and file-packers don’t understand code, they just move text around. Raw embeddings find code that looks similar but miss the relationships that actually matter. And almost nothing evaluates an agent’s change against the structure of the codebase before it ships. There’s no model of the world the agent is editing, and no guardrails on it.
The idea: build the model once, then use it for everything
ctx is a single local binary. You point it at a repo and it builds a queryable model of that codebase: every symbol, every call edge, every import, complexity, the hotspots. This is ctx index, and it’s fast enough that speed stops being an excuse. On this repo it indexes 870 symbols and 5,463 call edges in 0.36 seconds, reindexing only what changed after that.
Once that model exists, everything else is a query against it. And those queries fall into two jobs, which are the whole point of the tool: ground the input, govern the output.
Ground: the right context, in
The fix for “reads too much” isn’t a bigger context window, it’s better selection. ctx smart takes a task description, ranks files by meaning and by call-graph relevance, and trims the result to a token budget:
ctx smart "add rate limiting to the API" --max-tokens 8000
On the ctx codebase, where a full dump is around 545,000 tokens, that same task comes back as a bundle in the single-digit thousands: well over an order of magnitude smaller, same answer. The agent reads the code that matters instead of the whole repo, which is cheaper and, more importantly, more accurate, because you’re not asking the model to find a needle in half a million tokens of hay.
(The exact ratio moves as the repo grows, which is rather the point: the full dump balloons while a task-scoped bundle stays roughly flat, so the gap widens over time. Both numbers are measured on this repo at its current size, so treat the ratio as the durable claim and the absolute counts as a snapshot.)
There’s a whole family of retrieval commands sitting on the same model: ctx semantic for meaning-based symbol search, ctx source to pull the exact source for one symbol, ctx diff to bundle just the git changes with their call-graph context. Different shapes of the same question: what does the agent actually need to see right now?
Govern: guardrails, on what changes
This is the half most tools skip, and it’s the half I care about most. Grounding makes good edits more likely. It doesn’t make bad edits impossible. So ctx also checks what the agent produced, before it counts as finished.
The key design decision here is boring and load-bearing: the exit code is the verdict. 0 means clean, 1 means findings, 2 means error. That’s what lets these run as gates instead of suggestions.
# Enforce your architecture rules on the change
ctx check --against origin/main
# One composite gate: exit 1 means "not done yet"
ctx score --against origin/main --fail-on "check_violations>0,new_duplication>0"
ctx check reads architecture rules you write in .ctx/rules.toml (layer boundaries, dependency direction, structural limits) and enforces them against the actual edge graph. ctx score rolls complexity, duplication, and violations into a single diff-scoped scorecard. Before an edit lands, ctx query impact tells you what it would break across multiple hops, so the blast radius is known instead of discovered later.
The difference between this and a linter or a tool like knip isn’t the analysis, it’s when the analysis happens and who reads it. Knip runs at CI, hours after the code was written, and hands a report to a human who has to reconstruct the context and act on it. ctx runs inside the turn, hands structured output to the agent that just wrote the code and still has full context, and the gate means acting on it isn’t optional. Same finding, completely different position in the loop, completely different effect.
Where it actually lives: in the agent’s loop
None of this matters if you have to run it by hand. The point is that it’s automatic and in the loop, so ctx meets the agent where it works.
Over MCP, ctx serve --mcp exposes the whole world model as tools your agent can call directly: symbol search, definitions, references, callers, callees, smart context. Claude Desktop and other MCP-aware agents just get these as native capabilities. Every command also speaks --output json, so if you’d rather have a script or an agent shell out to ctx and read structured results, that works too.
And for Claude Code specifically, you don’t wire anything by hand:
ctx harness init --target claude
That installs the hooks: ctx map at session start so the agent begins oriented, ctx check after every edit, and ctx score as a Stop-gate so the turn can’t end while the change is failing your rules. The agent starts each session with a map, gets the blast radius as it edits, and can’t declare done past a gate. That’s the loop I wanted, running on its own.
What it is, and what it isn’t
Let me be precise, because the category is crowded and the distinctions matter.
ctx is not a file-packer. Repomix, gitingest, and files-to-prompt move text into a prompt; they don’t understand the code they’re moving. ctx is not an IDE indexer either. ctags and LSPs are built for a human navigating in an editor, not for grounding and gating a model.
What ctx is: a world model of your codebase built specifically to ground and govern LLMs. It combines tree-sitter call graphs with local vector search, so it finds code by meaning and then follows the relationships, which is the combination that neither grep nor embeddings gives you alone. It runs across Rust, TypeScript, JavaScript, JSX/TSX, Python, Go, Solidity, and YAML.
And it’s local, fully. Written in Rust, local embeddings, one portable SQLite file. No servers, no API keys required, and your source never leaves your machine. For code I actually care about, that last part is not a nice-to-have.
Try it
If you’re running agents against a codebase you have to maintain, this is for you:
cargo install agentis-ctx
Then ctx index, ctx embed, and point your agent at it. The getting started guide walks the whole loop, and using ctx with agents covers the MCP setup and the recommended workflow.
I’ll be honest about the state of it: the grounding half is mature and the numbers above are real, measured on ctx’s own repo. The governance half is where the interesting work is, and where I think the real value lives, because helping an agent read code better is table stakes now, but governing what it writes is the part almost nobody is doing locally and in the loop. If you try it, I want to hear where it breaks. That’s the fastest way this gets better.
Agents are going to keep writing more of our code. I’d rather they wrote it against a model of the codebase, and against my rules, than blind. That’s the whole pitch.