Skip to content

Code Atlas

An atlas is LatchAI’s answer to “I’ve never worked in this codebase — show me how it fits together.” An agent surveys an imported project, or several that play together, and publishes a set of views: interactive diagrams of the system’s parts, how they connect, and how a request or event flows through them, each with a written guide. The audience is someone new to the code, so views are layered — system first, drill into an area, land on a file.

Like a dashboard, an atlas is not a static picture: it is a brief handed to an agent with tools, producing a validated document the UI renders. And like a dashboard, it only works inside LatchAI because the agent runtime, the mounts, the event stream, the editor, and the vault are one process — a node on the map opens the real file in the real editor.

The system view of an atlas

The top-level view: services, stores, and the edges between them.

Asking someone to describe the map they want assumes they already know the codebase — exactly the thing they opened a map to fix. So there are no questions.

  1. Open the Workspace sidebar (the file tree).
  2. Hover a mount root, or one of its top-level folders. A 🗺 button appears on the row.
  3. Click it.

That is the whole flow. The tab opens immediately and the survey starts. Behind the button the app posts the folder to POST /api/atlas/standard, which writes a standard definition — no model call, nothing to wait for — and the app then kicks off the build with POST /api/atlas/<id>/run.

The standard definition is the same for every folder:

  • the baked-in newcomer brief — map this for someone who has never worked here, name the parts, lead with the way in, mark the read-path, then trace how work moves through the system end to end, and prefer the honest shape over exhaustive coverage;
  • one declared view, system, a graph titled The whole system;
  • flows: 2, and the icon 🗺;
  • a name and description derived from the folder: aureum-auth becomes Aureum Auth Map, How Aureum Auth fits together.

Two conveniences fall out of it. The same folder asked for twice is the same map, not a duplicate — the existing atlas is opened, and only rebuilt if it has never been built. And a folder that isn’t reachable through the workspace or a mount is refused up front (cannot map "…": not a folder in the workspace or a mounted project) rather than producing an atlas that surveys nothing.

The 🗺 action is on mount roots and their top-level folders only — the two granularities worth mapping. A 🗺 on every nested directory would be noise. The command palette has the same thing per mount (Visualize: <mount>), plus Open Atlas and Build Atlas for maps you already have.

A Maps section sits at the top of the Files sidebar, above the tree it describes. There is deliberately no atlas area in the activity bar: a code map belongs next to the code, not in a wing of its own.

Each row shows the map’s name and icon, when it last built, its scope, and — the honest bit — a stale badge counting exactly how many survey units have changed since that build (stale · 12/240). Hover actions are build and 🗑 delete. While a build is running the row spins and the list polls, so a map another window (or a second build) started still catches up. Nothing here rebuilds on its own: spending tokens on a background artifact stays a click.

The section only exists once you have a map: with none, the sidebar is just the file tree, and the 🗺 on a folder row is the way in.

Open a map and the tab has two sides, the same Rendered ⇄ Definition motion dashboards have. ✎ Definition shows everything the cartographer is told:

Field What it does
Name, Icon, Description The sidebar row and the tab
Scope One mount-relative root per line; several roots is the multi-project case
Declared views id | graph|flow | title — every one must be produced on every build; extras are allowed
Composer agent An agent definition supplying persona and model for the composer
The brief What matters in this codebase, and who is reading the map
Flows How many flows to trace when none are pinned

Editing any of them and pressing Save writes <home>/atlas/<id>.md and snapshots the previous file into version history. Because the definition is just a file, hand-writing one is a first-class path:

---
name: Platform Map
icon: 🗺
description: How the client, server, and worker fit together.
scope: [projects/api, projects/web]
agent: atlas-cartographer
views:
- system | graph | The whole system
- request | flow | Life of an API request
flows: 4
---
Emphasise the request path and the job queue; the test harness is noise.
The reader is a new hire on the backend team.

Scope entries are virtual paths — a mount name, or a directory inside one — the same address space the editor and every agent tool use. An entry that doesn’t resolve to a real directory is dropped with a console message, and an atlas left with no usable roots fails its build with a message saying so rather than mapping nothing.

