# MeshKore Standard

**Version:** 12.0.0 · **Updated:** 2026-06-02 · **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).

---

## 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

- `backlog`  — known, not scheduled
- `next`     — scheduled for next pass
- `active`   — in flight
- `blocked`  — waiting on another task / external input
- `done`     — completed (task moves to `log/<YYYY-MM>/` after some time)

### 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.

---

## 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:` trailers (v12)

**Applies to**: every commit whose author or co-author is an LLM
agent (Claude, GPT, Cursor's runner, etc.). Human-only commits MAY
omit. Mixed-authored commits MUST include — if an LLM touched the
diff, attribute it.

**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>
Co-Authored-By: <Display Name> <noreply@anthropic.com>
```

| Trailer | What | Examples |
|---|---|---|
| `Agent:` | 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:` | The exact MODEL ID as published by the vendor. | `claude-opus-4-8`, `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `gpt-5`, `cursor-default` |
| `Co-Authored-By:` | Existing trailer; kept for GitHub UI attribution. | `Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>` |

**Worked example:**

```
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
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
```

**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).

**Analytics example** — count commits per model over the last week:

```bash
git log --since=1week --format='%H%n%(trailers:key=Model,valueonly,separator=)' \
  | awk 'NR%2==0 && $0' | sort | uniq -c | sort -rn
```

**Unknown model.** A misconfigured runner that doesn't know its
model id MUST set `Model: unknown` — never omit silently. Easier to
find and fix later than to assume the field's absence is meaningful.

**Subagents.** The daemon spawns subagents via Claude Code; the
spawn-time briefing carries the agent slug and the resolved model id
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.

---

## 17. 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.

### 17.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.

### 17.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/)
└── …
```

### 17.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
```

### 17.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.

### 17.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.

---

## 18. 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>.
