# MeshKore Standard

**Version:** 25.0.0 · **Updated:** 2026-06-14 · **Canonical URL:** <https://meshkore.com/standard>

This is the **single source of truth** for the MeshKore folder convention,
file schemas, governance rules, and path conventions. Everything else
(adopt, install, operate, spec, editor-rules, governance.md, templates) is
either a marketing wrapper or a redirect to here. **If something said
elsewhere contradicts this document, this document wins.**

This page intentionally inlines everything. One URL, zero ambiguity.

- Machine-readable variant: <https://meshkore.com/standard.json>
- Plain-markdown variant: <https://meshkore.com/standard.md>
- Current version (single integer): <https://meshkore.com/standard/version>
- Versioning + changelog: see §11 below

---

## 1. What "applying the MeshKore Standard" means

A repo applies the standard when:

1. It contains a top-level `.meshkore/` folder with the **canonical layout** (§2).
2. The `.meshkore/public/cluster.yaml` file validates against the **schema** (§3).
3. Tasks live at the canonical path with the canonical **task frontmatter** (§4).
4. Docs live at the canonical path with the canonical **doc frontmatter** (§5).
5. Logs live at the canonical path with the canonical **log format** (§6).
6. The repo's `.gitignore` matches the **gitignore contract** (§2.2).
7. The optional editor-rules files (`CLAUDE.md`, `.cursorrules`, `.windsurfrules`) include the **boot block** from §8.

Layers L0 (folder-only) and L1 (folder + editor rules) require only steps 1–7. Higher layers (L2 hub registration, L3 daemon, L4 mesh) add capability on top but **do not modify** anything in steps 1–7.

---

## 2. Folder layout — canonical

The `.meshkore/` folder has the following shape. This is the **only**
shape; do not add or rename top-level entries without a standard bump.

```
.meshkore/
├── public/                    ← committed (only this)
│   ├── cluster.yaml           identity, type, admission, members, modules
│   └── README.md              "how to join this cluster"
├── docs/                      cross-cutting docs (gitignored)
│   ├── INDEX.md               map (no content) — see R6
│   ├── architecture/          "How is the system built?"
│   ├── product/               "What are we, and why?"
│   ├── conventions/           "What rules must I follow?"
│   ├── deploy/                "How do I run / deploy this?"
│   ├── security/              "What can go wrong?"
│   ├── ops/                   "What's happening day-to-day?"
│   └── governance.md          a thin pointer to this URL (no duplicated content)
├── modules/<id>/              per-module bucket (gitignored)
│   ├── README.md              what this module is
│   ├── tasks/<ID>-<slug>.md   active tasks
│   ├── log/<YYYY-MM>/         done tasks moved here, monthly buckets
│   └── diagrams/*.mmd         mermaid sources, next to the doc they illustrate
├── roadmap/                   generated state (gitignored)
│   ├── state.json             built by roadmap-build.py; NEVER edit by hand
│   └── state.js               same content wrapped as JS for the architect
├── timeline/                  event log (gitignored)
│   └── <YYYY-MM-DD>.jsonl     one event per line, append-only
├── log/                       human prose session logs (gitignored)
│   └── <YYYY-MM-DD>.md        one file per session day (local date)
├── agents/<id>.yaml           per-machine identity files (gitignored)
├── credentials/               secrets (gitignored, mode 0600)
├── scripts/                   helpers downloaded from /reference/cluster/scripts/ (gitignored)
├── architect/                    cached visualizer (gitignored)
└── .runtime/                  daemon ephemera (gitignored)
```

Notes:

- **`public/` is the only directory committed to git.** Everything else stays per-machine.
- `modules/<id>/` is the **single home of tasks**. There is no top-level `roadmap/tasks/<category>/` anymore. The `category` field in a task's frontmatter MUST equal `<id>`, and the daemon/build validates this.
- `docs/governance.md` is allowed but **must be a thin pointer to <https://meshkore.com/standard>**, not a duplicated copy. Drift is the bug we're avoiding.
- Diagrams (`.mmd`) live **next to the doc they illustrate** under `modules/<id>/diagrams/` or `docs/<category>/<slug>/diagrams/`. Never in a global `/diagrams/` pile.

### 2.1. The `general` module

If a task or doc does not belong to any declared code module (e.g. project-wide work, cross-cutting policy), it goes under the special module `general`. This module MUST be declared in `cluster.yaml` (§3) and is not optional.

### 2.2. `.gitignore` contract

Exactly these two lines, no more, no less:

```
.meshkore/*
!.meshkore/public/
```

This guarantees `public/cluster.yaml` and `public/README.md` are the only `.meshkore/` content that ever reaches git. Validators check this.

---

## 3. `cluster.yaml` schema — canonical

Required fields are marked `*`. Anything not in this schema is non-normative.

> **Which version of the standard is this repo on?** The single-integer
> file `.meshkore/STANDARD_VERSION` is the canonical answer (see §11).
> `cluster.yaml` does NOT carry the standard version — that would
> duplicate state and drift. The published-current version is at
> <https://meshkore.com/standard/version> (plain text, single integer).

> **Spec ↔ manifest ↔ daemon — three independent versions.** They MAY
> drift; the contract between them is:
>
> | Artefact | Versioned how | What it covers |
> |---|---|---|
> | This standard (`/standard.md`) | `STANDARD_VERSION` integer + CHANGELOG per bump | The folder shape, schemas, conventions, daemon API contract |
> | Reference catalog (`/reference/manifest.json`) | per-item `updated` dates + a top-level catalog `version` | What's downloadable (scripts, templates, prompts, stacks) |
> | Python daemon (`daemon.py`) | semver in the file header + `GET /info.version` | The implementation. May ship behind the standard temporarily |
>
> An operator's "I'm on standard v5" answer is the single integer in
> `STANDARD_VERSION`. The other two are implementation detail and don't
> need a per-project record.

```yaml
version: 1                                # * literal int, currently 1
id: my-cluster                            # * slug, [a-z0-9-]{2,40}
type: dev | comms | service | mixed       # * see §3.1
name: "Human-readable name"               # *
description: "One-liner."                 # optional

transport:                                # * (at least .endpoint)
  protocol: websocket | sse | nats        # default: websocket
  endpoint: wss://hub.meshkore.com/ws     # * URL
  fallback: https://hub.meshkore.com      # optional

bootstrap:                                # optional, advertised by the daemon
  hub:      https://hub.meshkore.com
  install:  https://meshkore.com/cluster/install
  operate:  https://meshkore.com/cluster/operate
  standard: https://meshkore.com/standard
  spec:     https://meshkore.com/standard/spec

git:                                      # optional
  repo:        <git remote URL>
  branch:      main                       # default
  auto_pull:   true                       # default
  auto_commit: false                      # default

architect:
  port: 5570                              # default

profile:
  capabilities: []                        # optional list of slugs
  visible_in_directory: false             # default; L2 sets true

admission:                                # see §3.2
  mode: pubkey | manual | open            # default: pubkey
  approval: auto | auto-on-github | manual
  github_users: []                        # required when approval=auto-on-github
  admission_token_lifetime: 3600          # seconds
  max_pending_requests: 50

members:                                  # see §3.3
  - id: alice-laptop                      # * slug
    role: coordinator | participant       # *
    pubkey: "ssh-ed25519 AAAA…"           # *
    pubkey_fingerprint: "SHA256:…"        # *
    github_user: alice                    # optional
    authorized_at: 2026-05-06             # *  YYYY-MM-DD
    authorized_by: <member-id>            # *

modules:                                  # * see §3.4
  - id: api                               # * slug, [a-z0-9-]{2,40}
    name: "Hub API"                       # optional
    kind: code | spec | docs | area       # *
    path: api/                            # optional repo path
    parent: <other-module-id>             # optional, for nesting
    description: "Rust relay (Axum)."
  - id: general                           # * always include
    kind: area
    description: "Project-wide work without a specific module yet."
```

### 3.1. Cluster types

| `type`    | Purpose |
|-----------|---------|
| `dev`     | Code repository where agents write code |
| `comms`   | Conversation-first agent (sales, support, oracle) |
| `service` | Long-running production agent (API, deploy bot) |
| `mixed`   | Combination of the above; behave as the superset |

### 3.2. Admission

`mode: pubkey` accepts agents that sign an admission challenge with a recognized public key. `approval: auto` admits any valid signature. `approval: auto-on-github` admits signatures from GitHub users listed in `github_users:`. `approval: manual` queues admissions for a human.

### 3.3. Members

Each member entry is the public projection of an agent identity. Public keys are committed because they're public by definition; private keys NEVER leave `.meshkore/credentials/`. Fingerprints are OpenSSH SHA256.

### 3.4. Modules

Modules are first-class. The architect renders one navigation row per declared module. Every task carries `category: <module-id>` matching one of these declarations; `roadmap-build.py --validate` exits non-zero on mismatch. Modules can nest via `parent:`. The `general` module is always present (§2.1).

### 3.4.1. Project bucket — canonical taxonomy (v13)

Every cluster MUST declare a `project` module — a meta bucket
holding cross-cutting work that doesn't belong to a single code
folder (deploy, design, docs, audit, security, infra, launch, …).

```yaml
modules:
  # Always first — the project bucket.
  - id: project
    name: "Project"
    kind: area
    description: "Meta bucket — cross-cutting work without its own code folder. Holds deploy, design, audit, general, etc."

  # Real source code modules — peers of `project`.
  - id: web
    name: "Web"
    kind: code
    path: web/
    description: "Solid + Vite frontend."

  # Minimum 4 area children of `project`. Grow on demand.
  - id: deploy
    name: "Deploy"
    kind: area
    parent: project
    description: "CI/CD, deploy scripts, runbooks, env config, cloud provider setup."
  - id: docs
    name: "Docs"
    kind: area
    parent: project
    description: "READMEs, architecture notes, operator manuals."
  - id: design
    name: "Design"
    kind: area
    parent: project
    description: "Visual tokens, brand, design system, UI patterns."
  - id: general
    name: "General"
    kind: area
    parent: project
    description: "Catch-all for project-wide work that doesn't belong to a single module yet."
```

Rules:

- The `project` bucket has `kind: area`, NO `parent:`, NO `path:`.
- Code modules (`kind: code`) sit as PEERS of `project`, not under it. They MUST set `path:` pointing at the source tree they own.
- Cross-cutting areas (`kind: area`) are CHILDREN of `project` via `parent: project`. Add new ones as the project earns them — never seed ahead of work.
- Every module MUST have a `.meshkore/modules/<id>/README.md` (the Context tab in the cockpit renders this verbatim). New stubs are ≤30 lines; sub-agents bump them as the module evolves.

Playbook: <https://meshkore.com/reference/prompts/roadmap-author/v1/cluster-modules.md>
Canonical reference cluster.yaml: <https://github.com/meshkore/meshkore/blob/main/.meshkore/public/cluster.yaml>

**Migration for pre-v13 clusters:** add the `project` module + at least the 4 minimum children + reparent `general` under `project`. Create stub READMEs for each. See CHANGELOG v13 for the catch-up procedure.

### 3.4.2. Module README — canonical schema (v13)

Every module declared in `cluster.yaml.modules` MUST have a README
at `.meshkore/modules/<id>/README.md`. The cockpit's Context tab
renders this file verbatim when the operator selects a module; an
empty file = an empty Context tab.

```yaml
---
title: "<Display name>"            # MUST match cluster.yaml.modules[<id>].name
category: modules                   # MUST be literal "modules"
tags: []                            # optional, free-form
updated: 2026-06-06                 # ISO date, bumped on every substantive edit
owner: rjj                          # optional, operator handle or agent slug
status: stub                        # stub | draft | stable
related: []                         # optional, other module ids
---

# <Display name>

**Purpose:** <1-2 lines — what this module is responsible for>

**Code:** `<folder>/`   <!-- required for kind:code modules; omit otherwise -->

**Status:** stub — generated by Roadmap Author <YYYY-MM-DD>. Sub-agents fill this in as they ship.

---

## Surface

<2-4 lines or 3-6 bullets describing what this module EXPOSES — URLs,
CLI commands, file paths, public functions. Contact points only,
no deep dive.>

## Out of scope

<1-2 bullets, optional: things the operator might expect to find
here but live elsewhere.>
```

