Agent files
Reference (the persisted form of an agent’s identity + persona, read by every launcher) · For: operators · ACL semantics: SPEC §9, Appendix B
An agent’s identity and persona live in one Markdown file instead of being passed flag-by-flag, the same shape Claude Code uses for subagents:
.cotal/agents/<name>.md---name: dave # → COTAL_NAME / card.namerole: builder # → COTAL_ROLE / card.role (presence + anycast address)description: … # → card.descriptiontags: [edit, test] # → card.tags ("what it can do")subscribe: [general, team.backend] # channels it reads at bootallowSubscribe: [general, team.>] # read ACL (omit = same as subscribe)allowPublish: [general, team.backend] # post ACL (omit = none, default-deny)model: opus # optional model overridevariant: high # optional connector-defined model variantcapabilities: [spawn] # control-plane capabilities (may start/despawn teammates)---You are a builder on a shared mesh of peer agents… ← the body is the personaFrontmatter is identity (an A2A-style AgentCard,
SPEC §6); the body is the persona, appended to
the session’s system prompt at launch: the one field that must be applied at launch,
because a session cannot change its system prompt afterward.
Fields
Section titled “Fields”Authoritative shape: agent-file.ts.
| Field | Type | Meaning |
|---|---|---|
name |
string, required | Display name → card.name. A launcher resolves a bare name to .cotal/agents/<name>.md. |
role |
string | The addressable service: presence label and the anycast address (SPEC §3). |
kind |
agent | endpoint |
Participation class; default agent. |
description |
string | One-line summary → card.description. |
tags |
string[] | Capability tags → card.tags. |
subscribe |
string[] | The active read set: channels subscribed at boot (mutable at runtime via join/leave). Must be ⊆ allowSubscribe. Default [general]. |
allowSubscribe |
string[] | The read ACL: channels it may read. Wildcard subtrees allowed (team.>). Omitted ⇒ same as subscribe. |
allowPublish |
string[] | The post ACL: channels it may publish to. Omitted ⇒ deny; posting is the dangerous capability, declare it explicitly. |
quiet |
string[] | Per-channel attention default: delivered but never wakes this agent (per-channel dnd). Concrete channels within the read ACL. |
muted |
string[] | Per-channel attention default: dropped on receive, @mentions included. |
model |
string | Model override handed to the agent CLI (Claude: opus / full id; OpenCode: provider/model). |
variant |
string | Connector-defined model variant (e.g. an OpenCode variant, see cotal models). |
capabilities |
string[] | Control-plane capabilities minted into the cred. spawn is the only one today: it grants the privileged control subject (spawn / named stop / persona definition), default-deny when absent, enforced by the broker, not a handler. |
owner |
string | Policy, not content: set once by definePersona (owner = creator); only the owner (or admin) may redefine the file over the wire. Never write it by hand. |
| (any other key) | string | Kept verbatim in meta so a connector can read its own launcher hints without core knowing them. |
The three channel verbs on one card, with the common recipes:
Channels & permissions. Attention semantics (quiet /
muted are one-way defaults; the runtime toggle is per-instance and resets on restart):
Connect Claude.
Discovery and resolution
Section titled “Discovery and resolution”- By name. A launcher resolves a bare name to
.cotal/agents/<name>.md(project catalog). This is a directory convention, not an HTTP well-known; mesh discovery stays NATS presence. The card built from the file is what gets broadcast. - One ref. The launcher sets
COTAL_AGENT_FILE=<abs path>(the who) the wayCOTAL_LINKcarries the where; the joined session reads its card straight from the file. IndividualCOTAL_*vars still override it (config). - Defaults. A bare
cotal spawnuses thedefaultpersona (COTAL_DEFAULT_PERSONAchanges the fallback); the harness comes from--agent/COTAL_DEFAULT_AGENT, else Claude. An explicit flag always wins over the file (run a mesh).
Every launcher consumes the file the same way; they differ only in how they run the spec:
| Launcher | How to point at a file |
|---|---|
Manager (cotal spawn --detach dave) |
auto-discovers .cotal/agents/dave.md in the manager’s workspace, or --config <persona-or-path>; same grammar as foreground (--model, --variant, --cwd, --prompt, ACL overrides, --share-tools). |
Foreground (cotal spawn dave) |
same resolution; the real agent TUI takes over this terminal. Works from any directory via the mesh registry. |
.cotal/ is gitignored (user-local, like .claude/); commit persona files you want
shared some other way. The demo ships committed examples under
examples/01-lateral-coordination/agents/.
Personas: short contracts, not titles
Section titled “Personas: short contracts, not titles”Expert-persona prompts (“you are a world-class…”) do not reliably improve accuracy. Keep the body to what the agent does and how it coordinates; a persona that needs facts should point at the source (the repo’s docs, a URL), not assert them.
Defining one at runtime
Section titled “Defining one at runtime”cotal_persona(name, prompt, model?) sends a persona to the manager, which writes the
same file and announces it; a later cotal_spawn(name, role?, agent?, model?, variant?)
brings it online, so a peer can mint a teammate with no hand-written file
(tool catalog). The write path takes content only (model /
persona); role, allowPublish, capabilities, and owner are policy and have no
slot, so a peer cannot grant itself a capability by redefining a file.
The operator-side counterpart is cotal personas (list / show / edit / new / rm); it
reads and writes the same files directly, offline, no mesh (CLI).