Skip to content

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.

  1. Create a markdown file under docs/src/content/docs/<section>/.
  2. Give it frontmatter (below).
  3. That is it — the sidebar is generated from the directory and each page’s declared order.
Terminal window
cd docs
bun install
bun run dev # live preview
bun run check # what CI runs: prose lint, then build

Every page declares four things:

---
title: Cloud runtime vendors
description: Configure a Fly Machines or velos vendor so the server builds a fresh runtime for each session.
kind: how-to
sidebar:
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.

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.

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.

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.

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.

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.