**Lifecycle**:

- **Create**: Roadmap Author generates a stub during bootstrap (`status: stub`).
- **Update**: Sub-agents (`custom` / `deploy` / `db` / `testing` / `docs`) bump the README on every commit that changes the module's externally-visible surface (`updated:` to today, raise `status:` `stub` → `draft` on first real commit, `draft` → `stable` once the core surface is settled).
- **Verify**: Roadmap Architect spot-checks on initiative close.

**Max 150 lines**. Split into sub-files under `.meshkore/modules/<id>/` if it grows.

Playbook with worked examples: <https://meshkore.com/reference/prompts/roadmap-author/v1/module-readmes.md>

---

## 3.5. Project context — canonical (v14)

**Definition**: project-wide INVARIANT knowledge loaded into every agent spawn. Theory-grounded in agent context engineering (Anthropic; Cline memory-bank; Cursor `.cursorrules`; Claude Projects): every token costs reasoning bandwidth, so context contains ONLY what changes an agent's decision and would otherwise be re-debated.

**What context IS**:
- The project's stable identity (overview, product, audience).
- Blindado tech decisions (stack — things that DO NOT change).
- Mental model of the codebase (architecture: layers, ownership, data flow).
- Hard rules (constraints — never-do / always-do).
- Domain terms (glossary).
- Decision log (ADR-style: context → decision → consequences).
- Criteria (when-X-vs-Y rules).

**What context IS NOT** (lives elsewhere):
- Audit procedure → `.meshkore/protocols/`.
- Credentials → `.meshkore/credentials/` + `credentials.age`.
- Bookmarks, crons, links, logs, config — each has its own `.meshkore/<storage>/`.
- Roadmap state → `.meshkore/roadmap/` (volatile).
- Module READMEs → `.meshkore/modules/<id>/README.md` (per-module surface, not project-wide).

**`context/` vs `docs/` — where things live** (v25):

`context/` is the **single source of truth for INVARIANTS** — the *what* and
the *what-never-changes*, loaded into every agent spawn. `docs/` is
**categorized reference for the *how*** — detail an agent consults on demand.
One fact has **one home**:

| Need                                          | Lives in                            | Loaded      |
|-----------------------------------------------|-------------------------------------|-------------|
| Identity, scope, status                       | `context/overview.md`               | every spawn |
| Who / value / anti-product                    | `context/product.md`                | every spawn |
| Blindado stack (locked tech)                  | `context/stack.md`                  | every spawn |
| Mental model: layers, ownership, data flow    | `context/architecture.md`           | every spawn |
| Never-do / always-do invariants               | `context/constraints.md`            | every spawn |
| Domain terms                                   | `context/glossary.md`               | every spawn |
| ADR decisions · when-X-vs-Y criteria          | `context/decisions/` · `criteria/`  | every spawn |
| Deploy & run procedures                        | `docs/deploy/`                      | on demand   |
| Runbooks, day-to-day ops, testing             | `docs/ops/`                         | on demand   |
| Threat model, audits                           | `docs/security/`                    | on demand   |
| Detailed architecture / convention write-ups   | `docs/architecture/` · `docs/conventions/` | on demand |

**Rule**: `docs/` **links to** `context/` (wikilink, below); it never copies an
invariant. High-level stack lives in `context/stack.md`; deploy/ops detail in
`docs/deploy/` + `docs/ops/`. There is **no `docs/stack/` category**.

**Anti-pattern** (field-observed): building a parallel `docs/product/`,
`docs/stack/`, `docs/architecture/` tree that re-states `context/`. That
duplicates the source of truth and drifts. Invariant the agent needs every
spawn → `context/`; reference detail consulted on demand → `docs/<category>/`.
Never both.

**Folder layout**:

```
.meshkore/context/
├── overview.md          REQUIRED  ≤200 words  WHAT + scope + status
├── product.md           REQUIRED  ≤200 words  WHO + value + anti-product
├── stack.md             REQUIRED  ≤200 words  blindado tech, table preferred
├── architecture.md      REQUIRED  ≤250 words  layers + ownership + data flow
├── constraints.md       REQUIRED  ≤250 words  never-do / always-do, bullets
├── glossary.md          OPTIONAL  ≤250 words  term → 1-line, table
├── decisions/           REQUIRED (folder)
│   ├── README.md        index, newest first
│   └── YYYY-MM-DD-<slug>.md  each ≤100 words, ADR-style
└── criteria/            OPTIONAL (folder)
    ├── README.md
    └── <slug>.md        each ≤100 words, when-X-vs-Y
```

**Brevity contract**:
- Total budget: **3000 words ≈ 4500 tokens**. Daemon emits `context_tree.token_estimate` in `/state` and refuses to dispatch when over budget.
- Style: bullets, tables, code blocks. NEVER essays. NEVER marketing copy.
- Frontmatter is intentionally minimal — `title` + `updated` (+ optional `status`). No tags, no owner, no related.

**Serialization at spawn**:
Daemon concatenates context into a block prepended BEFORE the agent-type role:

```
=== PROJECT CONTEXT ===   overview.md
=== PRODUCT ===           product.md
=== STACK ===             stack.md
=== ARCHITECTURE ===      architecture.md
=== CONSTRAINTS ===       constraints.md
=== GLOSSARY ===          glossary.md
=== DECISIONS (newest first) ===  decisions/*.md desc-by-date
=== CRITERIA ===          criteria/*.md alphabetical
=== END CONTEXT ===
```

Agents are instructed to treat everything above the `END CONTEXT` marker as invariant. Cached by daemon, refetched only on file mtime change.

**Write rules**:
- **Default: skip**. Most work does not warrant a context write.
- **Write when**: a new invariant lands (stack swap, security rule), an ADR-worthy decision is made, a criterion/threshold is established, a domain term needs locking.
- **Roadmap Author**: bootstraps the 6 canonical files on cluster generation. Stubs `decisions/README.md` + `criteria/README.md`.
- **Master Architect**: maintains overview + stack + architecture + constraints. Appends decisions + criteria as operator dialogue produces them.
- **Sub-agents**: append to `decisions/` or `criteria/` ONLY when their work crystallizes a new project-wide invariant. Default: skip.

**Cross-references** (v25): link between `.meshkore/` files with **wikilinks** —
`[[<path-relative-to-.meshkore>]]`, no extension:

```
[[context/decisions/2026-06-08-chat-turn-queue]]
[[docs/deploy/architect]]
[[modules/<id>/tasks/<task>]]
```

Resolved relative to `.meshkore/`; the `.md` extension is implied. Wikilinks
survive renames better than `[text](path.md)` and are the lingua franca of
Obsidian/Logseq/Foam. The daemon MAY lint broken wikilinks (non-blocking
warning) on its file-poll cycle. Use wikilinks for "see also X" references
between cluster files; keep standard markdown links for external URLs.

Playbook: <https://meshkore.com/reference/prompts/roadmap-author/v1/context-bootstrap.md>

---

## 4. Task frontmatter — canonical

Tasks live at `.meshkore/modules/<module-id>/tasks/<ID>-<slug>.md`.

Each task starts with a YAML frontmatter block:

```yaml
---
id: V30                                   # * task ID (uppercase letters + digits)
title: "Lease manager — module-level write locks"  # *
status: backlog | next | active | blocked | done   # *
priority: low | medium | high | critical  # *
owner: <member-id>                        # *
category: daemon                          # * MUST equal a declared module id
created: 2026-05-12                       # * YYYY-MM-DD
updated: 2026-05-13                       # * YYYY-MM-DD
tags: [leases, coordination]              # optional
depends_on: [V22, AG1]                    # optional list of task ids
initiative: agent-coordination            # optional, see §4.1
parent_initiative: agent-coordination     # optional, for sub-tasks of an absorbed initiative
order: 4                                  # optional, 0-9 sub-timeline tier
effort: "3-4 days"                        # optional, free-form
---

# Body — what to do, why, done-when.
```

### 4.1. Initiatives

A task may belong to an **initiative** — a cross-module body of work declared at `.meshkore/roadmap/initiatives/<id>.md`. Initiatives have their own frontmatter (id, title, status, priority, oneliner, modules, target, created, updated, owner). They aggregate tasks by `initiative:` link. The architect renders them in the Roadmap timeline.

### 4.2. Status meanings

- `draft`    — **spec not finalised, NOT for execution**. Operator-authored placeholder. Architect refuses to dispatch (Run-all + per-initiative skip). Operator-only transition `draft → next`.
- `backlog`  — known, not scheduled
- `next`     — scheduled for next pass
- `active`   — in flight
- `in_progress` / `doing` — synonyms of `active`
- `pending-operator` / `pending_operator` — needs the operator before code can proceed (paste creds, fund wallet, run manual step)
- `blocked`  — waiting on another task / external input
- `cancelled` — abandoned
- `done`     — completed (task moves to `log/<YYYY-MM>/` after some time)

#### Dispatchability (v15)

| Status | Dispatchable by Architect? |
|---|---|
| `next`, `active`, `in_progress`, `doing`, `planned` | ✅ yes |
| `draft` | ❌ never (operator owns promotion) |
| `done`, `cancelled` | ❌ terminal |
| `backlog` | ❌ parked |
| `blocked`, `pending-operator` | ❌ awaiting external |

**Initiative completion**: an initiative reaches `status: done` ONLY when every child task is `status: done`. A `draft` child keeps the initiative `active` — same rule as `next` / `active` / `blocked`.

### 4.3. Done tasks

When a task reaches `status: done` and is not actively referenced, move the file from `tasks/` to `log/<YYYY-MM>/`. The frontmatter stays exactly the same — the move is purely organizational.

---

## 5. Doc frontmatter — canonical

Docs live at `.meshkore/docs/<category>/<file>.md`. They follow Documentation Governance rules **R1–R6** below.

Each doc starts with:

```yaml
---
title: "Human-readable title"             # *
category: <subfolder>                     # * one of: architecture, product, conventions, deploy, security, ops, modules, governance
tags: [docs, governance]                  # optional
updated: 2026-05-13                       # * YYYY-MM-DD
owner: <member-id>                        # *
status: draft | stable | deprecated       # *
related:                                  # optional list of doc paths
  - architecture/cluster-layout.md
---
```

### 5.1. R1 — Categorize by intent, not by age

`.meshkore/docs/` has **seven** subcategories. Pick the one that fits the *intent* of the document:

| Subfolder       | What it answers |
|-----------------|------------------|
| `architecture/` | "How is the system built?" Technical detail for devs touching code |
| `product/`      | "What are we, and why?" Strategy, growth, positioning |
| `conventions/`  | "What rules must I follow?" Namespaces, taxonomies, policies |
| `modules/`      | "How does this specific code module work?" One file per module |
| `deploy/`       | "How do I run / deploy this?" Procedures, step-by-step |
| `security/`     | "What can go wrong?" Threat model, audits, mitigations |
| `ops/`          | "What's happening day-to-day?" Issues, testing notes, runbooks |

If a doc doesn't fit any, **do not create a new top-level folder** without thinking twice. Friction of an extra category > friction of nesting.

### 5.2. R2 — Max 200 lines per file

Long files become unreadable, and large diffs hide intent. If a file grows past 200 lines:

1. Identify the natural split (one section per file).
2. Create a sub-subfolder if the topic is large enough (`architecture/protocols/` is precedent).
3. Reference the split files from a parent INDEX or from the original doc (turning it into an overview).

