# MeshKore Standard

**Version:** 30.0.0 · **Updated:** 2026-07-05 · **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.

> **L3 is NOT a per-repo install (read this before "applying" the standard).**
> "Applying the standard" means giving the repo the `.meshkore/` layout above —
> it does **not** mean creating, downloading, or running a daemon inside the
> repo. The daemon is **one shared process per machine** that serves every
> project at once, routed by the `X-MeshKore-Project` header (§10). A member
> project has **no** `.meshkore/scripts/daemon.py`, runs nothing, and binds no
> port. To put a repo under the daemon, **add it in the Architect** (or
> `daemon.py init <path>`) — the already-running shared daemon onboards it
> (§10.3). Do not curl a `daemon.py` into the repo; do not bind `5570–5589`.

---

## 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
│   ├── cluster.yaml           identity, type, admission, members, modules
│   ├── README.md              "how to join this cluster"
│   └── AGENT_INSTRUCTIONS.md  canonical preamble + operator block (renders CLAUDE.md/…)
├── docs/                      cross-cutting docs (committed)
│   ├── 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 (committed)
│   ├── 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/
│   ├── initiatives/<id>.md    cross-module initiatives (committed)
│   ├── state.json             built by roadmap-build.py; NEVER edit by hand (gitignored)
│   └── state.js               same content wrapped as JS for the architect (gitignored)
├── 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/`, `docs/`, `modules/`, and `roadmap/initiatives/` are committed to git** (v27) — the project's identity, instructions, plan and task history travel with the repo. Runtime/secret/per-machine state (`.runtime/`, `credentials/`, `agents/`, `timeline/`, `log/`, `queues/`, `uploads/`, `snapshots/`, generated `roadmap/state.*`, downloaded `scripts/`) stays local. See §2.2.
- `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

A **deny-list** (v27 — replaces the pre-v27 `.meshkore/* + !public/`
allowlist that committed only `public/`). Everything under `.meshkore/`
is committed EXCEPT runtime, secret, per-machine, and generated state:

```
.meshkore/.runtime/
.meshkore/credentials/
.meshkore/agents/
.meshkore/timeline/
.meshkore/log/
.meshkore/queues/
.meshkore/uploads/
.meshkore/snapshots/
.meshkore/state.json
.meshkore/roadmap/state.json
.meshkore/roadmap/state.js
.meshkore/scripts/
```

This guarantees the project's brain — `public/` (identity + instructions),
`docs/`, `modules/` (READMEs, tasks, archived tasks, diagrams),
`roadmap/initiatives/`, and `STANDARD_VERSION` — travels with the repo,
while secrets, per-machine identity, daily logs, ephemera, generated
roadmap state, and the downloaded daemon/TLS under `scripts/` never reach
git. `daemon.py init` writes exactly this block; validators check it.

---

## 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)
  workflow: 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 Architect IDE 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 Architect IDE'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/workflows/`.
- 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
completed_at: 2026-06-19T15:42:30Z        # v26+ optional, set when done
resolved_by: A042                         # v26+ optional, agent that resolved it
resolved_by_conv: work-coder-V30-001      # v26+ optional, the resolving conv
commit_shas: [a1b2c3d]                    # v26+ optional, associated commits
---

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

## Resolution                             # v26+ — written when the task is done
<the resolving turn's summary; see §4.4>
```

### 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 (including the v26 resolution fields below) stays exactly the same — the move is purely organizational.

### 4.4. Resolution record (v26)

The roadmap is a single hierarchical store: an initiative holds tasks, and each task durably holds not just its description but **its resolution** — so the Architect IDE AND any CLI consumer read the same history without touching live chat state.

When an agent finishes a task (its turn emits `chat.assistant.final` with the task already `done`), the daemon stamps the task `.md`:

- **Frontmatter** (machine-queryable pointers): `completed_at` (ISO timestamp), `resolved_by` (agent_id, else conv slug), `resolved_by_conv` (the conversation), and optionally `commit_shas`.
- **Body** `## Resolution` (rich record): the agent's Output-Contract summary (≤8 lines + optional `<details>`), plus any images/links. The daemon writes/overwrites this section from the resolving turn's final message; agents may edit it afterward.