Declared views behave like dashboard slots: the composer must produce every one, by id and kind, and may publish more. Declaring a flow view is how you guarantee a traced path on every build.

Above the form is a plain-English box — describe the change you want and it hands the whole definition to the authoring model to rewrite. That is the intended front door for revision, and it is the same endpoint (POST /api/atlas/design) that can author a definition from scratch, given the real mount names to aim scope at.

The hard part, and the shape of the answer

Section titled “The hard part, and the shape of the answer”

A real codebase is several context windows of source. The design makes that a non-issue by construction: no fact that belongs in the diagram ever lives only in a model’s context window. Facts are extracted per unit, cached to disk, and assembled deterministically.

Phase 0 is code, not model. The engine walks the scope roots through the multi-root filesystem — file tree, sizes, a content hash per file — and partitions the tree into survey units: directory-aligned bundles capped at 30 files or 120KB of source. A directory holding a package.json, pyproject.toml, go.mod or Cargo.toml starts its own unit scope; a subtree small enough to be a leftover rather than an area (8 files, 32KB) folds up into its parent’s unit; a directory whose own files blow the cap splits deterministically into <dir>, <dir>#2, and so on. node_modules, dist, build, target, vendor, coverage, dotfiles, lockfiles, minified twins and anything over 512KB are excluded up front, and symlinks are not followed. Because partitioning is deterministic, the pipeline cannot drift here. The result is written as the run’s manifest.

Phase 1 is a fan-out of surveyors. Every unit whose content hash is already in the card cache is free. The rest are surveyed three at a time, each by an agent loop with a fresh context holding only that unit’s file list and exactly three tools — read_file, list_dir, record_card. Its one output is a card: what this code is for, its public surface, its internals, its dependencies (internal imports as virtual paths, externals separately), its entry points, a note per file, and an honest list of what it could not determine. record_card writes that straight to disk and stamps the unit id and content hash itself, because a model-guessed cache key would poison every future build. A surveyor that dies leaves a missing card, never a failed build.

Phase 2 is composition. The engine computes the raw import graph from the cards — unit-to-unit adjacency, ranked by how many import facts produced each edge — before the composer sees anything. The composer gets that graph, a one-line index of every card, and the previous build’s view and node ids, plus read_card to pull a unit’s full facts for the parts it is about to describe in detail. So it curates: grouping, labelling, deciding what a newcomer sees first. It is structurally unable to forget an edge, and forbidden to invent one.

Cards are cached content-addressed and globally at <home>/runs/atlas-cache/, keyed by the hash of the files they describe, which buys two things: a rebuild costs tokens proportional to churn, and a multi-project atlas re-surveys nothing that a single-project atlas already covered.

What Where
Definition — scope + brief <home>/atlas/<id>.md
Document — one build’s output <home>/runs/atlas/<id>/<ts>.json
Cards — per-unit survey facts <home>/runs/atlas-cache/<content-hash>.json
Manifest — what was surveyed <home>/runs/atlas/<id>/<ts>.manifest.json
Source archive — every tool call’s full payload <home>/runs/atlas/<id>/<ts>.sources/
Vault export <home>/workspace/atlas/<date>-<id>.md

The twenty most recent documents are kept per atlas, each pruned with its manifest and archive. The card cache is not pruned — it is the thing that makes the next build cheap.

The canonical artifact is graph JSON, zod-validated at publish time: typed nodes, edges, groups, flows, and narratives. Nodes carry a kind (service, module, ui, store, queue, external, entry, or not-yet-surveyed) and edges carry both a kind (import, call, http, event, data) and a confidence — whether the relationship was observed in the source or inferred.