### 5.3. R3 — One topic, one canonical file

If two files cover the same thing, fuse them. The other one becomes a one-line redirect (`See [path](path).`) or gets deleted. The Source-of-Truth table in `docs/architecture/reference.md` (or equivalent) lists which file owns which topic.

### 5.4. R4 — Frontmatter required

Every `.md` MUST start with the frontmatter block from §5. **Exemptions** (and only these):

- `INDEX.md` files (any folder)
- `README.md` files (any folder)
- `.meshkore/docs/governance.md` (it's just a pointer to this URL)
- Files under `.meshkore/log/` (session logs are append-only prose)
- Files under `.meshkore/timeline/` (machine-generated JSONL)
- Generated files (`state.json`, `state.js`, `directory.json`)

`roadmap-build.py --validate` rejects any other `.md` missing required fields.

### 5.5. R5 — Link, don't copy

When you reference content that lives elsewhere (in the public website, in another doc, in a generated artifact), **link to it**. Never paste a copy. If the source moves, you only have one place to update.

### 5.6. R6 — `INDEX.md` is a map, not content

Each folder may have an `INDEX.md` that **describes its subcategories and links to them**. It MUST NOT contain prose that duplicates the documents themselves. Same role as `MEMORY.md` plays for auto-memory: one line per entry, no narrative.

---

## 6. Logs — canonical format

Two distinct log streams. Don't confuse them.

### 6.1. Session logs — prose, human

Path: `.meshkore/log/<YYYY-MM-DD>.md` (local date, one per productive session day).

Content: human prose, topic-grouped, capturing what was done, what was learned, what hurt. Append-only — never edit past entries. Skipping a day is fine; gaps are signal.

No frontmatter required (this is an R4 exemption).

### 6.2. Timeline — events, machine

Path: `.meshkore/timeline/<YYYY-MM-DD>.jsonl` (UTC date, machine-appended).

Content: one JSON event per line. Generated by the daemon, the build script, or `.meshkore/scripts/timeline-append.py`. Never edit by hand.

Event shape:

```json
{ "ts": "2026-05-13T14:22:01Z", "type": "<event>", "author": "<member-id>", "text": "..." }
```

See §6.3 for the full event-type vocabulary.

### 6.3. Timeline event vocabulary

Every event type emitted by the daemon, the build script, or
`timeline-append.py` is named below. Custom types outside this list
are permitted but will not be rendered by the architect's panels;
extend the standard if a new type becomes common.

**Tasks** (lifecycle)
- `task.created`       — a new task `.md` file landed
- `task.transition`    — `status` field changed (payload: `from`, `to`)
- `task.started`       — runner picked up the task
- `task.completed`     — runner finished successfully
- `task.failed`        — runner aborted; payload includes `error`
- `task.cancelled`     — operator or system aborted before completion

**Chat** (coordinator + workers)
- `chat.user`          — operator typed something
- `chat.assistant.delta` — partial token stream from the AI client
- `chat.assistant.final` — final assistant message of a turn
- `chat.assistant.tool` — tool call inside an assistant turn
- `chat.cancelled`     — operator killed the turn mid-stream

**Tools** (claude-code / cursor / etc.)
- `tool.bash`          — shell command run by the AI client
- `tool.write`         — file write by the AI client
- `tool.edit`          — file edit by the AI client
- `tool.read`          — file read by the AI client

**Git / deploy** (linked to commits + provider deploys)
- `commit.created`     — local commit was just made
- `commit.pushed`      — branch pushed to origin
- `deploy.started`     — deploy script began
- `deploy.completed`   — deploy succeeded; payload includes provider + URL
- `deploy.failed`      — deploy aborted; payload includes provider + error

**Cron** (§v3 scheduler)
- `cron.fired`         — coordinator triggered a job
- `cron.skipped`       — non-coordinator daemon would have fired
- `cron.timeout`       — job exceeded `max_runtime_sec`
- `cron.error`         — job failed before completion
- `cron.finished`      — job completed (success or failure)

**Standard / state** (system-level)
- `state.rebuilt`      — daemon re-built `state.json` from the FS
- `links.updated`      — `.meshkore/public/links.yaml` was rewritten (§v4)
- `protocols.updated`  — a file under `.meshkore/protocols/` changed (§v5)
- `bookmarks.updated`  — `.meshkore/public/bookmarks.yaml` changed (planned)
- `daemon.shutdown`    — daemon is exiting

Every event MUST have `ts` (ISO-8601 UTC) and `type`. The `author`
field is the agent or member id that caused the event. Other fields
are type-specific and the architect accepts them opportunistically.

---

## 6.4. Chat-turn queue (v16)

**Purpose**: per-conv FIFO of operator-authored prompts that wait their
turn. Auto-flushes when the conv goes idle after a turn final. Lets
operators line up follow-ups while a long-running task is still in
flight, instead of waiting or interrupting.

**Storage**: `.meshkore/queues/<conv>.json` — committed: false.
Created on first enqueue; deleted when the last item dispatches.

**File shape**:

```json
{
  "conv": "<conv-slug>",
  "version": 1,
  "items": [
    {
      "id": "qt_<10hex>",
      "text": "<prompt body>",
      "created_at": "<ISO>",
      "position": 0,
      "status": "queued"
    }
  ]
}
```

`status`: `queued` | `sending` | `sent` | `failed` | `cancelled`.

**Limits**: max 50 items per queue, max 64 KB per text. Hard caps in the daemon.

**HTTP routes**:

| Verb | Path | Body | Behavior |
|---|---|---|---|
| GET | `/chat/conv/<conv>/queue` | – | list |
| POST | `/chat/conv/<conv>/queue` | `{ text }` | enqueue → returns new item |
| POST | `/chat/conv/<conv>/queue/<id>/edit` | `{ text }` | edit (409 if already dispatched) |
| POST | `/chat/conv/<conv>/queue/<id>/move` | `{ position }` | reorder (409 if dispatched) |
| POST | `/chat/conv/<conv>/queue/<id>/promote` | – | force-dispatch this item NOW (skip head) |
| DELETE | `/chat/conv/<conv>/queue/<id>` | – | remove |

**WS events**: `queue.item.added`, `queue.item.updated`, `queue.item.removed`, `queue.item.sent`. Each carries `{ conv, item, ts }`.

**Auto-flush**: when `chat.assistant.final` fires AND the conv summary's `live=false coordinating=false`, the daemon pops the queue head and dispatches it as a normal user message via `/chat/dispatch`. One turn → one flush.

**Scope exclusions**:
- NOT used by the architect or sub-agents to dispatch their own work — that goes through `/chat/dispatch` directly.
- NOT persisted across cluster swaps (queue belongs to one daemon).
- NOT exposed in the roadmap (queue items are chat-thread artifacts, not initiatives).

**Anti-patterns**:
- Treating the queue as a substitute for tasks/initiatives — it's transient.
- Auto-flushing the WHOLE queue when the conv goes idle — only the HEAD pops per flush, by design.
- Writing directly to `convMap` for queued items — every queued message must flow through `/chat/dispatch` for the normal lifecycle (timeline append, architect-wake, etc).

---

## 7. Path conventions — single table

The one cheatsheet that should resolve "where does this go?":

| You want to write…                              | Put it in… |
|--------------------------------------------------|-------------|
| A new task to track                              | `.meshkore/modules/<module>/tasks/<ID>-<slug>.md` |
| A done task being archived                       | `.meshkore/modules/<module>/log/<YYYY-MM>/<ID>-<slug>.md` |
| A cross-module initiative                        | `.meshkore/roadmap/initiatives/<id>.md` |
| How a code module works                          | `.meshkore/docs/modules/<id>.md` OR `.meshkore/modules/<id>/README.md` |
| Architecture, strategy, conventions, ops, …      | `.meshkore/docs/<category>/<slug>.md` (R1) |
| A diagram                                        | next to the doc it illustrates: `<slug>/diagrams/*.mmd` |
| Daily session log (prose)                        | `.meshkore/log/<YYYY-MM-DD>.md` |
| Event timeline (machine)                         | `.meshkore/timeline/<YYYY-MM-DD>.jsonl` |
| Cluster identity for the world                   | `.meshkore/public/cluster.yaml` |
| Credentials, tokens, env vars                    | `.meshkore/credentials/` (mode 0600, gitignored) |
| Per-machine agent identity                       | `.meshkore/agents/<id>.yaml` |
| A draft / proposal                                | task with `status: draft` in `modules/<id>/tasks/` |
| A pointer / map                                   | `INDEX.md` in the relevant folder (no prose) |

---

## 8. Editor boot block — canonical

Drop one of these files at the repo root. The content is the same paragraph; only the filename varies by editor. Source of truth is this section — when this section changes, the templates at `/reference/cluster/editor-rules/` are regenerated from here.

**Filenames:**

- Claude Code → `CLAUDE.md`
- Cursor → `.cursorrules`
- Windsurf → `.windsurfrules`

**Canonical content (paste verbatim):**

```
# MeshKore Standard

This repo applies the MeshKore Standard. The single source of truth is
https://meshkore.com/standard — read it at the start of every session
and apply it throughout.

What this means in practice:
- The .meshkore/ folder follows the canonical layout (standard §2).
- Tasks live at .meshkore/modules/<module>/tasks/<ID>-<slug>.md and
  carry the canonical frontmatter (standard §4). Done tasks move to
  .meshkore/modules/<module>/log/<YYYY-MM>/.
- Docs live at .meshkore/docs/<category>/<file>.md with the canonical
  frontmatter (standard §5) and obey governance rules R1-R6.
- Session logs go to .meshkore/log/<YYYY-MM-DD>.md (prose, no frontmatter).
- The timeline at .meshkore/timeline/<YYYY-MM-DD>.jsonl is machine-only.
- Public file: .meshkore/public/cluster.yaml (only thing committed).

Hard rules — never:
- Never commit anything under .meshkore/ outside public/.
- Never edit generated files (state.json, state.js, directory.json).
- Never push to origin without the operator explicitly asking.
- Never create a new top-level module or doc category without first
  declaring the module in cluster.yaml.

Refresh cadence:
- Fetch https://meshkore.com/standard/version once per session (or
  every 24 h, whichever comes first). It returns a single integer; if
  it's higher than .meshkore/STANDARD_VERSION, read the relevant
  section(s) in https://meshkore.com/standard/CHANGELOG.md and apply.
- For layer-specific engineering standards (audit, stack, deploy,
  testing), use the catalog at https://meshkore.com/reference/standards/.
```

If you need editor-specific extras (model preference, IDE quirks), add them BELOW the canonical block — never above, never replacing.

---

## 9. Hard rules — system-wide

- **Never commit** `.meshkore/credentials/`. Ever.
- **Never commit** anything under `.meshkore/` outside `public/`.
- **Never push** to `origin` without the operator explicitly asking.
- **Never edit** generated files: `roadmap/state.json`, `roadmap/state.js`, `directory.json`, anything under `.runtime/`.
- **Never invent** a new top-level module / category without first declaring it in `cluster.yaml`. Use `category: general` if unsure and ask.
- **Never duplicate** normative content from this page into a local doc. Link to here.
- **Every LLM-authored commit MUST carry `Agent:` and `Model:` trailers** in addition to `Co-Authored-By:`. See §9.1.

---

## 9.1. Commit attribution — `Agent:` + `Model:` + `MeshKore:` trailers (v12, revised v21)

**Applies to**: every commit whose author or co-author is an LLM
agent (Claude, GPT, Cursor's runner, etc.) in any MeshKore repo
(`daemon/`, `architect/`, `webapp/`, `.meshkore/`, sibling
component repos, downstream cluster repos). Human-only commits
MAY omit. Mixed-authored commits MUST include — if an LLM touched
the diff, attribute it.

> **Operator note.** Across non-MeshKore repos the project's general
> convention is **no co-authoring trailers at all**. MeshKore is the
> explicit exception: agent-driven work is the rule, not the edge case,
> and we need git history to be auditable per-role / per-model /
> per-cluster-version. The three semantic trailers below are how.

**Format**: standard git trailers appended to the commit body,
after a blank line, in this order:

```
<conventional commit title>

<body explaining why, not what>

Agent: <agent-role>
Model: <model-id>
MeshKore: py-<X.Y.Z>
```

| Trailer | Required | What | Examples |
|---|---|---|---|
| `Agent:` | **yes** | The agent ROLE that produced the commit. From the daemon's `agent_type` field or the conv slug. | `master`, `roadmap-architect`, `work-I13-CAT2`, `deploy-cavioca-prod`, `review`, `docs`, `custom` |
| `Model:` | **yes** | The exact MODEL ID as published by the vendor. Use `Model: unknown` if the runner truly doesn't know — never omit. | `claude-opus-4-8`, `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `gpt-5`, `cursor-default` |
| `MeshKore:` | **yes** (v21+) | The MeshKore DAEMON version this cluster was running when the agent authored the commit. Quote the literal `py-X.Y.Z` from the daemon's `DAEMON_VERSION` constant — embedded in every subagent briefing. | `py-1.12.15`, `py-1.12.14`, `py-1.11.2`, `py-1.10.13` |
| `Standard:` | only on standard bumps | Format `vN-1 → vN`. Use only when the commit edits the four standard files (md / json / CHANGELOG / version). | `Standard: v20 → v21` |
| `Section:` | only on standard bumps | Top-level key in `standard.json` that received the change. Companion to `Standard:`. | `Section: commit_attribution` |

**Removed in v21**: `Co-Authored-By:`. Was a pre-existing convention
from the Claude Code CLI default. Adds no information that
`Agent:` + `Model:` + git's own author/committer fields don't already
carry, and the operator's cross-repo convention is no-co-authoring.
**Do not add this trailer to MeshKore commits.**

**Worked example** (ordinary work commit):

```
feat(rail): show waiting-on pill while subagents stream

Master polled its waiting list every 2s — replaced with WS-driven
incrementals so the pill flips the moment a child finalises.

Agent: master
Model: claude-opus-4-7
MeshKore: py-1.12.15
```

**Worked example** (standard bump):

```
feat(standard): v20 → v21 — add MeshKore daemon-version trailer

Closes operator request 2026-06-09. Removes Co-Authored-By from
mandatory trailers (matches the cross-repo convention) and adds
`MeshKore:` so every agent commit records the daemon runtime.

Agent: master
Model: claude-opus-4-7
MeshKore: py-1.12.15
Standard: v20 → v21
Section: commit_attribution
```

**Why this exists.** Field-reported 2026-06-02: a fast "resync" pass
from a roadmap-architect looked indistinguishable in git history from
real subagent work. Trailers make the role and the model legible to
`git log` / `git blame` / analytics. They survive merges and
cherry-picks unchanged. They're parseable via
`git interpret-trailers --parse` (RFC 5322 headers). The v21
`MeshKore:` addition lets operators correlate commit activity with
specific daemon releases — "this regression cohort all carries
`MeshKore: py-1.12.13`; py-1.12.14 fixed the underlying issue".

**Analytics example** — count commits per (model, daemon) cohort
over the last week:

```bash
git log --since=1week \
  --format='%H%n%(trailers:key=Model,valueonly,separator=)%n%(trailers:key=MeshKore,valueonly,separator=)' \
  | paste - - - | awk -F'\t' 'NF==3{print $2,$3}' | sort | uniq -c | sort -rn
```

**Subagents.** The daemon spawns subagents via Claude Code; the
spawn-time briefing carries the agent slug, the resolved model id,
and the daemon's own `DAEMON_VERSION` so every subagent stamps its
own commits without operator action.

---

## 10. Validation

Local validator: `python3 .meshkore/scripts/roadmap-build.py --validate` checks:

- Folder layout matches §2
- `public/cluster.yaml` validates against §3
- Every task in `modules/<id>/tasks/` has `category: <id>` and the required frontmatter
- Every doc in `docs/<category>/` has the required frontmatter (unless exempt per §5.4)
- `.gitignore` contains the two-line contract from §2.2

The daemon runs this validator on every boot. The architect renders a yellow warning badge on any file flagged.

---

## 10.1. Multi-project on one machine

A single machine can host any number of MeshKore projects simultaneously. Each
project is a separate repository with its own `.meshkore/` folder and its own
Python daemon (§10.2). The daemons coexist on different ports:

- Default port: `5570`. If busy, the daemon walks up the range `5570–5589`
  and binds the first free port. The chosen port is written to
  `.meshkore/.runtime/port` and surfaced by `GET /health` on that port.
- The architect (web cockpit) probes the same range on load, lists every
  responding daemon in its leftmost **Projects** rail, and the operator
  picks which one to focus. Switching projects is a one-click action
  inside the architect — no relaunch, no separate URL.
- Each daemon is otherwise standalone: it watches only its own
  `.meshkore/`, accepts WebSocket connections only from `localhost`, and
  has no awareness of sibling daemons. There is no cross-project
  bleeding of state, members, credentials, or tasks.

`cluster.yaml.architect.port` (optional integer, default 5570) lets a
project declare a preferred port — useful when the operator wants stable
URLs per project. If declared, that port is tried first; on conflict
the daemon falls back to scanning 5570–5589.

A future capability — a single daemon process managing multiple
projects under one port — is on the roadmap as initiative
`single-daemon-multi-project` (see `.meshkore/roadmap/initiatives/`).
It is **not** part of v6.0.0 of this standard; the per-folder daemon
model described above is the canonical one.

---

## 10.2. Python daemon — canonical entry

The canonical L3 daemon is a **single Python script** distributed at
<https://meshkore.com/reference/cluster/scripts/daemon.py>. It is:

- **Stdlib only.** No `pip`, no `npm`, no virtualenv. Works on any
  machine that has `python3 ≥ 3.8` (every Mac, every Linux, every
  modern Windows).
- **Not an installable binary.** Corporate laptops with software-audit
  controls usually block unsigned binaries but allow scripts. The
  Python daemon sidesteps that whole problem class.
- **Drop-in.** Any AI agent in your editor (Claude Code, Cursor,
  Windsurf, …) can `curl` it into `.meshkore/scripts/daemon.py` and run
  it. Nothing leaves the repo.

### Install + run

```
mkdir -p .meshkore/scripts/tls
curl -fsSL https://meshkore.com/reference/cluster/scripts/daemon.py \
  -o .meshkore/scripts/daemon.py
curl -fsSL https://meshkore.com/reference/cluster/scripts/tls/fullchain.pem \
  -o .meshkore/scripts/tls/fullchain.pem
curl -fsSL https://meshkore.com/reference/cluster/scripts/tls/privkey.pem \
  -o .meshkore/scripts/tls/privkey.pem
chmod 600 .meshkore/scripts/tls/privkey.pem
python3 .meshkore/scripts/daemon.py
```

The `tls/` bundle is **optional but recommended** (§10.2.1). Without
it the daemon serves plain HTTP — works everywhere but the cockpit
on `architect.meshkore.com` won't have WebSockets / real-time
updates from this daemon and Chrome will flag every fetch as a
Local Network Access "Issue".

On first launch the daemon:

1. Reads `.meshkore/public/cluster.yaml` (§3).
2. Mints a bearer token at `.meshkore/credentials/portal-token` (mode
   `0600`) if one doesn't exist.
3. Binds the first free port in `5570–5589` (preferring
   `cluster.yaml.architect.port` if set). The chosen port is written to
   `.meshkore/.runtime/port` and the PID to
   `.meshkore/.runtime/daemon.pid`.
4. Builds `roadmap/state.json` from the markdown filesystem; rebuilds
   on file change (polled every ~1.5 s).
5. Serves the architect-facing API described in §10.3.

### Endpoints (the contract architects implement)

| Method | Path             | Auth | Purpose |
|--------|------------------|------|---------|
| GET    | `/health`        | no   | `{ok, port, identity, cluster_id, cluster_name, cluster_type, implementation, version, tls, endpoint, features, ts}` |
| GET    | `/info`          | no   | Same as `/health` + version + paths |
| GET    | `/state`         | no   | Filesystem-derived `state.json` |
| GET    | `/reload`        | yes  | Force rebuild + broadcast `state.rebuilt` |
| GET    | `/agents`        | no   | Listing of `.meshkore/agents/*.yaml` |
| GET    | `/events`        | WS   | Push events: `hello`, `heartbeat`, `state.rebuilt`, `daemon.shutdown`. WSS when `tls=true`. |
| POST   | `/shutdown`      | yes  | Graceful exit; the architect's Stop button uses this |

Auth = `Authorization: Bearer <portal-token>`.

### 10.2.1 Loopback TLS (`daemon.meshkore.com`)

The daemon advertises its scheme at `/health.tls` (boolean) and the
full URL at `/health.endpoint`. Two modes:

| `tls` | `endpoint` | When |
|---|---|---|
| `false` | `http://localhost:<port>` | No `tls/` bundle next to `daemon.py`. The legacy mode — works from a cockpit running at `http://localhost:<port>/architect` (same origin) but blocked from `https://architect.meshkore.com` by mixed-content + Chrome Local Network Access. |
| `true`  | `https://daemon.meshkore.com:<port>` | `tls/fullchain.pem` + `tls/privkey.pem` exist next to `daemon.py`. The DNS A record `daemon.meshkore.com → 127.0.0.1` is public, so any HTTPS origin can reach the local daemon over a real HTTPS+WSS connection. |

The cockpit reads `health.tls` and picks the URL deterministically.
The wildcard cert is for `*.daemon.meshkore.com`, ECDSA P-256,
renewable via `daemon/tls/renew.sh` (certbot + Cloudflare DNS-01,
~60-day cadence). Cert + key are intentionally public — the only
explotable thing they grant is impersonating `daemon.meshkore.com`
on the attacker's own loopback, which gives access to nothing.
Same pattern as Plex's `*.plex.direct`, Caddy 2 local TLS, etc.

To upgrade an existing daemon to the TLS bundle, run protocol
[`P4`](/cluster/protocols/P4-daemon-upgrade) — three `curl` calls
plus a graceful restart.

### Scope

The Python daemon is the **single canonical L3 entry** — read paths
(visualization, file-derived state, multi-project listing, lifecycle)
plus the runner surface: agent dispatch, chat, headless
`claude --session-id` sessions, cron scheduling. The architect
identifies the daemon via `/health.implementation == "python"` and
reads the advertised `features[]` array to enable/disable UI
surfaces.

### Stopping a daemon

`POST /shutdown` with the bearer token. The architect's Projects rail
exposes a Stop button on every row that does exactly this. The daemon
broadcasts `daemon.shutdown` on the events channel, then exits cleanly
(removes `.runtime/daemon.pid` + `.runtime/port`).

---

## 10.3. Adding more projects to your architect

The architect is a single web page; it is connected to **one transport at
a time** (your local daemon, or — when shipped — the cloud relay). To
add a project you don't currently see, the operator never has to write
code or run a CLI by hand. The flow is always *paste a prompt into your
agent in the new repo* and the agent does the work.

### Local mode — projects on this machine (canonical today)

1. Open your AI agent (Claude Code, Cursor, Windsurf, VS Code Copilot,
   …) in the repo you want to add.
2. Paste the prompt the architect generates for you (see §10.3.1). It
   tells the agent to apply this standard, scaffold `.meshkore/`, drop
   the editor boot block (§8), download the Python daemon (§10.2), and
   start it.
3. The Python daemon binds the first free port in `5570–5589`. Your
   architect probes the range continuously and adds the new project to
   the Projects rail (§10.1) within a couple of seconds.
4. Stop a project at any time from the rail's Stop button — it POSTs
   `/shutdown` (§10.2) with the bearer token.

No human terminal work, no installer, no admin rights. The architect's
"Add project" button (`#projects-rail-add` in `architect/public/index.html`)
opens a modal containing the ready-to-paste prompt.

### 10.3.1. The canonical "apply standard" prompt

This prompt — the body of the Add-project modal — is reproduced here
verbatim so it lives at one canonical URL. Agents reading this section
will recognize it.

```
Apply the MeshKore Standard to this repo so it shows up in my architect.

1. Read the canonical standard at https://meshkore.com/standard.
2. Create the .meshkore/ tree per §2 of the standard.
   Use https://meshkore.com/reference/cluster/templates/cluster.yaml.dev
   as the starter for .meshkore/public/cluster.yaml.
3. Drop the editor boot block from §8 at the repo root (CLAUDE.md /
   .cursorrules / .windsurfrules — pick whichever your editor needs).
4. Download the Python daemon into .meshkore/scripts/daemon.py:
       mkdir -p .meshkore/scripts
       curl -fsSL https://meshkore.com/reference/cluster/scripts/daemon.py \
         -o .meshkore/scripts/daemon.py
5. Start it:
       python3 .meshkore/scripts/daemon.py
   It will bind the first free port in 5570-5589, mint a bearer token at
   .meshkore/credentials/portal-token, and rebuild state.json from the
   markdown filesystem.
6. Print the chosen port + bearer token so I can verify the architect
   picks it up.
```

### Cloud mode — projects on other machines, or from your phone (planned)

A second, complementary connection mode is being built so the same
architect URL can manage projects that are NOT on the machine where
the browser runs:

- A teammate's laptop. Their `.meshkore/` is on their disk; you don't
  want to mirror it onto yours.
- A remote VM with no GUI.
- Your phone — no localhost, no Python.

The wire is straightforward. A local Python daemon (§10.2) gets a
companion **bridge** that opens an outbound WebSocket to a cloud relay
and authenticates with the operator's account. The architect, when
opened in "cloud mode", connects to the same cloud relay (instead of
`localhost:5570`); the relay routes architect ↔ daemon traffic through
the bridge. End-to-end the transport changes; the daemon's API
(§10.2 endpoints) is preserved exactly.

Once Cloud mode lands, the Add-project modal will surface a second
section with the *cloud registration prompt* (the equivalent of the
local one, but pointed at the cloud relay). The Projects rail will
gain a cloud badge per remote project so the operator can tell
local-vs-cloud at a glance.

**Out of scope for the standard:** the cloud relay's authentication,
billing, rate limiting, and the specific transport between bridge and
relay are an implementation detail of the operator's chosen hosting
(today, `cloud.meshkore.com`). The standard fixes the daemon contract
(§10.2). Anything that speaks that contract — whether served from
`localhost` or relayed through a cloud — is a valid backend for the
architect.

This subsection will fill in concrete URLs + the cloud registration
prompt as Cloud mode ships.

### 10.3.4. Bootstrap a new repo

The third operator path is "I have an empty folder, or no folder at
all, and I want to start a brand-new MeshKore project". The architect's
"+ Add project" wizard exposes this as tab **C**. The canonical prompt
the wizard hands to your AI agent:

```
Bootstrap a brand-new MeshKore project in this empty folder.

1. Read the canonical standard at https://meshkore.com/standard.
2. Initialise a git repo here (`git init`) if there isn't one already.
3. Create the .meshkore/ tree per §2 of the standard.
4. Write .meshkore/public/cluster.yaml from the dev template:
       curl -fsSL https://meshkore.com/reference/cluster/templates/cluster.yaml.dev
   …filling in {{cluster_id}} / {{cluster_name}} / {{cluster_description}}
   based on the folder name and ask me to confirm.
5. Drop the editor boot block from §8 at the repo root.
6. Add a top-level README.md describing the project (2-3 sentences).
7. Download the Python daemon:
       mkdir -p .meshkore/scripts
       curl -fsSL https://meshkore.com/reference/cluster/scripts/daemon.py \
         -o .meshkore/scripts/daemon.py
8. Start it: python3 .meshkore/scripts/daemon.py
9. Make a first task at .meshkore/modules/general/tasks/T1-hello.md
   with status: next, owner: me, category: general.
10. Print the chosen port + the bearer token so I can verify the
    architect picks it up.

Stop after step 10 — let me drive the rest from the architect.
```

Variants per stack (Rust API, Solid frontend, Python worker, etc.) can
add stack-specific steps between (6) and (7) using the recipes at
`/reference/stacks/`. The architect wizard's tab C may surface a stack
picker later; for now the prompt above is the canonical baseline.

### 10.3.5. Start a stopped daemon (revive a known project)

When the architect's browser already remembers a project (its
`cluster_id`, friendly name, last port, repo path on disk) but the
daemon isn't currently answering on that port, the operator sees the
project in the Projects rail with a **grey** dot and a "start ↗"
label instead of the regular live state. Clicking the row opens the
**Start project** modal — *not* the full Add-project wizard — because
the architect already has everything it needs to revive that single
project.

The modal contains two ready-to-copy blocks:

```
# Option a — run it yourself
cd "<repo_path remembered by the architect>"
python3 .meshkore/scripts/daemon.py
```

```
# Option b — paste this into a local AI agent (Claude Code / Cursor / …)
Start the MeshKore Python daemon for the project in <repo_path>.
Run:
    cd "<repo_path>"
    python3 .meshkore/scripts/daemon.py
The daemon will pick a free port in 5570-5589 and the architect on
architect.meshkore.com will auto-detect it.
```

If the architect doesn't have a remembered `repo_path` for the project
(e.g. localStorage was cleared, or the project came in via a stale
cluster_id), the prompt falls back to "open the repo where this
project lives and run python3 .meshkore/scripts/daemon.py". A "forget
this project" link in the same modal removes it from the rail if it
no longer exists.

### 10.3.6. Token handling

The architect connects to localhost daemons **without prompting for a
token** — the Python daemon's `/state` endpoint is reachable without
auth. When a write endpoint returns `401`, the architect prompts once,
stores the bearer in `localStorage` keyed by daemon base URL, and
never sends it to any origin other than the daemon.

For cloud-mode connections the token model is account-based; see the
Cloud subsection above and the work-stream owning `cluster-cloud`.

---

## 11. Versioning

Standard version: **6.0.0** (semver — five major iterations between 1.0.0 in May 2026 and 6.0.0).

- **Major** bump: breaking change to folder layout, schemas, or hard rules.
- **Minor** bump: new section, new optional field, new path convention.
- **Patch** bump: clarifications, wording, examples, fixing typos.

Changelog and migration notes live inline in this document under each
release header (see git history for the full evolution at
<https://github.com/meshkore/meshkore/commits/main/webapp/standard.md>).

When a major bump lands, the Python daemon polls `/standard/version` once an hour and refuses cluster writes if the local `.meshkore/STANDARD_VERSION` is behind. The operator catches up by reading the changelog sections between the two versions, applying the manual migration steps, and writing the new integer into `STANDARD_VERSION`.

---

## 12. Layer model — adoption (informative)

This is **summary** only. Source for adoption details: <https://meshkore.com/cluster/adopt>.

| Layer | What you opt into                                              | Needs account | Needs network |
|-------|----------------------------------------------------------------|---------------|----------------|
| L0    | Folder + governance + tasks (this document)                    | no            | no             |
| L1    | L0 + editor rules (`CLAUDE.md` / `.cursorrules`)               | no            | no             |
| L2    | L1 + hub registration (discoverability)                        | yes           | yes            |
| L3    | L2 + Python daemon + architect                                 | optional      | localhost      |
| L4    | L3 + mesh agent-to-agent calls                                 | yes           | yes            |

L0 and L1 are 100% defined by **this document** with zero extra dependencies. L2-L4 add capability but do not modify L0-L1.

---

## 13. Links registry — where modules live, locally and in production

Every project that ships anything online or runs anything locally MUST
declare it in **`.meshkore/public/links.yaml`** — a single committed
file that maps each module to where it is reachable, where it is
deployed, and what version is live. Projects that don't deploy
anything (pure libraries, specs, internal automation) MAY omit the
file or commit an empty `modules: []`.

This file replaces every ad-hoc "list of URLs" doc and is the source
of truth for operators, agents, the cockpit, and the daemon.

### 13.1. File location and shape

Path: `.meshkore/public/links.yaml` (committed, gitignored exceptions
already covered by §2.2).

```yaml
version: 1                              # * schema version of this file

modules:                                # * one entry per module that has any reachable surface
  - id: <module-id>                     # * MUST match a declared module in cluster.yaml.modules[].id

    local:                              # optional — present if the module runs locally
      url:        http://localhost:<port>
      command:    "<one-line command to start it>"
      health:     <relative or absolute health URL>     # optional

    prod:                               # optional — present if the module is deployed somewhere
      url:               https://<deployed-host>
      provider:          fly | cloudflare-pages | cloudflare-workers | vercel | render | self-hosted | other
      project:           <provider-side project/app name>   # e.g. fly app, pages project, worker name
      region:            <provider region>                  # optional, free-form
      deploy_command:    "<one-line command>"               # optional, what to run from repo root
      deployed_version:  "<semver or arbitrary tag>"        # optional
      deployed_sha:      <git short-sha of the deploy>
      deployed_at:       <ISO-8601 UTC>
      deployed_by:       <agent-id or member-id>            # optional

    repo:                               # optional — current source-of-truth tracking
      branch:    <branch the module is actively developed on>
      head_sha:  <short-sha at last update of this file>
      version:   "<semver>"             # optional, the version *in the repo*, not deployed

    notes: ""                           # optional, free-form one-line note
```

All sub-blocks (`local`, `prod`, `repo`) are independently optional.
A module that only runs locally omits `prod`. A library with no
runtime omits both `local` and `prod` and may still declare `repo` for
version tracking, or omit the entry entirely.

`provider` is a free-form string; the values above are the canonical
ones the cockpit recognises for icons/badges. Anything else renders as
plain text.

### 13.2. Who updates it and when

The file is **agent-maintained**. Every commit that touches a module
in a way that changes its reachable state MUST update the matching
entry in `links.yaml` in the same commit. Concretely:

- A **deploy** updates `prod.deployed_sha` + `prod.deployed_at` +
  `prod.deployed_version` for the deployed module.
- A **branch switch** for the active development branch of a module
  updates `repo.branch`.
- A **version bump** in the repo (per §6 commit conventions) updates
  `repo.version` and `repo.head_sha`.
- A **new local port** or a new `command` to start the module updates
  `local`.
- **Renaming or removing a module** in `cluster.yaml` updates
  `links.yaml` in the same commit.

The standard commit checklist (§6) is extended with one bullet:

> If your commit changed where a module runs, where it deploys, what
> branch it lives on, or what version it carries — update
> `.meshkore/public/links.yaml` in the same commit.

### 13.3. Daemon surface

The Python daemon (§10.2) parses `links.yaml` on boot and on file
change. It is served at:

| Method | Path             | Auth | Purpose |
|--------|------------------|------|---------|
| GET    | `/links`         | no   | The full parsed YAML, normalised to JSON |
| GET    | `/links/<id>`    | no   | One module's entry |
| POST   | `/links/<id>`    | yes  | Patch one module's `local` / `prod` / `repo` block (writes the file atomically) |
| WS     | `/events`        | —    | Broadcasts `links.updated` on any rewrite |

The `POST` endpoint is for cron-driven deploy agents and the cockpit's
inline editor — humans editing the YAML directly is also fine, the
daemon picks it up via the same file-watcher that drives `state.json`.

### 13.4. Schema validation

The daemon validates every entry on load. Errors are visible at:

- The architect surfaces invalid entries with an amber badge per row.
- `GET /links` includes a top-level `_errors: []` array when validation
  failed; non-failing entries still appear.

The reference schema lives at
`.meshkore/docs/conventions/links-yaml.md` (this file) plus the JSON
schema at `webapp/standard.json` (`schemas.links`).

---

## 14. Protocols — reusable multi-scope runbooks

A **protocol** is a named, indexed, reusable runbook for multi-step
work that crosses files, modules, or scopes (docs + code + commit +
deploy). When the operator says "apply protocol P<N>", an agent opens
the protocol's file and follows its `# Steps` section, then files a
run log.

Protocols are NOT tasks. Tasks are one-shot work items with a status
lifecycle (`backlog → next → active → done`). Protocols are reusable
runbooks executed many times.

### 14.1. Identifier convention

Protocols use the `P<N>` letter-prefix-then-number pattern, joining
the cluster's existing identifier family (T, V, D, C, N, AG). One
global numbering across the cluster — protocols are cross-cutting by
definition and shouldn't be scoped per-module.

### 14.2. Folder layout

```
.meshkore/protocols/                 ← gitignored except for committed protocols
├── INDEX.md                         registered protocols, human-curated table
├── P<N>-<slug>.md                   one protocol per file
└── log/<YYYY-MM>/
    └── P<N>-<YYYY-MM-DD>-<context>.md   append-only run logs
```

`.meshkore/protocols/` is gitignored at the parent level per §2.2 but
the protocol files and INDEX are committed via an explicit exception
(see §14.6 below). Run logs stay local per-machine.

### 14.3. Protocol file shape

```yaml
---
id: P<N>                           # * MUST match the filename prefix
title: "<one-line title>"          # *
scope: cluster | project | module  # * what the protocol affects
status: draft | stable | deprecated  # *
priority: low | medium | high | critical  # *
owner: <member-id>                 # *
created: YYYY-MM-DD                # *
updated: YYYY-MM-DD                # *
tags: [a, b, c]                    # optional
needs_inputs: ["<input>"]          # optional, names of CLI-style inputs
---

# Goal
# When to apply
# Inputs
# Preconditions
# Steps
# Verification
# Rollback
# Related
```

The seven body sections are MANDATORY. Each `Step` MUST name the
file(s), commands, or endpoints it touches so the executing agent can
audit coverage and the run log can record a per-step verdict.

### 14.4. Run log shape

```yaml
---
protocol: P<N>                     # * filename of the protocol that was run
date: YYYY-MM-DD                   # *
operator: <human-or-agent-id>      # * who initiated the run
agent: <executing-agent-id>        # * who executed
commit: <short-sha>                # if the run produced a commit
outcome: success | partial | failed  # *
inputs:                            # optional, the inputs the run was called with
  <key>: <value>
---

# <one-line summary>
## Per-step outcome
1. <step description> · DONE | SKIP | FAILED · <one-line note>
## Deviations
## Artifacts
```

### 14.5. Daemon surface

The Python daemon parses `.meshkore/protocols/` on boot and on file
change.

| Method | Path | Auth | Purpose |
|---|---|---|---|
| `GET` | `/protocols`            | no  | List: id, title, scope, status, updated, log_count |
| `GET` | `/protocols/<id>`       | no  | Raw markdown body + parsed frontmatter |
| `GET` | `/protocols/<id>/runs`  | no  | Recent run-log entries (latest 50) |
| WS    | `/events`               | —   | `protocols.updated` on file change |

POST-style endpoints to start a run, append step results, and mark
complete will land with the cockpit's protocols panel.

### 14.6. Gitignore exception

Add to `.gitignore` (the wizard scaffolds this on first run):

```
# MeshKore — commit only the public bootstrap, version marker, and protocols
.meshkore/*
!.meshkore/public/
!.meshkore/STANDARD_VERSION
!.meshkore/protocols/
.meshkore/protocols/log/
```

Protocols are committed (the cluster's playbook travels with the
repo). Run logs are per-machine and stay local.

### 14.7. When the operator says "apply P<N>"

The executing agent:

1. Opens `.meshkore/protocols/P<N>-*.md`.
2. Reads `# Preconditions` and refuses with a clear error if any fail.
3. Executes `# Steps` in order, collecting a per-step verdict.
4. Appends a run log under `log/<YYYY-MM>/P<N>-<YYYY-MM-DD>-<context>.md`.
5. Reports the run summary back with commit sha + any external deploy IDs.

---

## 15. Quality Gates — code health is mechanical, automated, layered

Any repo holding code in a MeshKore cluster ships **three layers** of
mechanical safety gates:

| Layer | Where it runs | When | Budget |
|---|---|---|---|
| **L1** — pre-commit hook | local (operator machine or daemon) | every `git commit` | <10s |
| **L2** — CI on push | GitHub Actions | every push to `main` + every PR | 1-5min |
| **L3** — nightly drift | GitHub Actions schedule | `0 3 * * *` | 10-30min |

This is **not** a code audit. The gates catch typecheck errors,
format drift, lint warnings, secret leaks, build failures, and the
test suite. Deep architectural / security review lives elsewhere.

### 15.1. Repo contract

A MeshKore-standard repo MUST ship:

- `.github/workflows/ci.yml` — the L2 workflow, taken verbatim from
  the matching stack template at
  <https://meshkore.com/reference/standards/quality-gates/>.
- `.git/hooks/pre-commit` — the L1 hook, installed via the same
  reference (it can't be committed because git ignores `.git/hooks/`
  — that's why the installer exists).
- `.meshkore.repo.yaml` — single file declaring the stack:

```yaml
stack: typescript            # | rust | python | cloudflare-worker
quality_gates_version: 1
```

Repos without the contract are tolerated (legacy / scratchpad) but
the architect badges them yellow.

### 15.2. Install

Once per repo, once per machine:

```bash
curl -sSf https://meshkore.com/reference/standards/quality-gates/install.sh \
  | bash -s <stack>
```

Idempotent — re-running is safe. `--force` overrides existing files.

### 15.3. Agent contract (autonomous loop)

When an agent works inside a Quality-Gates-installed repo, the daemon
prepends the **agent contract prompt** to its chat-dispatch:
<https://meshkore.com/reference/standards/quality-gates/agent-prompt.md>.
The contract forbids `--no-verify`, forbids `meshkore.skipL1`,
forbids deleting gates to make red turn green. On L1 failure: fix
in the same turn. On L2 failure: the cluster's prompt queue delivers
the failure on the next dispatch — agents never poll.

### 15.4. Bypass

`git config meshkore.skipL1 true` skips L1 on this clone. **Operator
only.** Agents that set this flag violate the contract.

### 15.5. Stack templates shipped today

- `typescript/` — Node TS libraries, CLIs.
- `cloudflare-worker/` — TS Worker (adds `wrangler --dry-run`).
- `rust/` — Cargo workspace (clippy as errors, fmt check).
- `python/` — ruff + optional mypy + pytest.

Adding a new stack template is a standard bump.

### 15.6. Cluster-cloud integration

Deferred. The prompt-queue piece that delivers L2/L3 failures to
agents lives behind the `quality-gates-standard` initiative, which
depends on `context-versioning-and-agent-sync`. Until both land:
L2/L3 failures show in GitHub Actions only; the operator sees them
manually.

---

## 16. Network resources file (v17)

Every cluster ships `.meshkore/public/RESOURCES.md`, a verbatim local
mirror of `https://meshkore.com/standard/resources.md`. The file is a
one-screen reference for the network's primary entry points (Oracle,
hub directory, agent address scheme, deploy playbook, daemon upgrade
flow) so that any AI agent reading the project context immediately
knows what the mesh offers and where to look.

### 16.1. Why this section exists

Before v17, the mesh worked but discoverability of the mesh itself
inside a project was poor. The standard lived at `meshkore.com/standard`,
the Oracle answered queries, the hub indexed live agents — but a
freshly-spawned subagent inside a project had no canonical pointer
to any of it. Operators were re-explaining the same URLs in every
prompt. The result: agents that didn't think to query the Oracle
when they could have, didn't know how to address other agents, and
didn't know how to update their own daemon.

A small, predictable file at a predictable path closes that gap.
The file is meant for AI consumption (short, link-heavy, declarative)
but humans can read it too; the canonical operator documentation
remains at `/reference/`.

### 16.2. Contract

- **Path.** `.meshkore/public/RESOURCES.md` — committed, public part
  of the `.meshkore/` tree (alongside `cluster.yaml` and `links.yaml`).
- **Canonical source.** `https://meshkore.com/standard/resources.md`.
  Owned by the standard.
- **Format.** Markdown. One screen. Table of URLs + short flows.
  Stable schema as defined by the canonical source.
- **Refresh.** The daemon, when it observes that the local
  `STANDARD_VERSION` is older than `https://meshkore.com/standard/version`,
  fetches the canonical `resources.md` and overwrites the local
  copy up to the `<!-- /canonical -->` marker. This piggybacks on
  the existing standard-upgrade flow (§11 Versioning); no separate
  job.
- **Operator extensions.** Operators MAY append project-specific
  resources BELOW the `<!-- /canonical -->` marker. Anything below
  it survives standard refreshes; anything above is overwritten on
  bump.

### 16.3. Agent context expectation

Every system prompt template that a MeshKore-aware tool (the daemon,
the Architect, the OpenClaw skill, partner integrations) sends to a
subagent SHOULD include a one-line awareness pointer such as:

> MeshKore network resources at `.meshkore/public/RESOURCES.md` —
> Oracle, hub directory, agent-use patterns, daemon upgrade flow.

The exact wording is up to the prompt template; what's mandated is
that the agent be told the file exists. Daemons that don't comply
still work — they just leave the subagent in the dark about the
mesh.

### 16.4. What the file does NOT contain

- **Live state.** Online flags, agent counts, message volumes —
  always queried fresh from the hub (`GET /agents/<id>`) or Oracle.
- **Project-specific URLs by default.** The canonical block has only
  network-level resources. Operators add project URLs below the
  marker.
- **Secrets, credentials, or private API endpoints.** This file is
  in `public/`; treat it as world-readable.

### 16.5. Verification

```bash
# Local copy exists and matches the canonical version
test -f .meshkore/public/RESOURCES.md
diff <(curl -s https://meshkore.com/standard/resources.md) \
     <(sed -n '/^<!--$/q;p' .meshkore/public/RESOURCES.md  | head -n -1)
```

The diff should be empty up to the `<!-- /canonical -->` marker;
anything below is operator content and may differ.

## 17. Local agent CLI instructions (v18)

Every cluster ships `.meshkore/public/AGENT_INSTRUCTIONS.md`, a single
canonical source of project-wide instructions for any AI assistant
the operator runs locally (Claude Code, Codex, Gemini CLI, Cursor,
Cline, Aider, Grok-Coder, etc.). The MeshKore daemon **renders** that
single source into the well-known per-CLI files at the repository
root (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, …) and keeps them in
sync. The operator never hand-maintains the per-CLI files — they
edit one file and the daemon fans it out.

The point: the operator gets compatibility with every agent CLI for
free, and a MeshKore-savvy preamble (network resources catalogue,
conventions, commit attribution rules) is mandated for every agent
that walks into a MeshKore project, regardless of which CLI is
driving it.

### 17.1. Why this section exists

Each AI agent CLI looks at a different file for its project-level
rules — Claude Code reads `CLAUDE.md`, the open Codex/Aider/general
convention is `AGENTS.md`, Gemini CLI reads `GEMINI.md`, Cursor reads
`.cursor/rules/`, and so on. Without coordination, an operator would
either:

- Hand-write the same content into N files, drift inevitable, OR
- Pick a favourite CLI's file and accept that other CLIs land
  blind.

The MeshKore standard fixes this by anchoring a single canonical
source. The daemon owns the rendering. Adding a new agent CLI later
means adding one render target — every existing project picks it up
on next bump.

### 17.2. Contract

- **Source path.** `.meshkore/public/AGENT_INSTRUCTIONS.md` —
  committed, public, hand-edited by the operator.
- **Source schema.** Two regions delimited by HTML comment markers:

  ```text
  <!-- MESHKORE_PREAMBLE_BEGIN — managed by the daemon, do not hand-edit -->
  ... canonical MeshKore preamble pulled from
      https://meshkore.com/standard/agent-instructions.md ...
  <!-- MESHKORE_PREAMBLE_END -->

  <!-- OPERATOR_CONTENT_BEGIN — this is your project. Edit freely. -->
  ... operator's project-specific rules ...
  <!-- OPERATOR_CONTENT_END -->
  ```

  The daemon overwrites everything between `MESHKORE_PREAMBLE_BEGIN`
  and `MESHKORE_PREAMBLE_END` on every refresh. Everything between
  `OPERATOR_CONTENT_BEGIN` and `OPERATOR_CONTENT_END` is preserved
  verbatim across refreshes.

- **Canonical preamble.** `https://meshkore.com/standard/agent-instructions.md`.
  Owned by the standard, versioned with it.

- **Refresh trigger.** Two events refresh `AGENT_INSTRUCTIONS.md`'s
  preamble block AND re-render the per-CLI targets:

  1. Standard version bump (`STANDARD_VERSION` < canonical online).
  2. Operator edits to `.meshkore/public/AGENT_INSTRUCTIONS.md`
     (file-mtime watch).

- **Render targets.** The daemon writes the contents of
  `AGENT_INSTRUCTIONS.md` to **all** of the following at the
  repository root (creating files that don't exist; overwriting
  the file otherwise — operator content survives because it lives
  in `AGENT_INSTRUCTIONS.md`, the rendered files are derived
  artifacts):

  | File | Audience | Status |
  |---|---|---|
  | `CLAUDE.md`  | Claude Code (Anthropic) | mandated v18+ |
  | `AGENTS.md`  | Codex / Aider / general convention | mandated v18+ |
  | `GEMINI.md`  | Gemini CLI (Google) | mandated v18+ |
  | `.cursor/rules/meshkore.mdc` | Cursor (Anysphere) | optional v18+, mandated v19+ |
  | `.clinerules` | Cline (VSCode extension) | optional v18+, mandated v19+ |

  Each rendered file gets a top comment line identifying the source
  so a curious operator immediately sees that the file is generated:

  ```text
  <!-- Auto-rendered from .meshkore/public/AGENT_INSTRUCTIONS.md per
       MeshKore standard §17 (v18+). Edit the source, not this file. -->
  ```

### 17.3. Why render instead of symlink

Symlinks fail on Windows runtimes and confuse some IDE tooling.
Symlinks also can't carry the per-CLI header comment. Rendering is
~5 ms of disk I/O per refresh; not a real cost.

### 17.4. What the preamble must include

A daemon implementing this section MUST include in the preamble at
minimum:

1. Pointer to `.meshkore/public/RESOURCES.md` (network resources
   catalogue from §16).
2. Pointer to the canonical standard URL.
3. The folder-layout invariants (only `public/` committed, tasks
   under `modules/<m>/tasks/`, logs under `log/`).
4. The commit attribution rule (§9.1).
5. The standard-evolution protocol pointer
   (`.meshkore/docs/conventions/standard-evolution.md`).
6. Brief mention of the network primitives the agent can call
   (Oracle URL, hub directory, `meshkore.com/agent/<id>` pattern).
7. The initiative-anchored execution rule (§24): an unanchored turn
   must locate or create the matching `(initiative, task)` as its
   first action *(added v24)*.
8. Pointers to the cluster's standing context (§3.5,
   `.meshkore/context/`, including `criteria/`) and reusable runbooks
   (§14, `.meshkore/protocols/`) *(added v24)*.

The canonical preamble at
<https://meshkore.com/standard/agent-instructions.md> is the
reference implementation.

> **v24 (2026-06-13).** Items 7–8 above were added so a standalone
> agent-CLI session — one that reads only the rendered `CLAUDE.md` /
> `AGENTS.md` / `GEMINI.md`, with no daemon driving it — receives the
> same load-bearing cross-cutting rules the daemon already injects
> into its own dispatched sub-agents. Before v24 the §17 preamble
> covered the mesh and the file-layout invariants but never named
> initiative-anchoring (a v23 hard rule) or pointed at `context/` /
> `protocols/`, leaving solo sessions to rediscover them.

### 17.5. Operator extension scope

Anything specific to the operator's project — coding conventions,
deploy rules, security boundaries, "always run tests before
committing" — lives in the `OPERATOR_CONTENT` block. It survives
preamble refreshes and ships into every per-CLI file unchanged.

The block is not size-limited by the standard, but the daemon MAY
warn if it exceeds 8 KB (most CLI tools truncate context past that).

### 17.6. Verification

```bash
# Source exists and has the two markers.
test -f .meshkore/public/AGENT_INSTRUCTIONS.md
grep -q "MESHKORE_PREAMBLE_BEGIN" .meshkore/public/AGENT_INSTRUCTIONS.md
grep -q "OPERATOR_CONTENT_BEGIN"  .meshkore/public/AGENT_INSTRUCTIONS.md

# Rendered targets exist and match the source byte-for-byte except
# for the leading auto-render comment.
for f in CLAUDE.md AGENTS.md GEMINI.md; do
  test -f "$f" || { echo "missing $f"; exit 1; }
  diff <(tail -n +2 "$f") .meshkore/public/AGENT_INSTRUCTIONS.md \
    > /dev/null || { echo "$f drifted from source"; exit 1; }
done
echo "ok"
```

## 18. Project repo — capturing `.meshkore/` so it doesn't live only on disk

Every MeshKore project has two layers, with different repo
strategies:

| Layer | What | Repo strategy |
|---|---|---|
| **Source code** | the actual program(s) being built | one repo per logical component (`agents`, `api`, `webapp`, …) |
| **Project** | `.meshkore/` (context, roadmap, modules, protocols, timeline, log, agents config) | depends on whether the project is single-repo or multi-repo (see below) |

The Project layer is what gets lost forever if the operator's machine
dies before they think to back it up. It contains decisions, history,
tasks, and the operator's snapshot of everything the cluster knows.

### 18.1. Single-repo projects

If the project IS one source repo (no second component), `.meshkore/`
**lives inside that repo**, gitignored selectively:

```gitignore
# .meshkore — only public/ + STANDARD_VERSION + protocols/ travel;
# the rest is per-machine working state. (Same rule as today's
# standard v6.)
.meshkore/*
!.meshkore/public/
!.meshkore/STANDARD_VERSION
!.meshkore/protocols/
.meshkore/protocols/log/
```

This is the current behaviour for repos that started life as
mono-repos. Nothing changes for them.

### 18.2. Multi-repo projects

If the project has **two or more source repos** (the MeshKore project
itself is a good example — agents, api, architect, daemon, master,
skills, webapp, worker), `.meshkore/` belongs to no single source
repo. The convention is then:

- A **dedicated private repo** called `<org>/project` (or
  `<org>/<project-slug>` if multiple projects coexist in the org).
- This `project` repo's `.git/` lives **inside the `.meshkore/`
  folder** — so the operator's working tree is `.meshkore/` itself.
- Tracks the whole `.meshkore/` content except per-machine /
  credential / runtime files.
- The MeshKore daemon reads `.meshkore/` from its working directory
  exactly as before — the daemon is unaware that `.meshkore/` happens
  to be a git working copy. No daemon API change.

Reference layout (the MeshKore project itself ships this way):

```
~/your-workspace/
├── .meshkore/              ← THE PROJECT REPO (git init here, push to org/project)
│   ├── .git/               ← repo's git lives here, hidden inside .meshkore/
│   ├── .gitignore          ← excludes credentials/ + .runtime/ + state.json
│   ├── docs/
│   ├── modules/
│   ├── roadmap/
│   ├── protocols/
│   ├── public/
│   ├── timeline/
│   ├── log/
│   ├── scripts/
│   ├── agents/             ← identity files (reference credentials/ — no inline keys)
│   └── STANDARD_VERSION
│
├── repo-a/                 ← source repo A (own .git, gitignored by .meshkore/)
├── repo-b/                 ← source repo B (own .git, gitignored by .meshkore/)
└── …
```

### 18.3. What MUST be gitignored in a project repo

Regardless of single-repo or multi-repo, **always** gitignore:

```
credentials/        # API keys, tokens, service-account files — NEVER commit
.runtime/           # ephemeral daemon state (pids, lockfiles, runtime logs)
roadmap/state.json  # regenerable via roadmap-build.py
roadmap/state.js    # idem
```

### 18.4. Auto-sync

A `.meshkore/scripts/operator-sync.sh` (downloadable from
`meshkore.com/reference/cluster/scripts/operator-sync.sh`) commits and
pushes any changes every N minutes via `launchctl`/`systemd`/`cron`.
Default cadence: 30 min. The script is single-instance-locked so
multiple cron triggers can't collide.

When cluster cloud (initiative `cluster-cloud`) ships, this script
becomes redundant — the daemon's cloud mirror handles it. Until then,
manual git sync via this script is the durability backstop.

### 18.5. Wire to the agent / robot consumer

Agents reading the cluster's docs at runtime (oracle, dev agents,
auditors) consult `meshkore.com/standard.json` to find the
canonical descriptions. The `project_layer` block of the JSON spec
mirrors this section so machine clients pick up the convention
without scraping prose.

