# 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`](./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. ```yaml 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 | Constraint | Meaning | | ------------ | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `version` | yes | integer constant `2` | Identifies this contract. Version `1` is explicitly rejected by this compiler and remains on the existing v1 path until M4 migration. | | `generation` | yes | positive safe integer | Desired-state generation. M2 uses it for mutation guards; M1 does not mutate it. | | `transport` | yes | constant `tmux` | M1–M5 support local tmux only. | | `tmux` | yes | strict object | Explicit local socket and holder-session configuration. | | `defaults` | yes | strict object | Default work directory and one supported local runtime. | | `runtimes` | yes | non-empty object | Declared local runtime reset policy map. | | `agents` | yes | non-empty array | Local fleet entries. Duplicate stable names are rejected. | ## Nested fields | Path | Required | Constraint | | ---------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- | | `tmux.socket_name` | yes | non-empty `[A-Za-z0-9_.-]+`; an explicit named socket prevents default-versus-named socket ambiguity | | `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.md` membership alone does not resolve a class; - retains `requestedClass` separately from `canonicalClass` in typed output; - canonicalizes only `implementer` to `code`, `reviewer` to `review`, and `operator-interaction` to `interaction`; - canonicalizes `tool_policy` with 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.local` classes 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](./role-classes.md) and [Customize Fleet Roles](../how-to/customize-roles.md). 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.