Contributing
Writing the docs
docs.vyre.run is built from the Markdown files under docs/ in the Vyre repository. People read the rendered pages; agents read the same files raw. So every page follows one contract, and npm test fails when a page breaks it. This page is that contract. The decision behind it is ADR 0019.
Every feature merges with its doc page. A feature is not done until the page that describes it is true, in the same change.
Where a page lives
A page is a .md file under docs/, in the folder of its section: get-started/, using/, concepts/, build/, reference/, architecture/, adr/, security/, contributing/. Its URL is its path without .md:
| File | Page | Raw Markdown |
|---|---|---|
docs/index.md | / | /index.md |
docs/using/vault.md | /using/vault | /using/vault.md |
docs/architecture/index.md | /architecture/ | /architecture/index.md |
Two folders are internal and never published, whatever is in them: docs/proposals/ and docs/design/boards/. They are listed under unpublished in docs/nav.json. A published page may not link into them.
Front matter
Every published page starts with a front matter block, then one # H1 that matches the title:
---
title: Vault
summary: One sentence, what the reader gets from this page.
audience: users, agents
owner: docs
status: stable
---
# Vault
| Key | Required | Allowed values |
|---|---|---|
title | yes | The page's name, as the nav and the # H1 show it. |
summary | yes | One sentence. It appears in search, in /llms.txt and under the title. |
audience | yes | A comma list from users, builders, operators, agents. |
owner | yes | One team: network, capsule-pro, capsule-sight, connectors, mobile, polish-cli, polish-surfaces, e2e, integrator, docs, planner, cc-plugin, glass-live, pwa. The owner keeps the page true (scripts/lib/docs/check.js holds the list). |
status | yes | stable (shipped on main), draft (partly shipped, may change), planned (not built yet). |
generated | no | The script that writes the page. Only generated reference pages carry it. |
Any other key is an error. The lists live in scripts/lib/docs/check.js (AUDIENCES, STATUSES, OWNERS); change them there, not here only.
The front matter parser reads a small subset of YAML: key: value, optional quotes, a trailing # comment after whitespace, and inline [a, b] lists.
The nav
docs/nav.json lists every published page, in order, by section:
{
"site": { "title": "Vyre docs", "url": "https://docs.vyre.run", "repo": "https://github.com/vyre-ai/vyre" },
"sections": [
{ "title": "Start here", "pages": ["index.md"] },
{ "title": "Get started", "pages": ["get-started/install.md", "get-started/first-day.md"] }
],
"unpublished": ["work/", "proposals/", "design/boards/"]
}
Only pages in the nav are published. A published page that is not in the nav fails the check, and so does a nav entry with no file, a page listed twice, or a nav entry in an unpublished folder. The order in the nav is the order of the sidebar, the previous and next links, /llms.txt and /llms-full.txt.
Moving a page: redirect stubs
When a page moves, leave a stub at the old path with only a title and a redirect:
---
title: Specification
redirect: architecture/spec.md
---
The redirect is a docs-relative path. The build turns each stub into two 301s in _redirects, one for the pretty URL and one for the .md, and a link to the old path is rewritten to the new page. A stub is not listed in the nav (list its target), may not point into an unpublished folder, and may not point at another stub.
Links
- Link between pages with relative
.mdpaths:[the vault](../using/vault.md#share-a-secret). The build rewrites them to page URLs. - Every link and every
#anchormust resolve. Anchors are GitHub-style heading slugs: lowercase, punctuation dropped, spaces to hyphens,-1,-2for repeats.## 11. The security flooris#11-the-security-floor. - A link to a file elsewhere in the repository (
../../core/vault/index.js) becomes a link to that file on GitHub. - Absolute paths on the site (
/llms.txt,/using/vault) are checked against what the site serves. External links are not checked. - Link to the generated reference pages rather than repeating their tables.
Includes
A line holding only an HTML comment that names a file splices that file into the page:
<!-- include: ../CHANGELOG.md -->
Inside fenced code, as above, the line is an example and is left alone.
The path is relative to the page and must stay inside the repository. The included file's front matter is dropped, and links inside it resolve from its own folder. Includes nest a few levels deep. The raw .md the site serves has the include already spliced in. The check scans included files for the same characters and hygiene problems as pages, and fails on an include whose file does not exist.
Page syntax
Beyond plain Markdown, pages have a few blocks of their own. Every one renders to useful static HTML with JavaScript off; the script only adds to it. Raw HTML stays forbidden.
Commands and what they print
Every fenced block gets a Copy button. In an sh or console block, a leading $ is not
copied, and in a console block that has prompts, lines without one are output and are not
copied either. A command ending in \ carries on to the next line.
```console
$ vyre status
vyred is up
```
Put what the reader should see after a command in an output block. It is labelled "You should
see" and has no Copy button.
```output
Vyre is ready.
```
Tabs
For a choice of path or system. Without JavaScript each tab is a section with its label above it; with it, a tab bar. Picking a tab picks the same label in every tab group on the site, and the choice is remembered.
::: tabs
::: tab On a server
Markdown for this path. Headings (h3, h4) are fine and get anchors.
::: tab On this Mac
Markdown for this path.
:::
Use the same labels on every page (On a server, On this Mac) so one choice follows the reader.
Callouts
> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION] and > [!GAP] (shown as
Known gap). Any of them may carry a title after the marker:
> [!WARNING] The HTTPS switch is off
> Wait for the certificate to finish, then press Check again.
> [!SNAG] is an "If this happens" box: the title (required) is what the reader sees, the body is
what to do. It is never collapsed, and its title makes an anchor like a heading does, so
troubleshooting can link to #the-page-says-not-found:
> [!SNAG] The page says "not found"
> Wait a minute for the certificate, then reload.
> [!WHY] answers a question for the curious, collapsed until clicked:
> [!WHY] Why does Vyre need a server?
> Your agents keep working while the Mac sleeps.
Screenshots
Keep shots next to the page in a shots/ folder (docs/get-started/shots/). An image alone in
its paragraph becomes a figure, captioned by its alt text, or by its title when it has one. Write
alt text that says what the screen shows.