publish_atlas rejects a view and says exactly why, and the composer fixes it on the next turn. The rules it enforces:

  • Every file on a node must exist, resolved through the multi-root filesystem, and sit inside the atlas’s scope. This is the anti-hallucination check: a model cannot invent a component the filesystem doesn’t corroborate.
  • A graph view carries at most 50 nodes. Over that, the answer is never a bigger canvas — group the units, and give the group’s node a drill pointing at a deeper view.
  • Every edge connects two node ids in its own view. No dangling arrows.
  • Every flow step lands on a node that exists in a published graph view. Graph views are validated first within a call, so a flow may reference nodes published beside it.
  • Node ids are path-derived and stable, so pins, links and chat references survive a rebuild. The cards a node names are rewritten to content hashes on the way in — the evidence trail the model has no way to know.

Views merge by id and are published incrementally, so the reader watches the map fill in. A build that publishes nothing is a failed build, not an empty atlas; a build that misses a declared view persists what it did produce and then fails, so a partial map is still there to read while the run log says which slot never arrived.

The header carries the survey’s honesty: when it last built, and 240 units · 31 surveyed · 209 cached · 0 missing. Units that could not be surveyed are counted, listed on hover, and drawn as unsurveyed nodes rather than quietly dropped.

Start here is an expandable orientation written for a newcomer — what this system is, what the pieces are called, which view to open first. Below it, a chip per view ( graph, flow) and that view’s written guide.

The interactive canvas renders the document with client-side layered layout, so the model never spends tokens on coordinates. Pan and zoom; a minimap appears past 25 nodes; edge labels hide themselves past 60 edges rather than turning the canvas into soup; groups get a colour legend; inferred edges render dashed, and the legend says so.

Drilling into an area of the atlas

Click a node for its files and the evidence behind each edge.

Click a node and a detail panel opens beside the canvas: its summary, group, size, the card hashes behind it, its files (click one and the real file opens in the editor — a trailing :42 is stripped), and its connections, each with direction, kind, whether it was observed or inferred, and evidence chips that open the file they cite. Double-click a node carrying drill and the canvas switches to that deeper view.

A flow view

Flow views trace one request or event across the whole system.

Flow views render as a mermaid sequence diagram plus a numbered list of steps — each with what happens, which part it happens in, and a clickable path:line.

Positions are ephemeral: drag a node to see something better, but nothing is written back, and the next layout re-frames the view.

Mermaid is an export, not the source: the vault export writes each view as a mermaid fence — greppable, Obsidian-ready, and pasteable into a pull request — with the scope, the survey stats, the orientation, every guide, a file list per node, and an Unsurveyed section when there is one.

The stale badge is computed, not guessed, and costs no tokens: a stat-only re-walk of the scope compared against the last build’s manifest. A changed size or mtime, a deleted file, or a new file in a unit’s tree all mark that unit stale.

Rebuilding (, or ↻ Build in the tab) re-runs all three phases. Phase 0 is deterministic, Phase 1 hits the cache for everything that didn’t change, and Phase 2 composes from scratch — with the previous build’s node ids in front of the composer, and instructions to keep them identical where the code hasn’t moved. After a large refactor, that is the cheapest correct thing: you pay for the units that actually changed.

An atlas build drives the same tool loop as everything else, appears in the Run Monitor and the token report, returns immediately, and publishes views as they land. Its events use the run id atlas-<id>-<timestamp> and the workflow id atlas:<id>; each surveyor is a node called atlas▸survey:<unit> and the composer is atlas▸compose, so a stalled unit is visible by name. Every published view emits an atlas.published event, which is what re-renders an open tab live.

Starting a build while one is in flight returns 409 — the UI treats that as “already building” and keeps the spinner rather than surfacing an error.

  • Surveyor text is not streamed. Three concurrent token streams would drown the Run Monitor and the card is the product, not the prose — their tool calls still show.
  • A first build of a large tree is slow, bounded by your provider: it is one agent loop per changed unit, three at a time.
  • flows: is guidance, not a guarantee. The composer is asked for that many flow views beyond any you declared, but only a declared flow view is enforced at the end of a build.
  • Scope is enforced where it’s checkable — at publish validation and when resolving roots — not in the filesystem layer. Every agent can still see every mount.
  • No pinned layouts. Node positions are recomputed each time a view is opened.