---

## 20. File snapshots (v19)

**Purpose**: pre-modification copies of files an agent is about to touch. Operator-readable, agent-creatable, retention-bounded. **NOT a git replacement** — git owns merged history; snapshots own the intermediate states *between* commits so the operator can inspect (and selectively restore via the agent) any moment between two commits without needing to author N micro-commits.

**Storage**:

```
.meshkore/snapshots/                           gitignored
├── 2026-06-09/                                date-bucket (gc unit)
│   ├── 20260609-165336310-A097-edit-bff1/     bucket-id
│   │   ├── _manifest.json
│   │   └── files/
│   │       └── apps/web/src/components/X.tsx  mirrors source path
```

**Bucket id**: `<YYYYMMDD>-<HHMMSSmmm>-<idHint>-<descSlug>-<4hex>`. Lexicographic = chronological. `idHint` = `agent_id || initiative_id || task_id || "X"`. `descSlug` from the `note` (or `task_id` fallback).

**`_manifest.json`** carries:

```json
{
  "id": "20260609-165336310-A097-edit-bff1",
  "date": "2026-06-09",
  "created_at": "<ISO>",
  "conv": "<conv-slug>",
  "agent_id": "A097",
  "agent_type": "custom",
  "initiative_id": null,
  "task_id": null,
  "msg_id": null,
  "note": "edit",
  "files": [{ "path": "...", "size": <bytes>, "sha256": "<hex>" }],
  "warnings": []
}
```

