Applies the document contract from
docs/plans/2026-08-20_stack-docs-flatten-and-alignment.md section 3, partially:
`kind` and `status` only. `parent` is deliberately held until the flatten in
section 4 lands, so that 127 documents do not have to be re-pointed by hand
when docs/fleet/NORTH_STAR.yaml moves to docs/NORTH_STAR.yaml.
Scope, measured on origin/next at 63069149:
127 live docs = all *.md under docs/ minus docs/archive/ minus docs/_old_structure/
104 stamped here
19 held operator judgement (plan section 9), worklist in the same PR
3 held the SUPERSEDED TASKS.md stamps, which cite the moving path
1 untouched docs/fleet/FLEET-DOCTRINE.md, already stamped in W1
Kinds applied: 54 guide, 34 record, 9 spec, 6 tracking, 1 projection.
Every row carries a confidence and a one-line rationale in the worklist.
Two collisions with the existing state, both flagged rather than resolved:
1. docs/README.md:150-160 already documents a front-matter convention
(title/type/audience/status/source_of_truth) with its own allowed values.
It is applied to 4 of 127 files. Its `status` vocabulary is
current|draft|deprecated|historical; the new contract's is active|superseded-by.
The key collides. This commit lets the new contract win and rewrites
`status: current` to `status: active` on those 4 files, keeping their other
legacy keys untouched. No code reads any of them: `git grep source_of_truth`
outside docs/ returns nothing. docs/README.md still prescribes the old
convention and is an operator row, so it is not edited here.
2. Two of the plan's 20 operator rows are YAML files, not markdown
(docs/fleet/examples/roster-v2.yaml, docs/openapi-tess.yaml), and the
contract's front-matter form has no defined meaning for a .yaml document.
That gap also applies to docs/fleet/NORTH_STAR.yaml, the source of truth
itself. Raised in the worklist.
A third row from the plan, docs/fleet/north-star.md, no longer exists: W1
renamed it to docs/fleet/FLEET-DOCTRINE.md.
Verification: 104/104 parse with the expected kind and status in front matter;
the check was shown to reject a wrong kind before it was trusted. The diff
removes 4 lines total, all of them `status: current`.
8.8 KiB
kind, status
| kind | status |
|---|---|
| guide | active |
Fleet Roster v2 Structural Contract
Status: FCM-M1-001 local-tmux structural compiler contract. This document describes parsing, strict structural validation, normalized in-memory representation, and deterministic rendering only. It does not authorize role resolution, lifecycle reconciliation, mutation, migration, remote placement, connector configuration, secret references, arbitrary commands, channels, gateway mapping, or any live-fleet change.
The executable schema is roster-v2.schema.json. The compiler exports
the same schema and its test parses this file and compares it structurally with the executable contract.
Format and canonical shape
The compiler accepts YAML or JSON. It reads only snake_case source fields and renders canonical, snake_case YAML. Rendering sorts runtime keys and agents by stable name. Agent names, class names, and tool-policy names are structural identifiers; whether a class or policy resolves is a later shared-resolver concern.
version: 2
generation: 1
transport: tmux
tmux:
socket_name: mosaic-fleet
holder_session: _holder
defaults:
working_directory: ~/src
runtime: pi
runtimes:
pi:
reset_command: /new
agents:
- name: coder0
alias: Coder 0
class: code
runtime: pi
provider: openai
model: gpt-5.6-sol
reasoning: high
tool_policy: code
working_directory: ~/src
persistent_persona: false
reset_between_tasks: true
lifecycle:
enabled: true
desired_state: stopped
launch:
yolo: true
Root fields
| Field | Required | Default | Constraint | Meaning |
|---|---|---|---|---|
version |
yes | none | integer constant 2 |
Identifies this contract. Version 1 stays on the compatibility path pending explicit migration. |
generation |
yes | none | positive safe integer | Desired-state generation and mutation/reconcile concurrency fence. |
transport |
yes | none | constant tmux |
M1–M5 support local tmux only. |
tmux |
yes | none | strict object | Explicit local socket and holder-session configuration. |
defaults |
yes | none | strict object | Default work directory and one supported local runtime. |
runtimes |
yes | none | non-empty object | Declared local runtime reset policy map. |
agents |
yes | none | non-empty array | Local fleet entries. Duplicate stable names are rejected. |
Nested fields
All nested fields in the v2 schema are required and have no implicit default. CRUD create is the only higher-level convenience: it records lifecycle.enabled: true and desired_state: stopped unless --persisted-start explicitly records running. That convenience still performs no runtime action.
| Path | Required | Constraint |
|---|---|---|
tmux.socket_name |
yes | [A-Za-z0-9_.-]*; empty string means the literal default tmux server, while a non-empty value names a socket |
tmux.holder_session |
yes | non-empty [A-Za-z0-9_.-]+ |
defaults.working_directory |
yes | non-empty string |
defaults.runtime |
yes | claude, codex, opencode, or pi; it must be declared in runtimes |
| runtimes..reset_command | yes | non-empty string; runtime key must be a supported local runtime |
| agents[].name | yes | unique [A-Za-z0-9][A-Za-z0-9_.-]* stable machine identity |
| agents[].alias | yes | non-empty display string |
| agents[].class | yes | [a-z][a-z0-9-]*; structural only in M1, semantic role resolution is FCM-M1-002 |
| agents[].runtime | yes | claude, codex, opencode, or pi; it must be declared in runtimes |
agents[].provider, model, working_directory |
yes | non-empty strings; provider/model capability resolution is a later card |
| agents[].reasoning | yes | low, medium, or high |
| agents[].tool_policy | yes | [a-z][a-z0-9-]*; structural only in M1 |
agents[].persistent_persona, reset_between_tasks |
yes | booleans |
| agents[].lifecycle.enabled | yes | boolean; stored now, reconciled in FCM-M3-001 |
| agents[].lifecycle.desired_state | yes | running or stopped |
| agents[].launch.yolo | yes | boolean; structured data only, not an arbitrary command escape hatch |
Semantic handoff
parseRosterV2 and normalizeRosterV2 remain synchronous and structural. After structural success,
call the asynchronous validateRosterV2Semantics handoff before using persona identity or authority.
That validator batches the baseline fleet/roles/ and operator fleet/roles.local/ scans, then
delegates every agent to the shared persona resolver.
Semantic validation:
- requires the winning role contract to be readable and non-empty;
LIBRARY.mdmembership alone does not resolve a class; - retains
requestedClassseparately fromcanonicalClassin typed output; - canonicalizes only
implementertocode,reviewertoreview, andoperator-interactiontointeraction; - canonicalizes
tool_policywith the same exact alias table; - rejects protected class/tool-policy mismatches in either direction, while accepting
class: operator-interaction with tool_policy: operator-interaction as canonical
interaction; - derives immutable protected authority only from canonical class; and
- accepts custom baseline or
roles.localclasses without granting protected authority.
agents[].alias remains display-only. Tess and Ultron are instance names, never semantic classes. Canonicalization happens before role-layer lookup, so a legacy-named override cannot redefine an alias as separate authority. See Role Classes and Authority and Customize Fleet Roles.
This handoff performs no filesystem, systemd, tmux, roster, credential, lease, certificate, or lifecycle mutation.
Fail-closed boundary
Every object is additionalProperties: false. The compiler rejects unknown, missing, malformed,
and wrong-type fields before producing a model. It specifically rejects remote/SSH/host/socket
per-agent fields, connector blocks, secret references, channel fields, arbitrary command fields,
and gateway fields because they are unsupported in the local-tmux M1 contract. It does not silently
ignore v1 camelCase input, version 1, or a source that does not parse to an object.
The v2 compiler is intentionally isolated from the existing v1 loader. Existing v1 rosters and current examples/profiles continue on their current path; FCM-M4 owns explicit inventory, preview, migration, and rollback. FCM-M2 owns generated-file/local-override quarantine, and FCM-M3 owns runtime lifecycle and reconciliation.