A CLI agent that sets `status: done` by hand SHOULD set `completed_at` and write `## Resolution` too, so the record is uniform regardless of who closed the task. Everything else in the body above `## Resolution` (the original spec) is preserved.

---

## 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/workflows/` 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)
- `workflows.updated`  — a file under `.meshkore/workflows/` 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.
- Committed to git (v27): public/, docs/, modules/, roadmap/initiatives/, STANDARD_VERSION.

Hard rules — never:
- Never commit runtime/secret/per-machine state: .meshkore/{.runtime,credentials,agents,timeline,log,queues,uploads,snapshots,scripts}/, state.json, roadmap/state.{json,js}.
- 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:`, `Model:`, and `MeshKore:` trailers** — and MUST NOT add `Co-Authored-By:` (removed v21). See §9.1.
- **Never create, write, or run a per-project daemon.** There is no `.meshkore/scripts/daemon.py`, no `daemon.py`/`make`/`Makefile` daemon target, and no version-poller inside a member project — and **never bind ports `5570–5589`**. ONE shared daemon per machine already serves every project, routed by the `X-MeshKore-Project` header (§10). "Applying the standard" = the `.meshkore/` layout only, never standing up a daemon.
- **Never hand-write the `MESHKORE_PREAMBLE` block** of `.meshkore/public/AGENT_INSTRUCTIONS.md` (or the rendered `CLAUDE.md`/`AGENTS.md`/`GEMINI.md`/…). The daemon renders that block verbatim from this standard (§17); editing it by hand causes drift. Only the `OPERATOR_CONTENT` block below the preamble is operator-owned.

---

## 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 v27 deny-list contract from §2.2 (runtime/secret/per-machine state ignored; public/ + docs/ + modules/ + roadmap/initiatives/ committed)

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

---

## 10.1. Centralized daemon — one process, many projects (v28)

A single machine hosts any number of MeshKore projects through **ONE shared
daemon process**. It is installed and run **once per machine** (it lives at
the operator's `meshkore-server/` root, alongside the Architect). Member
projects carry **no daemon code** — only their portable `.meshkore/` ledger.

- **One process, one port, one token.** The shared daemon binds the first free
  port in `5570–5589` (written to its own `.runtime/port`) and serves **every**
  registered project from that single base URL. Member projects **do not** bind
  any port and **do not** run anything.
- **Routing by header.** Every project-scoped request MUST carry
  `X-MeshKore-Project: <cluster-id>`; the daemon resolves the matching
  `ProjectContext` and answers for that project. Machine-level routes
  (`/health`, `/info`, `/projects`) are unscoped and need no header.
- **A bare `/health` or `/state` (no header) answers for the daemon's DEFAULT
  (boot) project** and may report a *different* `cluster_id` than yours. That is
  **expected** — it is **not** "wrong cluster" and **not** "daemon down." Add the
  `X-MeshKore-Project` header to talk to your project.
- **There is NO per-project daemon to start, restore, or launch.** Member
  projects intentionally have no `.meshkore/scripts/daemon.py`. Do not
  `git checkout`, download, or run one; the shared daemon already serves you.
- **Isolation.** Each project's state, runs, chat, crons, and credentials live
  in its own `ProjectContext`; a fault in one project returns a 500 for that
  request and never affects the others.
- **The Architect** discovers projects via `GET /projects` (not a port scan),
  lists them in its left **Projects** rail over one connection, and switches
  between them by changing the `X-MeshKore-Project` header — no relaunch.

The shared daemon keeps its OWN global `.meshkore/` (machine-local, not git):
ideas, the `projects.json` registry (id → path), credentials for external
clusters, and external-agent config. Each registered project supplies its repo
path; the daemon reads/writes that project's ledger there.

> **Historical note.** Through v27 the model was per-folder: each repo carried
> its own `daemon.py` and bound its own port (first-to-bind-wins). That model
> was **retired 2026-06-25** (initiative `daemon-centralized`). The text above
> is the canonical model as of v28; ignore older docs that describe per-project
> daemons.

---

## 10.2. Python daemon — the shared L3 implementation

"**Canonical L3 implementation**" means the reference implementation of the
**shared daemon service** — the one process that backs L3 for every project on
the machine. It is **not** a file each repo embeds and runs. The daemon is a
**single Python script** (stdlib-only, distributed as one bundled
`daemon.py`). It is:

- **Stdlib only.** No `pip`, no `npm`, no virtualenv. Works on any machine with
  `python3 ≥ 3.8` (every Mac, Linux, modern Windows).
- **Not an installable binary.** Corporate laptops that block unsigned binaries
  still allow scripts — the Python daemon sidesteps that whole problem class.
- **Installed once per machine.** The operator (or an AI agent, once) places it
  at the `meshkore-server/` root and runs it there. **Member projects do not
  download or run it.** Adopting a repo never means curling `daemon.py` into it.

### Install + run (once per machine, at `meshkore-server/`)

```
# In the meshkore-server root only — NOT inside a member project.
mkdir -p scripts/tls
curl -fsSL https://architect.meshkore.com/reference/cluster/scripts/daemon.py \
  -o daemon.py