If onboarding-address.dark.png sits beside it, the dark theme shows that file instead. The
build reads each PNG's width and height from the file, so the page does not jump as shots load.
Shots are taken, not drawn. npm run docs:shots (on the test box, never the Mac) starts the sample
world in a temp home and captures every shot listed in scripts/lib/docs/shots.js, in light and
dark, with CHROME pointing at a headless Chrome. Each entry there names the source files the
shot shows. docs/shots.json records a hash of those files at capture time, and docs-check warns about a
shot once any of them changes, and fails on it only with --release, which scripts/release-check.sh runs,
so a screen that moved on gets retaken before a release. An app change never turns another team's suite
or stage red. To add a shot, add an entry
to shots.js, run npm run docs:shots -- --only <name>, and put it on the page. Command output
is text, not a picture: paste it into an output block (npm run docs:shots -- --cli prints the
real output of the common commands from the sample world).
Demos
A widget that replaces its fallback when the page's script runs. The fallback is what readers
without JavaScript (and agents reading the .md) get, so make it complete: a screenshot and a
sentence, or a list of screenshots.
::: demo capsule

Type in Lumen and it finds agents, projects, threads, notes and logins.
:::
capsule: a Lumen you can type into, with results from the sample world only.onboarding: the fallback's list of screenshots, one per step, as Back and Next slides. Each step's name is its screenshot's title:
::: demo onboarding
1. 
2. 
:::
Only pages with a demo load the demo script. A name the site does not know fails the check.
Colours
A line holding only <!-- colors: dark --> or <!-- colors: light --> shows the palette from
core/config/theme.js as a table of live swatches (token, value, use). The .md the site serves
gets a Markdown table in its place. Change colours in theme.js (and TOKENS.md), not in a page.
Generated reference pages
docs/reference/{cli,tools,events,config,modules}.md are written from the code by scripts/gen-docs-reference:
| Page | Source |
|---|---|
cli.md | the command list in core/cli (what vyre help prints) |
tools.md | every ctx.tool definition, recorded by starting each module in a child process with a throwaway home |
events.md | each manifest's watches, with payload fields read from the emit calls |
config.md | core/config/index.js and every process.env.VYRE_ the shipped code reads |
modules.md | every module.json vyred would load |
Do not edit them by hand. Change the code (a command's summary, a tool's description), then:
npm run docs:ref # write the pages
node scripts/gen-docs-reference --check # exit 1 if any page is stale, write nothing
The index
npm run docs:ref also writes docs/reference/index.md and docs/index.json (served at
/index.json): every command and subcommand, tool, event, config key, VYRE_ variable, app
screen and concept, with the code file that defines it, the page that explains it, and every page,
line and heading anchor that mentions it. One lookup finds every instance of a thing. It reads
every published page, so run npm run docs:ref after editing any page, not only after changing
code; docs-check fails when the index is out of date.
docs-check also fails a stale mention: inline code or a sh/console command line naming a vyre
command or subcommand, a tool, a config key under a known section, or a VYRE_ variable that the
code no longer has. Placeholders (<name>), flags, file names and example modules are left alone,
and ADRs, the changelog, known gaps and the spec are indexed but never failed. If a line must show
an old or made-up name on purpose, end it with <!-- terms: ignore -->. Curated concepts live in
CONCEPTS in scripts/lib/docs/terms.js; add one there, with the page and heading that explain
it, when a page teaches a new word.
Check your pages
npm run docs:check # everything
node scripts/docs-check --no-reference # skip regenerating the reference pages
It prints one path:line: problem per problem and exits 1 if there are any. It checks:
- front matter: present, every required key, allowed values, no unknown keys;
- nav: every published page listed, every entry a real file;
- links: relative links,
#anchorsand docs.vyre.run paths resolve, nothing links into an unpublished folder; - redirect: stubs point at real, published, non-stub pages;
- syntax: every
:::container is known and closed, a demo names a widget that exists, a[!SNAG]or[!WHY]has its title, a colors line says dark or light. Headings inside tabs and[!SNAG]titles count as anchors; - characters: no em dash and no section sign, in pages and in files they include;
- hygiene: no real person's or business's name, nothing shaped like a secret, no email address outside the example domains, no IP address outside the documentation and private ranges (
scripts/lib/hygiene.js); - reference: the generated pages and the index match what the code and the pages make now;
- stale: no page names a command, tool, config key or variable the code no longer has;
- shots: every screenshot is in
docs/shots.json, and none is older than the code it shows.
test/docs-check.test.js runs the same check on the real tree under npm test, so a broken page fails the suite.
Build the site
npm run docs:build # into docs-site/ (gitignored)
node scripts/build-docs --out DIR # somewhere else
scripts/build-docs empties the output folder, then writes:
- each page as HTML at its pretty URL, and its Markdown source beside it at the same path with
.md, front matter kept and includes spliced; search-index.jsonfor the search box: one item per page intro, heading and[!SNAG], each with its anchor, so a result jumps to the section. Press/to search;llms.txt, an index of every page by section with its.mdlink and summary, andllms-full.txt, every page's Markdown in nav order in one file;sitemap.xml,robots.txt,404.html,favicon.svg;_redirects(a 301 for every stub) and_headers(Markdown and text content types, cache rules) for Cloudflare Pages;- one stylesheet and one script under
assets/, named by content hash, and the demo widgets' stylesheet and script, linked only from pages with a demo.
The same tree builds the same bytes: nothing reads the clock or the network. The renderer (scripts/lib/docs/markdown.js) has no dependencies. It renders CommonMark plus GFM tables, task lists, strikethrough and alerts (> [!NOTE], and > [!GAP], shown as Known gap), and the page syntax above. It does not render raw HTML: every < shows as text, so write <you>.vyre.run as it is. Indented code blocks are not supported; use fences.
The site deploys to the Cloudflare Pages project vyre-docs:
npx wrangler pages deploy docs-site --project-name vyre-docs --branch main
Style
- No em dash and no section-sign character, anywhere. Use a colon, a comma, or two sentences. Write "Section 5.1" for spec references. The check enforces this.
- Examples use the sample world only: the user alex, the firms Juniper Studio and Northwind Bakery, the assistant and agents juno and kit, the domains
example.com,juniperstudio.exampleand*.example, names likealex.vyre.run, and addresses from192.0.2.xor100.64.x.x. The check enforces names, emails and addresses. - Plain, direct English, in the second person. Short paragraphs. Concrete commands in fenced blocks. No marketing adjectives, no "simply", no "seamless".
- Write only what is true on main. Copy commands, flags, config keys, tool names and paths from the code. When something is designed but not built, say "Not built yet." or mark the page
draftorplanned. - A reader who is an agent should be able to act from the page alone.
A user page has this shape: one paragraph of what it is and why; the common tasks, with headings that are tasks ("Share a secret with an agent"); what it will not do; where to go next.
Known gaps
Document what the code does. When that differs from the spec, an ADR or a screen, say so on the page in a callout and give the gap its own section in known gaps, with what is true now, what to do instead, and the owning team:
> [!GAP]
> The pause switch pauses but does not resume. See [known gaps](../known-gaps.md#the-pause-switch-does-not-resume-a-watcher).
The change that closes a gap removes its callouts and its section.
A page template
---
title: Watchers
summary: Small programs Claude writes that watch something for you and file what they find into a project.
audience: users
owner: docs
status: draft
---
# Watchers
One paragraph: what a watcher is, and why you would want one.
## Write a watcher
Steps, with the exact commands:
vyre watchers create <name>
## Pause one
...
## What watchers will not do
...
## Where to go next
- [Projects and threads](projects-and-threads.md)
- [Tools reference](../reference/tools.md)
(In a real page, fence the command with three backticks instead of indenting it; indented code is not rendered.)
Where to go next
- ADR 0019, why the docs work this way.
- Contributing, the rules for code.