# MeshKore standard — changelog

The MeshKore standard is the plain-text protocol that describes the
`.meshkore/` folder layout, the cluster bootstrap, the agent docs
contract, and the daemon API surface. As the standard evolves, each
version gets a section here. Every running daemon checks
`https://meshkore.com/standard/version` periodically; when the local
`.meshkore/STANDARD_VERSION` falls behind, the operator (or the LLM
agent acting on their behalf) reads this changelog and applies the
catch-up manually.

The catch-up engine is the LLM. No declarative migration format. No
squash logic. Prose entries describe what changed and how to apply,
the LLM does the edit. This is on purpose — see
[`spec-evolution`](../../../.meshkore/modules/meshkore-spec/tasks/10-spec-evolution.md)
for the rationale.

---

## v38 — 2026-09-28 — A base team, and an Architect that coordinates

### What changed

- **§25.3 — the seed is a base team of five**: `a01` (Architect Agent,
  coordinates), `developer` (implements + tests, does not commit),
  `git-manager` (commits, branches, PRs, releases), `deployer` (ships +
  verifies) and `consultant` (`exposure: external`, read-only). Each card
  is a few lines: a one-sentence mission, where its context lives, how it
  works, whom it reports to.
- **§25.3 — a declared `context:` REPLACES the default pinned set** instead
  of adding to it. The knowledge map is still given in full. Keys resolve
  by node id or by context file stem.
- **§28 — `delegate` is the default verb**; `become` is for one short step.
  The Architect chains developer → git-manager → deployer from reports.
- **§28.3 — reports are agent-to-agent.** Markers are read anywhere on
  their line, the payload to its own closing brace; a bare echo of the
  report is stripped; `status: done` reads as `outcome: success`.
- **§28.5 — commits**: with a `git-manager` on the team, a developer
  reports `commit: null` + `files` and the git-manager commits.
- **§28.7 (new) — what the operator sees**: working teammates nested under
  their parent in the rail and gone when done; wake turns rendered as one
  sentence; a new agent is picked from the roster, Architect first.

### Why

v37 seeded one agent that `become`s every profile. On a real project
(mf27, 2026-09-28) that is one thread carrying the developer's diff, the
commit rules and the deploy runbook at once, and the operator saw the
agents' report JSON in the chat. Operator: *"un arquitecto que va lanzando
los agentes que le convienen… esos agentes cargan su propio prompt… hacen
las tareas y le devuelven el resultado"*.

### How to apply (catch-up from v37)

1. For each of `developer`, `git-manager`, `deployer`, `consultant` missing
   from `.meshkore/team/`, write the card (see §25.3 for the mission and
   `context:` of each). Skip `consultant` if any card already has
   `exposure: external`. Record that you offered them, so a card the
   operator later deletes is not re-added.
2. If `a01`'s body is still the shipped v37 text ("You are this project's
   agent: you hold the whole picture and you do the work"), replace it
   with the coordinator body; otherwise leave it.
3. Mint the consultant's token (§25.6).
4. `printf 38 > .meshkore/STANDARD_VERSION`.

A daemon at py-1.39.0 or later does 1–3 itself on the first resolve.

### What this enables

A fresh project opens as one row — the Architect — that hands each step to
a teammate who loads only what that step needs, with the history of what
happened readable in the Architect's own thread.

---

## v37 — 2026-09-27 — One agent per project

v36 cut the seeded roster from ten members to six. One day of using it
was enough to see that six is the same problem as ten: an operator
opening a fresh project still finds a payroll of strangers and still has
to work out which of them to talk to.

**§25 — a conforming daemon seeds exactly ONE member**: `a01`, titled
"Architect Agent", with an empty `context` (the project's default pinned
set). It is general — it holds the whole picture AND does the work,
because with a roster of one there is nobody to hand anything to.
`orchestrator`, `developer`, `deployer`, `tester` and `consultant` are no
longer seeded. `a01` is a starting point the operator is expected to
rename or replace, not a name anything depends on.

A roster of one is not a team of one. The agent reads the roster as a
catalog and can adopt another profile (`become`) or run one as its own
session (`delegate`). How many agent SESSIONS a project has open at a
given moment is a runtime fact, not a roster fact: zero, one and many are
all normal.

**§25.3 — migration is the daemon's job, not the operator's.** Upgrading
a cluster seeded under v29–v36 MUST, once per project and without anyone
running a script: rename the Architect card to the seeded id preserving
its frontmatter; **archive** (move, not delete) every other card whose id
that daemon itself seeded, leaving ids the operator chose untouched;
rewrite the card body only when it still matches prose the daemon
shipped; and repoint, in the same operation, every conv bound to the old
member id — a conv whose `member` no longer resolves does not fail
loudly, it silently loses its engine settings and its context slice.
Convs bound to an archived member keep their binding.

**§25.3 — an injected system boundary must be conditional on its
counterpart existing.** ORC3 (v35) let a daemon inject "the Architect
plans and does not write product code; execution belongs to the
Orchestrator" over the operator's own card. That was safe only while
every project was seeded with an Orchestrator. On a one-agent roster it
leaves the agent unable to act at all, directly under a card saying the
plan and the build are both its job. A boundary that contradicts the card
is worse than no boundary.

**§28 — the catalog will usually be empty**, and an agent that is the
only member MUST be told so in as many words rather than shown an empty
list to route around. An agent looking for teammates it does not have
invents them.

Housekeeping: `standard.json` still carried the v36-retired
`owns`/`delegates_to`/`never` fields, `canonical_member_count: 10`, a
`tester` note and a singleton-409 enforcement rule. All removed.

**How to apply.** Nothing for an operator to do by hand: upgrade the
daemon and every project on the machine migrates on its next resolve.
Verify with `GET /team` — one member, plus whatever you wrote yourself —
and check `.meshkore/team/.archive/` for the cards that moved.

---

## v36 — 2026-09-26 — The team in its minimal expression: one general agent, six fields, two verbs

**Why.** The operator's verdict on v29–v34 (2026-09-26): *"esta fórmula de
tener un arquitecto, un orquestrador del roadmap, luego un agente que hace
el deploy, un agente que hace el developer de la API… todo esto es muy
idílico pero no funciona porque al final se llena la lista de agentes y
confunden al usuario"*, and *"cuantas menos cosas para definir un agente
mejor"*.

Three things had accumulated:

1. **Two parallel axes of specialisation.** A conv's `agent_type` selected
   one of eight prompt registries, *and* its member card said what it was
   for. A project could have a `deployer` member and a `deploy` agent_type
   with different ideas of what deploying means. The second axis was also
   dead in practice — no UI could set it, so every conv was born `custom`.
2. **Eighteen frontmatter keys** to define "a coder": `kind`, `required`,
   `pinned_order`, `credentials_hint`, `agent_type`, `owns`, `never`,
   `delegates_to`, `refs`…
3. **A ten-member default roster**, filling the list before the operator
   had written anything of their own.

**What changed.**

- **§25.1 — every agent is general.** ONE role. No category of work is
  closed to any agent. What specialises one is exactly two things, both
  operator-visible: the prompt on its card, and the slice of the knowledge
  tree (§3.5) that card names.
- **§25.2 — six authored fields**, four of which the operator types:
  `id`, `title`, `client`, `model`, `context`, `exposure`. `emoji` and
  `color` are DERIVED from the id and must not be stored. **Retired**
  (accepted on input, dropped on write): `name` → `title`, `kind`,
  `required`, `agent_type`, `pinned_order`, `owns`, `never`,
  `delegates_to`, `refs`, `credentials_hint`.
- **§25.2 — `context:` is the specialisation mechanism.** A declared
  branch pins itself and its whole subtree into that member's briefing, in
  ADDITION to the project default (an agent that must deploy but does not
  know what it is deploying is worse off, not more focused). Unknown ids
  are ignored at spawn, not rejected at save: a card is written once and
  the tree keeps changing.
- **§25.3 — the default team is SIX**: `architect`, `orchestrator`,
  `developer`, `deployer`, `consultant`, `tester`. It is a starting point, not
  a structure anything depends on — delete five and the cluster still works,
  because an agent with no teammates does the work itself. `api-developer`,
  `ui-developer`, `ui-reviewer`, `commit-pr-reviewer` and `seo` are no
  longer seeded; anything that specific belongs to the operator.
- **§25.1 — no privileged members.** `kind: singleton` and `required` are
  gone; nothing is undeletable. A daemon MAY inject a system boundary for
  the ids `architect` and `orchestrator` (the Architect plans and does not
  write product code; the Orchestrator executes the queue and authors
  nothing) but MUST work when either is absent.
- **§28.1 — `become`, the verb that was missing.** `⟦become⟧ <member id>`
  adopts a teammate's profile for the next turn in the SAME conversation:
  no new session, no new row in the rail, nothing to wait for. Before
  this, the only way to act on the catalog was to spawn another session,
  which is why v33's section spent most of its length arguing against
  doing that. The daemon must announce the switch in the thread and must
  NOT change the CLI client.
- **§28.2 — the catalog is DERIVED**, from the live roster at spawn: id,
  title, the first sentence of the prompt, the context slice. `owns` /
  `delegates_to` / `never` are retired. A card opening "You own releases
  and deploys" already carries its catalog line, so authoring it twice
  guaranteed drift — and `delegates_to` was a hand-maintained graph that
  went stale every time a member was deleted, a class of bug deriving
  removes by construction.

**Migration.** A conforming daemon MUST migrate cards in place and MUST
NOT require the operator to do it by hand. The body is the operator's
prose and must survive byte-identical. Pre-v36 ids (`architect-master`,
`roadmap-orchestrator`) MUST keep resolving — including in any
remote-control auth gate, where getting it wrong either locks the operator
out of their own master chat or widens the check.

## v35 — 2026-09-20 — A skill declares what it returns (`returns`), and `card_match`

**What changed.** §26 gains a sixth requirement: a skill SHOULD declare the
response fields it promises, in a new `skills[].returns` array on the A2A card.
§27 gains a third state, `card_match`, sitting beside `online` and
`operational`.

**Why.** `operational` (v31) answers "does the route answer". It does not
answer "does it answer what the card promised", and we have been publishing the
first as if it settled the second.

Measured 2026-09-20 on `scribecast` — a genuinely `operational: 2/2` agent, top
result for its vertical:

```
POST https://transcript.agent.meshkore.com/v1/transcribe {"url":"…spNAUEgq_A8"}
→ 200.  video = {"id":…, "title":null, "channel":null, "duration_s":null}
```

Its card promises "the video title, channel and duration". The routes answer;
the promise does not hold. A caller planning against the card plans wrong, and
nothing in the network could tell them — because **a card had no machine-readable
way to say what a response contains.** A skill declared
`{id, name, description, tags, examples}`; the promise lived in English prose
inside `description`. There was nothing to check a response against, so the
check could not exist. This version creates the thing to check against.

**`returns` — the new field.** A flat list of dotted paths the successful
response is expected to carry:

```json
{
  "id": "transcribe",
  "name": "Transcribe a video",
  "returns": ["text", "video.title", "video.channel", "video.duration_s"]
}
```

Flat dotted paths, deliberately, not a JSON Schema: the question being answered
is "did the fields you promised arrive", and a schema invites validating things
we are not going to validate. Nested paths are supported because the fields that
went missing in the motivating case were nested.

**`card_match` — the new state.** `"<n>/<total>"` — of the paths a skill
declares in `returns`, how many were present and non-null in the probe response.
`null` when the skill declares no `returns`, which today is almost every skill.

**SHOULD, not MUST, and no fleet is broken by this.** An agent with no `returns`
is exactly as conforming as it was yesterday: `card_match` is `null`, and `null`
means "not declared", never "failed". `operational` is untouched — v31's
contract holds byte for byte, and a caller reading `operational` sees no change.

**Probing `card_match` costs a real call.** Unlike the `operational` probe,
which POSTs `{}` and is satisfied by any non-404/405/5xx, checking a response
shape needs a response — which means invoking the agent's actual work, on
someone else's compute and possibly their money. So: a probe MAY check
`card_match` only using an input the card itself published in `skills[].examples`,
MUST respect a `probe: false` opt-out on the skill, and MUST NOT probe a skill
whose pricing is not free. An agent that declares `returns` is inviting the
check; one that does not is not.

### Catch-up — applying v34 → v35 to a cluster

Nothing in `.meshkore/` changes. This version only adds an optional field to the
A2A card and an optional state to discovery surfaces.