**HTTP routes** (daemon py-1.12.13+, auth-required):

| Verb | Path |
|---|---|
| POST | `/snapshots` — body `{ paths, conv?, agent_id?, agent_type?, initiative_id?, task_id?, msg_id?, note? }` |
| GET | `/snapshots?limit=N` — list newest-first (default 200, max 1000) |
| GET | `/snapshots/<bucket>` — manifest |
| GET | `/snapshots/<bucket>/files/<repo-relative-path>` — raw file body |
| DELETE | `/snapshots/<bucket>` — operator-driven cleanup |

**WS events**: `snapshot.created` (with id + conv + agent_id + files), `snapshot.removed`.

**Config** (`cluster.yaml`):

```yaml
snapshots:
  enabled: true            # default
  retention_days: 7        # default; range 1-365
```

Omit the block entirely → defaults apply.

**Limits**: 50 files per bucket, 5 MB per file. Anything bigger is skipped + flagged in `warnings[]`.

**Garbage collection**: opportunistic on every `POST /snapshots` — sweeps date directories older than `retention_days`. No separate cron tick.

**Agent contract**: before any tool call that **Writes / Edits an EXISTING file**, agents MUST call `POST /snapshots` with the paths they're about to modify. Newly-created files are exempt (no prior content). The daemon copies + writes a manifest + appends to the daily log + broadcasts. Non-negotiable per v19.

