Mesh manifest (`cotal.yaml`)
Reference: every field of the mesh manifest. · For: operators · Walkthrough: Define a team · ACL semantics: SPEC §9
A manifest (cotal.yaml, kind: Mesh) describes a whole team (its channels, its agents,
and who may read and post where) in one file. It is channel-centric: you list the
channels, and under each one name the agents that may read and post; Cotal inverts that
into one least-privilege credential per agent. The manifest is a convenience over the CLI;
it adds no wire concepts. Today it is single-space (one space: per file).
The lifecycle (cotal topology view -f / up -f / spawn -f / down -f), ownership,
and teardown behavior are in the guide: Define a team.
Top level
Section titled “Top level”| Key | Required | Meaning |
|---|---|---|
apiVersion |
yes | Must be cotal/v1. |
kind |
yes | Must be Mesh. |
space |
yes | The space name (one per file; spaces: is not supported in v1). A space’s auth is bound to one root; to run a non-default space in a checkout that already ran cotal up (which sets up main), use a fresh directory. |
broker |
no | servers (comma-separated broker URLs: this sets the address/port; default nats://127.0.0.1:4222; no embedded creds), host (bind interface only, no scheme; does not set the port), auth (bool: JWT auth, default true; false is an open dev mesh). The port comes from servers/--server, never host/--host. |
runtime |
no | How the manager runs each agent: pty (default) · tmux · cmux. |
agent |
no | Default harness (claude / opencode / hermes) for agents that don’t set their own. There is no silent default; an agent needs this or its own agent:. |
personaPermissions |
no | reject (default): the manifest is the whole truth. include: a persona’s own channel grants are inherited for channels the manifest doesn’t declare. |
defaults |
no | Channel defaults applied unless a channel overrides: replay, replayWindow, deliveryClass (live / durable). Semantics in SPEC §7. |
agents |
no | name → persona (a channels-first manifest can seed rooms now and add agents later). |
channels |
yes | name → channel (below). |
Unknown keys are rejected (no silent ignore), and every error is reported with its file and line.
agents: (three forms)
Section titled “agents: (three forms)”agents: planner: ./agents/planner.md # 1) bare path: reuse a persona file as-is builder: # 2) a persona file + overrides (manifest wins) persona: ./agents/builder.md model: sonnet role: implementer instructions: Prefer the smallest change that works. lead: # 3) inline (no file): needs at least model or instructions model: opus role: lead capabilities: [spawn] # may spawn helpers instructions: Coordinate the team.Per-agent keys: persona, agent (harness override), model, variant, role,
description, instructions, capabilities (e.g. [spawn],
what it grants), personaPermissions (override the top-level
policy). Model strings and variants pass to the harness as-is: for Claude use the short
form (opus, sonnet) or the full id; for OpenCode use provider/model plus an optional
variant (cotal models --agent opencode lists both). Persona file format:
agent files.
channels: (the three access verbs)
Section titled “channels: (the three access verbs)”A channel carries its registry card (description, instructions, replay, …;
SPEC §7) plus three lists of agent names, the same verbs Cotal
uses everywhere (channels & permissions):
| Verb | ACL | Meaning |
|---|---|---|
subscribe |
— | Auto-listen at boot. A subscriber is implicitly allowed to read. |
allowSubscribe |
read | May read the channel. Omitted ⇒ defaults to subscribe. Must be a superset of subscribe. |
allowPublish |
post | May post. Default-deny: an empty or omitted list means nobody posts. |
A read-only channel (no agent posts, e.g. an operator writes the record by hand with
cotal send, which is a CLI action outside agent ACLs):
channels: decisions: description: The durable record of what we decided. subscribe: [lead] allowPublish: [] # read-only for agentsEvery name under a channel must be declared in agents:. Channel names must be concrete
(no wildcards in v1).
How access is resolved
Section titled “How access is resolved”You declare membership per channel; Cotal inverts it into each agent’s minted creds:
- Read comes from
allowSubscribe(orsubscribewhenallowSubscribeis omitted). - Post comes from
allowPublish, and is default-deny: an agent you don’t list cannot post, even to a channel it reads. subscribeonly sets what an agent auto-listens to at boot; it never widens read.
With personaPermissions: reject (the default) the manifest is the complete picture; a
persona file’s own channel grants are ignored, so the file you read is exactly what each
agent can do. Set include (top level or per agent) to also inherit a persona’s own
grants for channels the manifest doesn’t mention. cotal topology view -f always prints
the resolved graph, inherited scopes included.
For implementers: the channel-centric → per-agent inversion lives in
resolve.ts; the spawn -f
classification and teardown in
spawn-plan.ts and
down-manifest.ts.