The thesis

Nobody owns this code.
Something still has to know its shape.

The generation problem is solved. The comprehension problem is not, and it is now the whole job.

What actually changed

For thirty years the expensive part of software was producing it. Every tool we built — editors, type systems, frameworks, package managers — attacked the cost of writing. That cost has collapsed. A competent model produces a working thousand-line change in the time it takes to describe it.

What did not collapse is everything downstream: reading the change, deciding it is safe, finding what it broke, knowing where it belongs. Those cost exactly what they did in 2015. The bottleneck did not disappear; it moved one station down the line, and it now has a much bigger queue in front of it.

This is not a complaint about generated code being bad. Most of it is fine. The problem is structural: code is now produced by a process that keeps no memory and holds no responsibility, and the humans on the other side inherit artifacts they did not write, in a codebase they no longer have time to learn.

Three things broke at once

Review became the constraint

A reviewer's throughput is bounded by reading speed and working memory, neither of which has improved. When the diff arrives with no author who can explain the intent — because the author was a sampling procedure — review degrades into style-checking. The questions that matter are structural: what else calls this, what does the new coupling do to the module, is this the third implementation of a helper that already exists twice. None of them can be answered from the diff.

Onboarding lost its shortcut

Learning an unfamiliar codebase always worked the same way: find the person who wrote it and ask. That shortcut is gone in two directions at once — the people who wrote it are thinner on the ground, and increasingly nobody wrote it. What is left is reading, and the quantity to read went up by an order of magnitude.

Ownership evaporated

A module used to carry an implicit index in someone's head: why it is shaped like this, what depends on it, which parts are load-bearing. That index was never in the repository. It was in a person. When code is generated, revised, and regenerated by processes that do not persist, there is no head holding the index — and the artifact has to carry its own structure or nobody has it.

Documentation is not the fix

The instinct is to write more of it. That instinct produced the wikis we all ignore. The failure mode is mechanical, not cultural: a hand-written description of a system is accurate the day it is written and decays with every commit after. Multiply the commit rate by ten — which is precisely what happened — and the half-life of a hand-written diagram approaches zero.

Anything that has to be kept in sync by discipline will not be kept in sync. The only artifact that survives this rate of change is one derived from the code itself, recomputed rather than maintained.

What a map has to be

  • Derived, never authored. If keeping it true costs a human anything per change, it will be false within a month. The standing rule in nao: no feature may force authors to annotate or fill in detail by hand. Value comes from what the code already says.
  • Deterministic. Identical trees must produce byte-identical graphs. Otherwise every reported delta is contaminated by parser noise, and the tool cannot be used to judge a change.
  • Mid-altitude. Not the symbol — LSP has that. Not the prose overview — that is where staleness lives. The layer in between: entities, their relationships, and how a change propagates through them.
  • Legible to both consumers. A human needs it rendered — a graph they can drag, panels that follow the cursor. An agent needs it as compact ranked text with explicit truncation, because it pays by the token. Same graph, two renderings.
  • Honest about its own error. A map that overstates its accuracy is worse than no map, because people act on it. If call resolution is 93.7% recall, the tool says so, and says what an empty result does and does not prove.

The one thing code cannot tell you

A derived graph knows everything about how the code is wired and nothing about what it is for. No parser will tell you that these four modules constitute "billing", or that the cron job is the fallback for a webhook that fails silently. That layer is genuinely authored, and it is the one place where writing something by hand is unavoidable.

Elevator is the smallest language we could design for it. The design constraint was the decay problem above: nothing is mandatory beyond a name, so the spec is built sketch-first — one line the day you learn an area exists, depth only on the branch you actually just worked on. Depth where the work happens is also depth that stays fresh. And the parts that can be checked mechanically are: broken references, code claimed twice, paths that no longer resolve, identifiers named in a description that no longer exist in the file it points at.

What this is not

  • Not a replacement for LSP. Go-to-definition is precise and pointwise and should stay that way. nao occupies the altitude above it.
  • Not a linter. Smells are ranked signals to look at, not gates to fail a build on. The composite score exists to sort a refactor list, not to score engineers.
  • Not a service. No account, no upload, no telemetry. The engine is a binary; the graph is built on your machine from your checkout, and stays there.
  • Not for sale. Open source, AGPL-3.0. There is no paid tier waiting behind the good features.

The bet

If most code will be written by machines, then the durable, valuable artifact is not the code — it is an accurate, always-current model of what the code is: its entities, its dependencies, its risk surface, and the domain it serves. Humans need that model to review and to onboard. Agents need it to act without re-reading the world on every turn.

Build it once, derive it from source, and give it to both.

The older version of the same problem

None of this is new, incidentally. Anyone onboarding onto a decade-old enterprise codebase has been living it for years: undocumented domain vocabulary, a stack nobody on the team chose, logic hiding in config files, and three projects to hold at once. Generated code did not create the problem; it removed the last excuse for not solving it.

What that looks like point by point →