# MeshKore — Operator Manual

**You are working in a repository that participates in a MeshKore cluster.**
This is your manual: how to read context, write tasks, log activity,
edit docs, and commit. Same rules whether you're a manual session in
Claude Code / Cursor / Windsurf / VS Code, or an automated agent driven
by the local Python daemon (`python3 .meshkore/scripts/daemon.py`).

Read this once at session start. Apply throughout.

> Canonical URL: <https://meshkore.com/cluster/operate>
> Mnemonic: "operate the cluster"

---

## 0.1 · Standard version

This manual describes **MeshKore standard version 6**
(introduced 2026-05-20). Your local folder declares its current
version in `.meshkore/STANDARD_VERSION` — a single integer on one line,
no semver suffix.

```bash
cat .meshkore/STANDARD_VERSION    # → 6  if you are on the latest
```

The published-current version is always available as a 3-byte endpoint:

```bash
curl https://meshkore.com/standard/version    # → 6
```

If your local version is behind, open the
[changelog](https://meshkore.com/standard#11-versioning), read
every section between your version and the latest, and apply the
changes yourself. Plain prose — a human or an LLM can interpret it.
Each section has a **Apply manually** block. When done, write the new
number into `.meshkore/STANDARD_VERSION`.

**Catch-up prompt** — paste into your editor when behind:

> Read the MeshKore CHANGELOG from
> <https://meshkore.com/standard#11-versioning>.
> My local `.meshkore/STANDARD_VERSION` is `X`. Compose a plan to bring
> this folder up to version `Y`. Apply the plan, validate the folder
> still works, then write `Y` into `.meshkore/STANDARD_VERSION`.

**With daemon (automatic safeguard):** when the Python daemon is running,
it polls `https://meshkore.com/standard/version` every hour. If your
local version is behind the published one, the daemon refuses cluster
writes and the architect shows an amber banner. This prevents data
corruption from a folder format the daemon wasn't built for.

**Without daemon (manual users):** a stale folder still works fine —
the standard is forward-compatible by design. Catch up when you want
the new guarantees, not because something forces you.

Full versioning design lives in the
[Spec evolution initiative](https://meshkore.com/standard).

---

## 0 · Detect what you have

```bash
ls .meshkore/                                  # is this a cluster repo?
cat .meshkore/public/cluster.yaml              # cluster identity (committed)
cat .meshkore/STANDARD_VERSION                 # which version of the standard this folder follows (see §0.1)
test -f .meshkore/credentials/portal-token && echo "daemon initialized"
curl -fsS http://localhost:5570/health 2>/dev/null && echo "daemon running"
```

If `.meshkore/` does not exist, this is **not** a cluster repo. Ignore
this manual. If it exists, continue.

### Token policy (clarified 2026-05-20)

`http://localhost:5570/health`, `/state`, `/links`, `/protocols`,
`/agents`, `/info`, `/events` (WebSocket) — **no auth required**. The
architect at <https://architect.meshkore.com> auto-discovers and
binds to a localhost daemon without prompting for a token; this is
intentional (localhost-only origin = trust boundary). When you visit
the architect on a fresh browser session you should NOT need to paste
a bearer.

Bearer auth IS required for the write surface:
`POST /reload`, `POST /tasks/<id>/transition`, `POST /chat/dispatch`,
`POST /links/<id>`, `POST /cron/<id>/trigger`, `POST /shutdown`,
`POST /version/next`. Read the token from
`.meshkore/credentials/portal-token` (mode 0600, gitignored, minted on
first daemon start) and pass it as `Authorization: Bearer <token>`.
The architect's UI does this automatically using its localStorage cache
of the daemon's per-origin token.

---

## 1 · Folder map (what to read, what to write, what never to touch)

```
.meshkore/
├── public/                 ← committed; the world sees this
│   ├── cluster.yaml        identity, transport URLs, bootstrap info, MODULES
│   └── README.md           how to join
│
├── modules/<id>/           ← one folder per declared module (gitignored)
│   ├── README.md           module overview (prose)
│   ├── tasks/              active tasks for this module
│   ├── log/<YYYY-MM>/      done tasks for this module (archived)
│   └── diagrams/           mermaid `.mmd` files attached to this module
│
├── docs/                   ← cross-cutting project context (gitignored)
│   ├── governance.md       READ FIRST. R1-R6 rules.
│   ├── architecture/       project-wide architecture (not module-specific)
│   ├── product/            strategy, vision
│   ├── conventions/        namespaces, taxonomies, frontmatter, diagrams
│   ├── deploy/             runbooks
│   ├── security/           threats, audits
│   └── ops/                day-to-day, issues, testing
│
├── roadmap/                ← generated state only (gitignored)
│   ├── state.json          GENERATED — do not edit by hand
│   └── state.js            GENERATED — do not edit by hand
│
├── log/<YYYY-MM-DD>.md     ← daily session log — PROSE, see §5.1 (gitignored)
├── timeline/<DATE>.jsonl   ← append-only activity events — STRUCTURED, see §5 (gitignored)
├── STANDARD_VERSION        ← single integer; which version of the MeshKore standard this folder follows (§0.1). Committed.
├── agents/<id>.yaml        ← per-machine agent identities (gitignored)
├── credentials/            ← NEVER EDIT, NEVER COMMIT, NEVER LOG
├── scripts/*.py            ← helpers; run them, don't edit them
├── architect/index.html       ← the visualizer
└── .runtime/               ← daemon ephemera; never edit
```

> **Module-centric layout.** Everything for a module — its prose, tasks,
> done log and diagrams — lives together under
> `.meshkore/modules/<id>/`. The architect's left tree drives the centre
> tabs (Tasks · Context · Diagrams) for the selected module.

**Hard rules**:
- Edit only `docs/`, `roadmap/`, `timeline/`. Everything else either
  generated, secret, or runtime.
- Anything outside `.meshkore/public/` is gitignored. Don't try to make
  the system commit private content.

---

## 2 · Reading project context

The user typically tells you what they want; ground yourself in the
relevant docs before acting:

| Need | Where to look |
|---|---|
| How the system is built | `.meshkore/docs/architecture/*.md` |
| Strategy / why we exist | `.meshkore/docs/product/*.md` |
| Hard rules / namespaces | `.meshkore/docs/conventions/*.md` |
| One module's prose | `.meshkore/modules/<module>/README.md` |
| One module's diagrams | `.meshkore/modules/<module>/diagrams/*.mmd` |
| How to deploy X | `.meshkore/docs/deploy/*.md` |
| Security model | `.meshkore/docs/security/*.md` |
| Open issues / runbooks | `.meshkore/docs/ops/*.md` |
| The R1-R6 rules | `.meshkore/docs/governance.md` |

**Be selective.** Don't dump-load all of `docs/` at once. Open what's
relevant to the task. Use `grep -ril <topic> .meshkore/docs/` to find
the canonical file.

When the user asks "carga el contexto" or "carga la arquitectura", load
the relevant subfolder, not everything.

---

## 3 · Modules — what they are and how to declare them

A **module** is a first-class organizational unit of the project.
Tasks, docs and diagrams all live under a module. The architect's left nav
is one row per module.

A module can be:

| `kind` | Backed by | Typical examples |
|---|---|---|
| `code` | a code folder | `api/`, `webapp/`, `daemon/`, `worker/` |
| `spec` | schemas / conventions | `cluster` (the cluster spec), `meshkore-spec` |
| `docs` | a docs folder | dedicated `docs/`, `whitepapers/` |
| `area` | nothing — pure logical area | `admission`, `payments`, `seo`, `security` |

### Where modules are declared

Every cluster MUST have a `modules:` block in
[`.meshkore/public/cluster.yaml`](#). Until you declare modules
explicitly, the architect will fall back to "every top-level repo folder"
which is noisy and surfaces folders that have no tracked work.

```yaml
# Excerpt from cluster.yaml
modules:
  - id: api
    name: "Hub API"
    kind: code
    path: api/
    description: "Rust relay (Axum), Fly.io"
  - id: webapp
    kind: code
    path: webapp/
  - id: admission
    kind: area
    description: "SSH/GitHub-based cluster admission flow"
  - id: general
    kind: area
    description: "Project-wide work without a specific module yet."
```

| Field | Required | Notes |
|---|---|---|
| `id` | yes | Stable lowercase slug. Matches the folder under `tasks/<id>/` and the `category:` field of every task in this module. |
| `name` | no | Human label (defaults to `id`). |
| `kind` | no | `code` · `spec` · `docs` · `area`. Defaults to `area`. |
| `path` | no | Folder this module is rooted at. Optional for `area`/`spec`. |
| `parent` | no | Parent module id. Nests this module under `<parent>` in the architect's left nav. Selecting the parent aggregates this module's tasks. Omit to keep it at the top level (direct child of the auto-generated **Project** root). |
| `description` | no | One line, surfaced as tooltip in the architect. |

### Hierarchy with `parent:`

Modules form a flat list by default. Each module without `parent:` is
a top-level entry in the architect sidebar; declare `parent: <other-id>`
to nest under another module.

```yaml
modules:
  - id: api
    kind: code
    path: api/
  - id: api-routes
    kind: code
    path: api/src/routes/
    parent: api
  - id: api-db
    parent: api
```

Selecting `api` in the architect shows tasks from `api`, `api-routes`,
and `api-db`. Selecting `api-routes` shows only its own. Tasks always
reference a single leaf `category:` — aggregation across descendants
is computed client-side from the declared tree.

### The `project` meta-bucket

`project` is **a peer of the code modules** (api, webapp, architect,
daemon, …), not a virtual root above them. It exists to hold work
that doesn't belong to any single code module:

| Child of `project` | Purpose |
|--------------------|---------|
| `testing` | Backend smoke + frontend regressions + multi-agent CI gates |
| `audit` | Behavior-preserving refactors (Rust hardening, architect modularization, dead-code sweeps) |
| `future` | Forward-looking ideas, not actively planned |
| `general` | Catch-all for project-wide tasks that don't fit elsewhere |

Cross-cutting initiatives that genuinely span every code module
(e.g. `V23 — multi-device coordinator`) live as **direct tasks** on
`project` itself, no child module needed. Specific code work always
lives on the relevant code module (`architect/`, `daemon/`, etc.), never
on `project`.

### Always declare a `general` module

```yaml
- id: general
  kind: area
  description: "Project-wide work without a specific module yet."
```

This is the explicit catch-all for tasks that don't fit a specific
module (yet). Use `general` instead of leaving a task uncategorized.
**Never use `category: uncategorized`** — that name is not part of the
schema and the build script will warn about it.

### Adding a new module

1. Open `.meshkore/public/cluster.yaml`.
2. Add a new entry to `modules:` with at least `id` and `kind`.
3. Create the folder: `mkdir -p .meshkore/modules/<id>/tasks/`.
4. Run `python3 .meshkore/scripts/roadmap-build.py` (the daemon also
   re-scans on `POST /reload`).
5. Refresh the architect — the new module appears in the left nav.

**Rule of thumb:** if you're about to add a third task to the `general`
bucket on the same topic, that topic deserves its own module. Promote
it.

### What "bound to a module" means

Every task in `.meshkore/modules/<module>/tasks/<id>-slug.md` MUST have
`category: <module>` in its frontmatter, where `<module>` matches a
declared module's `id`. The build script logs a WARN for any mismatch
and `roadmap-build.py --validate` exits non-zero.

---

## 3.1 · Writing a new task

A task is **one `.md` file with YAML frontmatter** under
`.meshkore/modules/<module>/tasks/`. Pick the module first — see §3
above — then write the task.

### Naming

- File: `<ID>-<slug>.md`
- ID: short, unique within the cluster. Conventions in this repo:
  - `N<n>` next
  - `T<n>` general task
  - `C<n>` cluster initiative
  - `V<n>` architect v2 phase
  - Pick a free letter for new initiatives, or just `T<next-n>`
- Slug: lowercase, hyphenated, ≤30 chars, descriptive

### Frontmatter (required)

```yaml
---
id: T091
title: "GET /agents/stats endpoint"
status: next
priority: high
owner: rjj
category: api                    # MUST match a module id declared in cluster.yaml. Use 'general' if nothing fits.
agent_type: developer            # optional · see §3.2 (v2+).
                                 # one of: developer | reviewer | deployer | tester | auditor.
                                 # if absent, inferred from tags (deploy→deployer, audit→auditor,
                                 # test→tester, review→reviewer, else developer).
milestone: "v1.0"                # optional
created: 2026-05-06
updated: 2026-05-06
tags: [api, observability]
depends_on: []
effort: "1d"
files: [api/src/stats.rs]        # optional, what this task touches
---
```

### Body (markdown)

Free form. Recommended sections:

```markdown
# T091 — GET /agents/stats endpoint

## Why
(motivation, user need)

## Scope
- bullet 1
- bullet 2

## Done when
- check 1
- check 2

## Notes
(any relevant context, links, decisions)
```

### After writing

```bash
python3 .meshkore/scripts/roadmap-build.py    # regenerate state.json
```

Or, if the daemon is running:

```bash
curl -X POST http://localhost:5570/reload \
  -H "Authorization: Bearer $(cat .meshkore/credentials/portal-token)"
```

The architect picks up the new task on its next state fetch (or via WS push
if the daemon is running).

---

## 3.2 · Agent roles — `agent_type` field (v2+)

Tasks can request a specific **type** of agent to execute them. The
type, not a specific worker — an operator might have ten developer
workers; what matters when dispatching is the role.

### The five standard roles

| `agent_type` | Picks up | Inferred from tag fragments |
|---|---|---|
| `developer` | feature work, refactors, anything code-shaped | (default — used when no tag matches) |
| `reviewer` | code review, audit-lite, second pair of eyes | `review` |
| `deployer` | deploys, releases, infra moves | `deploy`, `release`, `ship` |
| `tester` | test-writing, regression suites, QA | `test`, `qa` |
| `auditor` | security audits, compliance, deep audit | `audit`, `security` |

### How the field is resolved

1. If the task's frontmatter has `agent_type: <role>`, that wins.
2. Otherwise the daemon infers by scanning `tags[]` for any of the
   fragments above. First match wins (in the table order).
3. If nothing matches, the role is `developer`.

The architect surfaces the resolved role next to the **▶ run** button in
the task card and lets the operator override it per-task via a small
popover.

### Daemon dispatch

When a task is dispatched (`POST /tasks/<id>/dispatch`), the request
body carries `{ "agent_type": "<role>" }` — the daemon's runner pool
picks any free worker of that role. If no matching worker is free, the
task queues until one frees up. (Daemon-side enforcement landed in
the daemon-runtime initiative; check that module's roadmap for the
status of role-aware routing.)

### Backwards compatibility

The field is optional. Tasks created under v1 (no `agent_type`)
continue to work — the daemon falls back to inferred-from-tags. v1
folders that don't touch their tasks at all keep behaving as before.

---

## 4 · Updating a task

Edit the file's frontmatter. The fields that change most:

```yaml
status: in_progress              # backlog | next | in_progress | blocked | done | cancelled
updated: 2026-05-06
```

When marking **done**: move the file to `modules/<module>/log/<YYYY-MM>/`:

```bash
mkdir -p .meshkore/roadmap/log/2026-05
git mv .meshkore/modules/<module>/tasks/T091-*.md \
       .meshkore/roadmap/log/2026-05/
# then edit status: done, updated: today
```

(Use plain `mv` if the path is gitignored — usually it is.)

Then regenerate state:

```bash
python3 .meshkore/scripts/roadmap-build.py
```

---

## 5 · Logging activity (the timeline)

Every notable thing — a chat exchange, a task transition, a commit,
a doc update — is one append-only event in
`.meshkore/timeline/<UTC-today>.jsonl`.

### With daemon (preferred)

```bash
# Send a chat message
curl -fsS -X POST http://localhost:5570/chat/send \
  -H "Authorization: Bearer $(cat .meshkore/credentials/portal-token)" \
  -H "Content-Type: application/json" \
  -d '{"text":"Starting T091 — implementing the stats endpoint."}'

# Stream incoming events (WebSocket)
# In the architect: open the Chat tab — it subscribes automatically.
```

The daemon handles `ts`, file locking, WebSocket broadcast.

### Without daemon (manual sessions)

```bash
python3 .meshkore/scripts/timeline-append.py \
  --type chat.user --author rjj --conv "$(date -u +%Y-%m-%d-%H%M)-stats" \
  --text "Starting T091 — adding the stats endpoint."
```

Or pipe a JSON object:

```bash
echo '{"type":"task.completed","id":"T091","agent":"claude-vscode-mac","commit":"abc123","summary":"Done."}' | \
  python3 .meshkore/scripts/timeline-append.py --from-stdin
```

### Event types you'll write

| Type | When | Required fields |
|---|---|---|
| `chat.user` | User typed something to you | `author`, `text`, `conv` |
| `chat.assistant` | You replied with a plan | `author`, `text`, `conv`, `linked_tasks?` |
| `task.created` | You created a task `.md` | `id`, `title`, `module`, `priority`, `conv?` |
| `task.assigned` | You picked it up / assigned to someone | `id`, `agent` |
| `task.started` | You began working | `id`, `agent` |
| `task.completed` | You finished it | `id`, `agent`, `commit?`, `summary` |
| `task.failed` | You couldn't | `id`, `agent`, `error`, `summary` |
| `task.transitioned` | Manual status change | `id`, `from`, `to`, `by` |
| `commit.pushed` | After `git push` | `sha`, `author`, `message`, `linked_tasks?` |
| `doc.updated` | After editing `docs/*.md` | `path`, `author`, `summary` |

The point: **anyone in the cluster can replay your session**. Be as
honest in events as you would in a commit message.

---

## 5.1 · Daily session log (prose)

Alongside the structured timeline you append **one prose log file per
calendar day** so a future-you can open one file and see what moved
that day without re-reading the whole task tree or scrubbing JSONL.

> **Canonical location:** `.meshkore/log/<YYYY-MM-DD>.md` (local date,
> not UTC). Gitignored. **Do not write anywhere else** — not in
> `_rjj/logs/`, not in `docs/log/`, not in a personal scratchpad. The
> architect's Context tab and the daemon's `/state` endpoint both
> look here.

### Shape

```markdown
---
date: 2026-05-12
owner: rjj
---

# 2026-05-12 — daily log

## <Topic A — e.g. "Architect V28 — carved out as an isolated module">

- Prose entry. One paragraph or a tight bullet block per coherent
  block of work. Reference task IDs in brackets like [V28], file
  paths inline, decisions and *why*.
- Another entry under the same topic.

## <Topic B — e.g. "Doc updates">

- ...

## Loose ends

- What you tried but didn't finish, and the command/approach a
  future-you would use to pick it up.

- HAPPENED · HH:MM · auto-emitted event (commit pushed, scrape
  cycle complete, deploy succeeded, …)
```

### Rules

| Rule | Why |
|---|---|
| One file per local date, named `<YYYY-MM-DD>.md` | predictable + sortable |
| Frontmatter (`date`, `owner`) at the top | tools index on it |
| Topic-grouped prose body (`##` headings) | scannable, reads like a journal entry |
| Tail of `HAPPENED · HH:MM · …` one-liners | catches what the daemon emits automatically |
| Append, don't rewrite past entries | the log is replay material, not a draft |
| Coarse granularity — one entry per coherent block | not per commit, not per edit |

### When to write

- **End of each productive session.** Append before you close your
  editor.
- After a notable HAPPENED event (a deploy, a scrape cycle, a
  blocker uncovered) when you have ~30 seconds.

### What goes in vs `timeline/`

- **`timeline/<DATE>.jsonl`** = machine-readable events (typed,
  required fields, one per discrete action). Replay material for
  agents and for the architect's live feed.
- **`log/<DATE>.md`** = human-readable narrative. *Why* you did
  things, what you learned, what's still loose. Replay material for
  humans.

Both are append-only. Both are gitignored. They complement each other —
never duplicate content between them.

### Common mistake

Agents arriving from non-MeshKore projects often default to writing
session logs to a generic personal path like `_rjj/logs/<DATE>.md` or
`docs/log/<DATE>.md`. **That is wrong for MeshKore clusters.** Cluster
tooling will not surface logs that live outside `.meshkore/log/`. If
your editor's rules file points anywhere else, override it for this
repo.

---

## 6 · Editing documentation

Read [`docs/governance.md`](https://meshkore.com/standard.md)
once. The R1-R6 rules in summary:

- **R1**: Pick the right category (architecture / product / conventions /
  modules / deploy / security / ops). If nothing fits, don't invent a new
  top-level — nest.
- **R2**: Max 200 lines. Split if it grows.
- **R3**: One topic, one canonical file. Don't duplicate.
- **R4**: Frontmatter required.
- **R5**: Link, don't copy.
- **R6**: `INDEX.md` is a map, not content.

### Doc frontmatter

```yaml
---
title: "API Architecture"
category: architecture
tags: [api, rest, protocols]
updated: 2026-05-06
owner: rjj
status: stable                # draft | stable | deprecated
related:
  - architecture/protocols/compatibility.md
  - conventions/url-namespace.md
---
```

### After editing

```bash
python3 .meshkore/scripts/roadmap-build.py --validate    # check frontmatter
python3 .meshkore/scripts/roadmap-build.py               # rebuild state
```

Optionally log a `doc.updated` event:

```bash
python3 .meshkore/scripts/timeline-append.py \
  --type doc.updated --author rjj \
  --summary "Added stats endpoint section to architecture/api.md"
```

---

## 6.1 · Diagrams (live next to their doc)

Diagrams (`.mmd`, Mermaid) are first-class context. They live **next to
the doc they illustrate**, never in a global "diagrams" silo.

```
.meshkore/docs/architecture/cluster-layout/
├── README.md              ← the prose
└── diagrams/
    ├── architecture.mmd
    └── folder-tree.mmd
```

When a doc has even one diagram, **promote it from `<slug>.md` to
`<slug>/README.md`** and put the `.mmd` files in a sibling
`diagrams/` folder. Diagram kinds (`architecture`, `schema`,
`sequence`, `flow`) are inferred from the filename prefix or set
explicitly in the diagram's YAML frontmatter.

The architect's **Context** tab (was Docs + Diagrams) auto-renders both
the prose and the diagrams as tabs above the body when you open the
doc — so a reader sees the explanation and the picture together.

Full convention: see `.meshkore/docs/conventions/diagrams.md` in this cluster.

---

## 6.2 · The context workflow (every change, every time)

Stale context is worse than no context. After every non-trivial change,
walk this short list before the commit:

1. Updated the relevant **module README**?
2. Updated or added the relevant **diagram**?
3. Updated **architecture / convention** docs if cluster-level?
4. **Closed / opened** the matching roadmap task?
5. Appended a **timeline event**?
6. **Rebuilt state** and verified in the Context tab?

Full guide: see `.meshkore/docs/conventions/context-workflow.md` in this cluster.
Treat it as a one-page PR checklist.

---

## 7 · Committing code + versioning

Every task that touches code or context produces **one commit + one
version bump**. Versions are **issued by the master daemon** so that
parallel agents can never collide on the same number. The full
convention lives at `.meshkore/docs/conventions/versioning.md` in this cluster.
Short version below.

### Commit message — Conventional Commits 1.0.0

```
<type>(<scope>): <subject> [<task-id>]

<body — optional>

<footer — optional, e.g. BREAKING CHANGE: …>
```

Allowed `<type>`: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`,
`test`, `chore`, `build`, `ci`, `revert`. Append `!` and add a
`BREAKING CHANGE:` footer for breaking changes. Always reference
the task ID in `[brackets]`.

```
feat(api): add GET /agents/stats endpoint [T091]

Returns total, online, msgs/day, p95 latency.
Schema in docs/architecture/api.md updated.
```

### Versioning — coordinator-issued SemVer

Before tagging or writing the version into `package.json`, ask the
master daemon for the next version:

```http
POST localhost:5570/version/next
Authorization: Bearer <token>
{ "bump": "patch", "task_id": "T091" }

→ { "version": "0.5.1", "tag": "v0.5.1" }
```

| When | `bump` |
|---|---|
| `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `build`, `ci`, `revert` | `patch` |
| `feat` | `minor` |
| any breaking change (`!` or `BREAKING CHANGE:`) | `major` |

`major` bumps require human approval (the daemon flags them in the
architect). Default to `patch` when unsure. The daemon serialises every
request through a file lock — **two agents cannot get the same
version**. Build-metadata suffixes like `0.6.0+claude-vscode-mac.3`
are reserved for personal in-flight work; never push those to the
shared branch.

### What to always commit

- Code changes in tracked source files.
- Public docs (`docs/`, `webapp/`, `api/`, etc.).
- `.meshkore/public/cluster.yaml` if the cluster identity changed.
- **`.meshkore/public/links.yaml`** if your commit changed where a
  module runs, where it deploys, what branch it lives on, or what
  version it carries. This file is the project's single source of
  truth for "where is everything deployed and at what version" —
  standard §13. The daemon serves it at `GET /links` and broadcasts
  `links.updated` on the WebSocket.

### What to never commit

- Anything under `.meshkore/credentials/`.
- Anything under `.meshkore/.runtime/`.
- Anything under `.meshkore/protocols/log/` — run logs stay per-machine.
- Generated state (`.meshkore/roadmap/state.json` is gitignored).
- Anything in `.meshkore/` outside `public/`, `STANDARD_VERSION`, and
  `protocols/`. The `.gitignore` enforces it.

### Protocols — "apply P<N>"

When the operator says **"apply protocol P<N>"**:

1. Open `.meshkore/protocols/P<N>-*.md`.
2. Read the Preconditions section. Refuse with a clear error if any
   precondition fails.
3. Execute Steps in order. For each step, do the action AND collect
   the verification signal.
4. Append a run-log entry to
   `.meshkore/protocols/log/<YYYY-MM>/P<N>-<YYYY-MM-DD>-<context>.md`.
5. Report the run summary back to the operator with commit sha + any
   external deploy IDs.

Protocols are listed at `.meshkore/protocols/INDEX.md` and served by
the local daemon at `GET /protocols`. Schema reference: standard §14
and `.meshkore/docs/conventions/protocols.md`. The two reference
protocols that ship with the standard:

- **P1** — bump-standard-version
- **P2** — deploy-project

### Tag + push + log

```bash
git tag v0.5.1
git push origin <branch> --tags
```

The daemon listens for `commit.pushed`, validates that the tag
matches the version it issued and that `[<task-id>]` references a
real task in `next` or `in_progress`. If valid, it transitions the
task to `done` and emits `task.completed`. If invalid, it emits
`commit.rejected`.

Manual sessions can append the event by hand (the daemon does it
automatically when it owns the commit):

```bash
python3 .meshkore/scripts/timeline-append.py \
  --type commit.pushed --author $(whoami) \
  --commit $(git rev-parse HEAD) \
  --summary "$(git log -1 --pretty=%s)"
```

---

## 8 · The daemon — your only point of control

The user starts **one daemon, once**. Everything else flows through it
and through the architect.

```
┌──────────┐     ┌────────────────┐     ┌────────────────────┐
│  Architect  │ ◄─► │ Master daemon  │ ◄─► │ Cluster channel    │
│  (web)   │ HTTP│ localhost:5570 │  WS │ (P2P + hub fallback)│
└──────────┘     │ + runner pool  │     └────────────────────┘
                 │ + state watcher│              │
                 └───────┬────────┘              │
                         │                       ▼
                ┌────────┴────────┐    ┌────────────────────┐
                │ Local runners   │    │ Follower daemons   │
                │ (claude-code,   │    │ on other machines  │
                │  cursor-cli,…)  │    │ in the same cluster│
                └─────────────────┘    └────────────────────┘
```

### Roles

- **Master daemon** — first `python3 .meshkore/scripts/daemon.py` to
  bind a port in `5570–5589` on a machine for a given project. Owns the
  HTTP API, the WebSocket the architect listens to, the outbound
  cluster channel, and the local runner pool.
- **Follower (same machine, same project)** — extra daemon processes
  for other identities sharing the same `.meshkore/`. Detect the busy
  port, run agent-only, register as runners with the master.
- **Sibling daemon (different project, same machine)** — a separate
  `python3 .meshkore/scripts/daemon.py` started in another repo. Binds
  the next free port in `5570–5589`. Independent: own `.meshkore/`, own
  WebSocket, own cluster channel. The architect lists every responding
  port in its leftmost **Projects** rail and the operator switches
  between them with one click. See standard §10.1.
- **Follower (other machine)** — daemon on another machine in the
  cluster. Joins the channel; receives signed dispatches from the
  master; executes locally; streams events back.

### What this means for the human

1. Run `python3 .meshkore/scripts/daemon.py` once from the repo root
   (or paste the install prompt — see
   [/cluster/install#fast-path](https://meshkore.com/cluster/install)).
   The daemon is the single file at
   [/reference/cluster/scripts/daemon.py](https://meshkore.com/reference/cluster/scripts/daemon.py),
   stdlib-only Python ≥ 3.8.
2. Open the architect in your browser. From there: assign tasks, ask the
   chat to run a task, approve admissions, watch agents work in real
   time.
3. Don't shell back in to "do the daemon's job". If something can't
   be done from the architect, that's a bug in the dispatcher (V17), not
   a workflow you should automate locally.

### What this means for the AI agent

When you (the AI) finish a task you were assigned by the master, you
report through the daemon endpoints; you do **not** edit
`state.json` directly. When the user types "continue with V8" in the
chat, the master dispatches it to a runner — that runner is you (or
a sibling).

### Workers — persistent agent sessions per device

The master daemon owns a **WorkerPool** (`.meshkore/.runtime/workers.json`).
Each worker is a long-lived "tab" of an AI client (Claude Code / Codex /
…) with a stable session id (UUID) and an optional module binding. The
daemon dispatches each task to the worker that owns the task's module,
and reuses that worker's session id on every call so context
accumulates across dispatches.

Defaults:
- One coordinator (claude-code · sonnet · no module).
- Add module-bound workers from the architect Cluster · Network panel
  (e.g. `api-worker` bound to `api`, model `sonnet`).
- The architect renders the pool as a hub/list view in real time, with
  the device name (`os.hostname()`) on each card.

Endpoints:

```
GET    /workers                       list (with device info)
POST   /workers                       { id, kind, model, module?, role, permissions?, name? }
PATCH  /workers/<id>                  edit model / module / permissions / name
DELETE /workers/<id>                  remove (cannot remove only coordinator)
POST   /workers/<id>/reset-session    forget conversation, keep worker
```

### Permissions (headless safe)

Workers are spawned with `cwd = <repo-root>` and a permission policy
declared up front so they never block on a "may I write?" prompt that
nobody can answer. Three levels:

| `worker.permissions` | claude flag | meaning |
|---|---|---|
| `unrestricted` (default) | `--permission-mode bypassPermissions` | No prompts. Required for headless. cwd boundary is the repo. |
| `edits` | `--permission-mode acceptEdits` | Auto-edit; asks for Bash/network. Useful with a human watching. |
| `safe` | (none) | Asks for everything. Only for live interactive debugging. |

The cwd boundary protects file tools (Edit/Write/Read won't escape
the repo). Bash is the residual risk; treat the daemon's user as the
trust boundary. Full threat model + hardening: `.meshkore/docs/conventions/permissions.md` in this cluster.

### Single-device baseline · multi-device roadmap

Today the system is **single-device-safe**. One brain (= one human +
one device + one daemon + N workers) per project. If two people ever
run daemons against the same cluster.yaml, only one should be
**actively starting tasks**; the other should treat the architect as
read-only.

The proper multi-device model — module ownership, per-module locks,
brain-to-brain delegate requests over the cluster channel — is V23.
See `.meshkore/docs/conventions/multi-device.md` in this cluster.

### Without the daemon (read-only mode)

If you open the architect and there's no `localhost:5570`, you get a
read-only snapshot. Tasks, docs, diagrams, history are all visible —
but Run / dispatch / chat / onboarding all need the daemon. The
architect shows an amber banner with the start command.

---

## 9 · Multi-session etiquette

You may not be the only one editing this repo. Other sessions (other
editors, other identities, the daemon) might be writing tasks and events
at the same time.

- Before writing a task: glance at the latest `.meshkore/modules/<module>/tasks/`
  to make sure your ID isn't already taken.
- Use unique conversation slugs (the daemon auto-generates timestamped
  ones; manual sessions: `<YYYY-MM-DD>-<HHMM>-<topic>`).
- After committing: pull before further work (`git pull --rebase`) if
  there's any chance someone else has been working in parallel.
- For the same .md file, conflicts are normal — resolve as usual.
- Treat the timeline as immutable. Don't edit past events. Append new
  ones to correct (`task.cancelled`, `doc.updated` with a "see correction").

---

## 10 · Pasting this URL into your editor (do once)

So you don't have to load this manual every session, paste this snippet
into your editor's instructions / rules / memory file:

### Claude Code (`CLAUDE.md` in the repo root)

```markdown
## MeshKore cluster

This repo participates in a MeshKore cluster. If `.meshkore/` exists,
follow the operator's manual at <https://meshkore.com/cluster/operate>
(or the local cached copy at `.meshkore/docs/governance.md` + scripts).
Read it once at session start. Apply throughout.

Quick reminders:
- Tasks: `.meshkore/modules/<module>/tasks/<ID>-<slug>.md` with frontmatter
- Timeline events: `python3 .meshkore/scripts/timeline-append.py --type … --text …`
- Daily session log (prose): append to `.meshkore/log/<YYYY-MM-DD>.md` at
  session end — see §5.1. NOT `_rjj/logs/`, NOT anywhere else.
- Refresh: `python3 .meshkore/scripts/roadmap-build.py`
- Never commit anything outside `.meshkore/public/`.
```

### Cursor (`.cursorrules`)

```
This repo uses MeshKore. If a .meshkore/ folder exists, follow the
operator's manual at https://meshkore.com/cluster/operate before
making changes. Tasks live in .meshkore/modules/<module>/tasks/ as .md
files with YAML frontmatter. Daily session logs (prose) go in
.meshkore/log/<YYYY-MM-DD>.md, NOT _rjj/logs/. Timeline events via
.meshkore/scripts/timeline-append.py. Never commit
.meshkore/credentials/ or anything outside .meshkore/public/.
```

### Windsurf (`.windsurfrules`)

(Same content as `.cursorrules` above.)

### GitHub Copilot Chat (`.github/copilot-instructions.md`)

(Same content as `CLAUDE.md` above.)

### Zed (`.rules.md`)

(Same content as `CLAUDE.md` above.)

The agent reads the rules file at session start; it remembers the URL
and conventions. Subsequent sessions don't re-fetch unless the URL
changes.

---

## 11 · Admission & member management (operators)

For cluster operators (the orchestrator). All from the Architect Manage
tab when the daemon is running:

- **Onboard a new agent**: click "+ onboard new agent" → fill identity,
  role, optional GitHub user → daemon generates one-shot token + a
  copy-paste prompt. Send the prompt to the new machine/teammate;
  they paste it into Claude Code / Cursor / etc. and the join flow
  runs automatically.
- **Approve pending requests**: when a joiner submits, their pubkey
  fingerprint shows up in "Pending admission requests". Verify the
  fingerprint matches what they tell you out-of-band, then click
  Approve. Auto-approve flows skip this step (auto-on-token,
  auto-on-github).
- **Revoke a compromised key**: click the member → ⋮ → Revoke. The
  pubkey is marked `revoked_at` (kept for audit, not deleted).
  Re-onboarding requires a fresh keypair (see the
  `revoke-and-rejoin` template in
  `meshkore.com/reference/cluster/onboarding/`).
- **Switch admission policy**: edit `.meshkore/public/cluster.yaml`
  `admission:` section. Three approval modes:
  - `manual` — safest, operator approves every key.
  - `auto-on-token` — fast for trusted circles.
  - `auto-on-github` — operator allowlists GitHub usernames; joiners
    federate via `github.com/<user>.keys`.

The full architectural model lives in
[`docs/architecture/admission.md`](https://meshkore.com/standard/spec#admission)
(or local
[`.meshkore/docs/architecture/admission.md`](https://meshkore.com/cluster/operate)).
Onboarding prompt templates at
<https://meshkore.com/reference/cluster/onboarding/>.

---

## 12 · Deploys — one command per surface

Each surface ships independently. There is no monorepo deploy command —
intentional: a bad webapp push can't take down the relay, a stuck Fly
rebuild can't block an agent worker update.

### 12.1 · Public site — `meshkore.com`

```bash
# from repo root
mkdir -p /tmp/webapp-stash
mv webapp/agent /tmp/webapp-stash/
mv webapp/capability /tmp/webapp-stash/
mv webapp/node_modules /tmp/webapp-stash/ 2>/dev/null || true
mv webapp/directory.json /tmp/webapp-stash/ 2>/dev/null || true
mv webapp/.last-content-state.json /tmp/webapp-stash/ 2>/dev/null || true
mv webapp/.changed-urls.json /tmp/webapp-stash/ 2>/dev/null || true

npx wrangler pages deploy webapp \
  --project-name meshkore-web \
  --branch main \
  --commit-dirty=true

# restore — ALWAYS, even on failure
mv /tmp/webapp-stash/* webapp/ 2>/dev/null
mv /tmp/webapp-stash/.*.json webapp/ 2>/dev/null
rmdir /tmp/webapp-stash
```

Why the stash dance: CF Pages caps at 25 MB total / 20 K files.
`webapp/agent/` alone has 67 K files (scraped catalog). Stash → deploy
→ restore.

Verify with `curl -sI https://meshkore.com/` and
`curl -s https://meshkore.com/<path> | shasum` against the local file.

### 12.2 · Architect SPA — `architect.meshkore.com` + `m.architect.meshkore.com`

```bash
cd architect
npx wrangler pages deploy public \
  --project-name meshkore-portal \
  --branch main \
  --commit-dirty=true
```

The same Pages project serves both `architect.meshkore.com` and
`m.architect.meshkore.com` (alias). The SPA detects viewport and
switches layout.

Verify with `curl -s https://architect.meshkore.com/ | grep -c '<title>'`.

### 12.3 · Hub relay — `hub.meshkore.com`

```bash
cd api
flyctl deploy
```

Rust + Axum. App `meshkore-relay`. Region `cdg`. Persistent SQLite at
`/data/meshkore.db` on Fly volume `meshcore_data`. Cold rebuild takes
~38 min; incremental layer-cached builds ~10 min. **Schedule deliberately.**

Health: `curl https://hub.meshkore.com/health` →
`{"status":"ok","agents_online":<n>}`.

Rollback: `flyctl releases rollback --image-ref <previous>` (find via
`flyctl releases`).

### 12.4 · Agent workers — `<name>.agent.meshkore.com`

For a worker that **already** has its custom domain bound (the five
live ones — oracle, image-gen, shop, food-vision, tester — do):

```bash
cd agents/<name>            # or agents/partners/<name>
npx wrangler deploy
```

The deploy replaces the script behind every domain bound to it.

For a **new** worker that needs its `*.agent.meshkore.com` custom
domain bound for the first time:

```bash
# 1. deploy the worker (creates it under <worker-id>.rjj.workers.dev)
cd agents/<name>
npx wrangler deploy

# 2. bind the custom domain via API (one-time, ~3 s)
CF_TOKEN=$(cat .meshkore/credentials/cloudflare-token.txt | tr -d '\n\r ')
ACCOUNT="875bd2c5943a18d6c520d894ed12905f"
ZONE="48640303f43db0cd6870a960c0c48d90"

curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/workers/domains" \
  -H "Authorization: Bearer ${CF_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "environment": "production",
    "hostname":    "<short-name>.agent.meshkore.com",
    "service":     "<worker-id>",
    "zone_id":     "'"${ZONE}"'"
  }'
```

Replace `<short-name>` with the desired subdomain (e.g. `oracle`,
`image-gen`, `shop`) and `<worker-id>` with the Worker name as
registered in Cloudflare (e.g. `meshkore-oracle`).

DNS resolves immediately; TLS cert provisioning takes 5-15 min. The
worker stays reachable via `<worker-id>.rjj.workers.dev` during
propagation, so deploys never block on this.

To remove a custom domain (rare):
`DELETE /accounts/${ACCOUNT}/workers/domains/{id}` after fetching the
id via `GET /accounts/${ACCOUNT}/workers/domains`.

### 12.5 · The forbidden actions

| Action | Why never |
|---|---|
| Single command that deploys everything | Couples unrelated failure modes; one bad push blocks all |
| Auto-deploy on every commit | Hub rebuilds are 38 min — too coarse for trivial doc changes |
| Deploy without committing first | History becomes opaque; rollback needs `git log` to be the truth |
| Force-push to `main` after a deploy | The deployed sha must always be a real git sha on the protected branch |

### 12.6 · Where the credentials live

- **Cloudflare API token** — `.meshkore/credentials/cloudflare-token.txt`
  (mode 0600). Scope: Workers/Pages/D1/KV write, DNS edit, Zone read.
  Not committed.
- **Fly API token** — managed by `flyctl auth`. Operator runs
  `flyctl auth login` once per machine. Not in repo.
- **Worker secrets** (per-agent API keys for Amadeus, Replicate,
  Gemini, etc.) — bound via `wrangler secret put <NAME>` per worker.
  Never in repo, never in `.meshkore/`.

If a deploy command claims a credential is missing, **stop and ask the
operator** rather than commit a fallback. Credentials are the most
fail-loud surface in the system.

### 12.7 · Status check & dead names

Live surfaces (verify with `curl -sI`):

- `https://meshkore.com/` · `https://www.meshkore.com/`
- `https://architect.meshkore.com/` · `https://m.architect.meshkore.com/`
- `https://hub.meshkore.com/health`
- `https://oracle.agent.meshkore.com/` · `image-gen.agent.meshkore.com` ·
  `shop.agent.meshkore.com` · `food-vision.agent.meshkore.com` ·
  `tester.agent.meshkore.com`

---

## TL;DR for impatient agents

1. Read `.meshkore/docs/governance.md` once.
2. To add a task: write `.meshkore/modules/<module>/tasks/<ID>-<slug>.md`
   with frontmatter, then `python3 .meshkore/scripts/roadmap-build.py`.
3. To log a structured event: `python3 .meshkore/scripts/timeline-append.py
   --type <type> --text "..."`.
4. To log a prose session entry at end of day: append to
   `.meshkore/log/<YYYY-MM-DD>.md` (frontmatter + topic groups, see §5.1).
   Not `_rjj/logs/`, not anywhere else.
5. Never commit `.meshkore/credentials/` or anything outside
   `.meshkore/public/`.
6. Reference task IDs in commits: `feat(...): ... [T091]`.
7. If the Python daemon is running, prefer its API
   (`localhost:5570/chat/send`, `/tasks/<id>/transition`) — the architect
   updates in real time.

That's it. You're operational.
