# lat.md: the markdown knowledge graph wired into your source files

> lat.md keeps what a coding agent would otherwise rediscover as markdown files under lat.md/, linked to real source symbols and checked for drift. The payoff is a validator that fails CI when a documented concept loses its reference, and the cost is that somebody, often the agent itself, keeps writing those links.

**vercel-labs/lat.md** — Agent Lattice: a knowledge graph for your codebase, written in markdown.

- Repository: https://github.com/vercel-labs/lat.md
- Website: https://www.lat.md
- Stars: 2,055 · Forks: 126
- Language: TypeScript
- License: MIT
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/vercel-labs-lat-md

## AGENTS.md stops scaling, so the knowledge moves into a directory

The project begins from a claim about agent instruction files: a single flat `AGENTS.md` can describe a small repository and becomes impractical as the codebase grows. Design decisions get buried, business logic goes undocumented, and agents invent context that somebody wrote down somewhere. lat.md's answer is to split that knowledge into markdown files in a `lat.md/` directory at the repository root, one file per subject, joined by `[[wiki links]]`.

The reader this is for is a team already running coding agents against a real codebase. This repository ships its own `AGENTS.md` and `CLAUDE.md` at the root, and `lat init` sets up popular coding agents with hooks and instructions meant to keep the graph updated and correct. Nothing here needs a hosted service or a database. The graph is a directory of files that arrive in a pull request next to the code they describe, and `lat check` is the command that decides whether that directory is telling the truth.

## Source files carry the backlinks that make the checker meaningful

Documentation drifts when nothing verifies it, so lat.md connects every concept to code in both directions. A markdown file points at a symbol with a link such as `[[src/auth.ts#validateToken]]`, and the source file points back with a `// @lat: [[section-id]]` comment, written as `# @lat: [[section-id]]` in Python and PHP. Headings nest three deep with `[[file#Section#Subsection]]`, so one file can hold a flow and the steps inside it.

`lat check` is what turns that convention into a rule. It runs full graph and documentation validation, and it flags any section marked `require-code-mention: true` that no `// @lat:` comment references. Put a test spec in `tests.md`, mark it, and a spec with no matching backlink in test code fails the check. That is a coverage signal read from the documentation side instead of counted from the test files, and `lat refs "auth#OAuth Flow"` gives you the reverse index, listing what points at a given section.

## npm install -g lat.md, then lat init in the repository you want indexed

Two npm packages cover two different needs. The full CLI installs globally and then scaffolds the graph in whichever repository you point it at:

```bash
npm install -g lat.md
```

Running `lat init` there creates `lat.md/` and configures the coding agents it recognises. If you only need validation and navigation, with no UI, no search and no embedding downloads, the core package is enough:

```bash
npm install -g @lat.md/core
lat-core check
```

Both can be installed together, because they expose different executable names: lat from the `lat.md` package, `lat-core` from `@lat.md/core`. The published `files` list is narrow, `dist/src` and `templates` only, and the lat binary resolves to `./dist/src/cli/index.js`, so a global install carries templates and none of this repository's own documentation.

## lat and lat-core expose different command sets for the same graph

The full CLI carries eight commands, and each takes a section id or a free text query:

```bash
lat init                        # scaffold a lat.md/ directory
lat check                       # run full graph and documentation validation
lat locate "OAuth Flow"         # find sections by name (exact, fuzzy)
lat section "auth#OAuth Flow"   # show a section with its links and refs
lat refs "auth#OAuth Flow"      # find what references a section
lat search "how do we auth?"    # semantic search via embeddings
lat expand "fix [[OAuth Flow]]" # expand [[refs]] in a prompt for agents
lat mcp                         # start MCP server for editor integration
```

`lat locate` matches sections by name, exactly or fuzzily. `lat section` prints one section with its links and its backlinks, while `lat refs` answers the opposite question, which other sections point here. `lat expand` takes a prompt fragment and fills the references in, which is the piece aimed at agent pipelines. `lat mcp` starts an MCP server for editor integration.

`lat-core` covers a different slice of the same graph: `locate`, `section`, `refs`, `expand`, `external`, and `paths`. `external` is documented only in that list, and `search` and mcp only in the full CLI list, so a script written against one executable name will not find every command. `lat paths` doubles as a diagnostic, listing cache and other storage paths with their purposes.

## Search runs on a bundled WASM model until you hand it a key

`lat search` works offline by default. Embeddings come from a local model, all-MiniLM-L6-v2, compiled to WebAssembly, with no native binaries and no network traffic, which is why a fresh install needs no API key. Moving to hosted embeddings takes a four step resolution. `LAT_LLM_KEY` takes a direct value, `LAT_LLM_KEY_FILE` names a file holding the key, `LAT_LLM_KEY_HELPER` names a shell command that prints it under a 10s timeout, and a config file entry named `llm_key` is the manual fallback for anyone who would rather edit settings.

`lat paths --config` prints where that file sits, and plain `lat paths` lists cache and other storage paths with their purposes. Keys look like `sk-...` for OpenAI or `vck_...` for the Vercel AI Gateway. After changing backend, `lat reindex` rebuilds the index, with `--local` forcing the offline model and `--remote` using your key; nothing else in the documented flow re-embeds the graph.

One detail deserves care. A helper command is arbitrary code executed by the CLI on your own machine, so `LAT_LLM_KEY_HELPER` is a trust decision, not a convenience. The docs also stay quiet on what happens when that command prints nothing, hits its timeout, or returns a rejected key.