1. Bump `.meshkore/STANDARD_VERSION` to `35`.
2. **If this cluster publishes agents**: for each agent you own, consider adding
   `returns` to its card's `skills[]`. Declare only what the skill ACTUALLY
   returns today — a `returns` that lists a field the code never sets is worse
   than no `returns`, because it converts silence into a published falsehood.
   Add `"probe": false` to any skill that must not be called by a prober.
3. **If this cluster consumes discovery results**: treat `card_match: null` as
   "not declared", never as a failure. Keep acting on `operational`.

## v34 — 2026-09-16 — A member is one session, not one per errand

v33 gave every agent the roster and a call to hand work to it. It then
created a NEW conv for each hand-off, and the consequence showed up the
moment more than one agent was working: five agents that each needed a
deploy produced five `deployer` sessions, started seconds apart, each
deploying the same working tree, none of them aware that the other four
existed. The agents list filled with per-errand convs nobody could read.

v34 routes by member instead. The first delegation to `deployer` opens
that member's session; every later one — from any agent, at any depth —
goes to the SAME session, busy or not, merged into its next turn. The
member sees the whole batch it is holding and decides that three
frontend briefs are one deploy. That judgement is the entire point: a
daemon that sequenced the batch itself would have replaced the thing it
was trying to buy.

Because a session now answers to several agents at once, `parent_conv`
(one slot) stops being the truth. Each unanswered brief is a row in an
**open-request ledger** on the member's conv, carrying who asked, the
anchor, and the chain the brief came down. One report can close several
rows, and every requester it closed gets its own wake.

**What changed**

- **§28.6 (new) — Member sessions.** Route by member; never mint a conv
  per delegation. Live delivery into the member's next turn. The ledger,
  and who decides the order (the member).
- **§28.3 — Wire protocol.** `202` now answers `{conv, request_id,
  member, reused, open_requests, depth, …}`. The report gains
  `"for":["rq-…"]`, naming the requests it answers; omitted means all of
  them. The wake goes to EACH requester the report closed and names the
  ids.
- **§28.4 — Invariants.** Recast on the request's own lineage: cycle is
  "a session already in your chain", depth is the chain length, and
  fan-out counts distinct member SESSIONS rather than briefs — two
  deploys handed to one deployer cost one unit, not two. Refusal codes
  are now `409 delegation-cycle`, `409 delegation-max-depth`,
  `429 delegation-fan-out`.

**Catch-up (5 steps)**

1. Pin `.meshkore/STANDARD_VERSION` to `34`.
2. Upgrade the daemon to `py-1.35.19` or later (`/self-update`). Nothing
   below works without it — this is a daemon-side contract.
3. Nothing to change on member cards: `owns` / `delegates_to` / `never`
   (v33) are unchanged.
4. If your cluster wrote tooling against `dg-<member>-<hex>` conv names
   or against `parent_conv` on a delegated conv, move it to
   `open_requests` in `/chat/snapshot`. Convs created before v34 keep
   the old shape and the old single-parent wake.
5. Keep `architect.wave_cap` at 1 for single-repo clusters. Member
   sessions remove the same-role race; two DIFFERENT members editing one
   repo is still unguarded until leases land.

---

## v33 — 2026-09-14 — Delegation between team members

**What changed.** A team member is now a role that other members can hand
work to, with the daemon — not the prompt — guaranteeing the shape of the
resulting agent tree. New **§28** defines when to delegate (three triggers
and nothing else), the `POST /chat/delegate` call, the `⟦report⟧` a child
owes its parent, the `[delegate-wake]` that resumes the parent, and four
invariants a conforming daemon MUST enforce (cycle, depth, fan-out,
re-entry). Amendments: **§25.2** gains the optional `owns` /
`delegates_to` / `never` fields; **§25.3** gains a tenth canonical member,
`tester`; **§24** states that delegated turns INHERIT their parent's
anchor instead of running the decision chain; **§9.1** makes `Agent:` the
member id for member-bound convs and adds the `Parent:` trailer for
delegated work; **§6.1** says the root of a unit of work writes ONE diary
entry covering its children too.

**Why.** Before this, a member card carried a mission in prose and an
engine, and nothing else — so an agent had no machine-readable way to know
who else existed or what they were for, and every attempt to hand work off
was improvised. The three fields are one line each because they are
injected into every other member's system prompt on every spawn. The
documentation duties exist because a tree of agents was otherwise leaving N
interleaved diary fragments, N near-duplicate initiatives, and commits with
no record of which agent tree produced them.

### Apply manually to a v32 cluster

1. **Member cards** (`.meshkore/team/*.md`). Add to each member's
   frontmatter, before `refs:`:
   - `owns:` — ONE line, ≤ 240 chars, phrased as "delegate to me when…".
     This is what every OTHER member sees; a member without it will not be
     delegated to.
   - `delegates_to:` — list of member ids it normally hands work to.
   - `never:` — ONE line: its hand-off boundary.
   Do NOT rewrite the prose body; it is the operator's. A daemon at
   py-1.35.14+ backfills the canonical members automatically on first
   touch of the project and leaves unknown members alone.
2. **Add the `tester` member** if your roster has none: `agent_type:
   testing`, and an init prompt whose first rule is *follow
   `.meshkore/workflows/W10-verify-<project>.md`; if it does not exist,
   report `outcome: no-recipe` and run nothing*. Write that W10 for your
   project — target URL, credential handle, browser script, API probes, DB
   assertions.
3. **`cluster.yaml`** — optionally set `delegation.max_depth` (default 3).
   Keep `architect.wave_cap` at 1 for a single-repo cluster until a lease
   manager exists: two agents writing one working tree is a merge hazard,
   not a feature.
4. **Commits** — start emitting `Parent: <parent member id>@<parent conv>`
   on delegated work, and make `Agent:` the member id when the conv is
   bound to one.
5. **Diary** — stop writing one `.meshkore/log/<date>.md` entry per agent.
   The root of the unit of work writes one entry for the whole unit,
   summarised from the children's reports; a release or deploy still writes
   its own.
6. Bump `.meshkore/STANDARD_VERSION` to `33`.

**Daemon support**: py-1.35.14+ (`POST /chat/delegate`, the report marker,
the general wake, the four invariants, the member-card backfill).

## v32 — legacy hub retired (2026-09-05)

- The Fly-hosted hub (`hub.meshkore.com`) and its seed population are
  **destroyed**. The hostname now serves permanent redirects into the
  unified API (`GET /agents/<id>` → `/v1/agents/<id>`).
- §3 `cluster.yaml` transport/bootstrap examples now point at the unified
  API: `wss://api.meshkore.com/v1/agents/ws` (WS), `https://api.meshkore.com`
  (hub/fallback). Agent docs live at `https://meshkore.com/reference/agents/`.
- §17 preamble: live state is queried at
  `GET https://api.meshkore.com/v1/agents/<id>` (was the hub URL).
- No schema changes; clusters only need the URL refresh in their
  `cluster.yaml` and re-rendered per-CLI instruction files.

## v31 — 2026-08-27 — Skill invocation is binding (§26) · `online` ≠ `operational` (§27)

**What changed.** Two new sections. Neither invents anything; both make an
existing rule enforceable and stop a claim we could not back.

**§26 — `POST /v1/<skill-id>`.** An agent that advertises a skill in its A2A
card MUST serve it at `POST <card.url>/v1/<skill-id>`. This has been published
in `/reference/agents/protocol-minimum` §3 for months and was honoured by
exactly the two agents anyone demos. Measured 2026-08-27: five MeshKore agents
advertised skill ids that mapped to no served path (`search-hotels` served at
`/v1/search`, `analyze-food` at `/v1/analyze`, …), one advertised a hostname
with **no DNS record at all**, and two *callers* were also wrong — the hosted
MCP server POSTed `/skills/<id>` and the public agent page told ~102,000
visitors to POST `<url>/<skill>`. A card you cannot construct a call from is
not a card. The rule now lives in the standard, with an explicit "fix the
agent, never special-case the caller" clause and a requirement that a failed
call name the URL it tried.

**§27 — `online` vs `operational`.** `online` means a heartbeat arrived.
`operational` means the card resolves and every advertised skill answers.
Those are different claims and the mesh was publishing the weak one where
callers act on the strong one — 48 of 54 nodes reported `online` while five of
our own product agents were not callable. `operational` is probe-derived,
timestamped, degrades rather than deletes, and is `null` (not `false`) when
never probed. Simulated partner agents pass the same probe as real ones.

**Two new hard rules** were appended for both.

### Apply manually (v30 → v31)

For a cluster: nothing to migrate. No folder, frontmatter or daemon-API
change. Bump `.meshkore/STANDARD_VERSION` to `31`.

For anyone operating an **agent** on the mesh:

1. Fetch your own card (`https://meshkore.com/agent/<id>/.well-known/agent.json`).
2. For every entry in `skills[]`, `curl -X POST <card.url>/v1/<skill-id> -d '{}'`.
   A 404 or 405 means you are non-conforming.
3. Rename the served path to match the advertised skill id, and keep the old
   path as an unadvertised legacy alias so callers in flight do not break.
4. Confirm `card.url` resolves in DNS.

For anyone writing a **caller**: build the target as `card.url` + `/v1/` +
the advertised skill id. Do not carry per-agent path exceptions — that pushes
someone else's drift into the network's own client, where it is invisible.

---

## v30 — 2026-07-05 — Team external gateway (§25 amendment: exposure + consultant)

**What changed.** §25 "Agent team" is amended with **§25.6 "External
exposure"** and a **ninth default member**.

- **`exposure` field.** Member frontmatter gains
  `exposure: internal | external` (default `internal`; absent =
  internal). Orthogonal to `kind`; committed in the member file (it's
  config, not a secret); the enum leaves room for a future `mesh`
  value.
- **Per-member bearer token.** Minted when exposure flips to
  `external` (or at seed time for `consultant`). **Non-expiring** —
  no TTL, no rotation schedule. Stored ONLY in
  `.meshkore/credentials/team-tokens.yaml`, NEVER in the committed
  member file. Operator lifecycle from the Team UI: **copy** /
  **rotate** (regenerate, old token dies instantly) / **revoke**
  (one action: PATCH the member back to `exposure: internal`, token
  destroyed instantly — outstanding asks 403 from that moment).
  Scope-limited: only its member's ask/poll surface.
