Reference: describes the TypeScript reference implementation (the
cotalCLI), not the wire contract. · For: operators · Wire contract: SPEC
cotal is the operator command line for the reference implementation: bring a mesh up, mint
identities, launch agents, watch what they do, and tear it all down. It is a thin client over the
wire contract: the normative subjects and schemas live in the SPEC; this page is
lookup material for the commands, not a walkthrough; if you are new, start with
Getting started.
npm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)
cotal --help # every command, grouped
cotal --version # cotal-ai version + each installed extension's (also `cotal -v`)
cotal <command> --help # one command's flags and usagenpx cotal-ai <command> runs it without a global install; in a dev clone, pnpm cotal <command>
runs it through tsx with no build step. Bare cotal prints help. Every command generates its own
--help, usage, and shell completion from its declared flags.
Commands come from the surfaces the binary composes: the base mesh CLI, the manager
(supervise), and the delivery daemon (deliver), plus any operator-installed extensions.
cotal ext add <npm-package> installs any registry providers a package contributes: commands,
runtimes, and local process lifecycle descriptors. The web dashboard and optional manager
runtimes ship this way.
| Area | Command | Purpose |
|---|---|---|
| Set up & lifecycle | setup |
Guided, configure-only setup (installs, seeds personas; launches nothing) |
| Set up & lifecycle | update |
Reconcile first-party extensions and check or opt into a coherent CLI upgrade |
| Set up & lifecycle | up |
Start a local mesh (nats-server + JetStream), or boot a whole manifest with -f |
| Set up & lifecycle | down |
Stop the whole stack, selected registered components, or a manifest deploy |
| Set up & lifecycle | backup |
Create an offline full-space or registry-only artifact from a preserved cut |
| Set up & lifecycle | clean |
Configurable cleanup: purge history (live), or wipe the local store / identity (stopped) |
| Set up & lifecycle | meshes |
List the running meshes on this machine |
| Set up & lifecycle | use |
Set the default mesh a bare cotal spawn joins |
| Set up & lifecycle | status |
Read-only diagnostics for setup, processes, and the selected mesh |
| Agents & personas | spawn |
Launch an agent from a persona (foreground, or --detach via the manager) |
| Agents & personas | models |
List connector model catalogs and variants from the manager |
| Agents & personas | ps |
List managed agents and their mesh status |
| Agents & personas | stop |
Ask the manager to stop a managed agent |
| Agents & personas | attach |
Stream and drive a managed agent's terminal (pty runtime) |
| Agents & personas | personas |
List, show, edit, create, or remove local personas |
| Agents & personas | supervise |
Run a manager daemon (the agent supervisor / control plane) |
| Agents & personas | runtimes |
List the agent runtimes the manager can spawn through and whether each is reachable |
| Messaging & watching | endpoints |
List every endpoint in the live presence roster, including infrastructure |
| Messaging & watching | send |
Send one message, then exit: DM a peer, post a channel, or ask a role |
| Messaging & watching | channels |
Inspect or set the channel registry |
| Messaging & watching | history |
Clear retained message history |
| Messaging & watching | console |
Live protocol view for a space (TUI, or --plain line stream) |
| Messaging & watching | web |
Browser dashboard (installed as the @cotal-ai/web extension) |
| Auth & meshes | mint |
Mint a creds file for a space (static auth mode) |
| Auth & meshes | login |
Sign in to a per-user-auth mesh's IdP (once per machine) |
| Auth & meshes | logout |
Revoke the IdP session and clear the cached login |
| Auth & meshes | actor |
Manage a user-auth space's actor ledger (grant / revoke / list) |
| Auth & meshes | doctor |
Credential-health diagnosis and repair (doctor auth) |
| Auth & meshes | join |
Join a space as your own presence (interactive) |
| Manifest | topology |
Validate and view a mesh manifest's access graph (read-only) |
| Extensions & misc | ext |
Install / remove operator CLI extensions |
| Extensions & misc | completion |
Print or install shell completion |
| Extensions & misc | feedback |
Send feedback to the Cotal developers |
| Extensions & misc | deliver |
Run the server-side Plane-3 delivery daemon |
| Extensions & misc | feedback-intake |
Run a self-hosted feedback intake server |
The manifest modes of up, spawn, and down (-f <cotal.yaml>) plus topology are covered
together under Manifest deploys.
cotal setup [--full] [--demo] [--yes]| Flag | Default | Meaning |
|---|---|---|
--full |
off | Redo the full guided flow (implies --demo) |
--demo |
off | Also seed the guided expert team (david, sven, me) |
--yes, -y |
off | Non-interactive accept-all (for agents / CI) |
Guided setup is configure-only: it checks prerequisites, installs the Claude Code plugin, and
seeds persona files, and it launches nothing (no mesh, no web, no manager). First run gets the
narrated flow; later runs print a status card. By default it seeds one default persona; the
david/sven/me team is opt-in via --demo. See Getting started and, for
maintainers, setup internals.
cotal update [--self]| Flag | Default | Meaning |
|---|---|---|
--self |
off | If a newer release exists, install that exact validated cotal-ai version globally and reconcile through the newly installed binary |
Without --self, update keeps the installed first-party surfaces coherent with the running
binary: it force-reconciles the four built-in connectors, then reinstalls other @cotal-ai/*
operator extensions at the binary's exact version. Each extension runs in an isolated child, so one
failure cannot poison later replays. It then checks npm; a newer binary is an informational notice
with cotal update --self as the next command, not an automatic install.
With --self, the npm check happens first. When a newer release exists, Cotal installs the exact
version it validated, resolves and verifies that package in npm's global root, then launches that
binary to reconcile connectors and first-party extensions to the new generation. An npx or dev-clone
invocation therefore installs and continues through a separate global copy; it never claims the
already-running process changed. If the binary is current, --self performs the normal local
reconcile without reinstalling it.
Third-party extensions are listed with their installed version and recorded spec but are not
auto-updated in v1. Floating third-party updates require @cotal-ai/* peer-range validation and are
a future follow-up. A failed connector/extension install, npm metadata check, or requested global
install is reported and makes the command exit nonzero. Independent extension attempts continue so
the output includes every failure; an unavailable npm registry does not undo a completed local
reconcile, but the command still exits nonzero because it could not establish that the install is
current.
cotal up [--detach] [--open] [--space <s>] [--server <url>] [--channels <path>] [--runtime <name>]
cotal up --restore <dir> [--restore-only registry] [--accept-missing-source]
cotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]| Flag | Default | Meaning |
|---|---|---|
--server <url> |
auto (free local port) | Listen URL override |
--host <host> |
— | Bind host override. With no --server, the broker URL is derived from it, so --host <addr> alone is enough to make a mesh reachable at that address; a --host/--server pair naming different addresses is refused. A wildcard bind (0.0.0.0, ::) keeps a dialable loopback URL. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote attach working |
--space <s> |
the folder's name | Space name |
--store-dir <dir> |
— | JetStream store directory |
--channels <path> |
.cotal/channels.json if present |
Channel-registry seed file (JSON). An explicit path that is missing is an error |
--restore <dir> |
— | Restore a completed offline backup before exposing the normal listener |
--restore-only registry |
artifact selection | Restore only the registry component |
--accept-missing-source |
off | Explicit disaster consent when the inode-bound preserved source is absent |
--open |
off (auth) | Unauthenticated dev mesh: no JWT, no ACLs |
--user-auth |
off | Per-user auth: people cotal login; connects are authorized against the actor ledger |
--idp <url> |
— | With --user-auth: the IdP auth base URL to pin on first enable |
--detach |
off | Run in the background (stop with cotal down) |
--file <cotal.yaml>, -f |
— | Launch a whole mesh from a manifest |
--dry-run |
off | With -f: print the plan, mutate nothing |
--runtime <name> |
pty (or the manifest's, with -f) |
Agent runtime for the mesh manager (pty built in; others are installed extensions, explicit-only). Resolved + probed before the broker starts; an uninstalled/unreachable runtime fails loud. With -f, overrides the manifest's runtime |
cotal up boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and
per-agent ACLs; --detach records the mesh so cotal spawn from any directory can find it. With no
--server, it auto-selects a free port if the default address is taken; an explicit --server
stays fail-loud on collision. --detach also brings up the control plane (delivery daemon in auth
mode, then the manager). The -f form is a manifest deploy; see
Run a mesh.
--user-auth --idp <url> starts the space's auth service alongside the broker (the NATS
auth callout plus the loopback token exchange); it is torn down with cotal down, and a
re-run of cotal up heals a dead service on a running broker. --user-auth and --open
contradict each other and are refused loudly; a running broker cannot change auth mode
without a cotal down first. See identity & auth.
cotal down
cotal down --preserve-state [--store-dir <dir>]
cotal down manager [delivery auth web nats ...]
cotal down web [--space <name>]
cotal down -f <cotal.yaml> | --run <id> [--dry-run]| Flag | Default | Meaning |
|---|---|---|
--file <cotal.yaml>, -f |
— | Tear down this manifest's deploy |
--run <id> |
— | Tear down one spawn -f run by id |
--space <name> |
current mesh | With components: the mesh whose target-addressed components (e.g. web) to stop |
--dry-run |
off | Print the manifest teardown or selected components, mutate nothing |
--preserve-state |
off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish ready |
--store-dir <dir> |
.cotal/nats |
With --preserve-state: the actual store path (required for a custom store) |
Bare cotal down stops the whole local stack in dependency order. Positional component names stop
only those self-registered local processes; for example, cotal down manager leaves delivery and
the broker running, and cotal down web is available when the web extension is installed. A
component that starts target-resolved (the web dashboard) is stopped the same way: cotal down web
resolves the mesh exactly like cotal web (registry current mesh first, --space to name one), so
it works from any directory; the other components always stop under the folder you run it in. The
-f / --run forms tear down a manifest deploy without stopping the whole mesh
and cannot be combined with component names. Stopping nats alone is refused while an unselected
registered daemon is still live; include those components or use bare cotal down.
Normal down remains destructive at the logical identity/durable layer. --preserve-state is a
different maintenance transition: it suppresses leave/deprovision cleanup, persists the manager's
same-principal resume inventory, stops the entire stack without removing run/auth artifacts, and
publishes a stable inode-bound cut only after every recorded process is proven stopped and the exact
recorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The
attempt is bound durably before the manager is fenced, the resume document and attempt-bound
cut-intent are fsynced before manager commit, and the manager's commitment itself is journaled
(cut-committed) before any process stops. A retry after a crash at any of those boundaries reuses
the exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without
needing the (by then intentionally dead) manager. A partial cut never publishes ready. It cannot
be combined with component names, manifest teardown, or --dry-run.
cotal clean <history|store|all> --force
cotal clean restore-attempt --attempt <id> --force
cotal clean restore-fallback --attempt <id> --force| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | history: target mesh |
--dms |
off | history: also clear DM history |
--store-dir <dir> |
.cotal/nats |
store/all: JetStream store directory |
--force |
— | Required: destructive, no prompting |
--attempt <id> |
— | restore-attempt: exact stale pre-commit attempt; restore-fallback: matching healthy committed restore |
One configurable cleanup verb; every target requires --force.
historypurges the retained message backlog on the running broker (channels, plus DMs with--dms). The same operation ashistory clear, which stays as an alias.storedeletes the stopped mesh's JetStream store (.cotal/nats): streams, durable consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables minted by an older, incompatible Cotal generation surviving adown/upcycle.allisstoreplus the space identity (.cotal/auth), the local creds and markers tied to it, any crash residue a normaldownwould have swept (stale pidfiles,run/), and the mesh's registry entry; the nextcotal upmints a fresh identity.
history needs the mesh up; store and all refuse while any recorded mesh process is still
alive or any same-root recorded broker endpoint remains reachable (run cotal down first). They
also refuse outright on a root that holds accounts for several spaces: the store and the broker
trust record are shared by every space on the broker, so both targets would take out all of them
and no --space can narrow that. down, backup and up --restore refuse there for the same
reason. cotal status lists the tenants on such a root. Personas
(.cotal/agents) and logs are never touched. A custom
store location is not recorded anywhere, so --store-dir must repeat whatever the mesh was
launched with. Custom cleanup targets must contain either the Cotal store-generation marker or a
real jetstream/ store directory; filesystem roots, project roots, and Cotal auth/maintenance trees
are always refused.
store and all also refuse every maintenance journal state. After a healthy committed restore,
restore-fallback is the only supported way to remove the recorded unchanged old-store inode; it
never deletes the active target, requires both the exact attempt id and --force, and retires the
completed restore journal so a later down --preserve-state can start a new backup cycle.
cotal down --preserve-state [--store-dir <dir>]
cotal backup create <dir> [--only full|registry] [--store-dir <dir>]
cotal up --restore <dir> [--restore-only registry] [--accept-missing-source]Backup is offline-only. It requires the stable ready record from down --preserve-state, an exact
store match, no live recorded process, and an unreachable exact endpoint from the recorded cut.
That endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile
is still refused. It claims the cut, reflink/copies the stopped source to a
private attempt clone, and opens only that clone on a random loopback bootstrap broker with an
independent parent/deadline watchdog. It validates the canonical stream and pull-consumer inventory,
writes native snapshots with consumers excluded, and stores conservative contiguous ACK-floor
checkpoints separately. The original store is never opened by the backup broker, and the stack is
not restarted implicitly. Artifact destinations must not overlap the preserved source or maintenance
attempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the
preserved source, or the maintenance attempt tree.
full is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, and
validated durable checkpoints. registry is the sole partial artifact. Presence, derived membership
feed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor
ledger files are excluded. Artifacts are exclusively created 0700; snapshot/checkpoint files and
the manifest are 0600; manifest.json is written last with exact sizes and SHA-256 values. The
directory is trusted operator input: hashes detect corruption, not malicious rewriting.
Restore validates and stages the exact allowlisted artifact bytes before moving or creating a store.
It requires the same space and existing trust state. The whole pre-commit window holds a journaled
liveness claim (coordinator, watchdogs, brokers, absolute deadline): ordinary up and a repeated
up --restore refuse while the claim is live, and a stale attempt is recovered only after the
deadline has elapsed and every recorded owner is proven dead — automatically by a retried
up --restore, or explicitly with cotal clean restore-attempt --attempt <id> --force. Nothing
ever rolls back a live attempt. A registry-only artifact restores as registry-only whether or not
--restore-only registry is passed; omitted infrastructure is always created and the exact
post-restore stream inventory is asserted before commit intent. Ordinary up from a preserved cut
resumes only the exact recorded source store and runtime; a contradicting --store-dir or
--runtime fails in preflight. Authenticated restores validate the complete
space trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;
full restores commit to the validated operator, system-account, data-account, and active-signer root
chain in addition to the static/user authority fingerprint. The composed commitment is revalidated
immediately before store mutation and never includes secret seeds. Restore never creates fresh auth.
Same-path restores atomically retain the old
source at the journaled fallback path; alternate targets retain it in place; a missing canonical
source needs explicit --accept-missing-source. Quarantine and target restores use current canonical
configs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a
commit-intent immediately before the normal listener starts. Archive bytes never instantiate the real
target: after quarantine validation, every stream is re-snapshotted from the validated quarantine
state into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back
the attempt-owned target; after it, ambiguity preserves both stores and records forward-repair
recourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.
Bootstrap brokers in every auth mode — including open — mount the store under a local account with
random operation-specific logins only, each carrying the exact per-phase subject permission matrix;
normal static credentials and user-auth sentinel/bearer connections are rejected, and no auth
service or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,
restore initiation, exact upload id, validation, and each checkpoint recreation use separate exact
authorities. Every checkpoint carries the source stream's message/first/last sequence state and must
match its snapshot record before mutation; core then derives and validates the only allowed start
policy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical DeliverAll
WorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a
start-sequence policy there. Registry-only restore creates every omitted canonical stream and transient
bucket on the isolated target before the normal listener is exposed. It deliberately does not resume
retained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and
stopped rather than being reprovisioned into a partial restore.
After listener readiness, the manager starts attempt-bound, validates retained credentials/tokens
without granting or reprovisioning, and resumes the exact persisted principals under cleanup
suppression. Registry-only restore uses the same flow with an empty agent set. commitResume is an
idempotent validation barrier only: success must be awaitingFinalize with an attempt-bound 64-hex
commit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that
exact evidence as manager-committed (restore) or resume-committed (ordinary resume), then calls
token-bound finalizeResume; only an active response for the exact token releases suppression. The
CLI records the same token in finalization evidence before a restore becomes active, or before an
ordinary resume retires and consumes the marker. Re-entry from either committed state skips the prior
idempotent activation/commit phases, retries finalization with the durable token, and finishes the
workspace transition. Failure before finalization preserves the committed state and cleanup
suppression; it is not rewritten through a degraded transition. Re-entry between any two earlier
boundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or
changed per-agent dependency is a named fail-closed result; the journal becomes degraded and remains
available for forward repair. A retry from resume-intent,
resume-active, or resume-degraded reuses the same attempt and inventory after the prior listener is
proven stopped. Every normal restore listener has an unguessable attempt-bound NATS server name. The
CLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity
immediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID
record, endpoint, and target identity all match that proof; degraded restore repair then moves through
the guarded workspace transition only after manager commit. If an uncommitted bound owner is provably
dead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the
same attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener
or ambiguous owner is preserved and refused, never adopted by reachability alone. A reconstructed
commit/degraded attempt without either the exact bound proof or a durable dead-listener replacement
record fails closed even when the recorded port is free. A later ordinary startup may pass an active
restore only when its details prove manager commit and its exact recorded listener is dead.
cotal meshes
cotal use <space>
cotal status [--space <s>] [--server <url>]meshes lists the running meshes on this machine; a * marks the current default a bare
cotal spawn joins. use <space> sets that default; the selection applies from every directory,
including inside another mesh's project. status is a read-only report: machine prerequisites
(starting with the installed cotal-ai version), the installed extensions and their versions, this
folder's .cotal/, the recorded meshes, and a live snapshot of the selected mesh (roster, channels,
membership feed). status takes only --space / --server to pick the mesh to inspect; it starts
nothing.
cotal spawn [<persona>] [--detach] [--name <n>] [--agent <a>] [--model <m>] [--variant <v>] [--prompt <text>] [--cwd <dir>]
cotal spawn -f <cotal.yaml> [--dry-run]| Flag | Default | Meaning |
|---|---|---|
--space <s> |
resolved mesh | Target space |
--server <url> |
registry entry | Broker URL override |
--creds <path> |
— | Control-caller creds for an off-registry manager (--detach only) |
--name <n> |
persona's name: |
Presence-name override (does not choose the persona) |
--config <persona-or-path> |
— | Persona catalog name or file path; wins over the positional |
--agent <a> |
COTAL_DEFAULT_AGENT, else claude |
Connector type (claude, opencode, hermes, …) |
--role <r> |
persona's role: |
Role override |
--model <m> |
persona's model: |
Model override |
--variant <v> |
persona's variant: |
Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |
--cwd <dir> |
this cwd | Working directory to root the agent at |
--prompt <text> |
— | Initial prompt auto-submitted at start |
--resume <id> |
— | Fork an existing session id into the mesh (claude only) |
--transcript / --no-transcript |
off | Mirror the session transcript to tr-<name> |
--share-tools <sel> |
none | Share named operator MCP servers with the agent |
--subscribe <a,b> |
persona's | Channel read-set override |
--allow-subscribe <a,b> |
= subscribe | Read-ACL override |
--allow-publish <a,b> |
deny | Post-ACL override |
--detach, -d |
off | Launch via the manager into a detached PTY (reattach with cotal attach) |
--file <cotal.yaml>, -f |
— | Deploy a manifest onto the running mesh |
--dry-run |
off | With -f: print the plan, mutate nothing |
--allow-stale <a,b> |
— | With -f: waive named stale agents (apply-only) |
--runtime <name> |
manifest's | With -f: override the manifest's runtime |
The persona (--config > positional > COTAL_DEFAULT_PERSONA > default) is loaded from the
target mesh's .cotal/agents/; the launch flags override the file. Foreground runs the agent
attached to your terminal; --detach hands the launch to the running manager. Both modes get the
durable backstop on a mesh that runs the delivery daemon; --live-only skips it for a foreground
spawn (messages posted while it is disconnected are then not replayed). A foreground exit retires
the agent's creds and broker footprint, like a manager despawn. See
Connect Claude Code and Agent files; -f is a
manifest deploy. (cotal start was merged into cotal spawn --detach.)
cotal models [--agent <connector>] [--refresh]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Which manager to reach |
--agent <connector> |
all registered connectors | Connector whose catalog to list |
--refresh |
off | Ask the connector to refresh its provider cache |
Asks the running manager for each connector's model catalog (model ids plus their variants)
for connectors that expose one (OpenCode today; a connector without a catalog says so). Pick a
result with cotal spawn --model <provider/model> --variant <v>.
cotal endpoints [--space <s>] [--server <url>] [--creds <path>]Lists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each
endpoint's role, kind, status, and current activity. Unlike ps, this is a read-only presence view;
it is not limited to child processes owned by the manager.
cotal ps [--space <s>]
cotal stop --name <n> [--space <s>]
cotal attach --name <n> [--space <s>]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Which manager to reach |
--name <n> |
— | Managed agent to stop / attach (required) |
These are operator clients over the running manager's control plane. ps lists managed agents with
their mesh status (starting… / working / waiting / offline); on a user-auth mesh it also
renders each managed agent's last credential-refresh outcome, fail-closed. attach streams and
drives an agent's terminal on the pty runtime; detach with the escape key (Ctrl-] by default; see
COTAL_DETACH_KEY). stop and attach need a running manager to talk to. On a
static mesh they are cross-agent admin operations. On a user-auth mesh, your own agents (any agent
under your owner) need only the spawn scope; another owner's agent needs admin on your ledger
row (identity & auth). Launch detached agents with
spawn --detach.
attach streams over the manager's own HTTP/WS face rather than the mesh. That endpoint binds
loopback by default, so nothing is exposed by accident; cotal up --host <addr> passes its bind
address down, which is what lets you attach to an agent whose manager runs on another machine. A
bare cotal supervise and an embedded manager stay machine-local. Set it directly with
supervise --console-host <host>.
That address is recorded on the mesh and carried forward, because it is a decision rather than
something later commands can work out for themselves (a broker dial address is not a manager bind
address). Every later manager launch for the same mesh reuses it — a same-root cotal up repair,
adopting a preserved or restored listener, a spawn -f manifest deploy — so a manager replacement
does not quietly move a reachable attach face back to loopback. Passing --host again overrides it,
so you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only
and records nothing.
Because that face carries terminal read and write for every managed agent, it is credentialed in two
tiers. A mesh caller receives a ticket bound to the single agent the manager just authorized,
single-use and short-lived, so one authorized attach can never be re-pointed at someone else's
agent. The console token is the operator's own, reaches every agent, and is printed only to the
manager's output. The roster, the live feed, and the PTY stream all answer 401 without one; the
static console shell is served openly, since it describes no agent.
cotal personas list [-v] [--running]
cotal personas show <name>
cotal personas edit <name>
cotal personas new <name> (--prompt <t> | --from <f>) [--role <r>] [--model <m>]
cotal personas rm <name> --force| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Which mesh's persona catalog |
--role <r> |
— | new: the persona's role |
--model <m> |
— | new: the persona's model |
--prompt <t> |
— | new: the persona's prompt text |
--from <f> |
— | new: seed the prompt from a file |
--verbose, -v |
off | list: include role / model / description |
--running |
off | list: mark personas live on the mesh |
--force |
— | rm: required, delete without prompting |
Personas are the local agent files under .cotal/agents/ that cotal spawn launches. See
Agent files for the file format.
cotal supervise [--runtime <name>] [--space <s>] [--server <url>] [--spawn <names>]| Flag | Default | Meaning |
|---|---|---|
--space <s> |
this folder's auth space | Space to supervise |
--server <url> |
the local mesh | Broker URL |
--runtime <name> |
pty |
Agent runtime (pty built in; extension runtimes are explicit-only) |
--console-port <n> |
— | Protocol-console port |
--console-host <host> |
loopback | Bind host for the console + attach endpoint. Loopback keeps it machine-local; cotal up passes the address it bound the broker to, which is what lets cotal attach reach this manager from another machine |
--roster <file> |
— | Declarative roster to boot at startup |
--launch <spec> |
— | Resolved manifest launch spec (from up -f / spawn -f) |
--spawn <names> |
— | Comma-separated personas to pre-spawn at startup |
The manager is the agent supervisor and control plane: it answers spawn --detach, stop, ps,
attach, and the cotal_* manager tools. cotal up --detach starts one for you; run supervise
directly to recover a dead manager or drive a custom runtime. Default runtime is pty; install an
optional provider first (cotal ext add @cotal-ai/orca, @cotal-ai/tmux, or @cotal-ai/cmux) and
select it explicitly. A missing provider or app fails loudly; there is no fallback. See Deploy.
cotal runtimesLists every agent runtime the manager can spawn through: the built-in pty, the official providers
(orca, tmux, cmux), and any custom provider installed via cotal ext add. Each installed
provider is probed so you can see what is actually reachable on this machine before selecting it:
pty built in
orca installed · reachable @cotal-ai/orca
tmux available · cotal ext add @cotal-ai/tmux
cmux available · cotal ext add @cotal-ai/cmux
installed · reachable / unreachable is the provider's own available() probe; available means
it is a known runtime you can add with the shown command. Selecting an unknown or uninstalled runtime
via up/spawn --runtime <name> fails loud and, for a known one, points at the exact cotal ext add
package — there is no silent fallback to pty.
cotal send dm <agent> "<text>" [--space <s>] [--server <url>] [--creds <path>]
cotal send msg <channel> "<text>"
cotal send ask <role> "<text>"| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Which mesh, and (off-registry) which credential |
One-shot messaging: connect, send a single direct message (dm), channel post (msg), or role
ask/anycast (ask), then exit. For a running conversation, agents use the mesh tools instead
(MCP tools).
cotal channels list
cotal channels set <name> [--replay | --no-replay] [--window <n>] [--desc <s>] [--instructions <s>]
cotal channels default --replay | --no-replay| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Target mesh |
--replay / --no-replay |
— | set/default: replay history to new joiners, or not |
--window <n> |
— | set: replay window size |
--desc <s> |
— | set: one-line channel description |
--instructions <s> |
— | set: instructions shown to joiners |
Inspects and edits the channel registry: replay policy, description, and joiner instructions. ACL
semantics (who may read or post) are set at mint / provision time, not here; see
Channels and permissions. On a user-auth mesh, list rides your
own login as is; set and default edit the registry over a short-lived
channel-writer view, which needs ledger scope admin (Identity & auth).
cotal history clear --force [--dms] [--space <s>]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Target mesh |
--dms |
off | Also clear DM history |
--force |
— | Required: clear without prompting |
Purges retained channel history; --dms extends it to direct-message history. An alias of
clean history. On a user-auth mesh the purge rides a short-lived purger view over
your login, which needs ledger scope admin (Identity & auth).
cotal console [--plain] [--space <s>]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Space to watch |
--plain |
off | Line stream instead of the TUI |
A live protocol view for a space: a lazygit-style TUI, or a plain line stream on --plain. On a
user-auth mesh it rides the read-only admin view over your login, which needs ledger scope
admin. See Watch a mesh.
cotal web [--detach] [--port <n>] [--no-open] [--space <s>]| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Space to serve |
--port <n> |
7799 |
HTTP port |
--detach |
off | Run in the background; stop with cotal down web or bare cotal down |
--no-open |
off | Don't open the browser |
The browser observability dashboard: presence, channels, and a live feed. It is not part of
cotal up: it ships inside cotal-ai as the @cotal-ai/web extension, seeded automatically on first
run (like the built-in connectors) so it always matches your CLI version. It self-registers cotal web
into this surface and serves
http://cotal.localhost:7799 (loopback; *.localhost resolves in Chrome/Firefox/Edge; Safari may
need http://127.0.0.1:7799). On a user-auth mesh the dashboard rides the read-only admin view
over your login, and a channel purge asks for its own channel-purger view per click; both need
ledger scope admin. Detached mode re-execs the current Cotal installation, writes diagnostics to
the mesh root's .cotal/web.log, and reports success only after the HTTP server answers. It requires
a recorded mesh root, but can be launched from any directory once cotal up has recorded the mesh.
See Watch a mesh.
cotal mint <name> [--profile <agent|observer|admin>] [--out <path>] [--signer]| Flag | Default | Meaning |
|---|---|---|
--profile <agent|observer|admin> |
agent |
Credential profile |
--out <path> |
.cotal/auth/creds/<name>.creds |
Output path |
--signer |
off | Emit a stripped account-signing file instead |
--force |
off | With --signer: overwrite an existing file |
--allow-subscribe <a,b> |
profile default | Read-ACL override |
--allow-publish <a,b> |
profile default | Post-ACL override |
Mints a NATS creds file for a space in static auth mode, scoped to a profile and (optionally)
explicit read/post ACLs. --signer emits an account-signing file for delegating minting to another
host. A per-user-auth space refuses mint: agents there join under a logged-in user
(login + actor grant), never via a handed-out creds file. See
Identity and auth.
cotal login --idp <auth base URL> [--client-id <id>]
cotal logout --idp <auth base URL>Signs you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it
once per machine. It prints your IdP subject, the id the operator grants against. After a
login, every command on that mesh works under your identity: each connect takes a fresh IdP
proof, exchanges it locally for a short-lived bearer, and is authorized against the actor
ledger at connect time. logout revokes the IdP session and clears the cache. See
identity & auth.
cotal actor grant <actor> --sub <IdP subject> [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>]
cotal actor revoke <actor> (--sub <IdP subject> | --owner <u_…>)
cotal actor list| Flag | Default | Meaning |
|---|---|---|
--space <s> |
the folder's | Space whose ledger to manage |
--sub <subject> |
— | The IdP subject (shown by cotal login) the actor belongs to |
--owner <u_…> |
— | The derived owner token (alternative to --sub) |
--scope <a,b> |
spawn,role:default |
Capability scope ('' = none; spawn = may run agents, role:<r> = may delegate role r, admin = cross-agent control) |
--allow-subscribe <a,b> |
> (all channels) |
Channel read ACL; the user's envelope, their agents can never read beyond it |
--allow-publish <a,b> |
> (all channels) |
Channel post ACL; also the envelope for their agents' posting |
--role <r> |
— | Role (scopes the task-queue consumer) |
--label <l> |
— | Display label for actor list (never the IdP subject) |
The actor ledger is the single authorization source of a user-auth space: no row, no access.
A bare grant is the full envelope (all channels, may spawn); the flags narrow it. A
re-grant replaces the row, so to add a capability, re-grant with it added to the current
scope (cotal actor list shows what a row holds). revoke denies the next exchange and the
next connect with no restart, and evicts the principal's live connections. Managed-agent rows
(written by the spawn path) live in a disjoint row space this command never touches. See
identity & auth.
cotal doctor auth [--fix]Credential-health diagnosis and repair for this folder's mesh: renders every managed
credential as healthy / near-expiry / expired and ends in healthy or the exact next
command; --fix applies the repairs it can. The one surface every stale-credential error
points at.
cotal join --space <s> --name <n> [--role <r>] [--channel <c>]
cotal join --link <url> | --token <t>| Flag | Default | Meaning |
|---|---|---|
--space <s> / --server <url> / --creds <path> |
resolved mesh | Which mesh, and which credential |
--name <n> |
— | Your presence name |
--role <r> |
— | Your role |
--channel <c> |
— | Channel to join |
--kind <k> |
agent |
Endpoint kind |
--link <url> |
— | Join link (cotal://…) |
--token <t> |
— | Join token |
--lifecycle-uid <uid> |
— | Required with --creds: the lifecycle UID minted alongside the credential (COTAL_LIFECYCLE_UID works too). A credential's durable grants name exact lifecycle-keyed resources, so join refuses to invent one |
--tls |
off | Connect over TLS |
An interactive presence: join a space under your own name and role, without launching an agent
harness. A --link or --token supplies the where and the auth in one value. See
Spaces and Identity and auth.
A cotal.yaml manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.
Three commands consume it, plus a read-only validator:
cotal up -f cotal.yaml # boot a fresh mesh from the manifest
cotal spawn -f cotal.yaml # deploy the manifest additively onto a running mesh
cotal down -f cotal.yaml # tear that deploy down (or --run <id> for one run)
cotal topology view -f cotal.yaml # validate + view the access graph, change nothingup -f and spawn -f differ in target: up -f brings up a new broker and applies the manifest;
spawn -f requires an already-reachable mesh and applies additively (ownership-scoped). On a
user-auth mesh, spawn -f deploys over your own login (the deployer view, gated on ledger scope
spawn): the manifest's agents land under your owner, a manifest claiming another owner is
refused, and seeding new channels additionally needs scope admin. Both take
--dry-run to print the plan without mutating anything. topology validates the manifest and
renders its channel / role / ACL graph. See Define a team and the
manifest reference.
cotal ext # same as `list`
cotal ext add <npm-package>
cotal ext remove <name>
cotal ext list
cotal ext root # print just the install prefix (scriptable)
cotal ext seed [--repair|--reset|--force]Operator-installed extensions: add installs an npm package into a cotal-owned prefix and records
every registry provider it contributes. Commands appear in help, completion, and dispatch; runtime
providers are lazy-loaded by commands such as supervise; local process providers participate in
status and selective down. remove and list manage them. The @cotal-ai/web dashboard is the
canonical command/process example. Installed packages and their location are described in
config.
Bare cotal ext lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm
root kept separate from npm's own global tree, so these packages never show up in npm list -g —
cotal ext (or the Extensions section of cotal status) is the canonical inventory. cotal ext root
prints only the path, for scripts. The versions shown are the manifest pin recorded at add time.
Removing an extension that owns a running local process is refused with the mesh root and its
cotal down <component> command; stop it first so uninstalling the package never strands a process
whose lifecycle provider is gone.
The first-party agent connectors (claude, opencode, codex, hermes, pi) are not compiled into
the binary. They are seeded on first run through the same ext add path a third party uses, and
appear in cotal ext list like any other extension. So you can remove one you do not want
(cotal ext remove @cotal-ai/connector-hermes), and a deliberately-removed connector STAYS removed
across upgrades. cotal ext add <your-package> adds a third-party connector the same way. The web
dashboard (@cotal-ai/web, providing command:web) is a fifth built-in seeded on the same path.
cotal ext seed is the maintenance entry for that seeding (it runs automatically on the first real
command of each boot, so you rarely call it):
| Flag | Meaning |
|---|---|
| (none) | Reconcile: seed any never-seeded built-in, refresh a seeded one whose version the binary bumped, leave a removed one removed. A no-op once current. |
--repair |
Recover after an interrupted seed or a lost authority (rebuilds the interrupted connector; restores the removed-vs-never-seeded record from its durable backup). |
--reset |
Discard the record and re-seed all six built-ins (the five connectors plus the web dashboard). Resurrects any you removed. Rebuilds cleanly over corrupt seed state. |
--force |
Re-seed the built-ins even when the version stamp is current or a downgrade. |
The default connector for a bare cotal spawn (no --agent) is claude; set COTAL_DEFAULT_AGENT
(e.g. opencode) to change it. An --agent naming a removed connector fails loud with the exact
cotal ext add to restore it. Set COTAL_SKIP_CONNECTOR_SEED=1 to turn off the automatic first-run
seed/refresh entirely (for a controlled or offline setup that manages connectors by hand); cotal ext seed still runs on request.
cotal completion <bash|zsh|fish|powershell> # print a stub to eval / source
cotal completion install [shell] # install it persistentlyPrints or installs shell completion. Completion candidates come from each command's declared flags and, where useful, live mesh state (spaces, personas, managed agents) resolved offline.
cotal feedback "<summary>" [--type <t>] [--email <e>] [--details <text>]| Flag | Default | Meaning |
|---|---|---|
--type <t> |
— | bug | idea | friction | praise | other |
--details <text> |
— | Longer free-form details |
--severity <s> |
— | low | medium | high |
--area <a> |
— | The part of Cotal this concerns |
--email <e> |
git email | Contact email (required on the keyless public path) |
--name <n> |
— | Your name (optional) |
--url <url> |
keyed / public intake | Intake URL override |
--key <k> |
COTAL_FEEDBACK_KEY |
Feedback key |
Sends feedback to the Cotal developers. With a key (--key / COTAL_FEEDBACK_KEY) it routes to the
keyed beta intake; without one it goes to the public cotal.ai intake and requires a contact email
(--email / COTAL_FEEDBACK_EMAIL, else your git email). Run a self-hosted intake with
feedback-intake.
Two long-lived infra roles ship with the CLI. They are not part of everyday operation; the delivery
daemon comes up automatically with cotal up --detach in auth mode.
cotal deliver --space <s> [--server <url>] [--creds <file>]
cotal auth-service --space <s> --server <url> [--port <n>]
cotal feedback-intake --keys <keys.json> [--port <n>] [--creds <file>]auth-service runs a user-auth space's identity plane (the NATS auth callout plus the
loopback token exchange and JWKS); cotal up --user-auth starts and supervises it for you,
so you run it directly only to recover one by hand.
deliver runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL
authority. It is auth-mode-only and single-instance (--shard/--shards accept only N=1);
--dev-mint mints a scoped cred from the local signer for standalone dev. See the
delivery daemon. feedback-intake runs a self-hosted feedback server
(requires --keys and a scoped --creds), announcing submissions into a space channel; flags
include --host/--port, --store, --space/--channel, --max-bytes, and --rate-limit.
cotal __complete <words…> is the internal entry the shell-completion stubs call to emit candidates
for the current command line; you never run it directly. cotal agent-bearer is machine-facing
plumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their
spawn-time secret; you never run it directly either. (cotal start is a removed tombstone: it
errors and points you to cotal spawn --detach.)