Code is naturally a graph — imports, calls, doc references. The obvious tool is Neo4j. Here's why I built a Cypher subset on SQLite instead, and the prior art that told me I'm not wrong to.
Code is naturally a graph. Files import files. Functions call functions. Docs reference symbols. The moment you want to ask a question like "what would break if I removed this export" or "which functions reach this one without going through the auth layer," you're asking a graph question, and a relational WHERE clause starts feeling like the wrong tool almost immediately.
So when I started building IntentWeave's evidence index — a local representation of a repo's AST, docs, and git history — the obvious instinct was: use a real graph database. Neo4j, Cypher, the whole stack. It's mature, the query language is genuinely pleasant to write, and there's a decade of tooling built around it.
I didn't do that for the core engine. Here's why, and why I don't think it was a compromise.
The problem with "just run Neo4j"
IntentWeave's core promise is that the index is local, free, and requires nothing beyond npm install. iw init && iw index build gets you a queryable representation of your repo in under three seconds, with zero servers and zero accounts. That constraint rules out a lot of otherwise reasonable architecture decisions, and a graph database with its own server process is one of them — the moment indexing your codebase means also running (and keeping alive, and upgrading, and securing) a database server, you've changed what the tool is. It stops being something you run in CI as a throwaway step and becomes infrastructure someone has to own.
That's not a knock on Neo4j — it's an excellent database for what it's for. It's a mismatch with "single SQLite file, works in a stateless CI runner, gone when the job ends."
I'm not the first to hit this wall
The most convincing evidence that this wasn't just me being stubborn came from a tool I didn't build: Joern, the code analysis platform used heavily in security research. Joern represents code as a Code Property Graph and, in earlier versions, actually ran on Neo4j with Gremlin-style querying. They moved away from it — replacing both the storage backend and the query language with their own embedded graph database, because the generic graph-database
approach hit real limits as the project matured. If a tool built specifically for deep, security-grade code graph analysis concluded that a general-purpose graph database wasn't the right long-term foundation, that's a strong signal for anyone building something adjacent.
What we built instead: CypherLite
IntentWeave's index is a single SQLite file. Nodes and edges are just tables:
-
Node labels:
FILE,SYMBOL,DOCSPAN,TODO,RATIONALE,SEMANTIC -
Relationship types:
IMPORTS,DEFINES,CALLS,ANNOTATED_BY,HAS_TODO,HAS_RATIONALE,SUMMARIZED_BY,CO_OCCURS,CO_CHANGES
On top of that, we wrote CypherLite — a hand-rolled subset of Cypher that parses a query like:
MATCH (a:SYMBOL)-[:CALLS]->(b:SYMBOL)
RETURN a.name, b.name
LIMIT 10
and compiles it down to SQL against those tables. No server, no driver, no separate process — it runs directly against .iw/index.db in the same process as everything else iw does. iw index schema prints the exact node labels, relationship types, and a set of named templates (@:callers-of, and others) so you're never guessing at property names before you write a query.
This is deliberately a subset. I didn't try to reimplement the full Cypher specification — things like arbitrary variable-length path patterns or the full aggregation surface aren't there.
What I optimized for is the 90% of ad hoc questions that don't fit any of our ~30 built-in query modes (retrieve, connections, context-pack, and so on) but also don't need the full power of
a general-purpose graph query language. "Find every function that calls validateToken without going through rateLimiter" is a completely reasonable one-off question, and it shouldn't require
standing up a database to ask it.
Where this ends up mattering
The unexpected payoff showed up somewhere I didn't originally design for: AI coding agents. I recently shipped an Agent Skill for
Claude Code, Copilot, and Cursor that teaches the agent to ground itself in a repo's real architecture — running iw index context-pack before touching unfamiliar code, checking iw intent check before finishing a change, and, when neither covers the question, falling back to a raw CypherLite query. Because there's no server to configure and no credentials to manage, an agent with ordinary shell access can just... run it. That's a much lower bar than "first, set up an MCP connector to a graph database," and it only works because the whole thing lives in one file.
The trade-off, honestly
You give something up here. If you need arbitrary, unbounded graph traversal at scale — the kind of workload Neo4j is actually built for — SQLite-backed CypherLite is not that tool, and I'm not claiming it is. What you get in exchange is a graph query layer that installs in three seconds, runs in a GitHub Actions job with no extra service, and an AI agent can invoke directly without any infrastructure conversation happening first. For a local-first dev tool, that trade
was an easy one to make once we actually weighed it — and Joern's own history made me a lot more confident we weren't missing something obvious by making it.
IntentWeave is open source. The CLI, the schema, and CypherLite itself are all in the [GitHub repo (https://github.com/intentweave/intentweave) if you want to see how the query compiler works.
Top comments (2)
The strongest point here is that “single SQLite file, works in CI, no extra service” is not an implementation detail, it’s the product boundary. Once a local dev tool requires a separate graph server, you’ve changed the operational model for both humans and coding agents.
I also like the Joern reference because it turns the tradeoff from a personal preference into prior art: sometimes the generic graph stack is elegant at query time but wrong for distribution, startup cost, and day-to-day ergonomics. For agent workflows especially, “ordinary shell access can use it immediately” is a much bigger advantage than people admit.
This feels very aligned with the kinds of local-first inspection loops that make tools like agent-inspect useful too: the more context/debug state lives in ordinary files and ordinary process boundaries, the easier it is to inspect, replay, and trust. Curious whether you expect CypherLite to stay a targeted escape hatch, or whether agents are already pushing it toward a first-class query surface.
Thanks, that's a fair way to put it; The file/process boundary is the actual product decision, the query language choice is downstream of that.
Right now it's designed as an escape hatch on purpose. The three built-in workflows (context-pack, retrieve, connections) cover what I expected agents to need most of the time, and CypherLite is there for when those don't. I don't have real usage data yet since the Skill only shipped a few days ago, so I genuinely don't know if that split holds up once agents are actually running it against real repos.
My guess is it stays an escape hatch rather than becoming first-class, mostly because agents seem to do better with a small number of well-labeled tools than with an open-ended query language they have to get the schema right for every time. Even with iw index schema available, that's still more room to hallucinate a wrong property name than picking from a fixed command. But that's a guess, not something I've tested against real agent logs. If it turns out agents reach for cypher constantly, that would be a signal the built-in queries are missing something, and honestly a more interesting finding than "cypher turned out to be popular."
Will check out agent-inspect, the description matches something I've been circling for iw itself - right now there's no way to see what an agent actually pulled from the index during a session, which sounds like exactly the "context/debug state in ordinary files" problem you're describing.