Writing docs
These docs live in the docs/ directory of the horsie repository and are
published to docs.horsie.dev on every push to main. They are in the same
repository as the code on purpose: a change that alters behaviour changes its
pages in the same commit, and CI reviews both together.
This page is the contract. It is also a page on the site, so it is subject to its own rules.
Add a page
Section titled “Add a page”- Create a markdown file under
docs/src/content/docs/<section>/. - Give it frontmatter (below).
- That is it — the sidebar is generated from the directory and each page’s declared order.
cd docsbun installbun run dev # live previewbun run check # what CI runs: prose lint, then buildFrontmatter
Section titled “Frontmatter”Every page declares four things:
---title: Cloud runtime vendorsdescription: Configure a Fly Machines or velos vendor so the server builds a fresh runtime for each session.kind: how-tosidebar: order: 3---| Field | Why it is required |
|---|---|
title |
The page heading and the sidebar label. |
description |
The meta description and the search snippet. A page without one is invisible to someone scanning results. |
kind |
One of tutorial, how-to, reference, explanation. See below. |
sidebar.order |
The sidebar is autogenerated, so position is the page’s own to declare. |
title, description and kind are enforced by the content schema, so
astro build rejects a page missing any of them. sidebar.order is enforced
by bun run lint:prose.
Sections follow the reader
Section titled “Sections follow the reader”| Section | Directory | For a reader who… |
|---|---|---|
| Start here | start-here/ |
does not yet know what this is, or has nothing running. |
| Using horsie | using/ |
has a working server and wants to do something with it. |
| Operating horsie | operating/ |
is responsible for the deployment. |
| CLI | cli/ |
is in a terminal. |
| How it works | internals/ |
wants to know why, usually before trusting it. |
| Contributing | contributing/ |
is going to change the code. |
If you cannot say which of those a new page is for, the page is probably two pages.
One kind per page
Section titled “One kind per page”Every page is exactly one of four kinds. This is the rule that stops a how-to slowly accreting architecture prose until nobody can follow it.
tutorial — a beginner follows it start to finish and reaches a working
result. No alternatives, no options, no explanation of why.
Test: could a reader skip a step and still succeed? Then the step does not belong.
how-to — a reader who already has a goal accomplishes it. Assumes
context, states prerequisites, may branch.
Test: does it start from a goal rather than from a concept?
reference — exhaustive, structured, dry. Every field, every flag, no
narrative.
Test: is anything here an opinion or a recommendation? Move it.
explanation — why the system is the way it is. No instructions.
Test: does the reader come away able to make a decision they could not make before?
The paragraph test. A paragraph that fails its page’s kind moves to the right page or is deleted. When a how-to needs a reason, it links to an explanation page rather than absorbing one.
What the prose must and must not say
Section titled “What the prose must and must not say”These are not style preferences. Each of them exists because the wording it rejects shipped once and had to be corrected by hand.
The product is “horsie server”, lowercase. Sessions are the unit of work, not the name of the product.
The local runtime runs on the reader’s machine and dials out. It is bring-your-own-compute, where their working files already are. Saying it runs on the server host inverts the entire point of it.
Cloud vendors are configured, not deployed. velos and Fly Machines are vendor kinds you fill in under Settings. velos was once a component you ran yourself and is not any more.
Present tense, shipped behaviour only. No roadmap, no plans, no “will”. Describe what the software does today. A claim that file edits appear as they happen was published before any diff rendering existed and had to be walked back in a follow-up.
Do not oversell, and do not hedge. If a limitation is real, name it — the workflow skills union, the eight of thirty-one hook events, the velos idle cost. A reader who finds a limitation you hid stops trusting the parts you described accurately.
Never name another agent harness. Not to compare against, not to credit,
not in passing. Describe the thing itself instead — “the upstream plugin
specification”, “the subscription backend”. The linter enforces this on the
capitalised product names; the lowercase codex in a URL a reader has to type,
and the .claude-plugin/ directory in the bundle format, are facts about things
you configure and are left alone.
No market, positioning or competitor material. These are product docs.
Use the field’s vocabulary, and use it precisely. These docs are written in the terms the space has settled on, because a reader who knows one system should be able to read this one. The load-bearing ones:
| Term | Means here |
|---|---|
| harness | the loop that decides what happens next — horsie-server |
| agent | a reusable configuration: model, instructions, tools, skills |
| session | one running agent and the append-only record of what it did |
| sandbox | the isolated place tool calls execute — a runtime |
| checkpoint | resumable state: a journal snapshot, or a hibernated sandbox |
| trace | the surface a run is reviewed on — the transcript |
| inner / outer harness | what ships, versus what a user assembles on top |
| compaction | keeping the working set small as the record grows |
| progressive disclosure | loading a skill’s body only when it is picked |
Do not invent a synonym for one of these, and do not use one loosely. “Runtime” and “sandbox” are the same object seen from two sides — horsie’s API says runtime, the field says sandbox, and a page may use either as long as it is consistent within itself.
Link, do not duplicate. Two copies of a fact become two versions of it. If you are about to explain something a page already explains, link to it.
What CI checks
Section titled “What CI checks”bun run check runs both, and the docs job runs it on every pull request.
bun run lint:prose (docs/scripts/lint-prose.mjs) validates frontmatter
and enforces the mechanical half of the rules above as a wordlist, over the
site’s pages and the repository README. Each rule reports the line and says why.
To quote a word the linter rejects — as this page has to — wrap the region:
<!-- lint-prose-disable -->… text containing "session server", "vendor agent", or similar …<!-- lint-prose-enable -->Use it to quote, not to argue with a rule. If a rule is wrong, change the rule.
bun run build validates the content schema and every internal link and
heading anchor. Renaming a page and leaving five links behind is the single
most common way these docs rot, so it fails the build rather than warning.
- Sentences over bullet fragments. Bullets are for lists of things, not for avoiding prose.
- Second person for instructions. “Press Save”, not “the user presses Save”.
- Bold for interface labels: Settings → Runtimes → Cloud vendors.
- Code fences carry a language, and shell blocks show the command without a prompt character, so a reader can copy them.
- Link with the destination’s name, not “here” or “this page”.
- Wrap prose at roughly 80 columns. It makes a diff readable.
What does not belong here
Section titled “What does not belong here”docs/superpowers/ holds internal specs, plans and handoffs. It sits outside
the site’s source tree and is never published. It is a working record, not
documentation, and it is not held to any of the above.
Design records — DESIGN.md, PRODUCT.md, CLAUDE.md — are also not
documentation, and the prose linter deliberately does not read them.