graphy
graphy
Give your coding agent a memory it can grep. Give it a codebase it can walk.
Every /clear wipes your agent's mind. Every compaction throws away the three hours it just spent
learning your code with you. graphy fixes that with a text file and a grep, then goes one step
further: it compiles the whole codebase, dependencies included, into a graph the agent walks
instead of reads.
pip install graphyos
cd /path/to/your/repo && graphy eat . && graphy shell install --repo "$PWD"Two commands. After that:
Sessions carry over. The next session opens by reading the end of the last one, verbatim. Not a summary. The actual exchanges, tool noise stripped, a sha on every tail.
Every conversation is searchable at grep speed. The archive is plain markdown under
.claude/recovery/sessions/. Two words give you every session where they were discussed together, as a heat map with the passages, in a tenth of a second.The code is a graph, not a pile of files. Does A reach B. What breaks if this changes. Who calls this class across every package in the ring. Answered from structure in milliseconds, served over MCP to Claude Code, Cursor or anything else that speaks it.
The memory is welded to the code.
eatmints the repo's own record as a shard beside the code: its commits, and every exchange in the archive bound to the symbols it names. Soblastlists the conversations that discussed a function beside its callers, andgraphy history --symbol <name>walks from the symbol into every session that mentioned it, oldest first, with the commits.graphy checknames the shard stale when the archive grows;graphy shell installre-mints it.
No summarizer. No embeddings. No model ever decides an edge or picks what mattered. The core is the Python standard library, ripgrep and three shell hooks, with zero runtime dependencies.
Created by Matt Hartigan, 2026. Apache 2.0.
How the memory works
Three Claude Code hooks. PreCompact and SessionEnd write the session as 1:1 user/assistant
exchanges to .claude/recovery/reseed_tail.md and archive every distinct tail in sequence.
SessionStart on startup, clear or compact prints the newest tail as context and names the file
holding the whole record. A tail the parser cannot trust is refused loud, never injected hollow; a
hook that cannot help never wedges the session.
python3 -m graphy.lightning.bloodhound "gallery" --with "showcase"
# co-occurrence: 5 file(s) hold BOTH terms within ±10 tokens
# HEAT — which files this term DOMINATES … the daisy-chained spans, hottest file firstlightning is the search half: ripgrep discovers the files, lightning walks each hit out to the
function, class or exchange that holds it. bloodhound maps two terms across the archive over
time. Every exchange is also a node in the code graph, bound to the symbols it names, so the walk
verbs answer "what did we say about this" with no archive read. Claude Code is the first harness; graphy shell install --harness codex and --harness cursor write the wiring those two read, and the capture reads each one's transcript by its shape. The lane in full:
engine/graphy/shell/README.md.
How the graph works
One command mints your package and every package it imports into a walkable substrate, resolves who calls what through the code's own scope, compiles it into a store, and audits the result. Every edge is structural, a wormhole between shards on a shared literal, or a label bound through real scope. A name match is never an edge. Every answer is a walk over structure, and every walk is a query, never a load.
The thirty-second demo
One blast-radius question on FastAPI, three ways, every answer scored against the store's own
list of dependents. Run on this box; the full output is in RECON.md §20.
`iterate_in_threadpool` in Starlette is about to change its signature. What in FastAPI breaks, and through which call chain?
┌────────────────────────────────┬──────────┬───────────┬──────────┬────────────┐
│ way │ seconds │ tokens in │ tok out │ dependents │
├────────────────────────────────┼──────────┼───────────┼──────────┼────────────┤
│ graphy alone, no model │ 0.00 │ 0 │ 0 │ 5/5 │
│ claude-haiku-4-5 + graphy MCP │ 14.8 │ 48266 │ 1286 │ 5/5 │
│ claude-opus-5 + 270 KB source │ 26.5 │ 103171 │ 2108 │ 5/5 │
└────────────────────────────────┴──────────┴───────────┴──────────┴────────────┘A cold small model with graphy's five walk tools (draw came after this run) and nothing else names the same five functions, with
the chain, as a frontier model handed the three right source files by hand. The store alone does
it in a millisecond.
bash engine/tenants/fastapi/rebuild.sh # the FastAPI tenant, from the vendored shard
.venv/bin/python engine/tenants/fastapi/demo.py --runner claude-code # on your Claude Code login
ANTHROPIC_API_KEY=… .venv/bin/python engine/tenants/fastapi/demo.py # or the SDK (pip install anthropic)Point any MCP client at the same server: Claude Code reads it from this repo's .mcp.json;
anywhere else, bash engine/tenants/fastapi/mcp.sh on stdio, or graphy mcp --tenant … --tenant-id …
over any eaten repo. Six tools: hunt · descend · blast · walk · draw · explain.
Over your own repo, no pointer to write: graphy eat . once, then the Claude Code plugin at
.claude-plugin/plugin.json runs graphy mcp --repo "${CLAUDE_PROJECT_DIR}" —
the server reads .graphy/tenant.json under the project root and the package name from the ring
receipt eat left, and refuses by name when the repo has not been eaten. The same argv is the MCP
registry's entry, server.json (uvx graphyos mcp --repo <repo>).
claude plugin marketplace add omnislash157/graphyos # the repo is the marketplace (.claude-plugin/marketplace.json) …
claude plugin install graphy@graphyos # … and the plugin; inside Claude Code the same two as /plugin …
claude --plugin-dir /path/to/graphyos # or a checkout, no marketplace: the checkout is the pluginThe page, one line
uvx --from 'graphyos[typescript]' graphy showcase . # no venv, no install — the page at .graphy/showcase/index.htmluvx is uv's runner: it makes the venv, installs graphyos
into it and runs graphy showcase over the repo you are in — the modules drawn, the pillars the
walk proposes, the ring, the MCP block, three questions. Run on this box from a scratch venv
holding nothing but uv (RECON.md §86). This is the drawing it makes of graphy itself, from
graphy's own store — docs/pillars.svg, written by the graphy tenant's rebuild,
re-rendered and compared byte for byte in the gate, never drawn by hand:
Install, then eat — two lines
pip install 'graphyos[estate,typescript]' # PyPI: graphyos 0.2.3 — the extras are optional
cd /path/to/your/repo && graphy eat . # a repo with several packages: graphy eat . --package <name>
graphy showcase . # the page: .graphy/showcase/index.html — open it in a browserThe distribution is graphyos; everything you type after install is graphy. Python 3.10+ on
Linux and macOS; on Windows, through WSL — eat resolves a venv on either layout by sysconfig,
but the hooks, shell install and the MCP pointer are bash, and the store lock is a named no-op
without fcntl.
[estate] is DuckDB for the parquet estate; without it every JSON door still works and the build
says CONTAINER SKIPPED instead of pretending. [typescript] is tree-sitter for the TypeScript
and JavaScript producer.
eat provisions the repo's own dependencies beside it (a venv with pip install <repo>, or
npm install for a package.json repo — printed, and a repo that will not install is minted
alone and the line says so), mints the package and every package it imports, resolves the labels
through scope, compiles the store, audits it, and ends with the three things you do next: the
MCP block to paste into Claude Code or Cursor, the drawing, three questions.
Eating a repo you do not trust runs its build. pip install <repo> runs that repo's build
backend on your machine, and npm install its dependencies' installs (scripts off). For a
stranger's repo, graphy eat . --no-provision (and graphy showcase <url> --no-provision)
runs nothing of theirs: the package is minted from its source with an empty ring, every import
left unresolved by name, no venv, no pip, no npm. --site-packages names an install you
already have and skips the provisioning.
What it reads today. Python, through the standard library's own parser; TypeScript (with TSX) and JavaScript (ESM and CommonJS), through tree-sitter. Nothing else yet: a language is a producer, a resolver and a locator, and those are the ones that exist.
eat prints every step as it lands and ends with what you do next:
MINT OK: httpx 538 nodes / 2548 edges -> …/.graphy/substrate/httpx_graph
MINT OK: httpcore 539 nodes / 2251 edges -> …/.graphy/substrate/httpcore_graph
…
RING: 7 shard(s) · stdlib skipped 62 · unresolved brotli, click, h2, … -> …/.graphy/substrate/ring.json
RESOLVE OK: httpx 1658 label(s) -> … edge(s) (import … · local … · reexport … · self … · super …)
BUILD OK: compiled 2522 nodes / … edges -> …/.graphy/substrate/.mesh_store_….sqlite
CONTAINER PENDING: 7 shard(s) — graphy estate emits them on the first ask, graphy container --emit writes them now
CHECK OK: descriptor valid; store fresh; journal readable for all declared graphs; container fresh for 0/7 shard(s), 7 pending until the estate asks
EAT OK: httpx + 6 ring shard(s) -> /path/to/repo/.graphy
the tenant: --tenant /path/to/repo/.graphy/tenant.json --tenant-id httpx
a walk: graphy walk --tenant … --tenant-id httpx --seed httpx://module/httpx --target certifi://module/certifi
the estate: graphy estate --tenant … --tenant-id httpx
the walks: graphy traversals --tenant … --tenant-id httpx [--replay]# 3. does A reach B — across packages, on the literal
graphy walk --tenant /path/to/repo/.graphy/tenant.json --tenant-id httpx \
--seed httpx://module/httpx --target certifi://module/certifi
# WALK PATH: hops=2 steps=httpx://module/httpx -> httpx://module/httpx._config -> certifi://module/certifi
# TRAVERSAL: source=live reads=15 stored=…/.graphy/substrate/traversals/<generation>/<seed>.parquet
# run it again: source=store reads=0 — the walk is kept as rows; a walk from another seed that
# crosses this one splices through it, and after the repo moves `graphy traversals --replay`
# names the hops that no longer hold
# 4. the whole ring as one SQL view: adj(corpus, src, dst, edge_type, attrs, dst_repr, src_repr) and nodes(corpus, id, …)
graphy estate --tenant /path/to/repo/.graphy/tenant.json --tenant-id httpx \
--sql "SELECT a.corpus, n.corpus, count(*) FROM adj a JOIN nodes n ON a.dst = n.id WHERE a.corpus <> n.corpus GROUP BY 1, 2 ORDER BY 3 DESC"
# httpx_graph httpcore_graph … ESTATE OK: 9 row(s) over 7 shard(s) in 5.8 ms
# 5. is it still true — the store against the repo's HEAD, the parquet against the shards
graphy check --tenant /path/to/repo/.graphy/tenant.json --tenant-id httpxEverything lands in <repo>/.graphy/, which ignores itself (your git status stays clean) and
is rebuilt from the previous shards every time. It holds a shard per package in the ring, so a
big application eats big: a company repo with numpy, networkx and livekit in its ring lands 165
shards and about a gigabyte beside the repo, in a hundred seconds (15,456 files parsed). A repo with several importable packages
is refused by name before anything is installed — graphy eat . --package <name> picks one.
Node ids are <package>://<module|class|func|method>/<dotted>.
Or let the script do all five
bash quickstart.sh https://github.com/encode/httpx.git
# … GRAPHY_QUICKSTART_OK: httpx eaten in 5.9s -> staging/quickstart/httpx/.graphyClones the repo, installs its dependencies into a venv of their own, eats it, runs the estate
query and the walk, and prints the done token with the time. That line is the proof this README
is true on the machine it ran on; RECON.md §16 records the last run.
What an edge is, and is not
Every edge is one of three things: structural (from the syntax tree — imports, contains,
inherits, decorates, calls), a wormhole (the same literal is a node id in two shards, free by
construction: starlette://module/starlette.routing is an edge target in FastAPI's shard and a
node in Starlette's), or a resolved label — a call target the producer left as text, bound
through the scope that actually binds it: a definition in the same module, the module's own
imports edges (through re-exports, one hop at a time), self against the containing class,
super against a resolved base. A name that no rule reaches stays text, with the qualified
literal it would need and the reason no node carries it. No name match is ever an edge.
Beyond the five
move | the door |
mint one package and its ring anywhere |
|
upstream moved: the newest release minted into a sibling substrate, proven, and the born/died diff per shard |
|
one question across all of it: who imports what, the most-inherited classes, who shells out — over every release in the index, in milliseconds |
|
the open index: the top N PyPI packages minted into one content-addressed index, in parallel, resumably |
|
push a shard into an index; pull one back, every byte verified |
|
the seam between shards, and the resolver |
|
the parquet: fresh or stale, by digest |
|
the stored walks; the hops a new generation broke |
|
the showcase: one page of your codebase, made by one command — the modules drawn (click to light what reaches what), the pillars the walk proposes, the ring, the MCP block, three questions, how to add a model |
|
the gallery: the same page over ten repos people know, one index, one receipt — the site at graphy-os.com, built inside the Dockerfile at the root so every deploy is a fresh gallery on the current engine |
|
draw it for the human: the pillars, the module map, one arm, a symbol's neighbourhood — computed from the store, ASCII in the terminal or one self-contained HTML+SVG page, no drawing by hand |
|
the doors: what a symbol calls down to the primitives, who depends on it, what explains it |
|
the MCP server over an eaten repo, for Claude Code / Cursor / any client |
|
the hooks and the gate, bolted onto your repo — and the memory lane with them: every session captured as 1:1 exchanges under |
|
the pillars proposed from the walk, with the evidence per unit; a partition file the fan-out takes |
|
the fan-out a cold agent reads; one package cut into named pillars |
|
two tenants in one process, and a walk from one into the other on a declared join — the hops printed, each tagged with its side |
|
the arm files' walk-derived half as a generated region, verified against the store on every rebuild |
|
the compiled hub an agent opens: partition, arms, drawings, first walk, GRAPH.md, thin pointers into the checkout |
|
a second language: TypeScript over tree-sitter onto the same nine words, the ring followed into node_modules |
|
a worked tenant with four walk-derived arms |
Not here yet, by name: the shard index. The board is GitHub Issues on this repo.
The working repo
engine/ THE PRODUCT — pip-installable, imports and runs as graphy, zero host reach
CLAUDE.md the router an agent in this repo works from
RECON.md the cold-start record: measured, dated, every number with its re-derive command
standalone_check.sh the departure gate: a fresh venv installs engine/ alone → GRAPHY_STANDALONE_OK
quickstart.sh the production proof → GRAPHY_QUICKSTART_OK
staging/ development input, never released; corpora/, containers/, quickstart/ are gitignored dataengine/ is the only place engine work lands. Run standalone_check.sh after every change to it.