A code graph for agents that spans repos and parallel features A developer built `ivar graph`, a cross-repository code graph for coding agents that keeps a shared SQLite base index of each repo's default branch and per-feature layers holding only changed files, so agents working on parallel feature branches see their own edits without duplicating the rest of the repo. The MCP server reindexes stale layer files before every query — about 18 ms per call on a feature with 177 changed files — and fails loudly rather than answering from the base when a layer cannot be refreshed. In a two-repo Fastify/React fixture, agents with the graph were compared against the same model and prompt without it. Watch a coding agent start on an unfamiliar change and most of the early turns look the same. It greps for a name, opens three of the matches, finds an import, opens that file, greps for the caller, opens two more. Each read puts a whole file into context, most of which has nothing to do with the question. By the time it knows where the change goes, it has spent a large share of its budget on navigation. A code graph shortens that loop. If the agent can ask "who calls renderInvoice , and what does it touch" and get the answer with the relevant source attached, it skips the grep-read-grep cycle. That part is not new. Two things about how ivar is used made the usual shape of a code graph a poor fit. The change spans repositories. A hall mounts an API, a web client and a shared package side by side. The interesting edges are between them: the web client's HTTP request and the API route that serves it, or a type exported by the shared package and used in both. An index built per repo does not have those edges. Several features run at once. Each feature is its own branch, checked out as its own set of worktrees, often with an agent working in each. A graph scoped to a folder is built for one checkout. Point it at five feature checkouts and you either index the same repo five times or answer every session from one of them, which means some agents see code that is not on their branch. ivar graph keeps a single SQLite database for the hall at .ivar/memory.db . It holds a base index of each repo's default branch, shared by every session. On top of it, each feature gets a layer that holds only the files the feature changed, committed or not. A feature session reads its own layer first and the base for everything else. A discovery session reads the base. A feature usually changes tens of files, so a layer is small next to the base. Queries from a feature session resolve symbols against the layer first and fall back to the base, which is how an agent sees its own edits without the graph duplicating the rest of the repo. Extraction uses tree-sitter and runs in parallel. Base indexing is incremental: ivar diffs each repo against the commit it last indexed and checks file size and modification time, so a rerun parses only what moved. A layer goes stale the moment the agent edits a file. Asking the agent to call a reindex tool after each edit works until it forgets. So before every query the MCP server compares the layer with the worktree and reindexes the files that changed. That check has to be cheap to run on every call. On a feature with 177 changed files it took about 18 ms per call, and about 21 ms right after an edit. If the layer cannot be refreshed, the call fails with an error. It does not quietly answer from the base: an answer about code that is no longer on disk is the kind of wrong an agent acts on with confidence. The MCP server advertises one tool by default, graph explore . The agent passes symbol names, paths, or a short intent, several at once. The answer includes: Next: call with a paths argument listing the files that did not fit. The source is the part that removes reads. The Next: hint is the part that keeps the agent from falling back to opening files one at a time: a follow-up with paths returns up to 12 whole files in one call. The other tools callers, impact, affected tests, paths and more are available behind ivar graph mcp --tools all , and the queries have CLI equivalents, which matters for providers such as omp that do not pass MCP servers to subagents. A subagent can run ivar graph explore renderInvoice in a shell and get the same answer. We ran agents on the same fixture with and without the graph, using the same model and the same task prompt in both arms. The fixture is two repositories: a Fastify API and a React web app that calls it. The agent explores the fixture to answer a question about how the code works, and its answer is scored against a hand-built answer key. Three runs per arm. | | With ivar graph | Without a graph | |---|---|---| | Median tokens | 460k | 738k | | File reads | 0 | 12 to 28 | | Answer-key coverage | 93 to 94 % | 91 to 94 % | The graph arm answered as completely while reading no files; the source it needed arrived inside the query results. Tokens fell by about 38 %. The agent implements a change in the fixture that has to pass an acceptance suite. Five runs per arm, and every run in both arms passed. | | With ivar graph | Without a graph | |---|---|---| | Median tokens | 1.53 M | 1.61 M | | Median file reads | 6 | 26 | Here the token saving is small, about 5 %. Most of an implementation run is likely spent editing, running tests and reading their output, and the graph does not help with that. File reads still fell from 26 to 6, and the reads that remain are the ones the provider requires before it lets an agent edit a file. The fixture is small, so we also measured the graph on a working hall of about 29,000 files, which indexed to 144,000 symbols and 1.9 million edges. | Measure | Result | |---|---| | Index size | 370 MB | | Cold index | 77 s | | Reindex, no changes | 0.17 s | | MCP server startup | about 5 ms | | graph explore | 12 to 80 ms | | Callers query | 0.4 ms | | Impact query | 0.2 ms | These are small samples: three and five runs per arm, on one fixture, with one model. The discovery answer key was built by hand, so coverage is a judgment against it, not an absolute measure. Token counts vary run to run, which is why the tables report medians and ranges rather than a single number. The implementation result in particular should be read as "fewer reads for about the same cost", not as a large saving. Build the index from the hall root: ivar graph index The first run also registers the graph MCP server in ivar.json pass --no-mcp to skip it . Then run ivar sync to write the provider configs, and start a feature session. ivar sync also keeps the base index current from then on, and ivar doctor reports repos whose index has fallen behind. The Code graph guide https://ivar.run/docs/guide/graph covers layers and limits, and the reference https://ivar.run/docs/reference/graph lists every subcommand and MCP tool.