A code graph your agent can actually ask
Generated into your work tree from the source, with no language model in the loop. Agents run one command to learn who calls a method, what extends a class or what a change touches, instead of opening dozens of files.
./install.sh --repo ~/code/my-projectWhat it gives an agent
Three things a grep loop does badly, each one command.
Precise answers
Callers, dependents, routes and tests, each with a file and line to open. Java call links match compiled bytecode on 13,380 of 13,380 high-confidence claims.
Whole hierarchies
kg subclasses returns every subclass or implementer at any depth, optionally filtered to those that override a method.
Honest labels
Every line is tagged [high] or [medium], and agents are told the source wins when a generated line is wrong.
Examples from real repositories
The diagrams below are drawn from the actual graph.json of each project, and the terminal output is what kg printed. Long package names are shortened with an ellipsis.
Routes, the controllers that handle them and the repository they use. Every edge in the diagram is [high]: written in the source.
graph.json of Spring Petclinic.route:GET /owners -> …owner.OwnerController#processFindForm view=owners/findOwners [high] view=owners/ownersList [medium] redirect=redirect:/owners [high] redirect=redire…
route:GET /owners/find -> …owner.OwnerController#initFindForm view=owners/findOwners [high] …/owner/OwnerController.java:89
route:GET /owners/new -> …owner.OwnerController#initCreationForm view=owners/createOrUpdateOwnerForm [medium] …/owner/OwnerController.java:72
route:GET /owners/{ownerId} -> …owner.OwnerController#showOwner view=owners/ownerDetails [high] …/owner/OwnerController.java:178
[medium] calls method:…owner.OwnerController#findPaginatedForOwnersLastName …/owner/OwnerController.java:145) [medium] calls method:…owner.OwnerControllerTests#processFindFormByLastName …/owner/OwnerControllerTests.java:193) [medium] calls method:…owner.OwnerControllerTests#processFindFormIgnoresSurroundingWhitespace …/owner/OwnerControllerTests.java:202)
[high] tests type:…owner.OwnerControllerTests …/owner/OwnerControllerTests.java:60
Every subclass of a Celery base class at any depth, in one call. The same query by hand is a loop of greps.
graph.json of Click and Celery.d=1 [high] click.core.Argument (extends Parameter) src/click/core.py:3800 d=1 [high] click.core.Option (extends Parameter) src/click/core.py:2963 2 subclasses
d=1 [high] celery.backends.base.KeyValueStoreBackend (extends BaseKeyValueStoreBackend) celery/backends/base.py:1420 d=1 [high] celery.backends.redis.RedisBackend (extends BaseKeyValueStoreBackend) celery/backends/redis.py:372 d=2 [high] celery.backends.arangodb.ArangoDbBackend (extends KeyValueStoreBackend) celery/backends/arangodb.py:23 d=2 [high] celery.backends.azureblockblob.AzureBlockBlobBackend (extends KeyValueStoreBackend) celery/backends/azureblockblob.py:24 d=2 [high] celery.backends.cache.CacheBackend (extends KeyValueStoreBackend) celery/backends/cache.py:183 d=2 [high] celery.backends.consul.ConsulBackend (extends KeyValueStoreBackend) celery/backends/consul.py:27 … 16 subclasses
[medium] calls method:click.core.Command#parse_args src/click/core.py:1399
d=1 [medium] method:click.core.Command#parse_args -> method:click.core#iter_params_for_processing src/click/core.py:1403 d=1 [medium] method:test_commands#test_iter_params_for_processing -> method:click.core#iter_params_for_processing tests/test_commands.py:417 d=2 [medium] method:click.core.Command#make_context -> method:click.core.Command#parse_args src/click/core.py:1393 d=2 [medium] method:click.core.Group#parse_args -> method:click.core.Command#parse_args src/click/core.py:2042 d=3 [medium] method:click.core.Command#main -> method:click.core.Command#make_context src/click/core.py:1591 d=3 [medium] method:click.shell_completion#_resolve_context -> method:click.core.Command#make_context src/click/shell_completion.py:711 d=3 [medium] method:click.core.Group#resolve_command -> method:click.core.Group#parse_args src/click/core.py:2142 7 callers within 3 levels
Callers of a function across React pages and Next.js route files. Dashed edges are [medium]: resolved by a rule, so check the cited line.
graph.json of Next.js and NestJS.[medium] calls method:src.app.[locale].(auth).(center).sign-in.[[...sign-in]].page.SignInPage#render src/app/[locale]/(auth)/(center)/sign-in/[[...sign-in]]/page.tsx:27 [medium] calls method:src.app.[locale].(auth).(center).sign-up.[[...sign-up]].page.SignUpPage#render src/app/[locale]/(auth)/(center)/sign-up/[[...sign-up]]/page.tsx:27 [medium] calls method:src.app.[locale].(auth).dashboard.user-profile.[[...user-profile]].page.UserProfilePage#render src/app/[locale]/(auth)/dashboard/user-profile/[[...… [medium] calls method:src.app.sitemap#sitemap src/app/sitemap.ts:21
[high] depends_on type:src.app.[locale].(auth).dashboard.layout [imports BaseTemplate] src/app/[locale]/(auth)/dashboard/layout.tsx:6 [high] depends_on type:src.app.[locale].(marketing).layout [imports BaseTemplate] src/app/[locale]/(marketing)/layout.tsx:5
[medium] calls method:packages.core.middleware.builder.MiddlewareBuilder.ConfigProxy#removeOverlappedRoutes packages/core/middleware/builder.ts:127 [medium] calls method:packages.core.middleware.route-info-path-extractor.RouteInfoPathExtractor#constructor packages/core/middleware/route-info-path-extractor.ts:22 [medium] calls method:packages.core.router.route-path-factory.RoutePathFactory#appendToAllIfDefined packages/core/router/route-path-factory.ts:106 [medium] calls method:packages.core.router.route-path-factory.RoutePathFactory#create packages/core/router/route-path-factory.ts:71 [medium] calls method:packages.platform-express.adapters.express-adapter.ExpressAdapter#normalizePrefix packages/platform-express/adapters/express-adapter.ts:597
Knowing what to trust
A graph that is sometimes wrong is only useful if the agent knows which parts. Each fact carries a tag, and the installer's instruction block tells agents how to treat each one.
Written in the source: a declaration, an import, an annotation, a literal path, a config entry.
Derived by a fixed rule, such as a call target resolved from a declared type. Open the cited line before relying on it.
Reserved for claims written by a model. The format supports it; the installer never produces it.
Install
Needs git and Python 3.10 or newer. Run it once per repository, then commit the result.
- Copies the generator to
~/.local/share/agent-graphwith its own virtual environment. - Generates
.agent-knowledge/for the repository. - Adds a marked block to
AGENTS.md,CLAUDE.md,.github/copilot-instructions.mdand a Cursor rule. - Installs git hooks that keep the graph fresh.
# Windows PowerShell .\install.ps1 --repo C:\code\my-project # remove everything it added ./install.sh --repo ~/code/my-project --uninstall
Staying fresh
The graph is a snapshot, so it has to follow the code. Two layers, both optional and both reversible.
Git hooks (default)
The installer adds post-merge, post-checkout, post-rewrite and pre-commit hooks. The graph refreshes after a pull, a branch switch or a rebase, and a commit that touches source files carries its updated graph. Existing hooks are kept. Skip with --no-hooks.
GitHub Actions (--ci)
--ci check fails a pull request when the committed graph is stale. --ci sync regenerates it on pushes to main and commits it only when the claims changed. Both modes were run end to end on a test repository.
Evidence, without the gloss
The graph is accurate and agents use it. Whether it saves tokens depends on the repository and the agent.
| Question | Result |
|---|---|
| Are the facts correct? | Java: 13,380 of 13,380 high and 8,947 of 8,957 medium call links match bytecode. Python: 87% of call edges whose caller ran were seen at runtime. TypeScript: 32 of 32 sampled edges correct. |
| Do agents use it? | Yes. Claude Code, Codex, Cursor and the Copilot CLI all found and used kg from the generated instructions. 192 controlled runs gave correct answers in every arm, and a later 36-run trial added Cursor and Copilot. |
| Does it save tokens? | It depends on the question and the agent. On a large Java repository, yes: Codex used 398k tokens against 953k. On Python and TypeScript, one-hop lookups cost the same or more. On a multi-level question (callers up to three levels away, with kg callers --depth 3), 8 runs per arm on Click: Codex 95k tokens against 255k (0.37x), Cursor 122k against 358k (0.34x), Copilot 178k against 158k (no gain). kgen route turns such results into per-agent advice. |
| How accurate is a repository's graph? | kgen audit checks any repository against its own source text. Click: every sampled call edge is real, and 98% of bare and self. calls have an edge. Express: every sampled edge is real, but only 55% of bare and this. calls have one, and calls through untyped values such as res.send(...) in example code are missed. |