**Daily-log integration**: every snapshot creation auto-appends to `.meshkore/log/<YYYY-MM-DD>.md`:

```
## 16:53:36 · snapshot `<bucket-id>`
- agent A097 (custom) — <note>
- 3 file(s): apps/web/src/X.tsx, ...
- restore: `cat .meshkore/snapshots/<date>/<bucket-id>/files/<path>`
```

So the diary tells the same story the chat does — interleaved with the file-change trail.

**Scope exclusions**:
- NOT a versioned filesystem (retention drops old buckets).
- NOT a git replacement (commits are the source of truth for merged history).
- NOT a transaction log (captures BEFORE state only).
- NOT exposed without auth.

**Future cockpit UI** (out of scope for v19, planned for V108+):
- Snapshot list inline on each chat message that triggered modifications.
- Snapshot list inline on each task card in the roadmap.
- Snapshot list inline on each daily-log entry.
- Diff snapshot ↔ working tree from the cockpit.

Playbook: <https://meshkore.com/standard#20-file-snapshots-v19>

---

## 22. Chat references by `#<id>` (v20)

Agents do roadmap work in two places: file mutations (creating /
splitting / archiving initiative and task markdown) and chat output
(narration to the operator). The two need to stay legible together.

**The rule.** Every chat line in which an agent adds, removes,
renames, defers, splits, blocks, or otherwise touches an initiative
or task must include the item's id in the form `#<id>`. Bare titles
(`the voting initiative`, `the spiral mural task`) are not enough —
the cockpit chat renderer turns `#<id>` into a click-target that
scrolls the roadmap to the matching story.