- **Ask/poll HTTP surface** (async — turns take 30 s – minutes):
  `POST /team/<id>/ask` `{text, session?}` +
  `Authorization: Bearer <member-token>` + `X-MeshKore-Project` →
  `202 {request_id, conv}`; `GET /team/requests/<rid>` →
  `{status, result_text, conv}`. Errors: 401 unknown token · 403
  wrong member or `internal` exposure · 404 unknown member/request ·
  429 at capacity. `session` → same conv → thread continuity.
  External asks are ordinary member instances (visible in the chat
  rail, init prompt on turn 1, member's model/effort).
- **A2A card.** `GET /team/<id>/.well-known/agent.json` served for
  every exposed member.
- **Perimeter.** v1 = loopback binding: nothing off-machine can reach
  the surface; the token only discriminates local callers.
  Cross-machine = future mesh exposure via identity (did:key + hub),
  out of scope for v30 — never port-forward the daemon.
- **`consultant` — ninth default member.** `kind: profile`,
  `agent_type: custom`, `model` = strongest alias, `exposure:
  external` with a token pregenerated at scaffold/first-boot. Mission:
  standing info point for external agents (social bots, collaborators,
  integrators) — cited facts from the project's docs/context/source;
  never invents; the caller owns voice and publishing.
- **Hard rule** added: tokens never in committed files; never expose
  the ask/poll surface off-machine by port-forwarding.

**Why.** The v29 team made members first-class but only the operator
(via the cockpit) could talk to them. Other software on the same
machine — another cluster, a bare Claude Code session, a content
pipeline — needs to consume a member as a service, with the source of
truth living INSIDE the cluster next to its docs and source. The
centralized daemon (§10) already serves every project on the machine
over loopback, so project-to-project collaboration is one local HTTP
call; loopback IS the v1 security perimeter.

**How to apply (catch-up from v29).**
1. `CHANGELOG.md` — this entry on top.
2. `standard.json` — `"version":"30.0.0"`, `"updated":"2026-07-05"`;
   inside `agent_team`: add the `exposure` field to
   `member_schema.fields`, append `consultant` to `default_team`
   (nine members), update `seeding` to nine, and add the
   `external_exposure` key (see the shipped file for the full shape);
   append the token/perimeter hard rule to `"hard_rules"`.
3. `standard.md` — first-line `**Version:** 30.0.0` + date; the §11
   `Standard version: **30.0.0**` line; in §25: add `exposure` to the
   25.2 schema, add the `consultant` row to the 25.3 table (retitle
   "9 members", seed "all nine"), and add `### 25.6 External exposure
   (v30)`.
4. `version` → `30` (no trailing newline).
5. `api/edge/src/index.ts` → `const STANDARD_VERSION = "30"`.
6. `.meshkore/STANDARD_VERSION` → `30`.
7. Daemon: mint tokens into `.meshkore/credentials/team-tokens.yaml`
   on exposure flips; seed `consultant` (with token) into new
   projects; backfill existing projects once.
8. Convention doc:
   `.meshkore/docs/conventions/team-external-requests.md`.

**What this enables.** Any local software consumes a team member as a
service with three lines of config (endpoint, project header, member
id + token). The default `consultant` gives every project a standing,
citable info point for social bots, collaborators and integrators from
birth — the caller owns voice, the member owns facts.

---

## v29 — 2026-07-03 — Agent team (default roster in the standard)

**What changed.** New **§25 "Agent team"**. Every cluster now has a
persistent, typed **team** — a roster of *members* from which working
agent *sessions* (instances) are spawned. The standard defines a
canonical **8-member default team** and the daemon MUST seed it when
`.meshkore/team/` is absent.

- **Model.** A *member* is a definition file `.meshkore/team/<id>.md`
  (frontmatter + init-prompt body) with a mandatory `model`. An
  *instance* is a chat conversation bound to a member
  (`conv_meta.member`). Two members are required **singletons**
  (`architect-master`, `roadmap-orchestrator`) — cannot be deleted,
  at most one live instance each. The rest are **profiles** with
  unlimited instances.
- **Models & effort.** Every member defaults `model` to the strongest
  available alias (opus-tier, so it tracks upgrades); `effort`
  (low/medium/high/max) is the per-member tuning knob.
- **Schema.** §25.2 gives the normative frontmatter
  (id/name/emoji/color/kind/required/agent_type/model/effort/
  pinned_order/refs/credentials_hint/created/updated) + markdown init
  prompt body. Machine schema mirrored in `standard.json#agent_team`.
- **Default team.** §25.3 table: architect-master, roadmap-orchestrator,
  developer (the chat-rail `+` default), api-developer, ui-developer,
  deployer, ui-reviewer, commit-pr-reviewer.
- **Git contract.** `.meshkore/team/` is committed (v27 deny-list
  default); explicitly NOT added to the §2.2 gitignore deny-list.
- **HTTP/WS.** `/team` CRUD + `POST /team/draft` + `team.*` WS events;
  DELETE refuses `required: true`; a second singleton instance → 409.

**Why.** The cockpit conflated three things called "agent" (the
`AGENT_PROMPTS` role registry, per-conv runtime, and the declared
external-agents yaml store). None was "my team": a persistent roster
of typed members each with a purpose, an init prompt, and a model.
Putting the default roster in the standard means every cluster seeds
the same team.

**How to apply (catch-up from v28).**
1. `CHANGELOG.md` — this entry on top.
2. `standard.json` — `"version":"29.0.0"`, `"updated":"2026-07-03"`;
   add the top-level `"agent_team"` key (see the shipped file for the
   full shape); append the team hard rule to `"hard_rules"`.
3. `standard.md` — first-line `**Version:** 29.0.0` + date; the §11
   `Standard version: **29.0.0**` line; add `## 25. Agent team (v29)`
   BEFORE `## 21. Where everything else points` (which stays trailing).
4. `version` → `29` (no trailing newline).
5. `api/edge/src/index.ts` → `const STANDARD_VERSION = "29"`.
6. `.meshkore/STANDARD_VERSION` → `29`.
7. Seed `.meshkore/team/<id>.md` for the 8 default members if the
   folder is absent; keep it committed (never gitignore it).

**What this enables.** A stable, discoverable roster per cluster; the
Team tab as roster-management UI; the chat-rail `+` spawning a
`developer` instance with no modal; and cross-cluster tooling reading
rosters via `GET /team`.

---

## v28 — 2026-06-30 — Centralized daemon model (one shared daemon, not per-project)

**What changed.** §10 (the daemon section) is rewritten to describe the
**centralized** daemon. ONE shared daemon process per machine serves **every**
project from a single base URL, routed by the `X-MeshKore-Project: <cluster-id>`
header. Member projects carry **no** daemon code (`.meshkore/scripts/daemon.py`
is gone), bind **no** port, and run nothing. Specifics:

- §10.1 retitled "Centralized daemon — one process, many projects." Replaces the
  old per-folder/first-to-bind-wins text. A bare `/health` or `/state` (no
  header) answering for a *different* `cluster_id` is now documented as EXPECTED.
- §10.2 disambiguates "canonical L3 implementation" = the reference
  implementation of the **shared service**, installed **once per machine** at
  the `meshkore-server/` root — not a file each repo embeds. Install snippet and
  endpoint table updated; added the machine-level `/projects` routes and the
  `X-MeshKore-Project` routing note.
- §10.3 ("Adding a project") = register the repo path with the shared daemon
  (`POST /projects {path}`); the daemon scaffolds + serves it. The §10.3.1 and
  §10.3.4 canonical prompts no longer download/run a daemon and now explicitly
  forbid it. §10.3.5 rewritten ("A project shows offline") to the two real
  cases: not-registered (re-add) vs. shared daemon not running (start the one
  machine daemon).
- §1 gains an up-front note: "L3 is NOT a per-repo install."
- Two new §9 hard-rules: (a) never create/run a per-project daemon or bind
  `5570–5589`; (b) never hand-write the `MESHKORE_PREAMBLE` block (the daemon
  renders it — §17). `standard.json` adds a `daemon_topology` key and rewrites
  `python_daemon` / `adding_projects` / (removed) `multi_project`.
- Fixed two pre-existing drifts caught in review: §9 no longer says commits add
  `Co-Authored-By:` (removed v21), and §11 now reads `28.0.0` (was stale at
  `6.0.0`).

**Why.** The per-folder daemon model was retired 2026-06-25 (initiative
`daemon-centralized`), one day **after** v27 shipped, so §10 never caught up. A
team adopting v27 told an agent to "apply the standard"; the spec literally
instructed it to `curl daemon.py` into `.meshkore/scripts/` and run it, so the
agent stood up a fake per-project daemon that bound 5570 and collided with the
real shared daemon. The daemon's own briefings were already centralized; only
the human/spec docs had drifted.

**How to apply (catch-up from v27).**
1. If your repo (a member project) contains `.meshkore/scripts/daemon.py`, any
   `daemon.py`, or `make`/`Makefile` daemon targets that an agent created, delete
   them — the shared daemon serves you. Never bind `5570–5589` from a project.
2. Talk to the daemon at its single base URL with **both** `Authorization:
   Bearer <token>` and `X-MeshKore-Project: <your cluster id>` on project-scoped
   requests. A bare `/health` reporting another `cluster_id` is fine.
3. Add/restore a project via the Architect "+ Add project" → `POST /projects
   {path}`, not by launching a daemon.
4. Do not hand-edit the `MESHKORE_PREAMBLE` block of `AGENT_INSTRUCTIONS.md` /
   `CLAUDE.md` — only `OPERATOR_CONTENT`.
5. Set `.meshkore/STANDARD_VERSION` to `28`.

**What this enables.** Removes the single biggest first-run failure for adopting
agents (standing up a colliding daemon) and aligns the spec with the shipped
`daemon-centralized` runtime, so global features (Ideas, mesh, cross-project)
have one coherent control plane.

---

## v27 — 2026-06-24 — Git contract: commit the project's brain (docs / modules / roadmap)

**What changed.** The `.gitignore` contract (§2.2) flips from an
**allowlist** (`.meshkore/* + !public/` — only `public/` reached git) to a
**deny-list**. `public/`, `docs/`, `modules/` (READMEs, tasks, archived
tasks, diagrams), `roadmap/initiatives/`, and `STANDARD_VERSION` are now
**committed**; only runtime/secret/per-machine/generated state is ignored:
`.meshkore/{.runtime,credentials,agents,timeline,log,queues,uploads,snapshots,scripts}/`,
`state.json`, and `roadmap/state.{json,js}`. `folder_layout` `committed`
flags updated to match (docs/ + modules/ + roadmap/initiatives/ → true;
`roadmap/state.*` stay false as generated artifacts). The §9 hard-rule
"Never commit anything under .meshkore/ outside public/" is replaced by
"Never commit runtime/secret/per-machine state (…)". §8 editor-boot block
and §10 validation updated.

**Why.** The roadmap, task history, and docs ARE the project's durable
record — a fresh clone with only `public/` lost the plan and everything the
agents had done. Committing them lets the project's brain travel with the
repo and survive across machines. This codifies what operators were already
doing in practice (the V107.13 onboarding deny-list); the pre-v27 standard
had drifted behind that practice.

**Catch-up (LLM, on behalf of the operator).** In each cluster: rewrite the
repo-root `.gitignore` `.meshkore/` block from the two-line allowlist to the
deny-list above (or re-run `python3 .meshkore/scripts/daemon.py init` in a
fresh folder to see the canonical block), then `git add .meshkore/docs
.meshkore/modules .meshkore/roadmap/initiatives .meshkore/STANDARD_VERSION`
and commit. No file moves, no schema changes. Daemon py-1.27.0 `init`
already writes the v27 contract; py-1.27.1 ignores `scripts/` wholesale.

## v26 — 2026-06-19 — Task resolution record (durable per-task history)

**What changed.** `task_frontmatter` gains four optional fields —
`completed_at`, `resolved_by`, `resolved_by_conv`, `commit_shas` — and a
reserved body section `## Resolution`. Together they make a task durably
hold not just its description but its **resolution**: who closed it, when,
in which conversation, with what summary. The roadmap is now a single
hierarchical store where each task carries its own outcome, readable by the
cockpit AND any CLI consumer without inspecting live chat state. See §4.4.

**How to apply.** No migration needed — all fields are optional and absent
on existing tasks. The daemon (py-1.23+) writes them automatically when a
turn finishes a task: on `chat.assistant.final` with the task already
`done`, it stamps the frontmatter pointers and writes the turn's
Output-Contract summary into `## Resolution`. A CLI agent that sets
`status: done` by hand SHOULD also set `completed_at` and write
`## Resolution`, so the record is uniform regardless of who closed the task.

**What this enables.** Per-task summaries that survive cockpit reloads; a
real execution history ("ejecutado · fecha · tokens" → expandable summary);
task↔commit and task↔conversation links; and richer roadmap visualisations
driven entirely by the file store.

---

## v25 — 2026-06-14 — Context: docs boundary, serialization hard-rule, wikilinks

Closes three gaps around `.meshkore/context/` surfaced when a cluster rebuilt
a parallel `docs/` tree that duplicated `context/` (the human standard page
never explained the boundary, so it was reinvented from scratch).

### What changed

- **`context/` vs `docs/` boundary is now explicit** (§3.5 "where things live"
  table + `context.relationship_to_docs`). `context/` = single source of truth
  for INVARIANTS (the WHAT, loaded into every spawn); `docs/<category>/` =
  categorized reference for the HOW (detail, on demand) that LINKS to context/
  and never copies an invariant. There is NO `docs/stack/` category — high-level
  stack is `context/stack.md`; deploy/ops detail is `docs/deploy/` + `docs/ops/`.
- **Serialization is now a `hard_rule`.** The daemon MUST serialize
  `.meshkore/context/` into every briefing (the `=== PROJECT CONTEXT === … ===
  END CONTEXT ===` block, before the role) per `context.serialization_to_agent`.
  Previously prose-only; a daemon injecting only the legacy single-file
  `docs/context.md` is now explicitly non-conformant.
- **No-duplication is now a `hard_rule`.** Never restate a context/ invariant in
  docs/. One fact, one home.
- **Wikilink cross-references standardized** (new `cross_references` key):
  `[[<path-relative-to-.meshkore>]]`, no extension, for see-also links between
  `.meshkore/` files. Daemon MAY lint broken links (non-blocking, advisory).
- `glossary.md` stays OPTIONAL + recommended (unchanged).

No frontmatter/schema break — purely additive rules + clarifications.

### How to apply (catch-up from v24)

1. Update `.meshkore/STANDARD_VERSION` from `24` to `25`.
2. Ensure your daemon serializes `context/` into briefings (the §3.5 block,
   before the role). If it still injects only the legacy `docs/context.md`,
   upgrade the daemon (the implementation that satisfies the new hard_rule).
3. If you built a parallel `docs/{product,stack,architecture}/` tree that
   restates `context/`, collapse it: keep invariants in `context/`, leave
   `docs/` as categorized reference that wikilinks to `context/`.
4. Adopt `[[wikilink]]` cross-references for see-also links between cluster
   files (optional but recommended).

---

## v24 — 2026-06-13 — Preamble propagation of cross-cutting rules

The §17 agent-CLI preamble (`agent-instructions.md`, rendered into
`CLAUDE.md` / `AGENTS.md` / `GEMINI.md`) now carries two load-bearing
rules it was missing, so a **standalone** agent session — one the
operator opens directly in the repo, with no daemon dispatching it —
boots with the same baseline knowledge the daemon already injects into
its own sub-agents.

### What changed

- **Preamble gains "Initiative-anchored execution" (§24).** A new
  numbered convention in the preamble: every turn anchors to an
  `(initiative, task)` pair; an unanchored turn must locate or create
  the matching initiative + task as its FIRST action. This was already
  a `hard_rule` since v23 and is embedded in the daemon's
  `_section_core_rules()`, but it never reached the static preamble —
  so a CLI session reading only `CLAUDE.md` didn't know about it.
- **Preamble "Where to dig deeper" now names `context/` and
  `protocols/`.** Explicit pointers to `.meshkore/context/` (§3.5 —
  standing invariant knowledge, including `criteria/`, the base
  acceptance criteria) and `.meshkore/protocols/` (§14 — the cluster's
  P-numbered runbooks), plus `.meshkore/docs/` and its `INDEX.md`.
- **`standard.json#agent_instructions.preamble_must_include`** gains
  two entries (items 7–8) so the requirement is machine-checkable.
- No file-format break. No new folder, no daemon endpoint, no schema
  change to any frontmatter.

### How to apply (catch-up from v23)

1. Update `.meshkore/STANDARD_VERSION` from `23` to `24`.
2. Re-sync the preamble: fetch
   `https://meshkore.com/standard/agent-instructions.md` and overwrite
   the block between `<!-- MESHKORE_PREAMBLE_BEGIN -->` and
   `<!-- MESHKORE_PREAMBLE_END -->` in
   `.meshkore/public/AGENT_INSTRUCTIONS.md`. Leave the
   `OPERATOR_CONTENT` block untouched.
3. Re-render the per-CLI targets from `AGENT_INSTRUCTIONS.md`: each of
   `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` is the auto-render header
   comment (line 1, naming the CLI) + a blank line + the verbatim
   `AGENT_INSTRUCTIONS.md` body. (If your daemon build ships the §17
   render loop it does this on the version bump; if not, do it by hand
   — see the verification block in §17.6.)
4. No daemon restart required for the content itself. The
   initiative-anchoring BEHAVIOUR was already live as of py-1.12.23
   (v23); v24 only makes the static preamble state it.

### What this enables

A fresh Claude Code / Codex / Gemini CLI session opened directly in a
MeshKore repo — with no daemon driving it — now reads, from the file
its CLI auto-loads, that it must anchor to an initiative+task and
where the project's context, criteria, and runbooks live. Closes the
gap where that knowledge reached only daemon-dispatched sub-agents.

---

## v23 — 2026-06-10 — Initiative-anchored execution

Every agent turn anchors to `(initiative, task)`. The cockpit's
roadmap timeline becomes 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.

**Why now**: operator field report 2026-06-10 — "Lo ideal es
siempre estar ejecutando una tarea de una iniciativa. Cuando abrimos
un agente y le damos instrucciones, debería localizar la iniciativa
correspondiente si no se la damos, y dentro, coger la tarea de la
que estamos hablándole. Y si no hay iniciativa, crearla, con sus
tareas al planificar."

**Spec** (full details in `standard.md` §24):

- **Decision chain (3 cases)**:
  1. ANCHOR-PRESENT → acknowledge and continue.
  2. INITIATIVE-PRESENT, TASK-MISSING → pick or create a task.
  3. NEITHER-SET → match operator intent against existing
     initiatives; if no match, CREATE initiative + 1-3 tasks,
     anchor to the FIRST task.
- **Creation contract**: initiative file at
  `.meshkore/roadmap/initiatives/<slug>.md`, tasks at
  `.meshkore/modules/<module>/tasks/<id>-<slug>.md`. Slug shapes
  align with Standard §4 + §22.
- **Concurrency**: parallel agents create parallel initiatives;
  the cockpit roadmap timeline shows them all live.
- **Cockpit alignment**: the chat scope strip already shows the
  anchor. Agents that create/swap the anchor mid-conv push to the
  conv meta so the strip follows.

**Hard rule** added to `standard.json#hard_rules`:

> Every agent turn must anchor to (initiative_id, task_id) before
> performing code work. Agents whose dispatch arrived without an
> anchor MUST locate or create the matching initiative + task as
> their first action, then continue.

**Daemon**: py-1.12.23 embeds the rule in `_section_core_rules()`
(the universal-rules block already mounted in every agent's
briefing since py-1.7.0). New convention at
`.meshkore/docs/conventions/initiative-anchored-execution.md`.

**Apply manually to v22 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `22` to `23`.
2. Bump the local daemon to `py-1.12.23` (or later).
3. The cockpit needs no client-side change — the chat scope strip
   already renders the anchor; v23 changes what agents DO with it,
   not what the cockpit DISPLAYS.

No file-format break. Existing initiatives + tasks stay verbatim;
the rule is about how agents BEHAVE when they receive a turn.

---

## v22 — 2026-06-10 — Storage reporting endpoint (`GET /storage/usage`)

The daemon now exposes a per-bucket disk-usage breakdown of
`.meshkore/` so the cockpit can render a Storage panel and operators
can tune retention / trigger purges in informed ways.

**Why now**: with v19 file snapshots + v22 chat attachments now landing
on disk, `.meshkore/` grows in places the operator doesn't see at the
moment of writing. The old "find . -name X | du" loop was the only way
to spot which bucket was eating bytes. The cockpit needs a first-class
surface for capacity tuning.

**Spec** (full details in `standard.md` §23):

- `GET /storage/usage` — no auth, returns bytes + file counts per
  well-known bucket: `log`, `snapshots`, `uploads`, `queues`,
  `timeline`, `agents`, `modules`, `docs`, `roadmap`, `.runtime`,
  `credentials`.
- Each entry: `{name, bytes, files, exists, retention_days?}`. The
  retention field appears only when the daemon enforces a sweep for
  that bucket (today: `snapshots`=7, `uploads`=30).
- Top-level: `total_bytes`, `total_files`, `generated_at`,
  `cache_ttl_secs`.
- Server-side cache: 5 s TTL — polling at 1 Hz is safe; recomputing
  rglobs every tick is not.

**Daemon**: py-1.12.22 ships `StorageReport` (cached walker) + the
HTTP route. Plumbed into `Daemon.__init__`; the cockpit's Storage
panel hits the endpoint on Config zone visit.

**Apply manually to v21 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `21` to `22`.
2. Bump the local daemon to `py-1.12.22` (or later).
3. The cockpit's Storage panel will appear automatically — no
   per-cluster config change.

No file-format break. Frontmatter, snapshots, queues, uploads
unchanged; v22 is additive — the endpoint is new, nothing existing
moves.

---

## v21 — 2026-06-09 — Commit trailers: add `MeshKore:`, drop `Co-Authored-By:`

§9.1 revised. The mandatory trailer set on every LLM-authored commit
in a MeshKore repo is now exactly three:

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

`Co-Authored-By:` is REMOVED from the mandatory list — it carried no
information the other trailers + git's own author field don't already
record, and the operator's cross-repo convention is no-co-authoring.
MeshKore was the only repo using it; that's now corrected.

The new `MeshKore:` trailer quotes the daemon's `DAEMON_VERSION`
literal (`py-X.Y.Z`) at the moment the agent authored the commit.
The daemon embeds it in every subagent briefing, so the value is
always known. It lets `git log` filter the cohort of commits
produced under a given runtime — useful for tracing regressions to a
specific daemon release and for measuring how a new daemon feature
ripples through real work.

**Why now**: operator pointed out 2026-06-09 ("estás dejando una
información de co-authoring en cada commit"). The cross-repo
convention is no-co-authoring; MeshKore is the explicit exception
because agent-driven work is the norm, not the edge case. But the
exception should add semantic trailers, not the legacy display-name
boilerplate. While editing §9.1 we also added the daemon-version
trailer the operator asked for: "podrías complementar obviamente
esa info con la versión de MeshKore que estamos usando en ese momento".

**Spec** (full details in `standard.md` §9.1):

- Three required trailers, in order: `Agent:`, `Model:`, `MeshKore:`.
- Two optional trailers, only on standard bumps: `Standard: vN-1 → vN`
  + `Section: <top-level json key>`.
- `Co-Authored-By:` — DO NOT add. Removed from §9.1.

**Hard rule** updated in `standard.json#hard_rules`:

> Every commit produced by an LLM agent must carry `Agent:`, `Model:`,
> and `MeshKore:` git trailers identifying the agent role, the model
> id, and the cluster's daemon version. Do NOT add `Co-Authored-By:`
> — git author already records the committer.

**Daemon**: py-1.12.15 — the universal-rules block in
`_section_core_rules` now reproduces the new three-trailer template,
and the canonical preamble (`webapp/standard/agent-instructions.md`)
points at §9.1 with the updated example. Every subagent spawned from
py-1.12.15 onward stamps the new format without operator action.

**Apply manually to v20 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `20` to `21`.
2. Bump the local daemon to `py-1.12.15` (or later) — fetch the
   latest `daemon.py` from this repo or use the cockpit's
   daemon-update flow.
3. Going forward, new commits stop adding `Co-Authored-By:` and
   start adding `MeshKore: py-<your-daemon-version>`. Past commits
   are NOT rewritten (history is history); the new format applies
   forward only.
4. If you have a custom CLAUDE.md / AGENTS.md / .cursorrules with
   the §9.1 example hand-copied in, update the example to match
   §9.1 (or just delete the local copy and let the daemon's
   MESHKORE_PREAMBLE block be the source of truth).

No file-format break. Frontmatter, snapshots, chat-id refs (v20),
all untouched.

---

## v20 — 2026-06-09 — Chat references by `#<id>`

When an agent's chat output adds, removes, renames, defers, splits, or
otherwise touches an initiative or task, every mention of that item in
the same line MUST include `#<id>` (the item's frontmatter `id:` with
a `#` prefix). Bare titles ("the voting initiative") are no longer
acceptable for load-bearing mentions — the cockpit's chat renderer
turns `#<id>` into a click-target that scrolls the roadmap timeline to
the matching story.

**Why now**: the V108 cockpit timeline ships a `#<id>` chip at the head
of each initiative's title row. Without a matching chat convention,
agents would still narrate roadmap mutations in prose ("split the
voting initiative into two") and the operator would have to grep file
names to verify. With the convention in place, chat ↔ roadmap UI share
the same vocabulary and the operator pattern-matches by sight.

**Spec** (full details in `standard.md` §22):

- Id shape: `^[A-Za-z][A-Za-z0-9_-]{1,31}$`. Examples in production:
  `I18`, `I21`, `PAYMENTS`, `T-vote-API`, `WORK-21`.
- The `#` prefix is non-negotiable — the chat renderer regex looks for
  `#` + an id token.
- Exclusions: bare prose is fine when (a) planning aloud, (b)
  introducing a brand-new id in the same line, (c) writing narrative
  paragraphs where mentions aren't load-bearing.
- Canonical chat lines:
  - `✓ 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.`

**Hard rule** added to `standard.json#hard_rules`:

> Every chat mention of an initiative or task an agent is creating,
> modifying, deferring, splitting, or removing must include `#<id>`
> (the item's frontmatter id with a `#` prefix). Bare titles are not
> addressable in the cockpit UI.

**Daemon**: py-1.12.14 embeds the rule in the universal-rules block of
every agent's briefing (`daemon._section_core_rules`). Every spawn
from this version forward sees it — no client-side opt-in needed.

**Apply manually to v19 clusters**:

1. Update `.meshkore/STANDARD_VERSION` from `19` to `20`.
2. Bump the local daemon to `py-1.12.14` (or later) — fetch the latest
   `daemon.py` from this repo or run the cockpit's `/daemon/update`.
3. (Optional) Add `.meshkore/docs/conventions/chat-id-references.md`
   if you want a local copy — the rule lives in the daemon prompt
   regardless.
4. The cockpit V108 timeline already renders the `#<id>` chip; no
   client-side change needed.

No file-format break. Existing initiatives and tasks keep their ids
exactly as-is; the rule is about how agents REFER to them in chat.

**Convention**:
[`chat-id-references.md`](../../../.meshkore/docs/conventions/chat-id-references.md)

---

## v19 — 2026-06-09 — File snapshots (`.meshkore/snapshots/<YYYY-MM-DD>/<bucket>/`)

Pre-modification copies of files an agent is about to touch. Lets the
operator inspect any intermediate state between two commits without
needing 20 micro-commits. **Not** a git replacement — git owns merged
history; snapshots own the moments in between.

**Why now**: operators were either committing 20× per chat turn to keep
intermediate states reachable, or accepting that "anything between two
commits is lost". Both are wrong. A small, daemon-managed, retention-
bounded snapshot tree solves it.

**Spec** (full details in `standard.md` §20):

- Storage: `.meshkore/snapshots/<YYYY-MM-DD>/<bucket-id>/{_manifest.json, files/<src-path>}` — gitignored, lazy, retention-bounded.
- Bucket id: `<YYYYMMDD>-<HHMMSSmmm>-<idHint>-<descSlug>-<4hex>`; lexicographic = chronological.
- Manifest: `{ id, date, created_at, conv, agent_id, agent_type, initiative_id, task_id, msg_id, note, files: [{path, size, sha256, skipped?}], warnings }`.
- Limits: 50 files per bucket, 5 MB per file.

**HTTP routes** (daemon py-1.12.13+):

| Verb | Path |
|---|---|
| POST | `/snapshots` (create) |
| GET | `/snapshots?limit=N` (list newest-first) |
| GET | `/snapshots/<bucket>` (manifest) |
| GET | `/snapshots/<bucket>/files/<repo-relative-path>` (raw file body) |
| DELETE | `/snapshots/<bucket>` |

**WS events**: `snapshot.created`, `snapshot.removed`.

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

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

Omit → defaults apply. Garbage collection runs opportunistically on every `POST /snapshots` (sweeps date dirs older than `retention_days`); no extra 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. The canonical `AGENT_INSTRUCTIONS.md` preamble was updated to spell this out so every CLI agent CLAUDE.md / AGENTS.md / GEMINI.md inherits the rule via the daemon's auto-render.

**Daily-log integration**: every snapshot.create auto-appends a section to `.meshkore/log/<YYYY-MM-DD>.md` with bucket id + agent + file list + restore hint. So the diary tells the same story the chat does — interleaved with the file-change trail.

**Catch-up procedure for daemons running v18**:

1. Bump the local daemon binary to **py-1.12.13** or later. The new routes are no-op friendly until first use.
2. (Optional) Add to `cluster.yaml`:
   ```yaml
   snapshots:
     enabled: true
     retention_days: 7
   ```
   The defaults are identical, so most clusters can skip step 2.
3. Set `.meshkore/STANDARD_VERSION` to `19`.
4. Re-render the auto-managed AGENT_INSTRUCTIONS preamble (per v18 protocol) so the §17 preamble includes the new snapshot-contract paragraph. The render is idempotent — operator content under `OPERATOR_CONTENT_BEGIN` is preserved.
5. Restart the daemon process so the new HTTP routes are registered. `/reload` rebuilds state but does NOT reimport the script.

**Future cockpit UI** (out of scope for v19, planned for V108+): snapshot list inline on each chat message that triggered modifications, on each roadmap task card, and on each daily-log entry. Diff snapshot ↔ working tree from the cockpit. V19 ships storage + protocol; UI follows once the data path is settled.

**Anti-patterns**:
- Snapshotting newly-created files (no prior content to preserve).
- Treating snapshots as long-term archive — retention will drop old buckets.
- Restoring files behind the agent's back (operator instructs the agent to restore; agent reads the snapshot via `/snapshots/<bucket>/files/<path>` and writes the working tree).
- Committing the `.meshkore/snapshots/` directory — gitignored on purpose.

Specs:
- Standard §20 (`/standard#20-file-snapshots-v19`)
- HTTP surface in `standard.json#snapshots.http_routes`
- Daemon code: `Daemon.snapshot_*` family (helper, list, get, file, delete, gc) at `daemon.py` py-1.12.13+

---

## v18 — 2026-06-09 — Local agent CLI instructions (`.meshkore/public/AGENT_INSTRUCTIONS.md`)

Every cluster now ships 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 — and the
MeshKore daemon **renders** that source into the well-known per-CLI
files at the repository root (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`,
…) and keeps them in sync.

**Why now**: each agent CLI looks at a different file for project
rules and operators had been hand-maintaining the same content N
times — drift inevitable, MeshKore-specific conventions (network
resources from §16, commit attribution, standard evolution
protocol) often missing in one or another. Anchoring a single
source in the standard, with a daemon-managed preamble, fixes
compatibility once.

The new §17 in the standard renumbers the old "Project repo" to §18
and "Where everything else points" to §19 — no internal links were
referencing the old numbers, so the change is purely typographic in
the spec body. Cross-references in CHANGELOG / docs / daemon code
that point at §17 by literal string should be reviewed.

**Catch-up steps for daemons running v17**

1. In every cluster you manage, create
   `.meshkore/public/AGENT_INSTRUCTIONS.md` with the two markers
   defined in §17.2:
   - `<!-- MESHKORE_PREAMBLE_BEGIN -->` … `<!-- MESHKORE_PREAMBLE_END -->`
   - `<!-- OPERATOR_CONTENT_BEGIN -->` … `<!-- OPERATOR_CONTENT_END -->`
2. Fetch `https://meshkore.com/standard/agent-instructions.md` and
   paste its content into the preamble block. (Verbatim — no edits.
   The daemon will keep this fresh on subsequent bumps.)
3. If the operator already had a `CLAUDE.md` / `AGENTS.md` /
   `GEMINI.md` at the repo root with project-specific content, move
   the project-specific portions into the OPERATOR_CONTENT block.
4. Render to the three mandated targets at the repo root by writing
   each file as: `<auto-render header comment>` newline +
   verbatim contents of `AGENT_INSTRUCTIONS.md`. Mandated targets:
   `CLAUDE.md`, `AGENTS.md`, `GEMINI.md`.
5. Add a file-watch on `.meshkore/public/AGENT_INSTRUCTIONS.md` so
   operator edits re-render the three targets. Add the same render
   step to the existing standard-upgrade flow so a future preamble
   change propagates.
6. (Optional, mandated in v19) also render
   `.cursor/rules/meshkore.mdc` and `.clinerules`.

**Where it does NOT apply**

- Agents NOT running as a CLI in the operator's repo (the mesh's
  remote agents like meshkore-image-gen) are unaffected — they
  follow their own Tier-2 PublicCard contract from §16 / addressing.
- The OPERATOR_CONTENT block is operator-sovereign; the daemon never
  touches it. If you delete the markers, the daemon refuses to
  refresh the file rather than risk overwriting hand-edited content.

Specs:
- Standard §17 (`/standard#17-local-agent-cli-instructions-v18`)
- Canonical preamble: `/standard/agent-instructions.md`
- Operator playbook: `/reference/agents/local-instructions`

---

## v17 — 2026-06-09 — Network resources file (`.meshkore/public/RESOURCES.md`)

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

**Why now**: the mesh has worked for months but discoverability of
the mesh itself inside a project was poor. Operators were
re-explaining the same URLs in every conversation. A small file at
a predictable path closes the gap. Companion landing surface on the
public site at `meshkore.com/connect#call` shows the operator the
exact URL to paste.

**Catch-up steps for daemons running v16**

1. Copy `https://meshkore.com/standard/resources.md` to
   `.meshkore/public/RESOURCES.md` in every cluster you manage. The
   canonical block ends at `<!-- /canonical -->`; anything below is
   project-specific operator content and survives future refreshes.
2. When the standard-version-check task runs (daily; existing flow),
   add a step: if `STANDARD_VERSION` < `published_version`, re-fetch
   the canonical `resources.md` and overwrite the local copy above
   the marker. Same auth model and storage path; no new endpoints.
3. In every system-prompt template the daemon emits for subagents,
   add a one-line pointer like: *"MeshKore network resources at
   `.meshkore/public/RESOURCES.md` — Oracle, hub directory,
   agent-use patterns, daemon upgrade flow."* No need to embed the
   file content; the agent reads the path on demand.
4. (Optional, recommended) Mirror the same pointer in the project's
   root `CLAUDE.md` / `AGENTS.md` so agents that read the project
   docs first also find it.

**Where it does NOT apply**

- Skill invocations stay direct from caller to agent endpoint (no
  proxy via `meshkore.com`). The new file is metadata only.
- Live state (online/offline flags, message counters) is never
  baked into the file — query the hub for those.

Specs:
- Standard §16 (`/standard#16-network-resources-file-v17`)
- Canonical file: `/standard/resources.md`
- Web surface for "Use a specific agent": `/connect#call`

---

## v16 — 2026-06-08 — Chat-turn queue (`.meshkore/queues/<conv>.json`)

A per-conv FIFO of operator-authored prompts that wait their turn.
When a conv is mid-turn and the operator wants to line up a follow-up
without interrupting, they hit the clock-icon in the composer. The
queue persists to disk; the daemon auto-flushes the head when the
conv goes idle after the next turn final.

**Why now**: operators were either (a) sitting on the prompt for
minutes waiting for an agent to finish, (b) firing immediately and
interrupting the in-flight turn, or (c) losing context by switching
away. The queue lets them script a small sequence of follow-ups
without any of those failure modes. Field-reported 2026-06-08.

**Spec** (full details in `standard.md` §6.4):

- Storage: `.meshkore/queues/<conv>.json`, gitignored, created lazily.
- File shape: `{ conv, version, items: [{ id, text, created_at, position, status, sent_at?, failed_reason? }] }`.
- Status enum: `queued | sending | sent | failed | cancelled`.
- Limits: 50 items per queue, 64 KB per text.

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

| Verb | Path |
|---|---|
| GET | `/chat/conv/<conv>/queue` |
| POST | `/chat/conv/<conv>/queue` (enqueue) |
| POST | `/chat/conv/<conv>/queue/<id>/edit` |
| POST | `/chat/conv/<conv>/queue/<id>/move` |
| POST | `/chat/conv/<conv>/queue/<id>/promote` |
| DELETE | `/chat/conv/<conv>/queue/<id>` |

**WS events**: `queue.item.added | updated | removed | sent`.

**Auto-flush rule**: when the daemon emits `chat.assistant.final`
AND the conv's `live=false coordinating=false`, pop the head and
dispatch it through the normal `/chat/dispatch` path. One turn =
one flush; the head waits if it's not ready (e.g. status changed
to `sending` already by /promote).

**UI contract** (cockpit V107.41+):

- ChatComposer renders a queue button (clock icon) between play and
  paperclip. Shown when the conv is busy OR there are items waiting.
- Queued items render ABOVE the textarea, max-height 30vh, scrollable.
- Each row: drag handle (⋮⋮) + text (click to edit) + ✕ delete.
- Operator hits **play** to send NOW (current behavior, talks to the
  in-flight turn). Operator hits **clock** to enqueue.
- Live updates via WS — no polling.

**Future-proofing**: a planned multi-agent column view reads
`state.queues[conv]` per agent and renders each queue as a checklist
next to the agent's column. Same daemon contract — no new storage.

**Catch-up procedure** for v15 clusters:

1. Bump daemon to **py-1.12.12** or later.
2. Bump cockpit to **V107.41** or later.
3. Set `.meshkore/STANDARD_VERSION` to `16`.
4. Restart the daemon (`POST /reload` does NOT pick up new Python
   modules — needs a full kill + respawn).
5. The queue directory is created lazily; nothing to migrate.

**Scope exclusions** (not in v16):
- Optional auto-promote-all-on-idle modes (single-head-per-flush is the design).
- Cross-cluster queues (queue belongs to ONE daemon).
- Architect / sub-agent enqueue (they dispatch directly).

**Anti-patterns**:
- Auto-promoting all items in one round when the conv goes idle.
- Treating the queue as a substitute for tasks/initiatives.
- Writing directly to `convMap` for queued items — must flow through `/chat/dispatch`.

---

## v15 — 2026-06-08 — `draft` task status — non-dispatchable

New canonical task status: **`draft`** — "spec not finalised, not for
execution". An operator-authored placeholder that the architect MUST
NOT pick up in either Run-all or per-initiative dispatch.

**Rationale**: pre-v15 there was no way to distinguish "this task
exists but isn't ready to run" from "this task is queued for the
next pass" (both lived under `next` / `backlog` with no consistent
semantics). Operators on cavioca/ikamiro field-reported that the
architect kept trying to dispatch tasks they were still authoring,
forcing them to use `backlog` as a fake-draft state — which then
conflicted with the actual "parked idea" meaning of backlog.

**Dispatchability table** (v15):

| Status | Dispatchable? |
|---|---|
| `next`, `active`, `in_progress`, `doing`, `planned` | ✅ |
| `draft`, `done`, `cancelled`, `backlog` | ❌ |
| `blocked`, `pending-operator` | ❌ (awaiting external) |

**Operator-only transition**: only the operator promotes `draft → next`. The architect MUST NOT auto-promote (would defeat the purpose). The architect's end-of-pass `decisions:` bucket counts skipped drafts so the operator knows what was left out:

```
decisions: 4 task(s) status:draft skipped — operator owns promotion to next.
```

**Initiative completion**: an initiative with ANY child task in `draft` stays `active`. Same rule as `next` / `active` / `blocked`. The reconcile already filters naturally (`all_done = all(status == "done")`).

**Visual contract**: the cockpit renders `draft` with a dashed slate border on both the task id chip (TaskCard) and the StatusBadge in the initiative's task grid. Distinct from `next` (warm amber, ready) and `backlog` (gray, parked).

**Catch-up procedure** for v14 clusters:

1. Bump daemon to **py-1.12.11** or later — earlier versions don't know about `draft` and would dispatch them by mistake. (Cluster check: `curl https://daemon.meshkore.com:<port>/health | jq .version`.)
2. Bump cockpit to **V107.40** or later — earlier versions render unknown statuses as the generic gray fallback (technically safe but visually wrong).
3. Set `.meshkore/STANDARD_VERSION` to `15`.
4. Re-author any tasks the operator was using `backlog` as a fake-draft: change `status: backlog` → `status: draft` if the intent is "not ready, do not run".
5. Restart the daemon (`POST /reload` or kill+respawn) so spawn briefings include the v15 FORBIDDEN rule.

**Anti-patterns**:
- Architect promoting `draft → next` autonomously to "make Run-all work" — that's the bug the new state prevents.
- Operators using `draft` for "I'm thinking about this" and `backlog` for "I'm thinking about this later" — pick one (`draft` for not-ready, `backlog` for not-scheduled).
- Renaming all old `next` tasks to `draft` "just in case" — only mark `draft` when you genuinely don't want the architect to touch the spec.

---

## v14 — 2026-06-06 — Project context tree (`.meshkore/context/`)

A dedicated `.meshkore/context/` folder is now part of the standard.
It holds the project-wide INVARIANT knowledge that the daemon injects
into every agent spawn — theory-grounded in agent context engineering
(Anthropic, Cline memory-bank, Cursor `.cursorrules`, Claude Projects).

**Six canonical files + two growable folders**:

- `overview.md`, `product.md`, `stack.md`, `architecture.md`, `constraints.md` — required, ≤200-250 words each.
- `glossary.md` — optional, ≤250 words.
- `decisions/` — required folder, ADR-style entries each ≤100 words, named `YYYY-MM-DD-<slug>.md`.
- `criteria/` — optional folder, when-X-vs-Y rules each ≤100 words.

**Total budget**: 3000 words ≈ 4500 tokens. Daemon emits
`context_tree.token_estimate` in `/state`; cockpit shows the budget
badge; architect dispatch refuses to launch when over budget.

**Scope exclusions** — explicitly NOT in context:
- Audit → `.meshkore/protocols/`.
- Credentials → `.meshkore/credentials/`.
- Bookmarks, crons, links, logs, config — each in its own storage.
- Roadmap state → `.meshkore/roadmap/`.
- Module READMEs → `.meshkore/modules/<id>/README.md`.

**Why this exists**: pre-v14 the cockpit's Context tab fell back to
`docs/architecture/cluster-layout` (legacy doc) and the per-module
README we shipped in v13. Project-wide invariants had no home.
Operators (and agents) ended up re-debating settled decisions
because the context wasn't pinned in one place. Field-reported by
ikamiro 2026-06-06 — empty Context tab on a project that clearly
HAD context (idea, stack, vision) scattered across docs/.

**Catch-up procedure for v13 clusters**:

1. **Create** `.meshkore/context/` with the 6 canonical files.
   Bootstrap each ≤200-250 words using bullets/tables/code blocks.
2. **Seed** `decisions/README.md` and `decisions/<one initial ADR>.md`.
3. **Seed** `criteria/README.md` (optional but recommended).
4. **Migrate** any existing `.meshkore/docs/context.md` content
   into the new tree by intent (vision → overview, tech list →
   stack, decision rationale → decisions/, criteria → criteria/).
5. **Bump** `.meshkore/STANDARD_VERSION` to `14`.
6. **Bump** daemon to py-1.12.8+ (spawn-briefing integration).
7. **Reload** the daemon (`POST /reload` or restart).
8. Cockpit must be on V107.34+ to render the Context tab against the
   new tree.

**Word/token budget enforcement**: each file's frontmatter declares
`title` + `updated`. The daemon validates word counts at boot; over
the per-file limit logs a `warn` in `/health.warnings[]` and the
cockpit shows an over-budget badge. Hard refusal at the dispatch
boundary keeps agents from drowning in oversize context.

**Anti-patterns**:
- Essay paragraphs (context is bullets/tables/code).
- Marketing copy (no taglines).
- Duplicating module READMEs (different audience).
- Volatile state (task progress, last-deployed-sha — those live in roadmap).
- Frontmatter bloat (only title + updated allowed; no tags/owner/related).

---

## v13 — 2026-06-06 — `project` bucket required in cluster.yaml

The `project` meta module is no longer just a playbook convention —
it's a schema requirement. Every `cluster.yaml.modules` MUST now
include:

- One `project` entry: `kind: area`, no `parent:`, no `path:`. The
  meta bucket holding cross-cutting work.
- The 4 minimum area children of `project`: `deploy`, `docs`,
  `design`, `general` — each with `parent: project`.
- Real code modules as PEERS of `project` (not under it), each with
  `kind: code` and `path: <folder>/`.

**Why now.** Pre-v12.1 the convention lived only in the
Roadmap-Author playbook. Clusters created from older standards (or
hand-rolled `cluster.yaml`) ended up without the bucket, leaving
cross-cutting work mixed with code modules and the cockpit's Context
tab empty (no project-level docs). Ikamiro field-reported
2026-06-06 — `cluster.yaml` listed `general / web / api / ai / chain
/ ops` flat, and Context said "No project-level docs found yet."

**Catch-up procedure** for a pre-v13 cluster:

1. **Read your current `.meshkore/public/cluster.yaml`.** Identify which existing modules are cross-cutting areas (deploy, docs, design, ops, general, …) vs which own real source folders (web, api, daemon, …).
2. **Add the `project` bucket** as the first entry in `modules:`. `kind: area`, no parent, no path.
3. **Reparent every cross-cutting area** under `project` by adding `parent: project`. The 4 minimum (`deploy`, `docs`, `design`, `general`) must be present even if a module didn't exist before — add stub entries.
4. **Leave code modules at the top level**, with `kind: code` and `path:`. They stay as peers of `project`.
5. **Create `.meshkore/modules/<id>/README.md` stubs** for every module declared (including `project` itself + every child). Use the shape in [module-readmes playbook](https://meshkore.com/reference/prompts/roadmap-author/v1/module-readmes.md): ≤30 lines, status `stub`, frontmatter required.
6. **Bump `.meshkore/STANDARD_VERSION` to `13`**.
7. **Restart the daemon** so `/state` re-reads `cluster.yaml` and emits the new module tree.

**Anti-pattern**: keeping `general` as a top-level peer instead of a
child of `project`. The bucket only earns its keep when every
cross-cutting area sits under it.

**Backfill**: existing tasks keep their `category: <module-id>`
unchanged — the module id doesn't need to move, only its `parent:`
in `cluster.yaml`.

---

## v12 — 2026-06-02 — Commit attribution trailers (`Agent:` + `Model:`)

Every LLM-authored commit now MUST carry two new git trailers in
addition to the existing `Co-Authored-By:` line:

```
Agent: <agent-role>      # e.g. master, roadmap-architect, work-I13-CAT2
Model: <model-id>        # e.g. claude-opus-4-7, claude-sonnet-4-6
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
```

The `Agent:` value comes from the daemon's `agent_type` field or the
conv slug. The `Model:` value is the exact vendor model id. Human-
only commits MAY omit both. See `commit_attribution` in
`standard.json` and §9.1 of `standard.md` for the full spec.

**Why now.** Field-reported 2026-06-02: a 2-minute "resync" pass
from a roadmap-architect agent committed nothing but rewrote 21 files
to kick the daemon's stale in-memory state. From the git log alone
there's no way to tell whether a commit was a fast probe by a thin
agent or substantive work by a heavyweight one. The trailers make
role + model legible to `git log` / `blame` / analytics; survive
merges + cherry-picks unchanged; parseable via
`git interpret-trailers --parse`.

**Catch-up procedure** for a v11 cluster:

1. Append the v12 prose from §9.1 of `webapp/standard.md` into the
   local cluster's `closure-protocol.md` R2 (or wherever the local
   commit convention lives). Don't copy the rule body — link to
   <https://meshkore.com/standard#91-commit-attribution--agent--model-trailers-v12>.
2. If the cluster runs `meshkore-py`, bump to **py-1.12.7** or later
   — the daemon's spawn-time briefing includes the agent slug + model
   so subagents stamp commits automatically (no operator action).
3. Update the cluster's `CLAUDE.md` boot block: add a line under
   "Conventions" pointing at §9.1.
4. Set `.meshkore/STANDARD_VERSION` to `12`.

**Anti-pattern.** Embedding `Agent:` / `Model:` inside the commit
title or body prose ("// committed by roadmap-architect on Opus 4.7")
defeats the parser. Always use the trailer form at the end of the
body, after a blank line.

**Backfill.** Old commits stay unchanged. Don't rewrite history to
add trailers retroactively — the rule applies to commits authored
after the cluster bumps `.meshkore/STANDARD_VERSION` to 12.

---

## v11 — 2026-05-30 — Cycles (optional task timebox)

Linear-style **Cycles** land as an optional third dimension on the
roadmap. The two existing primitives — Initiative (work-stream) and
Task (one week of work) — are unchanged. Operators who don't want
sprint cadence ignore cycles entirely; nothing breaks.

New folder convention:

```
.meshkore/cycles/
  <YYYY>-w<ISO week>.md       # weekly cycles
  <YYYY>-<ID>.md              # ad-hoc cycles
```

Cycle file frontmatter:

```yaml
---
id: 2026-w22
title: "Sprint 22 — Cluster Cloud P0"
starts: 2026-05-25     # ISO date
ends:   2026-06-07
status: active         # planned | active | done
goal:   "Cerrar CC01..CC06 antes del cierre de junio."
---
```

Body is free-form markdown (planning notes, retrospective, links).

New optional task frontmatter key:

```yaml
cycle: 2026-w22
```

Missing key = task is not in any cycle (default). Present with an id
that doesn't match an existing `.meshkore/cycles/<id>.md` file =
the daemon emits a `task_cycle_broken` integrity hint on the next
briefing (warn, never block).

Daemon work (py-1.10.25 → next bump):
- `/state` payload gains a top-level `cycles:` slice:
  `[{ id, title, starts, ends, status, goal, tasks: [<id>...], path }]`.
  `tasks:` is the list of task ids whose frontmatter has
  `cycle: <this id>`.
- Each task in `/state.roadmap.tasks[]` gains the `cycle` field
  (string id or null).
- The FS-signature watcher now includes `.meshkore/cycles/` so
  adding / editing a cycle file fires `state.rebuilt` and the
  cockpit refreshes.
- StateIntegrityChecker gains `task_cycle_broken` (warn-not-block,
  symmetric with `task_initiative_broken`).

No new HTTP endpoints. Cycles are pure data — no scheduler, no
auto-rollover of unfinished work. The LLM rolls over on demand from
chat (same posture as `standard-versioning`).

Note on numbering: the original spec body (written 2026-05-26) said
"bump to v3", but the standard had already advanced to v10.1 by then.
This entry lands as v11 — the next additive bump after v10.1 — to
preserve monotonic numbering.

`STANDARD_VERSION` 10 → 11. No `DAEMON_VERSION` schema requirement
(daemon binary still serves the same endpoints; only payload shape
gains the additive `cycles:` slice).

---

## v10.1 — 2026-05-27 — Challenge-response auth (`/auth/challenge`)

Daemon adds `GET /auth/challenge?nonce=<n>` returning
`{ nonce, sig: HMAC-SHA256(portal-token, nonce), alg, version, ts }`.
Cockpit runs this handshake on every `switchToPort` to a new instance
when a token for that cluster is already in local storage, and refuses
to attach on mismatch. Defeats MITM-by-cert-leak: an attacker who
serves a valid TLS cert for `daemon.meshkore.com` (our wildcard is
public by design) still can't fake the daemon because they don't have
the operator's `portal-token`.

Backwards-compatible patch on top of v10. Daemons advertise the
capability via the `auth.challenge` feature flag; cockpits without
support fall back silently. **No `STANDARD_VERSION` bump** — additive
endpoint, no schema change. `DAEMON_VERSION` bumped `py-1.8.0 → py-1.8.1`.

Limitations:
- Doesn't protect first-time connection to a NEW cluster (no shared
  secret yet). Operator must verify `cluster_id` by hand on that
  initial bind.
- Future: per-cluster ACME (`<cluster_id>.daemon.meshkore.com` with
  its own cert) closes this fully. See initiative
  `local-tls-subdomain` → `daemon-multi-tenant-tls`.

## v10 — 2026-05-27 — Loopback TLS (daemon.meshkore.com)

The Python daemon now serves HTTPS + WSS on its port range
(5570-5589) when a bundled TLS cert is present alongside `daemon.py`.
The wildcard cert is for `*.daemon.meshkore.com`, which resolves to
`127.0.0.1` via a public Cloudflare DNS A record. This lets the
cockpit at `architect.meshkore.com` reach the local daemon from any
HTTPS origin without:

- mixed-content rejections (HTTPS page → `ws://localhost`)
- Chrome Local Network Access "Issues" (every public→loopback fetch
  was flagged; sessions accumulated thousands)
- the operator having to grant per-site permission via Chrome flags

The pattern is the same one Plex (`*.plex.direct`), Caddy local
HTTPS, and Tailscale Funnel use. Cert + key are deliberately public:
the only thing an attacker can do with them is impersonate
`daemon.meshkore.com` on their own loopback, which gives access to
nothing.

### What changed

- `daemon.py` (`DAEMON_VERSION = py-1.8.0`):
  - New `_find_tls_bundle()` + `_build_tls_context()` helpers.
  - `serve_forever()` wraps the listening socket with `ssl.SSLContext`
    when `tls/fullchain.pem` + `tls/privkey.pem` exist next to
    `daemon.py`. Falls back to plain HTTP otherwise (backwards
    compatible — operators who don't pull the `tls/` directory keep
    working unchanged).
  - `/health` exposes `tls: bool` and an `endpoint` field
    (`https://daemon.meshkore.com:<port>` when TLS is on,
    `http://localhost:<port>` otherwise).
  - `_features()` adds `tls.loopback` when TLS is enabled.
- `daemon/tls/` ships the wildcard cert + key + `README.md` +
  `renew.sh` (certbot DNS-01 against Cloudflare).
- Cockpit `MIN_DAEMON_VERSION = py-1.8.0`. The V47 upgrade modal
  fires on any daemon at `py-1.7.x` or below — daemons keep working
  on HTTP, but the operator is nudged to upgrade so the cockpit can
  flip TLS on by default.
- Cockpit feature flag `localStorage['mc-daemon-via-tls']` (or
  `?tls=1`) toggles the cockpit's URL scheme between
  `https://daemon.meshkore.com:<port>` and the legacy
  `http://localhost:<port>`. Surfaced in the Header About modal.

### How to apply (catch-up from v9)

1. `printf '10' > .meshkore/STANDARD_VERSION`
2. Run the daemon-upgrade flow:
   ```bash
   curl -fsS https://meshkore.com/reference/cluster/scripts/daemon.py \
     -o .meshkore/scripts/daemon.py
   curl -fsS https://meshkore.com/reference/cluster/scripts/tls/fullchain.pem \
     -o .meshkore/scripts/tls/fullchain.pem
   curl -fsS https://meshkore.com/reference/cluster/scripts/tls/privkey.pem \
     -o .meshkore/scripts/tls/privkey.pem
   chmod 600 .meshkore/scripts/tls/privkey.pem
   ```
3. Restart the daemon:
   ```bash
   curl -X POST http://localhost:$(cat .meshkore/.runtime/port)/shutdown \
     -H "Authorization: Bearer $(cat .meshkore/credentials/portal-token)"
   python3 .meshkore/scripts/daemon.py &
   ```
   Confirm the boot log says `tls=on (daemon.meshkore.com)`.
4. In the cockpit's Header → click the logo → About modal → toggle
   "TLS mode" to on. Reload. The cockpit will now hit
   `https://daemon.meshkore.com:<port>` for everything.

The catch-up is safe to skip — operators who don't apply it stay on
plain HTTP and continue to work, just with the mixed-content + LNA
Issues as before. The bump is mandatory only for operators who want
the silent / real-time cockpit experience over HTTPS.

### What this enables

- Real-time cockpit (`wss://` WebSocket events) when cockpit is
  served from any HTTPS origin (`architect.meshkore.com` today).
- Zero Chrome LNA Issues on session — no more 1k+ Issue
  accumulation while debugging.
- Foundation for the cockpit to talk to **multiple parallel
  daemons** without each one having to mint its own self-signed
  cert.

### See also

- [`local-tls-subdomain`](../../../.meshkore/roadmap/initiatives/local-tls-subdomain.md) initiative.
- [`P4-daemon-upgrade`](../../../.meshkore/protocols/P4-daemon-upgrade.md) protocol for daemon-only updates that don't touch the standard schema.

---

## v9 — 2026-05-26 — Project repo convention (multi-repo `.meshkore/` backup)

New §17 of the standard. Codifies what the operator should do with
their `.meshkore/` folder so it doesn't only live on the laptop:

- **Single-repo projects**: `.meshkore/` stays inside the source repo,
  gitignored selectively (same as today's behaviour, just documented).
- **Multi-repo projects**: `.meshkore/` becomes its **own private git
  repo** at `<org>/project` (or `<org>/<project-slug>`). The `.git/`
  lives **inside** the `.meshkore/` folder — the daemon stays unaware
  that the folder happens to be a git working copy.

A reference script `operator-sync.sh` plus a `launchctl` plist
template ships at
`meshkore.com/reference/cluster/scripts/operator-sync.sh` — default
cadence 30 min, single-instance-locked, drops a log under
`.meshkore/.runtime/logs/operator-sync.log`. Optional but recommended
until cluster cloud lands.

### Mandatory `.gitignore` patterns in any project repo

```
credentials/        # NEVER commit
.runtime/           # ephemeral daemon state
roadmap/state.json  # regenerable
roadmap/state.js    # regenerable
```

### Apply manually

For existing multi-repo projects:

```bash
cd <workspace>/.meshkore
git init -b main
# ... add gitignore from §17.3 ...
git add -A
git commit -m "Initial: import project workspace"
gh repo create <org>/project --private
git remote add origin git@github.com:<org>/project.git
git push -u origin main

# Install the auto-sync
curl -o scripts/operator-sync.sh \
  https://meshkore.com/reference/cluster/scripts/operator-sync.sh
chmod +x scripts/operator-sync.sh
# (install your platform's scheduler — see §17.4)
```

For single-repo projects: no action; existing gitignore rule from
v6 (`.meshkore/*` with `!.meshkore/public/` exceptions) is already
the right shape.

### Renaming from v8 pre-release

In an internal iteration the repo name `<org>/operator` was floated
before this changelog landed. The canonical name is `<org>/project`
from v9 onwards. If a cluster already created `<org>/operator`, just
`gh repo rename project --repo <org>/operator --yes` and update the
remote URL in `.meshkore/.git/config`.

### What did NOT change

- No daemon API change.
- No `.meshkore/` schema change — same folder shape.
- `cluster.yaml` schema unchanged.
- The L1/L2/L3 Quality Gates from v8 are unaffected.

---

## v8 — 2026-05-26 — Quality Gates (L1/L2/L3 mechanical safety net)

New §15 of the standard. Every code repo in a MeshKore cluster
opts in to three independent gate layers that catch mechanical
issues (types, lint, format, secrets, build, basic tests) before
they reach `main`.

### What changed

- New §15 — Quality Gates. Three layers documented:
  L1 (pre-commit, <10s, local), L2 (CI on push, GitHub Actions,
  1-5min), L3 (nightly drift, optional).
- New repo contract: every code repo MUST have `.github/workflows/ci.yml`
  + `.git/hooks/pre-commit` + `.meshkore.repo.yaml` with declared
  `stack` and `quality_gates_version`.
- Four stack templates shipped at
  `meshkore.com/reference/standards/quality-gates/`:
  `typescript/`, `cloudflare-worker/`, `rust/`, `python/`. Each has
  `ci.yml` + `pre-commit` + (where useful) nightly audit job.
- One-line installer:
  `curl … install.sh | bash -s <stack>`. Idempotent.
- Agent contract prompt at
  `meshkore.com/reference/standards/quality-gates/agent-prompt.md`.
  The daemon prepends this to every chat-dispatch in repos with
  Quality Gates installed. Forbids `--no-verify`, forbids
  `meshkore.skipL1`, forbids deleting gates to make red turn green.
- §16 takes the old §15 ("Where everything else points") slot —
  one-line rename, no semantic change.

### What did NOT change

- No daemon API change — the daemon doesn't need to know about
  Quality Gates yet. The hooks live on disk, the workflows live on
  GitHub. Integration with the prompt queue (L2 failure → agent's
  next dispatch) is deferred to the `quality-gates-standard`
  initiative, which depends on `context-versioning-and-agent-sync`.
- No `.meshkore/` schema change.
- No `cluster.yaml` schema change (`stack:` already declarable, just
  encouraged now).

### Apply manually (existing repos)

For each existing MeshKore-managed code repo:

```bash
cd <repo>
curl -sSf https://meshkore.com/reference/standards/quality-gates/install.sh \
  | bash -s <stack>     # typescript | rust | python | cloudflare-worker
git checkout -b chore/install-quality-gates
git add .github .meshkore.repo.yaml
git commit -m "chore: install meshkore quality gates v1 (<stack>)"
git push -u origin chore/install-quality-gates
# open PR, merge after L2 passes
```

Skip this for repos that are deliberately gate-less (scratchpads,
archived). Architect's repo panel will badge them yellow.

### Note on v7

v7 was an in-flight bump that didn't reach the public version file
before v8 work landed. The standard sequence ships v6 → v8 to keep
the public version monotonic. Nothing functional was added in v7.

---

## v6 — 2026-05-20 — Adoption polish (timeline vocab + migrate wizard + verify + token clarity)

This is a polish bump driven by operator feedback after a real-world
migration of an external repo (charms-wallet) hit eight distinct
friction points. No new files in `.meshkore/`; the changes are
documentation completeness + script hygiene + redirect cleanup.

### What changed

- **New §6.3 in the standard** — full timeline event vocabulary
  (`task.*`, `chat.*`, `tool.*`, `commit.*`, `deploy.*`, `cron.*`,
  `state.rebuilt`, `links.updated`, `protocols.updated`,
  `bookmarks.updated`, `daemon.shutdown`). §6.2 previously said "see
  spec v1 §11" but §11 is versioning — now fixed.
- **§3 clarification** — `.meshkore/STANDARD_VERSION` is the
  canonical answer to "which standard does this repo apply"; new
  table documents the spec ↔ manifest ↔ daemon relationship (three
  independent versions that may drift).
- **New wizard prompt** at
  `/reference/prompts/migrate-existing-repo.md` — the missing
  counterpart to `project-from-scratch`, covering ad-hoc legacy layouts
  (`_rjj/`, `_docs/`, `notes/`, …) with deterministic legacy → canonical
  mappings.
- **New `verify.py`** at `/reference/cluster/scripts/verify.py` — runs
  the §2/§3/§4/§5 conformance checks (layout, cluster.yaml fields,
  frontmatter, gitignore contract, STANDARD_VERSION freshness vs the
  published version, links.yaml shape, protocol filenames). Stdlib-only.
- **`migrate-tasklist.py` fixed** — was emitting to the pre-v3 layout
  (`roadmap/tasks/<module>/`); now emits to the §2 canonical
  (`modules/<module>/tasks/`).
- **`/changelog` aliases land** — `/changelog`, `/changelog.md`,
  `/standard/changelog`, `/standard/changelog.md` all 301 to
  `/standard/CHANGELOG.md`. Previously every variant 404'd.
- **Token policy clarified in `cluster/operate.md`** — explicit list
  of read endpoints (no auth) vs write endpoints (bearer required);
  the architect's localhost-only trust boundary is documented.
- **Editor boot blocks self-identify** — CLAUDE.md, .cursorrules,
  .windsurfrules, copilot-instructions.md each open with a comment
  naming the source URL + "kept local, gitignored" so an operator
  arriving at a repo recognises them instantly instead of asking "is
  this ours or someone else's?".
- **`/reference/manifest.json` cross-refs** — gains `version`,
  `changelog`, and `version_relationship` fields under
  `canonical_standard`, pointing at the integer endpoint + per-version
  blocks. Bumped to manifest schema v5.

### How to apply (catch-up from v5)

Backward compatible. Nothing in this bump forces a folder reshape.

1. **Pull the latest daemon**: `webapp/reference/cluster/scripts/daemon.py`.
   No new endpoints; only consistency.

2. **Pull the new `verify.py`** and run it once:
   ```bash
   curl -fsSL https://meshkore.com/reference/cluster/scripts/verify.py \
     -o .meshkore/scripts/verify.py
   python3 .meshkore/scripts/verify.py
   ```
   Address every ERROR; treat WARN as discretionary.

3. **Update your editor boot block** if you keep a local copy at repo
   root — the new template starts with a self-identifying comment.
   Drop your old `CLAUDE.md` / `.cursorrules` / `.windsurfrules` and
   re-curl from `/reference/cluster/editor-rules/`.

4. **Bump the marker**: `echo 6 > .meshkore/STANDARD_VERSION`.

### What this enables

- A second wizard (`migrate-existing-repo`) means the *"apply the
  standard"* prompt now has the right entry point for both empty
  repos and pre-existing ones.
- Agents finally have a programmatic conformance check (`verify.py`).
- Timeline event vocabulary is exhaustive enough that the architect's
  Diary panel (V43) can render every event type without surprises.
- No more *"which version am I on?"* ambiguity — the integer in
  `.meshkore/STANDARD_VERSION` is the single source of truth.

### Operator feedback this addressed

The full migration feedback from 2026-05-15 is preserved in
`.meshkore/log/2026-05-20.md`. Friction items #1–#8 are now closed
(token contradiction, missing changelog URL, stale migrate script,
missing event vocab, no self-identifying headers, no verify script,
no existing-repo wizard, no spec/manifest/daemon relationship doc).
The two open items — "bootstrap script for any legacy layout" and
"daemon-side `applied_standard` check at boot" — are covered
respectively by the new `migrate-existing-repo` prompt + `verify.py`
combo and by `verify.py`'s network check against `/standard/version`.

---

## v5 — 2026-05-20 — Protocols (reusable multi-scope runbooks)

### What changed

- New top-level folder **`.meshkore/protocols/`** holds named,
  indexed, reusable runbooks for multi-step work that crosses scopes
  (docs + code + commit + deploy).
- Identifier scheme **`P<N>`** added to the cluster's identifier
  family (P joins T, V, D, C, N, AG). One global numbering.
- New standard section **§14** describes the file shape, run-log
  shape, daemon surface, and "apply P<N>" semantics.
- Python daemon gains four endpoints:
  - `GET  /protocols`            no-auth, full list
  - `GET  /protocols/<id>`       no-auth, raw markdown + frontmatter
  - `GET  /protocols/<id>/runs`  no-auth, last 50 run entries
  - WS broadcast: `protocols.updated` on file change
- Gitignore exception added: protocols + INDEX are committed; run
  logs stay per-machine.
- Two protocols ship with the standard so projects have working
  references:
  - **P1** — bump-standard-version (the very protocol that produced
    this section).
  - **P2** — deploy-project (build + lint + clean-workspace check +
    per-module deploy via `links.yaml` + post-deploy patch + verify).
- Editor boot blocks (CLAUDE.md / .cursorrules / .windsurfrules /
  copilot-instructions.md) teach agents the "apply P<N>" pattern.

### How to apply (catch-up from v4)

**Backward compatible** — projects without `.meshkore/protocols/`
work exactly as before; the daemon treats a missing folder as an
empty protocol set.

1. **Create the folder** + index:
   ```bash
   mkdir -p .meshkore/protocols/log
   curl -fsSL https://meshkore.com/reference/cluster/templates/protocol.md \
     -o .meshkore/protocols/_template.md     # reference template (delete later)
   ```

2. **Update `.gitignore`** so protocols are committed but run logs
   stay local:
   ```
   !.meshkore/protocols/
   .meshkore/protocols/log/
   ```

3. **Write your first protocol** if useful (deploy, release, audit,
   data migration — whatever you re-explain to your agents most
   often). Follow the schema in §14.3.

4. **Bump the marker**: `echo 5 > .meshkore/STANDARD_VERSION`.

5. **Pull the latest daemon** so `/protocols` endpoints work. Older
   daemons simply don't expose them — no breakage.

### What this enables

- "Apply protocol P<N>" replaces "let me explain again how we ship
  a standard version / deploy the project / run a release".
- Cron-driven scheduled work can reference a protocol id instead of
  inlining a long command.
- The architect's cockpit (next iteration) renders the protocols
  list, lets the operator launch a run, and streams step-by-step
  outcome from the WS.
- Audit: every protocol run leaves a markdown log entry with commit
  sha + deploy IDs + per-step verdict. No more "what did we change in
  the last release" archaeology.

---

## v4 — 2026-05-20 — Links registry (`.meshkore/public/links.yaml`)

### What changed

- New committed file **`.meshkore/public/links.yaml`** declares, per
  module: local URL + start command, production URL + provider +
  deployed sha/version/timestamp, current repo branch + head sha.
- New standard section **§13** describes the schema, the
  agent-maintenance contract, and the daemon surface.
- Python daemon gains four endpoints:
  - `GET  /links`         no-auth, full registry as JSON
  - `GET  /links/<id>`    no-auth, single module
  - `POST /links/<id>`    bearer-auth, patches the file atomically
  - WS broadcast: `links.updated` on every rewrite
- Commit convention §6 extended: any commit that changes where a module
  runs, where it deploys, what branch it lives on, or what version it
  carries MUST update `links.yaml` in the same commit.
- Editor boot blocks (CLAUDE.md / .cursorrules / .windsurfrules /
  copilot-instructions.md) extended to remind agents to keep
  `links.yaml` in sync on every meaningful module change.

### How to apply (catch-up from v3)

**Backward compatible** — projects that don't deploy anything may
skip the file entirely; the daemon treats a missing file as
`{ version: 1, modules: [] }`.

1. **Create `.meshkore/public/links.yaml`** with at minimum:

   ```yaml
   version: 1
   modules: []
   ```

2. **For every module that has a reachable surface** (local port,
   production URL, or both), add an entry. Use the template at
   <https://meshkore.com/reference/cluster/templates/links.yaml> or
   read §13.1 of the standard.

3. **Bump the marker**: `echo 4 > .meshkore/STANDARD_VERSION`.

4. **Pull the latest daemon** at
   `webapp/reference/cluster/scripts/daemon.py` so the `/links`
   endpoints work. Older daemons will simply ignore the file — no
   breakage.

5. **Going forward**: every time you deploy a module, switch its
   active branch, or bump its version, update its entry in
   `links.yaml` in the same commit. The architect's cockpit will
   surface the registry in a future release (the file already drives
   the daemon today).

### What this enables

- Single source of truth for "where is everything deployed and at what
  version" — no more ad-hoc `links.md` lists or scattered per-module
  README sections.
- Cron-driven deploy agents can patch `prod.deployed_*` fields via
  `POST /links/<id>` immediately after a successful deploy.
- The cockpit gains a Deployments view (future task) that reads
  `/links` over the WS event stream.

---

## v3 — 2026-05-19 — Daemon-owned cron scheduler

### What changed

- `cluster.yaml` gains two **optional** top-level keys:
  - `crons:` — list of cron jobs. Each entry: `id`, `name`,
    `schedule` (5-field POSIX cron expression), `cmd`, optional
    `cwd`, `env`, `enabled`, `max_runtime_sec`, `restart_policy`,
    `retention_runs`, `destructive`.
  - `crons_owner:` — the `device_id` of the **coordinator** daemon.
    Only this daemon fires jobs; peers tick at 10 s intervals and
    emit `cron.would_have_fired` events. (Coordinator failover is
    deferred to Cluster Cloud P1.)
- `.meshkore/.runtime/crons.json` is the per-machine runtime state
  (last_run, next_run, history). Gitignored, recreated on first
  scheduler tick.
- `.meshkore/.runtime/logs/cron/<job_id>/<ts>.log` holds the captured
  stdout/stderr of each run.
- The Python daemon (`webapp/reference/cluster/scripts/daemon.py`)
  validates the `crons:` block on load. Unknown / malformed entries
  are skipped with a logged warning; the rest of the daemon's
  features (state, agents, WS) continue normally.
- Schema reference:
  [`docs/conventions/cluster-yaml-crons.md`](../../../.meshkore/docs/conventions/cluster-yaml-crons.md).
- Architecture diagram:
  [`docs/architecture/daemon.md § Cron scheduler`](../../../.meshkore/docs/architecture/daemon.md).

### How to apply (catch-up from v2)

**Backward compatible** — a v2 daemon ignores the new keys; a v3
daemon validates and processes them. No `.meshkore/` reshape needed.
The catch-up is purely additive:

1. **Pull the latest daemon** at `webapp/reference/cluster/scripts/daemon.py`
   (or re-clone the repo) so your local daemon has the v3 validator.
   The TypedDicts and `_validate_crons_block` were added in commit
   `eb1ce40` on branch `daemon/cron-scheduler` and merged to `main`
   when the cron scheduler is feature-complete.
2. **Optional**: add a `crons:` block + `crons_owner:` field to your
   `cluster.yaml`. If you don't want crons yet, do nothing — the
   daemon parses an empty `crons:` set without warning.
3. **Bump the marker**: `echo 3 > .meshkore/STANDARD_VERSION`.
4. **Restart the daemon** so it re-reads `cluster.yaml`. The new
   block is visible at `GET /cron/list` (when D-CRON-04 ships).

If you have an older daemon (no v3 validator) and the upstream
operator has already added a `crons:` block: nothing breaks — your
daemon's `parse_simple_yaml` reads the keys but `Cluster` doesn't
expose them. Upgrade the daemon when you want the feature.

### What this enables

- Replacement of macOS LaunchAgent / cron-tab / GH-Actions cron with
  one portable, stdlib-only Python scheduler that works on every OS
  Python runs on.
- Single-coordinator firing — no double-run when the same repo lives
  on multiple machines.
- Cockpit observability (CronsPanel + log viewer + manual trigger
  arriving with the cron-dashboard initiative).

---

## v2 — pre-changelog anchor

The `STANDARD_VERSION` marker was bumped from 1 to 2 between
2026-05-12 (the spec-evolution task's "today is 1" anchor) and the
creation of this file. Specific changes weren't tracked in changelog
form because this file didn't exist yet. From v3 forward, every bump
carries a section here.

---

## v1 — initial anchor (2026-05-12)

The first formally-versioned shape of `.meshkore/`:

- `.meshkore/public/cluster.yaml` (committed) — cluster identity,
  bootstrap URLs, admission policy, module list.
- `.meshkore/credentials/` (gitignored) — per-machine secrets.
- `.meshkore/agents/` (gitignored) — declared agent identities.
- `.meshkore/modules/<id>/tasks/*.md` — work units, scoped per
  module.
- `.meshkore/docs/{architecture,conventions,deploy,ops,security}/`
  — internal documentation.
- `.meshkore/roadmap/{initiatives,log,manual-tasks.md}` — planning.
- `.meshkore/log/<YYYY-MM-DD>.md` — daily activity log (operator
  convention added 2026-05-12).
- Standard discovery endpoint at `meshkore.com/standard/version`
  (this file's sibling).