curl -fsSL https://architect.meshkore.com/reference/cluster/scripts/tls/fullchain.pem \
  -o scripts/tls/fullchain.pem
curl -fsSL https://architect.meshkore.com/reference/cluster/scripts/tls/privkey.pem \
  -o scripts/tls/privkey.pem
chmod 600 scripts/tls/privkey.pem
python3 daemon.py
```

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

On launch the shared daemon:

1. Reads its own global `.meshkore/` ledger (`projects.json` registry,
   credentials, config) and mints a bearer token (mode `0600`) if absent.
2. Binds the first free port in `5570–5589`; writes its `.runtime/port` + PID.
3. Lazily loads a `ProjectContext` for each registered project (from
   `projects.json`), reading each project's `.meshkore/public/cluster.yaml` (§3)
   and building its `state.json` from the markdown filesystem (rebuilt on file
   change, polled ~1.5 s).
4. Serves the architect-facing API below — machine-level routes unscoped,
   project routes resolved by the `X-MeshKore-Project` header (§10.1).

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

**Project routing.** `/health`, `/info`, and `/projects` are machine-level
(unscoped). Every **project-scoped** route (`/state`, `/reload`, chat/dispatch,
runs, tasks, …) MUST carry `X-MeshKore-Project: <cluster-id>` in addition to the
bearer token; without it the daemon answers for its default project (§10.1).

### 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 Architect IDE 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 Architect IDE 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 workflow
[`P4`](/cluster/workflows/W4-daemon-upgrade) — three `curl` calls
plus a graceful restart.

### Scope

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

### Removing a project · stopping the daemon

- **Remove one project** from the daemon without affecting the others:
  `DELETE /projects/<cluster-id>` (machine-level). This unregisters the project
  (its on-disk `.meshkore/` ledger is untouched); the Projects rail drops the
  row. There is no per-project process to kill.
- **Stop the whole machine's daemon**: `POST /shutdown` with the bearer token.
  It broadcasts `daemon.shutdown`, then exits cleanly (removes its `.runtime/`
  files). This stops **all** projects at once, so it is rare.

---

## 10.3. Adding a project to your architect

Adopting a project means **registering its path with the already-running shared
daemon** (§10.1) — never standing up a daemon. The daemon owns the onboarding:
it scaffolds (or validates) the project's `.meshkore/` ledger and starts serving
it. The operator never downloads `daemon.py`, picks a port, or runs a CLI by
hand.

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

1. In the Architect, click **"+ Add project"** (`#projects-rail-add` in
   `architect/public/index.html`) and point it at the repo's path on disk.
2. The Architect calls `POST /projects {path}` on the shared daemon. The daemon
   scaffolds the project's `.meshkore/` ledger if missing (its `daemon.py init` /
   `scaffold.py` path owns the canonical layout — §2), registers it in
   `projects.json`, and lazily loads a `ProjectContext` for it.
3. The project appears in the Projects rail immediately, served from the same
   base URL and reached with `X-MeshKore-Project: <cluster-id>`. **Nothing new
   runs; no port is bound for it.**
4. Remove it any time with the rail's Remove button → `DELETE /projects/<id>`
   (the on-disk ledger is preserved).

If you'd rather an AI agent prepare the repo's `.meshkore/` content first (tasks,
docs, context), it can — but its job ends at the **files**. It must **not**
download or run a daemon. After the files exist, add the project in the Architect
as above; the shared daemon onboards it.

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