**Where `<id>` comes from.** Initiative ids live in the `id:`
frontmatter of `.meshkore/roadmap/initiatives/<file>.md`. Task ids
live in the `id:` frontmatter of `.meshkore/modules/<mod>/tasks/<file>.md`.
Both follow the regex `^[A-Za-z][A-Za-z0-9_-]{1,31}$`. Examples in
production: `I18`, `I21`, `PAYMENTS`, `T-vote-API`, `WORK-21`.

**Canonical chat lines** (copy these patterns):

```
✓ added #I18 task #T-vote-anon-toggle
✗ removed #T-fixture-loader from #I19 (folded into #T-spiral-prefetch)
↻ split #I21 into #I21 (chain) + #I27 (storage)
🔧 #I12 DEMO2 stub (AMOY_PRIVATE_KEY unset → testnet fixture)
✓ #I18 done (6/6 shipped). → #I19.
```

**Exclusions.** Three cases where bare prose is fine:

1. **Planning aloud.** When you're still thinking through scope and
   haven't committed to a specific item, bare prose is fine ("we
   could do something about live-vote pulses…").
2. **First-create in the same line.** `+ #I27 (chain & storage
   refresh)` is fine — the create-line owns the id.
3. **Operator-facing prose.** Narrative paragraphs can use titles
   freely, as long as load-bearing mentions (the ones the operator
   would click to verify) carry the `#<id>`.