## The Action bundles its own checker but shows a floating tag

Validating in CI does not need a Node runtime on the runner. The workflow asks for a read-only token and calls a published action tag:

```yaml
permissions:
  contents: read
jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: vercel-labs/lat.md@action-v1
        with:
          working-directory: .
          profile: 'false'
```

No Node setup, npm install, project build or embedding credentials are involved, because the Action ships its checker and parser assets itself, and validation errors fail the job. Three things are worth a closer reading.

The example pins `action-v1`, a floating tag, in the same block where the text tells you to use an immutable `action-vX.Y.Z` tag or a commit SHA. Source branches lack the generated runtime, so a workflow pointed at a branch cannot start, and `action-v1` is the only spelling the README ever shows.

Repository identity differs between files. That step names `vercel-labs/lat.md@action-v1`, the CI badge and the `repository` field in `package.json` point at `1st1/lat.md`, and `homepage` in the same file names the GitHub repository while the project header names `https://www.lat.md`. Maintainers build and test the artifact with `pnpm build:action` and `pnpm test:action`, and the Publish check action workflow exercises Linux, Windows and macOS before it publishes tags. `profile: 'false'` is passed through without an explanation of what a profile switches on.

## Building the tooling needs Node.js 22, pnpm and a Rust toolchain

Using the tool and building it are different jobs. Working on the project requires Node.js 22, pnpm and Rust installed through rustup, with `rust-toolchain.toml` pinning the Rust side, and the pnpm setup installs the matching WASM target and build tools project-locally:

```bash
pnpm install
pnpm buildall
pnpm test
```

The version field in `package.json` reads 0.12.2, the three newest tags are v0.12.0 and v0.12.1 on 2026-07-15 with v0.12.2 on 2026-07-29, and the last push was on 2026-10-06, so `main` sits about two months past the newest tag with no version bump in `package.json`. Setup detail lives in `lat.md/dev-process.md`, and the repository keeps its own `lat.md/` directory, which means the tool documents itself in the format it enforces on you.

The package is MIT licensed. That matters here less for redistribution than for the output: what the checker writes is plain markdown and source comments, readable and editable without the CLI, so dropping the dependency later costs you a directory, not a database migration.

## Where the graph has no answer, and when the setup is wasted

Three limits are worth stating plainly. The README does not document rollback: nothing explains how to delete a section that turned out to be wrong, or what `lat check` does with a `// @lat:` comment pointing at a section id that no longer exists. Validation and search also cost differently, since `@lat.md/core` deliberately omits search and embedding downloads, so a CI job stays small while a developer machine pays for the model weights.

Correctness is opt-in in practice, too. The graph stays true only if `lat check` runs, and agents are expected to call it before finishing work. A team that reviews only `src/` and never looks at the `lat.md/` diff will drift, and nothing in the tooling forces that second review.

The honest alternative is the flat file this project argues against, and the difference is mechanical rather than philosophical. One `AGENTS.md` has no backlinks to lose, no test spec that must be mentioned, and no check that can fail. It also cannot tell you that a documented concept stopped existing in the code, which is the exact failure the checker was built to catch.

## Conclusion

Adopt lat.md when coding agents work in a repository too large for one flat AGENTS.md, and accept that the graph stays true only if lat check runs on every change. Skip the setup for a small service or a prototype, where a single file is cheaper than a directory plus a checker. Before switching, run lat check on a repository you already understand, read which sections it reports as unreferenced, and confirm that the people reviewing diffs will keep writing // @lat: comments by hand, rather than dropping them after a refactor.

## FAQ

### How do I install lat.md and start using it on a repository?

Run npm install -g lat.md, then run lat init in the repository you want to index, which scaffolds the lat.md/ directory and configures the coding agents it recognises. If you only need validation and navigation, install @lat.md/core globally instead and call lat-core check; the two packages install side by side because they expose different executable names.

### Does lat.md need an API key or a network connection for search?

No. lat search works offline by default, using a bundled local embedding model, all-MiniLM-L6-v2, compiled to WebAssembly with no native binaries and no network. Supplying an OpenAI sk- key or a Vercel AI Gateway vck_ key switches search to hosted embeddings, and lat reindex with --local or --remote rebuilds the index.

### How does lat.md keep test specs tied to actual test code?

You mark a section with require-code-mention: true, and every such spec must then be referenced by a // @lat: comment in test code. lat check flags any spec that has no backlink, so coverage can be reviewed from the knowledge graph rather than by counting test files.

### What happens when the graph drifts out of date with the code?

lat check runs full graph and documentation validation and enforces referential consistency, and in the GitHub Actions setup any validation error fails the job. The README does not document a rollback path or how to remove a section that is wrong, so that part is left for you to work out.

### What does the lat-core binary leave out compared with the full lat CLI?

lat-core provides locate, section, refs, expand, external, and paths, so it covers validation and navigation without UI, search or embedding downloads. The full lat binary adds init, check, search and mcp, and both packages can be installed together because they publish different executable names.

## Sources

- [License: MIT](https://github.com/vercel-labs/lat.md/blob/main/LICENSE)
- [Project website](https://www.lat.md)
- [README](https://github.com/vercel-labs/lat.md/blob/main/README.md)
- [Releases](https://github.com/vercel-labs/lat.md/releases)
- [vercel-labs/lat.md on GitHub](https://github.com/vercel-labs/lat.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/vercel-labs-lat-md