Your job is ONLY the .meshkore/ files. Do NOT create, download, or run a
daemon, do NOT add a daemon.py / Makefile / make target, and do NOT bind
any port (5570–5589 belongs to the one shared daemon already running on
this machine). "Applying the standard" = the folder layout, nothing more.

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).
   Do NOT hand-write the MESHKORE_PREAMBLE block of AGENT_INSTRUCTIONS.md —
   the daemon renders it (§17); edit only the OPERATOR_CONTENT block.
4. Tell me the repo path. I'll add it in my Architect ("+ Add project"),
   which registers it with the shared daemon (POST /projects); the daemon
   scaffolds anything missing and starts serving it via the
   X-MeshKore-Project header. There is nothing for you to launch.
```

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

Your job is ONLY the files. Do NOT create, download, or run a daemon and do
NOT bind any port — one shared daemon already runs on this machine and will
adopt this folder once I add it in the Architect.

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 (do NOT hand-write the
   MESHKORE_PREAMBLE block — the daemon renders it).
6. Add a top-level README.md describing the project (2-3 sentences).
7. Make a first task at .meshkore/modules/general/tasks/T1-hello.md
   with status: next, owner: me, category: general.
8. Tell me the repo path so I can add it in my Architect ("+ Add project");
   the shared daemon registers and serves it.

Stop after step 8 — 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. A project shows offline

With the centralized model there is **no per-project daemon to "start."** A
project is unreachable in only two cases:

1. **It isn't registered.** The shared daemon is running, but this project's
   path isn't in `projects.json` (e.g. the Architect's localStorage was cleared,
   or the repo was cloned fresh). Fix: **re-add it** via "+ Add project" (§10.3)
   → `POST /projects {path}`. The daemon re-registers and serves it; no relaunch.
2. **The shared daemon itself isn't running** (the whole rail is dark). Fix:
   start the one machine daemon at the `meshkore-server/` root —
   `cd <meshkore-server> && python3 daemon.py` (§10.2). This brings **all**
   registered projects back at once.

Never "restore" a `.meshkore/scripts/daemon.py` inside a member project — that
file does not exist in the centralized model, and creating one collides with the
shared daemon's `5570–5589` ports. A "forget this project" link in the rail
removes a stale row via `DELETE /projects/<id>`.

### 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: **30.0.0** (see the first-line header and `CHANGELOG.md` for the full release history since 1.0.0 in May 2026).

- **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 Architect IDE, 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 Architect IDE 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 Architect IDE'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. Workflows — reusable multi-scope runbooks

A **workflow** 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 workflow W<N>", an agent opens
the workflow's file and follows its `# Steps` section, then files a
run log.

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

### 14.1. Identifier convention

Workflows use the `W<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 — workflows are cross-cutting by
definition and shouldn't be scoped per-module.

### 14.2. Folder layout

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

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

### 14.3. Workflow file shape

```yaml
---
id: W<N>                           # * MUST match the filename prefix
title: "<one-line title>"          # *
scope: cluster | project | module  # * what the workflow 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
---
workflow: W<N>                     # * filename of the workflow 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/workflows/` on boot and on file
change.

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

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

### 14.6. Gitignore exception

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

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

Workflows 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 W<N>"

The executing agent:

1. Opens `.meshkore/workflows/W<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>/W<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 workflow 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/workflows/`) *(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/` /
> `workflows/`, 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, workflows, 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 + workflows/ travel;
# the rest is per-machine working state. (Same rule as today's
# standard v6.)
.meshkore/*
!.meshkore/public/
!.meshkore/STANDARD_VERSION
!.meshkore/workflows/
.meshkore/workflows/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/
│   ├── workflows/
│   ├── 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 Architect IDE 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 Architect IDE.

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 Architect IDE 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 Architect IDE'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
Architect IDE's Storage panel renders it automatically — no client-side
schema change required.

**Use cases**:

- Architect IDE 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 Architect IDE'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 Architect IDE'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).

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

---

## 25. Agent team (v29)

Every cluster has a **team**: a persistent roster of typed *members*
from which working agent *sessions* are spawned. The team is the thing
the operator means by "my agents" — not the code-level role registry
(`AGENT_PROMPTS`), not the per-conv runtime, and not the declared
external-agents store (`.meshkore/agents/*.yaml`). The default team is
part of this standard; a conforming daemon MUST seed it when
`.meshkore/team/` is absent, and from then on the operator owns it.

### 25.1 The model

- **Member** — a definition file `.meshkore/team/<id>.md` (frontmatter
  + init-prompt body). A member has a mission, an init prompt, and a
  **mandatory `model`**. It is a template, not a running agent.
- **Instance** — a chat conversation bound to a member
  (`conv_meta.member`). Instances are the sessions that do the work;
  each turn is a fresh `claude -p` briefed from the member's init
  prompt. The member binding is editable **until the first dispatch**,
  then it freezes; `model` and `effort` stay editable for the whole
  life of the conv (they apply from the next turn).
- **Two kinds of member:**
  - `kind: singleton`, `required: true` — exactly **two**:
    **`architect-master`** (owns and maintains the roadmap; the
    project "CEO") and **`roadmap-orchestrator`** (executes the queue
    on Run All; wraps the legacy `roadmap-architect` role). A singleton
    **cannot be deleted** and may have **at most one live instance**.
  - `kind: profile` — one definition per role, but an **unlimited**
    number of instances may run (`developer`, `api-developer`,
    `ui-developer`, `deployer`, `ui-reviewer`, `commit-pr-reviewer`, …).
- **Models & effort** — every member's `model` defaults to the
  **strongest available alias** (an opus-tier alias so it tracks model
  upgrades). `effort` (`low`/`medium`/`high`/`max`) is the usual
  per-member / per-instance tuning knob; operators downgrade the model
  only when they explicitly want to.
- **First boot** — the agents column contains only the
  `architect-master` instance (the coordinator conv, now bound to that
  member). All other members are started as instances by the operator.

### 25.2 Member schema — `.meshkore/team/<id>.md` (normative)

Frontmatter block, followed by a markdown body (the init prompt,
injected as the instance's system prompt on the first turn):

```yaml
---
id: api-developer            # ^[a-z][a-z0-9-]{1,31}$ — matches the filename stem
name: "API Developer"        # display name (English UI)
emoji: "🔌"                  # roster/avatar glyph
color: "#F59E0B"             # hex accent
kind: profile                # singleton | profile
required: false              # true ⇒ cannot be deleted (only the two singletons)
agent_type: custom           # AGENT_PROMPTS baseline the prompt builder starts from
model: opus                  # MANDATORY — the strongest alias by default (lib/models.ts)
effort: default              # default | low | medium | high | max — the tuning knob
exposure: internal           # internal | external — default internal (v30, see §25.6)
pinned_order: 20             # roster sort key (ascending)
refs:                        # docs / workflows the member should read
  - .meshkore/context/architecture.md
  - .meshkore/context/stack.md
credentials_hint: ".meshkore/credentials/"   # where secrets live (never inlined)
created: 2026-07-03
updated: 2026-07-03
---
# API Developer
(markdown body — the init prompt: mission, how you work, limits)
```

`model` is mandatory on every member — a member with no model is
non-conformant. `id` MUST equal the filename stem. `kind` and
`required` are immutable once seeded (the UI presents them read-only).

### 25.3 Canonical default team (9 members)

| id | kind | required | agent_type | mission |
|---|---|---|---|---|
| `architect-master` | singleton | yes | custom | Knows context/credentials/mission; creates & manages the roadmap (the "CEO"). |
| `roadmap-orchestrator` | singleton | yes | roadmap-architect | Executes the roadmap queue (Run All); dispatches worker instances. |
| `developer` | profile | no | custom | Generic, module-agnostic coder — the chat-rail `+` button's default member. |
| `api-developer` | profile | no | custom | Backend / API implementation. |
| `ui-developer` | profile | no | custom | Frontend / UI implementation. |
| `deployer` | profile | no | deploy | Deploys & release ops. |
| `ui-reviewer` | profile | no | review | Visual / functional review (uses MeshKore Verify `POST /verify`). |
| `commit-pr-reviewer` | profile | no | review | Commit & PR review. |
| `consultant` | profile | no | custom | Standing info point for **external** agents — social bots, collaborators, integrators. Answers about THIS project with cited facts from its docs/context/source; never invents; the caller owns voice and publishing. Ships `exposure: external` with a token pregenerated at scaffold / first boot (v30, §25.6). |

Every member seeds with `model` = the strongest available alias
(opus-tier today) and `effort: default`. The daemon MUST seed all
nine when `.meshkore/team/` does not exist; operators MAY edit init
prompts, delete profile members, and add new ones. The two singletons
cannot be deleted.

### 25.4 Git contract

`.meshkore/team/` is **project storage and is committed** — it travels
with the repo under the v27 deny-list default (§2.2). It is
deliberately **NOT** in the gitignore deny-list; do not add it. The
roster is part of the project's identity, like `docs/` and
`roadmap/initiatives/`.

### 25.5 HTTP / WS surface

The shared daemon exposes the roster so the cockpit and cross-cluster
tooling can read and mutate it:

- `GET /team` — list members; `GET /team/<id>` — one member.
- `POST /team` — create; `PATCH /team/<id>` — update;
  `DELETE /team/<id>` — delete. DELETE refuses a member with
  `required: true` (the two singletons) and returns an error.
- `POST /team/draft` — turn free text into a structured member draft
  (the new-member wizard's backend).
- `team.*` WS events — `team.created` / `team.updated` / `team.deleted`
  push roster changes to connected cockpits in real time.

Singleton enforcement: dispatching a second live instance of a
singleton member returns `409`. A dispatch that arrives **without** a
`member` falls back to the legacy `AGENT_PROMPTS[agent_type]` path, so
pre-v29 conversations keep working.

### 25.6 External exposure (v30)

A team member can be exposed as a **service** that other software on
the same machine consumes — another cluster, a bare Claude Code
session, a social-network bot's pipeline. This is the **team external
gateway**.

**`exposure` field.** The member frontmatter gains
`exposure: internal | external` (default `internal`; an absent field
means `internal`). It is orthogonal to `kind` — an exposed profile
member still allows unlimited instances. The enum leaves room for a
future `mesh` value (cross-machine, see the perimeter note below)
without another migration. `exposure` is config, not a secret: it IS
committed in the member file.

**Per-member bearer token.** When exposure flips to `external` (or at
seed time for `consultant`) the daemon mints a **non-expiring** bearer
token for that member — no TTL, no rotation schedule. It is stored
**ONLY** under `.meshkore/credentials/team-tokens.yaml` (`credentials/`
is deny-listed in §2.2) and MUST NEVER appear in the committed member
file. Lifecycle is operator-driven from the Team UI:

- **copy** — show + copy the token to hand to a consumer;
- **rotate** — regenerate; the old token dies instantly;
- **revoke** — one action: `PATCH` the member back to
  `exposure: internal` and the token is destroyed instantly — every
  outstanding ask 403s from that moment.

The token is scope-limited: it can ONLY call the ask/poll surface for
its own member. It is not the portal token and grants nothing else.

**Ask/poll HTTP surface.** Agent turns take 30 s – minutes, so the API
is async (ask, then poll):

- `POST /team/<id>/ask` `{text, session?}` with
  `Authorization: Bearer <member-token>` +
  `X-MeshKore-Project: <cluster-id>` → `202 {request_id, conv}`.
- `GET /team/requests/<request_id>` (same auth) →
  `{status: queued|running|done|error, result_text, conv}`.
- Error semantics: `401` missing/unknown token · `403` token does not
  match the member, or the member is `exposure: internal` (incl.
  post-revoke) · `404` unknown member or request id · `429` the
  daemon is at capacity — back off and retry.
- `session` gives conversation continuity: the same `session` value
  maps to the same conv, so the member remembers the thread across
  asks.
- External asks are **ordinary member instances** — the conv appears
  in the chat rail like any other, the init prompt is injected on
  turn 1, and model/effort come from the member.

**A2A card.** For every exposed member the daemon serves
`GET /team/<id>/.well-known/agent.json` — an A2A card so any
A2A-speaking client can self-configure against the member.

**Perimeter (v1 = loopback).** The shared daemon binds **loopback
only** (§10) — nothing off-machine can reach this surface at all; the
token only discriminates between local callers. Cross-machine
consumption is explicitly out of scope for v30: it is future **mesh
exposure** built on identity (per-member did:key + hub registration),
NOT port-forwarding the daemon.

**Convention doc**: [`agent-team.md`](../../../.meshkore/docs/conventions/agent-team.md)
· external gateway: [`team-external-requests.md`](../../../.meshkore/docs/conventions/team-external-requests.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` + `workflow.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>.
