CLI options
How the unified CAFleet CLI (cafleet) accepts configuration parameters. This
page catalogs the arguments, conventions, and error strings.
Subcommand summary
One row per subcommand. The subject — the id a command acts on — is a
required positional argument (see Positional subject
ids); ids that describe a relationship rather than
the subject stay as flags. "Id flags" are member create's --fleet-id (the
fleet the new member joins) and the two-party pair --from-member-id
(sender) + --to-member-id (recipient) on the message commands.
Environment variables
Every CAFLEET_-prefixed variable cafleet reads:
A flag wins over its environment variable, and the environment variable wins over the hardcoded default.
The default database URL expands the home directory at startup. An explicit
CAFLEET_DATABASE_URL must be an absolute-path sqlite:/// URL; a literal
~ in an override is not expanded.
Global Options
--version, placed before the subcommand, prints cafleet <version> and
exits 0, short-circuiting before subcommand dispatch.
Output shapes
One row per subcommand. Text output is the human/pane form — message bodies
truncated per Message Body Truncation; the
machine form is --json.
setup, server, and the monitor loop form stream progress or run a loop
rather than emitting a one-shot payload, so they carry no output-shape row.
The message send row describes its exit-0 shapes; when an attempted pane
notification fails after persistence, the command instead exits 1 through
stderr — see
Notification outcome and partial failure.
Member list output
Member ping output
Monitor scan output
Text mode — one section per roster entry, separated by one blank line.
<name> is the raw DB value (stdout is not a keystroke path, so no
sanitization). kind uses the three-value member discriminator:
director, monitor, or member. The Director remains first.
An annotated entry (the pane token is — when no pane exists; a failed
capture keeps its real pane id):
JSON mode — a top-level array, same order, one object per entry
mirroring member capture's keys plus name / kind /
coding_agent / error, in this pinned key order:
The shared capture path retains typed scan results until the output branch. Only the text presenter builds the section headings; JSON presentation does not construct or discard them. Both modes preserve the current roster order, raw member names, line count, error annotations, and success timestamps.
On an annotated entry content, captured_at, and content_sha256 are
null; error carries the exact annotation string from text mode;
pane_id is null for a pending placement and the real pane id for a
failed capture. lines always echoes the requested depth.
JSON output (--json)
--json is the single output switch — a shared per-subcommand flag, placed
after the subcommand name, canonically trailing, after all other
arguments:
It switches the output to compact single-line JSON; non-ASCII (like the …
truncation suffix in an inline preview) is emitted as UTF-8, not escaped.
JSON is always the complete, untruncated machine form — full envelopes, full
message bodies; text output is always truncated per
Message Body Truncation. The trailing position
keeps JSON invocations inside the existing per-subcommand allow patterns (see
permissions.allow coverage).
The command index records JSON availability.
Commands marked no reject --json with the parser's unknown-argument
error (exit 2) — including the root group itself, so a
pre-subcommand cafleet --json <grp> <cmd> does not parse.
Positional subject ids
The id a command acts on rides as a required positional integer immediately
after the subcommand name: FLEET_ID on fleet show / fleet delete /
member list / both monitor forms, MEMBER_ID on member show / delete / prompt
/ ping / capture and message poll, MESSAGE_ID on message ack /
show. Everything else is derived from the subject row: a member id is
globally unique, so the member row names its fleet; a message row names its
recipient and fleet. A missing subject is the parser's native
missing-required-argument error, a non-integer its native invalid-value
error (both exit 2).
Ids are DB-assigned integers, typically 1–4 digits, pasted in full — there is
no prefix resolution and no environment default: a spawned member reads its
ids from the literal FLEET ID: / YOUR MEMBER ID: / DIRECTOR MEMBER ID:
lines rendered into its spawn prompt and passes them as literal arguments on
every command. Members pass the literal integer — never a shell variable —
because Claude Code's permissions.allow matches Bash invocations as literal
command strings (see
permissions.allow coverage).
Sender and recipient (--from-member-id, --to-member-id)
Two-party commands name both parties as flags — the roles need labels:
--from-member-id is the sender (message send, message broadcast) and
--to-member-id is the recipient (message send). Both are typed int
(a non-integer is the parser's invalid-value error, exit 2). The fleet is
derived from the sender row; a sender/recipient pair from different fleets
fails — see Error Messages. A pane-touching target must be
an active member with a placement row — see
Member targeting and key delivery.
permissions.allow coverage
The allow set is generated mechanically, one Bash(...) pattern per
allow-listed subcommand:
- One pattern per subcommand, matching the subcommand prefix —
Bash(cafleet <grp> <cmd> *). The positional subject id and trailing flags such as--jsonare covered by the same pattern. Bothmonitorforms ride the singleBash(cafleet monitor *)pattern —cafleet monitor scanneeds no pattern of its own. member promptis excluded so it stays underpermissions.ask— its positional text body is operator-controlled, in both the plain and the--shellform.
Apply the patterns to your user-level ~/.claude/settings.json manually; the
repo does not ship a committed permissions block.
Message Body Truncation
The four subcommands that emit a user-supplied delivery body —
cafleet message {send,poll,ack,show} — truncate the text body in text
output to the first CAFLEET_MAX_TEXT_LEN Unicode codepoints plus a single
… (U+2026) suffix. Length is measured in Unicode codepoints, never bytes.
There is no untruncated text form — the full body is available via
--json.
The limit is CAFLEET_MAX_TEXT_LEN — see
Environment variables.
This applies to CLI text emit sites only — the WebUI /api/* responses
(webui-api.md) and member capture content are untouched.
Text bodies (positional TEXT / --file)
message send, message broadcast, and member create take their body as a
positional argument (TEXT; named PROMPT on member create) with --file PATH as the alternative. Exactly one of the positional and --file must be
supplied — supplying neither or both is the parser's native
argument-group error (exit 2). --file - reads the body from stdin; use
--file to avoid placing the body in the initial CAFleet invocation. A
resolved spawn prompt still enters downstream backend/multiplexer argv, so
keep role-by-path prompts compact. The body is used verbatim (no stripping); an empty
or whitespace-only body is rejected uniformly across inline / file / stdin —
the error strings are in Error Messages.
member prompt also takes its text as a positional TEXT, but has no
--file alternative — its body is a one-line keystroke by contract.
cafleet setup — Onboarding and Schema Management
cafleet setup is a plain command — the single onboarding and
schema-management entry point: the recommended end-user path (see
Quickstart) and the migrations-apply path for
contributors and CI, idempotent and safe to re-run. Command help: Migrate the database schema and install the coding-agent assets (skills and presets). It takes no
positional arguments — a bare cafleet setup <word> fails with the parser's
unexpected-argument error, while a word following the flag (cafleet setup --coding-agent claude <word>) is greedily consumed as another flag value and
fails with the parser's invalid-value error unless it names an agent (both
exit 2).
The one flag is --coding-agent AGENT...: optional, multi-value
(space-delimited), and repeatable — --coding-agent claude codex and
--coding-agent claude --coding-agent codex are valid and equivalent. It is
a choice over claude / codex / opencode, with duplicates deduplicated
and an unknown value failing with the parser's invalid-value error
(exit 2). Its help text is
Install the named agent's assets (space-delimited, repeatable; default: all agents).
The command runs two halves, in order:
-
db half — initializes or migrates the registry database to the head of the migration chain embedded in the binary (idempotent). Each refusal message below becomes the db-half failure
<msg>;<M>/<N>are the integer migration versions:The unversioned and newer-schema refusals retain precedence. Before applying pending migrations, check existing members using the active-monitor index predicate; a new DB without members skips the diagnostic. Conflicting fleet ids and member ids are ascending. Pending migrations use one grouped transaction, so a failure rolls back every pending schema/history change. The index also rejects a duplicate introduced after the diagnostic. If a recheck after migration failure finds duplicates, use the same diagnostic; otherwise retain the original migration error. The DB half never manipulates panes or chooses which monitor survives. Recovery uses the prior compatible binary and may require restoring its assets first; see duplicate-monitor recovery.
-
assets half — installs, from the data embedded in the binary at build time with no network access, each selected agent's two skill directories plus its bundled preset where one exists (creating the agent's directories as needed) at the directories resolved per Config-dir resolution, and upserts one
asset_installsrow per installed agent keyed on(coding_agent, path)(see Assets half). The selection:
Validation applies uniformly wherever the assets half resolves an agent's
identity path — a targeted agent in the selector form, and every agent in
the no-flag form, which resolves all three identity paths: a config-path
validation failure fails the assets half with the pinned
validation error as <msg>.
The halves fail independently (db half failed: <msg> / assets half failed: <msg>); if any half that ran failed, the command exits 1 with the failed
halves joined by ' and '.
The assets-half pre-flight can fire only after a db-half failure or an
externally broken schema — the db half always runs first within the same
command. A DB-half failure does not automatically fail or skip the assets
half: if asset_installs remains usable, assets can be updated and recorded
even when duplicate monitors prevent the schema upgrade.
Config-dir resolution
Every surface that installs to or checks a backend's user-level config directory resolves it through the backend's native config-location environment variable, falling back to the default when the variable is unset:
opencode splits by purpose: agents/ is in OPENCODE_CONFIG_DIR's
documented search list, so the preset may relocate and remain a valid
--agent cafleet discovery path; skills are not in that list — opencode
discovers them only at fixed paths — so the skills install ignores the
variable.
Validation. A set variable must hold an absolute path. Any other value —
the empty string, a relative path, a literal unexpanded ~/… — fails at
resolution time with exit 1:
Validation is lazy: a variable is read and validated only when a site
actually resolves that backend's directory. cafleet setup --coding-agent claude with an invalid CODEX_HOME succeeds because the selector resolves
only the targeted agent's directory; plain cafleet setup resolves all
three identity paths, so an invalid variable fails its assets half. The
spawn preconditions themselves read
none of the three variables for claude and codex (PATH-check-only;
opencode's resolves the preset base) — but the
stale-assets guard fronting every fleet-scoped
command, member create included, resolves all three identity paths. One
exception to strict lazy failure: doctor catches per-agent resolution
errors and renders them as issues instead of aborting (see
doctor).
Recorded-path identity. Every surface that keys on "the agent's resolved
path" — the asset_installs rows, the
stale-assets guard, and doctor's setup column — uses
one canonical path per agent, the resolved base directory, stored absolute
exactly as resolved (no canonicalization beyond the absolute-path
validation):
Assets half
Each agent's preset, where one exists, is a static file embedded in the binary next to the skills; install targets resolve per Config-dir resolution:
Each backend replaces the two embedded skills sequentially, removes a leftover
cafleet-research entry, then replaces its preset where present. Existing targets
are deleted before their replacements are written. Unrelated skills remain
untouched. A symlink target is unlinked as an entry;
its referent is not deleted. Skills-operation errors retain failed to install skills into <skills_dir>: <error> and preset-operation errors retain failed to install preset into <target>: <error>.
The command prints each success line after that operation completes, then records the installed version after all operations succeed. The preset line appears only for codex and opencode:
Shared diagnosis and connection reuse
Guards and command work share the invocation's SQLite connection. Setup attempts both halves and reuses an open connection; doctor reports every section and reads installed versions only at schema head. CLI presenters retain the documented output and guard order.
Schema-version guard
Every non-setup command — the fleet, member, and message groups, the
monitor command (both forms), and server — classifies the database
schema against the head of the migration chain embedded in the binary before
its command body runs, and before the
stale-assets guard. setup (it must remain runnable
to repair the database) and doctor (it reports instead of blocking) are
exempt. <M> is the recorded version, <N> the embedded head:
Opening the database creates an empty DB file when one is missing, so the
guard detects "missing" post-hoc as the no-ledger/no-tables state — the same
classification doctor's database section reports as no database.
Connection-level failures (an unreadable file, a bad URL scheme) keep their
existing failed to open database at '<path>': <e> / scheme errors — those
are environment errors, not schema states. With this guard in front, the
stale-assets guard runs only against an at-head schema, so no missing or
outdated schema state can surface a raw SQLite error from a guarded command.
Stale-assets guard
Every fleet-scoped surface — the fleet, member, and message groups plus
the monitor command — validates the recorded assets installs after the
schema-version guard passes and before any
subcommand body runs. The guard resolves each agent's identity path per
Config-dir resolution and checks only the row at
that path. Apply the following precedence:
Staleness checks use version records at currently-resolved paths; agents without
a matching row contribute nothing to staleness. Plain cafleet setup installs
agents at their resolved paths. Three surfaces are exempt:
--help renders at parse time and exits before any command body runs, so
neither group-level help (cafleet fleet --help) nor subcommand help
(cafleet fleet create --help) triggers either guard — both always print
help, even under a missing database or a missing or stale install.
cafleet fleet — Fleet Management
Fleet lifecycle; writes directly to SQLite — no server required.
fleet create
Omitting a required flag exits 2 with the parser's native missing-required-argument error naming the flag.
Must be run inside a tmux or herdr session — outside one it exits 1 with
Error: cafleet fleet create must be run inside a tmux or herdr session and
writes nothing. One invocation creates the fleet, its root Director, and its
monitor member in a single DB transaction, with owned-pane compensation
for a failed bootstrap. The
Director and monitor identities are hardcoded (Director:
name="Director", description="Root Director for this fleet"; monitor:
name="monitor", description="Monitor member for this fleet"); there are
no name / description / effort flags for either. Output shapes are in
Output shapes.
The command runs a single-transaction ladder — see data-model.md for the transaction description:
- Multiplexer preconditions, before any write.
- Resolve the monitor prompt body from
--monitor-file(file, or stdin via-). - Backend checks before any write: backend lookup,
--monitor-modelvalidation, and the binary-on-PATHavailability check (Error: binary <name> not found on PATH, exit 1). - In one SQLite transaction: insert the fleet row, the Director member +
placement, backfill
fleets.director_member_id, and insert the monitor member row with the monitor card marker. - Substitute the four identity placeholders into the prompt body
(same substitution and error strings as
member create). - Spawn the monitor pane (detached split,
CAFLEET_DATABASE_URLas the only forwarded environment variable). On successfulsplit_window, immediately transfer pane ownership to the CLI guard and return the id from the callback, without intervening fallible work. - Insert the monitor placement row (same session/window context as the Director), then commit. Disarm all creation guards before calling the existing text/JSON output path.
Failures follow the shared creation compensation order, including transaction rollback, pane ownership and uncertain-cleanup diagnostics. Exact errors are in Error Messages.
Once its pane boots, the monitor member sends ready, launches the
cafleet monitor wake loop, and sends monitor live (see
Monitoring).
member create --role monitor remains the mid-run recovery path for
re-spawning a dead monitor.
fleet list
The only flag is the optional shared --json.
Lists all non-soft-deleted fleets in created_at DESC, fleet_id DESC order
(higher id first when timestamps tie). Each row exposes director_member_id so
the Director's id can be recovered after fleet create output scrolls away.
fleet show
cafleet fleet show FLEET_ID [--json] — the positional FLEET_ID names the
fleet to show.
Exits 1 with Error: fleet 'X' not found. if the row does not exist.
Intentionally returns soft-deleted rows, so audit info stays reachable.
fleet delete
cafleet fleet delete FLEET_ID [--json] — the positional FLEET_ID names
the fleet to delete.
Soft-deletes the fleet in one transaction: stamps deleted_at, deregisters
every active member (root Director included), and removes their placement rows;
messages are untouched. It is idempotent (Deregistered 0 members. on
re-run). Unknown FLEET_ID
exits 1 with Error: fleet 'X' not found.. Member panes are not closed —
run cafleet member delete per member first for a clean teardown.
cafleet doctor — Environment Diagnosis
A full-environment diagnosis that renders all sections even when the
multiplexer is unavailable or the database is missing or stale — no early
abort. Diagnosis order: multiplexer, database, coding agents. doctor is
exempt from the schema-version guard and the
stale-assets guard — a missing or outdated database
and a stale or missing install are reported, not fatal.
The only flag is the optional --json, a trailing
per-subcommand flag.
Text layout
The first output line of the whole report is cafleet <version>. Each
section is led by a single-width verdict glyph (✓ U+2713 / ✗ U+2717)
plus the section name; detail lines are indented two spaces beneath. A
worked example (at-head schema, one stale agent, one superseded record):
Multiplexer section
✓ with the five detail lines (backend, session, window_id,
pane_id, presence). On any multiplexer or environment failure (no
supported multiplexer, ambiguous environment, binary not on PATH, pane not
discoverable): ✗ multiplexer with the resolver's error message as the
single detail line, and the report continues. One issue.
Database section
One detail line; the five states (<M> recorded version, <N> embedded
head):
A connection failure (unreadable path) renders ✗ with the connection error
as the detail line (one issue). A ✗ database never suppresses the
coding-agents section, but the recorded rows are read only when the database
report is ✓ (at head) AND the asset_installs table exists. Whenever the
rows are not read — any non-head state, or an at-head ledger with a
hand-dropped table — the section renders with no recorded-install data:
every resolvable agent shows the – state. No superseded footnotes render,
and — in the non-head states — doctor exits 1 for the database issue;
either way, never a raw SQLite error from asset_installs.
Coding agents section
A light box-drawing framed table (┌ ─ ┬ ┐ │ ├ ┼ ┤ └ ┴ ┘), header separator
only, no per-row rules. Column alignment uses display width, never byte
length. One row per agent in the fixed order claude, codex, opencode.
Records at other paths only feed informational footnote lines under the
table, one per superseded row, ordered ascending (coding_agent, path),
~-abbreviated:
Footnotes are informational — they never count as issues.
Footer and exit code
Last line: no issues found, 1 issue found, or <N> issues found (proper
pluralization). Exit code: 0 when no issues, 1 otherwise — the – state and
footnotes never count. Every failure is a rendered issue; no failure exits
before output.
--json
Mirrors the sections with ok booleans, unabbreviated absolute paths, and
the issue count. source holds the winning env-var name (no $) or the
literal "default"; state is "ok" | "stale" | "not_installed" | "error"
("error" is the per-agent resolution-error state; "not_installed" never
contributes to issues). Every agent row carries the same keys. error is
the validation message for "error", and otherwise null, without an Error: prefix. Section error fields likewise hold
the detail text when ok is false, else null.
On a multiplexer failure the multiplexer object is {"ok": false, "backend": null, "session": null, "window_id": null, "pane_id": null, "presence_var": null, "presence_value": null, "error": "<message>"}. On an
agent resolution error the row is {"coding_agent": "...", "path": null, "source": "<VAR>", "recorded_version": null, "installed_at": null, "state": "error", "error": "<VAR> must be an absolute path (got '<value>')"} — the
raw invalid value appears only inside error; path stays null because
no path resolved. schema_version is null when the ledger is absent.
When the recorded rows are not read (a non-ok database report, or a
missing asset_installs table), resolved agents use "not_installed"
and path errors remain
"error". Recorded fields are null and superseded is empty. Exit-code semantics are identical to text mode.
cafleet server — Admin WebUI Server
Starts the admin WebUI app under the built-in HTTP server (single process, no auto-reload, no worker or log-level flags). CLI commands do not require this server.
Flag wins over env var; env var wins over the hardcoded default. The
schema-version guard runs before the server starts,
so a missing or outdated database fails with its cafleet setup guidance
instead of a runtime SQLite error on the first request. The WebUI
assets are embedded in the binary at build time, so the served SPA always
matches the binary; port-in-use errors propagate unwrapped.
cafleet message — Message Broker
message poll takes the positional MEMBER_ID (the requester); message ack / show take the positional MESSAGE_ID; send / broadcast name the
parties with --from-member-id / --to-member-id. The broker derives the
fleet from the subject row, and every subcommand runs behind the
stale-assets guard.
The envelope schema is canonical in
Message envelope; truncation and --json are
canonical above; per-subcommand output shapes are
in Output shapes.
message send
cafleet message send --from-member-id ID --to-member-id ID (TEXT | --file PATH) [--json]
Notification outcome and partial failure
message send persists the message row first, then attempts one inline-preview
pane notification — unless the send is a self-send or the recipient's placement
has no pane id, which are intentional skips (see
Push notifications). The
notification is attempted at most once; no layer retries it. The persisted row
is never deleted, rolled back, or duplicated on a notification failure — it
stays input_required and recoverable through the normal poll/ack path.
An attempted notification failure exits 1 with:
<raw backend error> is the multiplexer's error detail inserted verbatim; it
may contain the backend command, its payload argv, and a newline-delimited
stderr detail (see
Multiplexer backends). The
formatter adds no separate copy of the sent message body. The --json failure
behavior follows the existing global error contract: --json selects
successful command output only and never produces a JSON error envelope.
The recovery contract is no-resend:
- Treat
<message-id>as authoritative proof that persistence succeeded. - Repair or re-engage the recipient pane.
- Run
cafleet member ping <recipient-id>as its own shell-tool invocation, or have the recipient runcafleet message poll <recipient-id>as its own shell-tool invocation. - Consume and ACK the existing row normally. Do not issue a second
message sendfor the same content.
message broadcast
cafleet message broadcast --from-member-id ID (TEXT | --file PATH) [--json]
delivered=<k> counts the inline previews that landed. A failed preview only
lowers delivered — broadcast keeps its single summary envelope, its
recipients/delivered counts, and exit 0, with no per-recipient failure
detail.
message poll
cafleet message poll MEMBER_ID [--json] — the positional MEMBER_ID is the
recipient whose inbox is fetched. An unknown or inactive member exits 1 with
Error: Member <member-id> not found.
Returns only un-acked (input_required) deliveries addressed to the member.
message ack
cafleet message ack MESSAGE_ID [--json] — the positional MESSAGE_ID names
the message to acknowledge. The recipient and fleet are derived from the
message row; existence and input_required state are the only guards.
message show
cafleet message show MESSAGE_ID [--json] — the positional MESSAGE_ID
names the message to fetch; existence is the only guard.
cafleet member — Member Lifecycle + Pane Interaction
The cafleet member subgroup owns the member lifecycle: create registers a
member and spawns its coding-agent pane; delete tears it down; prompt
/ ping keystroke an existing member's pane; capture reads it;
show and list are registry reads (no multiplexer requirement). All run
behind the stale-assets guard.
Member targeting and key delivery
Resolution shared by the pane verbs (capture / prompt / ping) and the
registry verbs, by target state:
Any active member (the root Director included) is a valid target;
there is no caller-auth check. Key sequences are
delivered literally (send-keys with shell=False) — shell meta, key
names, and multi-byte characters all arrive as plain characters.
member create
Register a member and spawn its coding-agent pane. It takes no identity
flag: the acting Director is auto-resolved from fleets.director_member_id
first thing, before registration (the resolved id also feeds the member's
backend inheritance and the spawn-prompt substitution). A fleet has exactly
one root Director by construction, so no override flag exists.
Concurrent monitor registration
The CLI's one-per-fleet check remains before the monitor-first check and before
registration or pane creation, preserving the existing validation order.
Registration also opens an IMMEDIATE DB transaction and checks the active
monitor slot inside it. The partial unique index enforces the same predicate
for all writers, including direct broker calls and status/card/fleet updates.
Only a conflict with that constraint maps to the typed
ActiveMonitorExists { fleet_id, member_id } error; the CLI renders the
existing Error: fleet <fleet-id> already has an active monitor member (member <member-id>), exit 1, without the register failed: prefix.
Other SQL failures keep their existing classification. The losing registration
adds no member or placement and never creates a pane. Ordinary broker
registration retains its existing behavior; requiring an active monitor
before creating an ordinary pane remains the CLI's monitor-first policy.
Spawn command per backend
The per-backend spawn argv and auto-approval flags live in Coding-agent backends.
Spawn-prompt substitution
member create and fleet create render their resolved spawn prompt bodies
with the same formatter, substituting exactly four placeholders:
Identity reaches the spawned member as literals rendered into its prompt; the
only environment variable forwarded into the pane is CAFLEET_DATABASE_URL.
The formatter accepts only the four exact names above and doubled literal
braces ({{ / }}), not Python format specifications, conversions, or
attribute/index access. An unknown placeholder or
malformed brace expression exits 2. Member creation attempts deregistration;
fleet creation attempts bootstrap transaction rollback. Cleanup failures follow the primary error
(see Error Messages).
The spawn always creates the pane without stealing focus (tmux
split-window -d): the Director's pane and active window stay active. In the
default output, pane renders (pending) until the pane id is patched onto
the placement.
Creation failure compensation
Member creation registers a pending placement, renders the prompt, creates a
pane, and patches its id. The CLI owns a registration guard after successful
registration and acquires a pane guard only when split_window succeeds.
The backend owns the pane before that return; see
pane ownership.
Continue remaining compensation even if an earlier cleanup fails. A backend
PaneCleanup::Attempted result is never killed again by the CLI. A failed
split with no confirmed id reports that the pane id is unknown and cleanup
is unconfirmed, including for member creation; it does not infer which other
pane should be closed. Creation rollback uses kill_pane(id, true) rather
than send_exit.
Keep the primary cause and its exit category. Member split failures retain
the primary reason tmux split-window failed: <error>; placement errors retain
placement update failed: <error> or placement row vanished before pane-id patch. Placeholder failures retain their usage error and exit 2. Append
applicable cleanup diagnostics after the primary error, on stderr:
cleanup failed for pane <id>: <detail>cleanup failed for member <id>: <detail>cleanup failed for fleet <id> transaction: <detail>
Report transaction rollback failures explicitly. Existing rollback-success
suffixes may describe confirmed compensation; they must not claim success or
complete rollback when cleanup failed or pane compensation is unconfirmed.
Guards disarm through explicit finish/rollback, preventing repeated kill
or deregistration; Drop handles only remaining unhandled ownership. Once a
member placement is confirmed or fleet commit succeeds, disarm all creation
guards before the existing emit call. Successful text/JSON output keeps its
shape, ordering, and nulls. No new stdout-failure exit or diagnostic contract
is introduced. Normal member delete behavior and persisted-message
notification failures remain unchanged.
member delete
cafleet member delete MEMBER_ID [--json] — the positional MEMBER_ID names
the member to delete.
Tears down the target's pane (when one exists) and soft-deletes the member.
Targeting the root Director is blocked (see
Error Messages). A placementless or pending-placement
delete is a pure registry soft-delete and succeeds outside a multiplexer.
The pane path kills the pane immediately (tolerating an already-gone pane),
then soft-deletes; exit 0. Pane status renders (no placement) for a
placementless target and (pending — no pane) for a pending placement.
member show
cafleet member show MEMBER_ID [--json] — the positional MEMBER_ID names
any active registry entry — placed or placementless (root Director included).
Registry read — no multiplexer requirement. Text is the compact one-line row;
the detailed view — kind (director / monitor / member), skills, and the
placement sub-dict — is the --json payload (see
Output shapes).
member list
cafleet member list FLEET_ID [--json] — the positional FLEET_ID names the
fleet whose roster is listed; no identity flag. Lists every active
registry entry of the fleet —
the root Director, ordinary members, and placementless rows. An empty roster
prints 0 members..
Field projection and absent values are in Member list output.
Rows and activity follow the data-model query contract.
Text idle values render as Ns / Nm / Nh.
Per-member detail such as description and registered_at lives on
member show.
member prompt
Director-only keystroke primitive with two forms. The plain form keystrokes
TEXT into a member's pane as a submitted user turn — for text that only
takes effect when it arrives as a direct user turn (slash commands, skill
invocations, and other magic commands a broker message body cannot trigger).
The --shell form keystrokes ! TEXT so the coding agent's ! shortcut runs
the command natively — honored by all three backends; it is the dispatch half
of the cafleet skill's bash-via-Director fallback protocol. Broker messaging
remains the canonical
coordination channel; the plain form is not a substitute for message send.
Shell metacharacters — pipes, &&, ;, $(...), and backticks — are
forwarded opaquely. The newline check runs first, against the original text.
The --shell flag controls only the payload prefix; both forms use the same
Esc safeguard and failure semantics:
In both forms the leading Esc (as in member ping and inline previews) keeps
the dispatch from blindly confirming a pending permission prompt. In the plain
form the trailing Enter submits a real user turn and opens the member's turn
directly. The --shell form's bang output only stages in the pane — the ping
advances the member's turn to consume it.
The flag performs no content inspection: plain-form TEXT beginning with !
is delivered verbatim without the shell mechanics.
Output shapes are in Output shapes.
member ping
cafleet member ping MEMBER_ID [--json] — the positional MEMBER_ID names
the target member.
Re-pokes a member's inbox: keystrokes Esc → cafleet message poll <member-id> — then resume your work if something was still running. → Enter into the target's pane
(the leading Esc is the permission-prompt safeguard — see
Push notifications). The manual
re-poke for a pane that missed the broker's automatic on-delivery
notification, owned by the Director and the monitor member (whose fixed-ping
exception is the one automatic use — see
Monitoring); the action is wholly fixed by the
command — no operator-controlled body — which is why member ping sits in
permissions.allow while member prompt stays in permissions.ask.
A pending placement (a placement row whose pane_id is not yet patched) takes
the skip path: no keystroke is sent and the command succeeds — the pending
member's inbox is intact and it polls it on spawn, so there is nothing a ping
would add. Exit code 0 on both success paths in every mode; the skipped
JSON key is present on both paths (stable schema).
Both success projections are in Member ping output.
A keystroke non-delivery, an unknown member, and a missing placement row all still exit 1.
member capture
cafleet member capture MEMBER_ID [--lines N] [--ansi] [--json] — the
positional MEMBER_ID names the target member.
--lines: Number of trailing lines to capture (default: 20).--ansi: Preserve ANSI escapes in the raw capture. The default strips ANSI escapes and cleans carriage-return redraws.
Output shapes are in Output shapes; target resolution is
shared with the member keystroke verbs — see
Member targeting and key delivery. A
pending placement is a hard error (see Error Messages).
JSON stamps captured_at from the local UTC
clock at the capture read boundary and computes
content_sha256 = sha256(content.encode("utf-8")) from the exact emitted
content. The default mode hashes the ANSI-stripped,
carriage-return-defragmented
string; --ansi hashes the ANSI-preserving string. No normalization occurs
after the selected mode, and capture content is never stored in SQLite.
The shared CaptureSnapshot::from_raw(raw, ansi, now) supplies this
content, timestamp, and lowercase SHA256 hex for both member capture and
monitor scan. It hashes the final selected content's UTF-8 bytes, including
Unicode and empty captures. Existing line validation/windowing and timestamp
format stay unchanged. Text member capture prints content without adding a
newline; JSON retains its existing fields and ordering.
cafleet monitor — Supervision Scheduler
cafleet monitor is a two-form command. The bare positional form runs the
supervision loop; the scan subcommand is a one-shot batch capture. Both
forms run behind the stale-assets guard.
The loop form
cafleet monitor FLEET_ID [--tick N] [--interval N] takes the positional
FLEET_ID subject. The conceptual model is
canonical on the Monitoring concepts page; there
is no stop subcommand — deleting the monitor member kills the pane hosting
the loop, and a still-running loop self-terminates on its next tick after
fleet delete.
--tick: The scan-tick cadence in seconds (an integer ≥ 1, default 5). The tick is the floor on interval precision — see Monitoring.--interval: The wake interval in seconds (an integer ≥ 0);0disables the wake while the loop keeps heartbeating. When omitted, falls back toCAFLEET_MONITOR_WAKE_INTERVAL(default 600).
The startup-resolved interval (--interval > CAFLEET_MONITOR_WAKE_INTERVAL
600) is stamped into the fleet's
monitor_runtimerow at each start and re-read on every tick, so aPATCH /api/monitoredit changes the running loop's cadence within one tick.
Runs the loop in-process and blocks. The monitor member hosts it as a
long-lived execution using its backend's launch primitive; the loop writes to its stdout — one
<iso-ts> tick -> wake monitor <monitor-member-id> (<N> members) line per
delivered wake). On startup it runs the multiplexer precondition guard,
atomically claims the single-instance monitor_runtime row, installs
SIGTERM/SIGINT handlers (a clean stop clears the row), and — immediately
after the successful claim, before the first tick — prints the startup line
the monitor member confirms before sending monitor live to the Director:
Monitor resource cleanup
MonitorLease ownership begins immediately after a successful runtime
claim. Each successful SIGTERM/SIGINT registration retains its own handle.
Registration failure (including the second handler), startup write or flush
failure, tick failure, normal stop, and owner displacement all release the
registered handles and attempt the ownership-checked runtime clear. Startup
still writes the exact line above after handler installation and before the
first tick; a flush failure is an error rather than a successful startup.
clear_monitor_runtime(fleet_id, pid) clears only that owner, preserving a
replacement PID and the existing wake ledger. If work and clear both fail,
retain the primary error/exit category and append the clear diagnostic. If
only clear fails, return that error. SIGKILL or a crash cannot run cleanup;
existing stale-owner reclaim remains the recovery path. Signal tests use
per-call injected registration handles and a stop flag, without changing
process-global signal handlers.
cafleet monitor scan
cafleet monitor scan FLEET_ID [--lines N] [--ansi] [--json] — a one-shot
batch capture of the whole fleet: the Director's pane plus every active
member's pane, captured back-to-back and printed in a single invocation.
No loop runs, no monitor_runtime row is claimed, the command performs no
DB writes, and capture content is never stored in SQLite.
--lines: Trailing lines captured per pane (an integer ≥ 1, default 20).--ansi: Preserve ANSI escapes in every captured content. The default strips ANSI escapes, as inmember capture.--json: Emit the JSON array instead of the text sections.
Roster. The scan requires a live fleet (a soft-deleted or unknown fleet
errors — see Error Messages) and a resolvable
multiplexer — the same guards as the loop form. The roster is the Director's
row first, then every other active member owning a placement row, ascending
by member_id. A member with no placement row (not spawned via
cafleet member create) is excluded, mirroring the wake roster's join; a
placement row with a NULL pane (pending placement) stays in the roster as
an annotated entry. A fleet with no members scans the Director's pane only.
Per-entry capture. For each roster entry, in order:
The scan always completes: an annotated entry never aborts the remaining captures, and a scan whose every entry is annotated still exits 0.
Text/JSON layouts, key order and annotated-entry nulls are in Monitor scan output.
Error Messages
Application errors print as text on stderr, including with --json; the flag
selects successful output only. Shared guards and backend validation own their
exact strings at the linked sections. The rows below retain command applicability
and exit categories, and define the remaining exact diagnostics.