**UI integration.** The cockpit's V108 timeline (`architect/`)
renders a small `#<id>` chip at the head of each initiative title
row, status-tinted to match the node colour. Chat and roadmap UI
share the same vocabulary; the operator can pattern-match by sight.

**Enforcement.** The rule lives in the universal-rules block of
every agent's briefing (daemon `_section_core_rules`). No automated
linter today — prompt + review. Convention details:
`.meshkore/docs/conventions/chat-id-references.md`.

---

## 23. Storage reporting (v22)

`.meshkore/` accumulates state over time — daily logs, file
snapshots, chat attachments, the timeline, the cron-job log
captures. Operators need a single place to see how much disk it's
using and which buckets are growing fastest so they can tune
retention or trigger purges.

**Endpoint.** `GET /storage/usage` returns a per-bucket breakdown of
`.meshkore/` plus the total. No auth — the response carries metadata
only (bytes + file counts), never contents.

**Shape**:

```json
{
  "root": ".meshkore/",
  "total_bytes": 12345678,
  "total_files": 432,
  "buckets": [
    { "name": "log",         "bytes": 12345, "files": 18, "exists": true },
    { "name": "snapshots",   "bytes": 23456, "files": 102, "exists": true, "retention_days": 7 },
    { "name": "uploads",     "bytes": 78901, "files": 24,  "exists": true, "retention_days": 30 },
    { "name": "queues",      "bytes":    0,  "files": 0,   "exists": false },
    { "name": "timeline",    "bytes": 56789, "files": 9,   "exists": true },
    { "name": "agents",      "bytes":  4321, "files": 7,   "exists": true },
    { "name": "modules",     "bytes": 91234, "files": 80,  "exists": true },
    { "name": "docs",        "bytes": 65432, "files": 41,  "exists": true },
    { "name": "roadmap",     "bytes": 32109, "files": 27,  "exists": true },
    { "name": ".runtime",    "bytes": 23456, "files": 15,  "exists": true },
    { "name": "credentials", "bytes":   200, "files": 2,   "exists": true }
  ],
  "generated_at": "2026-06-10T20:48:00Z",
  "cache_ttl_secs": 5
}
```

**Caching.** The daemon caches the report for `cache_ttl_secs` (5 s
default) so polling at 1 Hz is safe — the rglob walks under heavy
trees (`timeline/`, `docs/`) are non-trivial otherwise.

**Retention semantics.** A bucket reports `retention_days` only when
the daemon enforces a sweep for it. Today: `snapshots` (default 7,
Standard §20) and `uploads` (default 30, Standard §22 attachments).
The rest accumulate until the operator culls (`log`, `timeline`,
`modules`, etc.).

**Extending.** When a new feature adds a bucket with its own retention,
add an entry to `StorageReport._BUCKETS` + `_retention_for` and the
cockpit's Storage panel renders it automatically — no client-side
schema change required.

**Use cases**:

- Cockpit Config zone shows a Storage panel with the per-bucket bars
  + a total, the operator can spot growth at a glance.
- Future: per-bucket "Purge older than N days" buttons + per-bucket
  retention dials writing back to `cluster.yaml`.
- Fleet operations: an external prober can `GET /storage/usage` across
  all daemons and alert on the ones approaching disk limits.

**Out of scope (v22)**:

- Contents — this is bytes + counts only, never file bodies.
- Per-file detail — bucket-level is the operator-facing grain.
- Mutation — retention dials + purge buttons are a follow-up. v22
  ships the read-only report so the UI can land.

---

## 24. Initiative-anchored execution (v23)

**The rule.** Every agent turn anchors to (initiative, task). The
roadmap is the project's guide; the cockpit's roadmap timeline is
a live picture of "what is happening right now in this project".
Unanchored agent runs leave no trace in the roadmap, can't be
cross-referenced from the daily log, and break the operator's
mental model.

**Decision chain** — the agent runs this *before* any code edit,
deploy, or file mutation. Stop at the first match.

1. **ANCHOR-PRESENT.** `conv_meta.initiative_id` AND `task_id` are
   set. Acknowledge `→ working on #<init> · #<task>` and continue.
2. **INITIATIVE-PRESENT, TASK-MISSING.** Read
   `.meshkore/modules/<initiative.modules[0]>/tasks/` and pick a
   task whose scope matches. If none, CREATE a new task under the
   initiative (frontmatter contract below) and link it.
3. **NEITHER-SET (cold start).** Read existing initiatives via
   `GET /state/initiatives`. Match by title + oneliner + module
   scope. If no clear match, CREATE a new initiative + 1-3 tasks
   that decompose the operator's request; anchor this conv to the
   FIRST task.

There's no step 4. Informational turns (e.g. `¿qué versión del
daemon?`) skip anchoring because there's nothing to track.

**Creation contract.**

```yaml
# .meshkore/roadmap/initiatives/<slug>.md
---
id: <slug>          # ^[a-z][a-z0-9-]{1,31}$
title: "<human title>"
status: active
priority: medium
oneliner: "<one-sentence what>"
modules: [<primary>, …]
target: "<timing>"
created: <YYYY-MM-DD>
updated: <YYYY-MM-DD>
owner: rjj
related: []
---

## Why this exists
## Done when
## Task plan
```

```yaml
# .meshkore/modules/<module>/tasks/<id>-<slug>.md
---
id: <id>           # ^[A-Za-z][A-Za-z0-9_-]{1,31}$
title: "<deliverable>"
status: active     # the FIRST task; siblings start `next`
priority: medium
owner: rjj
category: <module>
initiative: <slug>
created: <YYYY-MM-DD>
updated: <YYYY-MM-DD>
tags: []
depends_on: []
blocks: []
---

## Scope
## Done when
## Files
```

Exactly one module per task — see Standard §4 + the
`one-task-one-module` memory.

**Concurrency.** Three agents in parallel = three live initiatives
in the cockpit's timeline. Each agent's anchor is independent;
agents only fight over the same task when the architect explicitly
dispatches them onto it (which the daemon's dispatch-mutex prevents
unintentionally).

**Cockpit alignment.** The chat scope strip already shows the
anchor. When an agent creates or swaps the anchor mid-conv it
MUST (a) persist the file changes, (b) state the new anchor in
chat using `#<id>` notation (Standard §22), (c) push to the conv
meta so the scope strip follows in real time.

**Enforcement.** Prompt-level. The daemon embeds the rule in
every agent's briefing via `_section_core_rules()` (py-1.12.23+).
No HTTP-level reject for unanchored dispatches in v23 — drift
will be addressed with a soft daemon check later if necessary.

**Out of scope (v23):**

- Auto-archiving done tasks — the existing chat-session
  auto-archive (Standard §… `chat_state_api`) handles that.
- Autonomous initiative split/merge — agent SUGGESTS, operator
  approves.
- Back-fill for pre-v23 unanchored convs — grandfathered.

**Convention doc**: [`initiative-anchored-execution.md`](../../../.meshkore/docs/conventions/initiative-anchored-execution.md).

---

## 21. Where everything else points

Pages that wrap or describe this standard, for human navigation:

| Page                                                       | Role |
|------------------------------------------------------------|-------|
| <https://meshkore.com/standard>                            | **canonical (this page)** |
| <https://meshkore.com/standard.md>                         | this page, markdown variant |
| <https://meshkore.com/standard.json>                       | this page, structured schemas |
| <https://meshkore.com/standard/version>                    | current version, single integer |
| <https://meshkore.com/standard/CHANGELOG.md>               | per-version changelog with apply-manually blocks |
| <https://meshkore.com/cluster/adopt>                       | adoption ladder (L0-L4) |
| <https://meshkore.com/cluster/install>                     | install the L3 daemon |
| <https://meshkore.com/cluster/operate>                     | operator manual |
| <https://meshkore.com/standard/spec>                     | wire protocol + daemon API |
| <https://meshkore.com/standard>                    | versioning + changelog |
| <https://meshkore.com/reference/cluster/templates/>        | `cluster.yaml` + `links.yaml` + `protocol.md` starters |
| <https://meshkore.com/reference/cluster/editor-rules/>     | editor files generated FROM §8 |
| <https://meshkore.com/reference/standards/>                | engineering standards (audit, stack, deploy, quality-gates…) |
| <https://meshkore.com/reference/standards/quality-gates/>  | L1/L2/L3 stack templates + agent contract |
| <https://meshkore.com/reference/manifest.json>             | machine-readable catalog index |

Anything else linking to the standard but disagreeing with this page is a bug — file it at <https://github.com/meshkore/meshkore/issues>.
