The structure of how EmptyOS documents itself — surfaces, audiences, what's authored vs generated, and a registry of every doc. This is the file to read (and update) when you change how the docs work. The reader-facing map lives in README.md; this file is the architecture behind it.
EmptyOS's principle is self-documenting (CLAUDE.md Principle 7): facts that drift
are generated from the code, not hand-maintained. This doc encodes that rule so
the docs can't quietly rot the way docs/APPS.md did (it sat at "70 apps" from April
to June while the system grew past 110).
The same underlying facts render to four surfaces for four audiences. The factual
parts of all four are fed by one shared extractor — scripts/doc_data.py — so
they can't disagree.
| Surface | What | Where | Generated by |
|---|---|---|---|
| Repo docs | README, docs/*.md, CLAUDE.md |
the git repo | authored + the generators below |
| Public site | eos.binbian.net project/marketing site | publish app → vault site source (30_Resources/EmptyOS-Site/) |
scripts/generate_emptyos_site.py |
| In-product docs | eos app info <id>, /topology, /system capability inspector |
the running daemon | live, from manifests + kernel state |
| Agent context | .claude/rules/*, .agent-bus/, CLAUDE.md |
the repo, loaded into an AI's context | authored |
The public site is a renderer, not a separate tool: the same
scan_apps() / scan_capabilities() / scan_release() that produce APPS.md and
TIERS.md feed the site's app/capability/plugin pages. Fix a fact in the extractor →
every surface picks it up.
| Layer | Question | Primary docs |
|---|---|---|
| User / Visitor | What is this? How do I run it? What can it do? | README, GETTING-STARTED, APPS, live demo |
| App Builder | How do I build an app / plugin / skill? | APP-DEVELOPMENT, .claude/rules/*, eos app info, APP-SPECS |
| Operator / Deployer | How do I deploy / package / configure? | DEPLOYMENT, TIERS, AUTH, PRIVACY, RELEASING |
| Contributor / Agent | How does it work? How do I evolve it? | CLAUDE.md, DESIGN, SYSTEM-INTERNALS, .claude/rules/*, .agent-bus/ |
Authored vs Generated — the load-bearing rule:
- Generated (facts that drift) carry a
<!-- auto-generated by … — do not hand-edit -->banner and a--checkmode that fails CI when stale. APPS.md, TIERS.md, the capability/plugin tables on the site, and the rootDESIGN.mddesign-token block. - Authored (judgment, philosophy, narrative, patterns) is hand-written: README,
DESIGN, GETTING-STARTED, APP-DEVELOPMENT, every
.claude/rules/*.
Shipped vs Internal — release.toml [include] paths lists the docs that ship in a
public git clone: README, CLAUDE.md, DESIGN, GETTING-STARTED, APP-DEVELOPMENT, APPS,
TIERS. Everything else under docs/ is internal (design notes, audits, roadmaps).
code (manifests, capabilities/types.py, release.toml)
│
▼
scripts/doc_data.py ← one deterministic extractor (no kernel boot)
│ │ │
│ │ └────────────► scripts/generate_tiers_doc.py → docs/TIERS.md
│ └────────────────► scripts/generate_apps_doc.py → docs/APPS.md
└────────────────────► scripts/generate_emptyos_site.py → eos.binbian.net pages
▲
scripts/doc_llm.py ───────────────┘ (best-effort, cached LLM prose polish; --llm)
doc_llm.py adds crisp one-line app summaries via the daemon's /assistant/api/chat
(one-shot, context:false, Bearer token), cached by manifest hash in
docs/doc-summaries.json (tracked) so unchanged apps never re-call. It is enrichment, never a
dependency — every generator produces accurate output with no daemon, using the
manifest description as the baseline summary.
| Script | Produces | Refresh | Check (CI) |
|---|---|---|---|
scripts/doc_data.py |
(shared extractor — no output) | --dump / --json |
— |
scripts/doc_llm.py |
docs/doc-summaries.json (tracked) cache |
(called with --llm) |
— |
scripts/generate_apps_doc.py |
docs/APPS.md |
python scripts/generate_apps_doc.py [--llm] [--public-only] |
--check |
scripts/generate_tiers_doc.py |
docs/TIERS.md |
python scripts/generate_tiers_doc.py |
--check |
scripts/generate_emptyos_site.py |
site pages in the vault (incl. one per-app page apps/<id>.md per public app) |
python scripts/generate_emptyos_site.py |
--dry-run |
scripts/gen-design-md.py |
root DESIGN.md token block |
python scripts/gen-design-md.py |
--check |
The committed docs/APPS.md is comprehensive (public + extension tracks) for
contributors. release-public.py regenerates it with --public-only in the
snapshot (after the tier filter), so a public clone's catalog lists only the apps
that actually ship (core + standard) — no references to extension/labs apps
absent from the bundle. APPS.md intentionally omits per-app route counts: those
recompute from @web_route on every code edit and would churn the doc (and the
gate) on unrelated changes. Route totals live in the site/eos app list instead.
The --check modes are wired into scripts/preflight.py (scope docs) and the
release path, so doc drift fails loudly — same pattern as the check-*.py scanners
(.claude/rules/self-audit-loops.md).
Generated (do not hand-edit):
| Doc | Surface | Owner script | Shipped |
|---|---|---|---|
docs/APPS.md |
repo | generate_apps_doc.py |
yes |
docs/TIERS.md |
repo | generate_tiers_doc.py |
yes |
DESIGN.md (root) |
repo | gen-design-md.py |
(token contract) |
site apps/plugins/capabilities.md |
public site | generate_emptyos_site.py |
n/a |
site apps/<id>.md (per-app intro, public version only) |
public site | generate_emptyos_site.py |
n/a |
Authored — primary (shipped):
| Doc | Audience | Purpose |
|---|---|---|
README.md |
User | Front door — what/why/quick-start/architecture |
docs/GETTING-STARTED.md |
User | Install, configure, first boot |
docs/APP-DEVELOPMENT.md |
Builder | Build an app (pattern, manifest, UI) |
docs/DESIGN.md |
Contributor | Architecture, philosophy, consciousness model |
CLAUDE.md |
Contributor / Agent | Conversation-mode boot prompt (full architecture) |
Authored — internal reference (current):
| Doc | Audience | Purpose |
|---|---|---|
docs/DEPLOYMENT.md |
Operator | 5 deployment lanes; Hetzner + Caddy walkthrough |
docs/RELEASING.md |
Operator | Release flow + tier packaging |
docs/AUTH.md |
Operator | Single-user auth pin + rationale |
docs/PRIVACY.md |
Operator | Cloud-consent + vault-stays-local contract |
docs/CONVERSATION-STACK.md |
Builder | 5 chat backends × 8 frontends decision map |
docs/CONTEXT-PACKING.md |
Builder | Token-compression domain |
docs/SYSTEM-INTERNALS.md |
Contributor | Kernel boot, loaders, event bus internals |
docs/APP-SPECS.md |
Builder | Per-app spec sheets |
docs/EOS-MARKDOWN-PROFILE.md |
Builder | Vault Markdown storage + rendering contract |
docs/SOFT-SCHEMA.md |
Builder | VaultModel soft-typed frontmatter |
docs/FRONTEND-DESIGN-LANGUAGE.md |
Builder | Visual + interaction DNA (read before touching a page) |
docs/ENGINEERING-APP-WORKFLOW.md |
Builder | Algorithm-doc → KB → engine → calculator pipeline |
docs/ROOMS-V3.md |
Builder | Multi-participant rooms + review gate |
docs/DOGFOOD-AGENT.md, docs/fix-agent.md |
Contributor | Self-test/fix loop internals |
docs/app-builder.md |
Builder | In-app app generation |
.claude/rules/* |
Agent | ~45 pattern rules loaded into AI context |
.agent-bus/ |
Agent | Canonical store rippled to CLAUDE/GEMINI/AGENTS boot files |
Authored — point-in-time (historical; not current reference): docs/BACKLOG.md,
docs/MIGRATION.md, docs/HOME-COMPANION-REDESIGN.md, docs/EMPTYOS-EM-ROADMAP.md,
docs/OPEN-SOURCE-BORROWING-PLAN.md, docs/KB-APP-ALIGNMENT-AUDIT.md,
docs/EXPORT-ANALYSIS.md, docs/AGENT-BENCH.md, docs/AGENT-TEAM-PATTERNS.md,
docs/CABLE-REPORT-LIMITS.md, docs/cable-rating-em-engine-integration.md,
docs/ENGINEERING-WORK-LOOP.md, docs/legacy/. These captured a moment (a redesign, an
audit, a plan). Keep for history; don't treat as the current state — verify against code.
- A fact that drifts → generate it. If you find yourself hand-editing a count, a
capability list, an app table, or a tier roster, that belongs in
doc_data.py+ a generator, not in prose. - Add a new doc → register it here (and add a line to
README.md). State its audience, authored/generated, and shipped/internal. - Promote a doc to shipped → add it to
release.toml [include] pathsand mark it shipped here. - Author prose once. Philosophy/narrative lives in DESIGN/CLAUDE; don't restate it in generated docs — link instead.
- Run the gate.
python scripts/preflight.py --scope docsbefore a release; it runs every generator's--check.