Mechanism 3 of 4 · The living graph
The absence this makes loud: a file the map has never seen.
A code map that goes quietly out of date is worse than no map, because confidently-wrong structure is exactly what an agent will act on. So this one records freshness as a first-class state, and will tell you the difference between a file that went stale and a file it has never heard of.
Who builds it
The graph is not built by a parser Graphban runs over a checkout it doesn't have. Your external coding agent is the producer — it has the real repository in context, so it is the source of truth, and it calls describe_code as a byproduct of work it was doing anyway.
Graphban's own connected model is the consumer: semantic search over node summaries, neighbourhood reads, structural queries, and natural-language questions about the codebase answered from the graph without a checkout. Nodes carry a path, a kind (module · file · symbol · doc · config), a one-paragraph summary that gets embedded, a content hash, and a freshness flag. Edges are directed and typed — imports · calls · owns · tested_by · references — and an edge may point at a file nobody has described yet.
Docs and config are graph kinds too. Work that touches AGENTS.md or a deploy template has a blast radius, and a map that only knows about source files can't show it.
The key
Everything you'll see on the map encodes exactly one of three things. Keeping them separate is what makes a glowing node mean one unambiguous thing.
Hue is spent entirely on kind. Presence gets shape and opacity instead — which is why a held node still shows its own colour and freshness underneath, and the cloud never tints the node itself.
Staying true
Re-describing a directory with prune=true does not delete the nodes it didn't see. It marks them stale. A partial describe should never destroy history — and a node that vanishes takes the meaning of its edges with it.
It is also honest about its own limits. With no checkout, the server cannot distinguish a deleted file from one nobody has described — so it only flags what is decidable from the path string, and refuses to guess further. And when nothing has ever been described, it says so explicitly rather than reporting zero stale nodes and looking healthy.
Queries
Ranks by inbound degree. A file that imports forty things must not outrank one that forty things import — the first is cheap to change, the second is your blast radius.
Connected groups, largest first, each with an anchor to name it. This is what the galaxy view collapses to when a map exceeds the detail budget — when it collapses at all.
Shortest route between two files, walked undirected, with each hop reporting which way the edge actually points.
All three are deterministic and read-only. An unknown edge type is refused, not silently narrowed — quietly dropping a filter you asked for returns a smaller answer that looks like a complete one.
Scale
Past a certain size a force-directed graph stops being a picture and starts being a hairball. The usual answer is to draw the same thing slower. Above its detail budget Graphban instead changes what it draws: connected components collapse into super-nodes, each labelled by its highest-degree member, and clicking one enters it with a breadcrumb back.
Search still sees through it. Pressing / matches across collapsed components and enters the one holding the hit — a find that can't see what the view is hiding is a find that reports zero results for a node sitting right there. And zero genuine hits dims the graph and shows a literal 0, rather than leaving the map looking like nobody searched.
The numbers above are real. Running components() against this repo's own live graph during the acceptance walk returned [146, 3, 1, 1, 1, 1] — 95% of the graph in a single component, because almost everything is reachable from the models module. Six components clears a "more than one component" test easily, so the original guard would have rendered one enormous dot beside five specks and called it a summary.
That guard had tests, and they passed. They used ten equal components of ninety nodes each — the friendly case, invented rather than measured. The check was green while being wrong about the only shape of graph anyone would ever point it at. It took deploying it and reading the live map to find, and the regression test now uses the measured shape instead of a convenient one.
The rest of the view is ordinary care taken seriously: layout runs in a worker so the main thread stays responsive, positions are computed from the unfiltered edge set so toggling a filter never reshuffles the map underneath you, labels fade in by level of detail, and the whole thing is keyboard-navigable with one tab stop into the canvas and arrow keys between nodes.
Presence
The map shows you where your fleet actually is right now. The distinction between a lease and a declaration is the entire design, because only one of them can expire.
Presence is a surveillance surface with a person's name attached, so it is reachable only with a signed-in session — never with an agent key. And it uses a deliberately stricter path matcher than the collision divvy does: over-matching is safe when you're deciding what to block (you over-block, you never collide) and a lie when you're drawing a picture of where people are.
Contention
The divvy exists so the third picture cannot happen. So when it does, one of exactly three things is wrong: a lease lapsed in a race, the path matcher has a gap, or somebody claimed work outside the divvy. When the glow is wrong, the fleet is wrong — which is why this is drawn as an alarm and not as a busy-looking file.
Off the map
They are listed, not dropped. An area that matches nothing on the map goes into a tray with its holder and a reason — undescribed or stale — because a presence payload that silently omits what it couldn't resolve is an absence reading as a clean result.