@cotal-ai/pi 0.109.22 → 0.109.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27466,10 +27466,10 @@ import { isConcreteChannel as isConcreteChannel3, channelInAllow as channelInAll
27466
27466
  import { parsePrincipalKey as parsePrincipalKey2, peerLabel as peerLabel3 } from "@cotal-ai/core";
27467
27467
 
27468
27468
  // ../connector-core/dist/docs-bundle.generated.js
27469
- var DOCS_VERSION = "0.109.22";
27469
+ var DOCS_VERSION = "0.109.25";
27470
27470
  function loadDocsBundle() {
27471
27471
  return {
27472
- "version": "0.109.22",
27472
+ "version": "0.109.25",
27473
27473
  "generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
27474
27474
  "pages": [
27475
27475
  {
@@ -27566,7 +27566,7 @@ function loadDocsBundle() {
27566
27566
  "kind": "Reference: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract.",
27567
27567
  "category": "Reference",
27568
27568
  "summary": "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.",
27569
- "body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract, whose normative subjects and schemas live in the [SPEC](../SPEC.md). This page is\nlookup material for the commands. If you are new, start with\n[Getting started](getting-started.md).\n\nFor the other integration interfaces, see the [API reference](api-reference.md). For a\nwalkthrough, use [Choose a workflow](using-cotal.md).\n\n## Find a command\n\n| Task | Sections |\n|---|---|\n| Install, prepare or upgrade | [setup](#setup), [update](#update), [completion](#completion) |\n| Select and operate a space | [Selecting a space](#selecting-a-space), [Mesh registry](#mesh-registry), [up](#up), [down](#down) |\n| Launch and manage agents | [spawn](#spawn), [Managed seats](#managed-seats), [persona](#persona), [models](#models) |\n| Message and inspect peers | [who](#who), [whoami](#whoami), [chat](#chat), [send](#send), [history](#history) |\n| Deploy a team | [Manifest deploys](#manifest-deploys) |\n| Operate services and integrations | [service](#service), [Endpoint control](#endpoint-control), [linear](#linear) |\n| Coordinate longer work | [run](#run) |\n| Observe and recover | [watch](#watch), [console](#console), [Backups](#backups), [doctor](#doctor) |\n| Script commands or manage usage reporting | [Structured output](#structured-output), [telemetry](#telemetry) |\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal # the selected space, common commands, and where to start\ncotal help # the same, then every command group (also `cotal --help`)\ncotal help <command> [<subcommand>] # one command's or subcommand's usage and flags\ncotal version # cotal-ai version + each installed extension's (also `cotal --version`, `cotal -V`)\ncotal version --json # the same as one JSON document (also `-j`)\n```\n\nThe shell installer prints its absolute launcher path. Use it when an older installation still\nwins plain `cotal`. Having `~/.local/bin` somewhere on PATH does not establish precedence, and a\nchild installer cannot change the parent shell's command cache, aliases or functions. Inspect\n`type -a cotal` in that shell. After putting the new directory first, refresh its command cache\nwith `hash -r` (or `rehash` in zsh). An alias or function must be corrected separately. To restore\nconnectors after an old executable rebuilt them, run the verified newer absolute executable with\n`ext seed`, without `--force`.\n\n`npx cotal-ai <command>` runs it without a global install. Bare `cotal` starts nothing: it names\nthe space this folder has registered, else the selected space, else, with nothing selected, the space\nthis folder prepared, and lists common commands. It reads no\nbroker. When this folder's own records show the stack of its registered space stopped, as after\n`cotal down` there, and another space is selected, it names the selected space, which the commands\nrun there target, as for a prepared folder. Otherwise, when that folder's space is not the selection,\nit adds that the folder's space applies while it runs and the selected space applies outside the\nfolder and while it is stopped. When the folder holds a space that is prepared, or whose stack is\nstopped, it names what it holds. In a copy or a moved folder, which holds the records of a stack that\nruns from another folder, it says what the folder holds: a copy of a controller stack, a legacy\nroot's records and the folder that space is registered for, or records moved from their recorded\nfolder. It ends that line with the step the commands there name. It points a prepared space to the `cotal up` that starts it, with\n`--root <dir>` when the space is prepared in another folder and `<dir>` quoted when a shell would\nsplit it, and a machine with no space to `cotal setup`. With spaces on the machine but none selected\nor in this folder, it names `cotal use <name>` and `cotal space` instead. Every command generates its own help, usage, and shell completion from its declared\nflags, and `cotal <command> --help` prints the same help as `cotal help <command>`. A word after the\ncommand that names none of its subcommands refuses with `--help` too, as `cotal help` and the command\nitself do (exit 1), rather than print the command's help. A subcommand's help gives its whole\nusage, from `cotal` through its arguments, and lists only the options that subcommand takes.\n`-v` is not a version flag.\n\nSome commands have short aliases, listed in `cotal help` and under Shorthands in each\ncommand's help: `sp` (spawn), `a` (attach), `s` (status), `r` (run), `x` (ext), `h` (history),\n`t` (team), `p` (persona), `ch` (channel), `w` (watch), `ep` (endpoint), `mgr` (manager), `svc` and\n`services` (service), and `cfg` (config). Subcommands can have their own, such as\n`cotal ext rm` for `cotal ext remove`. An alias behaves the same as its command. A command or\nsubcommand is never selected by a prefix, so `cotal spaw` is an unknown command. The refusal for an\nunknown command, leading flag, subcommand or option names the nearest one when it is one edit away,\nor two for a word longer than four characters, so `cotal spaw` suggests `spawn`. `cotal despawn`\nsuggests `stop`, the command Cotal's own text calls a despawn. A command, alias,\nor extension command that would claim a word another one already claims is refused when it\nregisters or installs.\n\nIn a dev clone, `pnpm cotal <command>` runs `bin/cotal.ts` through `tsx`, but `bin/` loads the other\npackages from their built `dist/`. Run `pnpm build` after `pnpm install`, and again after you edit a\npackage, or the command fails with `ERR_MODULE_NOT_FOUND` for `@cotal-ai/cli/dist/index.js` or runs\nthe old code. A Windows source build needs the matching MSVC Developer Command Prompt to compile\nits first-party process identity binding. Published packages already contain the x64 and arm64\nbinaries and need no compiler or native download.\n\nCommands that change the installation run the automatic connector seed and stop when the\noperator-global seed store is not current for the checkout's version, which includes a fresh config\nand a store another version wrote. The error includes\n`this would migrate the machine-wide store and can take an installed cotal down`. A\ncheckout never writes the connector store an installed `cotal` uses. For day-to-day\nwork, set `COTAL_SKIP_CONNECTOR_SEED=1` to skip that seed step:\n\n```bash\npnpm install\npnpm build\nCOTAL_SKIP_CONNECTOR_SEED=1 pnpm cotal status\n```\n\nPointing `XDG_CONFIG_HOME` at a scratch dir is not enough by itself. Isolated release tests\nthat must seed from a checkout also set `COTAL_ALLOW_CHECKOUT_SEED=1`, as described under\n[Built-in connectors are seeded extensions](#built-in-connectors-are-seeded-extensions).\n\nBefore publication, the release workflow installs the packaged candidate into fresh homes and\ncaches on Linux, macOS and Windows, on x64 and arm64 with Node 22, 24 and 26. Each setup seeds\nconnectors, starts the broker, delivery daemon and Manager, checks native process identity and\nManager control, then stops and restarts them. Every platform must pass before normal or snapshot\npublication can proceed.\n\nAn undeclared flag is a usage error, and so is a flag given more than once unless it is\nrepeatable, as `--opt` and `down --session-store` are. A word that starts with a dash and a digit,\nas `-1` does, after a flag that takes a value is that flag's value, so `--limit -1` gets the\ncommand's own check of `--limit`. So is an unknown command, and a first word\nthat is not one of the command's subcommands when it declares them. So is input a command checks\nbefore it acts, such as a missing argument, an invalid flag value or two flags that conflict, and\n`cotal help` with a subcommand that does not exist. So is a word that starts with an en dash, an em\ndash or a minus sign followed by one of the command's flag names, as an autocorrecting editor writes\n`--`: the refusal names the dash and the flag to type. After `--` such a word is an argument as\ntyped. The command prints the error, then any remedy,\nthen its help on stderr, writes nothing on stdout, exits 1, and does not run. A refusal for a missing\n`--yes` outside an interactive terminal prints its error and a remedy that names `--yes`, without the\nhelp. A control character in\na word the error quotes, from the command line or from a file, shows as its `\\uXXXX` escape, so a\ntyped word cannot clear the screen or retitle the terminal. A refusal that repeats an argument\nlonger than 128 characters, such as an unknown command, a space name or a run id, shows its first\n128 characters followed by `\u2026 (<n> characters)`, in text and in the `--json` message. The command\nstill checks and uses the whole value. A usage error is found before any effect, so it never seeds\nconnectors or writes configuration. Help, version and completion never do either.\nA word that starts with `-` reads as an option. A message or name that starts with one goes after\n`--`, which the refusal names, and an option value that starts with one is written `--limit=-1`,\nwhich the refusal gives. An option given with no value, last or just before another option, is\nrefused naming the option and its placeholder. A short option takes its value after a space, so `-c=general` is refused\nrather than read as the value `=general`. An option typed with a typographic dash, as pasted from\ntypeset text, is refused naming the option it meant, rather than read as a positional.\n\nCommand output, including error lines on stderr, the guided `setup` and `meshes add` prompts and\nthe `console` screen, is colored only when stdout is a terminal, so piped or redirected output is\nplain text. A terminal whose `TERM` is `dumb` gets no color either. A non-empty `NO_COLOR` turns color off on a terminal too. `FORCE_COLOR` turns color on even when output is\npiped or `TERM` is `dumb`, unless it is `0` or `false` in any letter case, and it takes precedence over `NO_COLOR`\nwith no Node warning about the pair. `--no-color` turns\ncolor off for that run, whatever the environment says. It sets no `NO_COLOR` for the programs the\nrun starts, but they are no longer forced into color by `FORCE_COLOR`.\n\nAt any question the CLI asks, Ctrl-D on an empty line or an input that closes cancels the question\nas Ctrl-C does, so the command reports its own cancelled outcome.\n`--no-input` means the run never prompts, opens an editor or opens a browser. A command that would\nprompt behaves as it does without a terminal, `web` prints its link without opening it, and\n`personas edit` is refused. A refusal for a missing `--yes` names what kept the command from asking:\n`--json`, `--no-input`, `COTAL_NO_PROMPT=1` or no interactive terminal. `--json` implies `--no-input`. `--no-color` and `--no-input` work before\nthe command word on every command, and after it on every command except one that takes its words\nraw. After `--` they are ordinary words. Each is given once, as any flag is, before or after the\ncommand word. No command may declare a flag with either name. The output\nflags `--json` (`-j`) and `--plain` may also come before the command word, as in\n`cotal --json status`; they are read as the command's own flags, so a command without that output\nrefuses them as it does after the command word. A command that takes its words raw, such as the\nhidden completion entry, has no flags to read them as and refuses them before its word with a usage\nerror. A `--` before the command word ends these leading flags. An installed extension\nwhose command declares one is an internal error, and `cotal` exits 1 before running any command.\nShell completion treats the current `--` word as a flag prefix until it is committed. After `-- `,\nit completes positional values without offering options. A string option's value is completed\nfrom its own candidates or file hint, without inserting other option names.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager and the\ndelivery daemon, plus any operator-installed extensions. Hidden entries such as the deprecated\n`deliver` and `auth-service` are not completed.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, service presets and local process lifecycle descriptors. The `web` dashboard and\noptional manager runtimes ship this way. A `@cotal-ai/*` package named without a version installs\nthis cotal's version, as `setup` and `update` do; a version or tag you name is installed as given.\n\n## Structured output\n\nA command whose help has a `structured output` line naming `json` takes `--json` (`-j`) and writes\none JSON document on stdout. Most commands have one, among them `cotal status`, `cotal space`,\n`cotal service`, `cotal up`, `cotal down`, `cotal ps`, `cotal history` and `cotal config`.\n`cotal help <command>` shows whether a command and each of its subcommands has one.\n`cotal version --json` writes:\n\n```json\n{\"schemaVersion\":1,\"command\":\"version\",\"target\":null,\"outcome\":\"ok\",\"completeness\":\"complete\",\"result\":{\"version\":\"<version>\",\"extensions\":[{\"package\":\"@cotal-ai/web\",\"version\":\"<version>\"}]},\"warnings\":[]}\n```\n\n| Field | Meaning |\n|---|---|\n| `schemaVersion` | `1` |\n| `command` | The command that ran |\n| `target` | What it acted on, or `null` for a command that reads only local metadata |\n| `outcome` | `ok`, `no-op`, `refused`, `failed`, `partial` or `unknown` |\n| `completeness` | How much of the read scope the result covers: `complete`, `partial` or `not-checked`. It says nothing about whether the work succeeded |\n| `result` | The command's result. A `partial` read keeps what it read; a refused, failed or unknown run has `null` |\n| `warnings` | One `{code, target, message}` for each thing it could not read or check |\n| `error` | Present when the outcome is not `ok` or `no-op`: `{code, message, remedy, cause}`. `remedy` is the step to take, present when the refusal names one, and the text report prints it under Next. `cause` keeps an endpoint's own code, outcome and safe detail |\n| `changed` | What a command that did not succeed changed anyway |\n\nDiagnostics stay on stderr. A command that refuses or fails under `--json` reports it with the\nenvelope alone and writes no text report on stderr. The text report goes to stderr only when the\nenvelope cannot be written to stdout, and the exit code is the same either way. A usage error under\n`--json` is a `refused` envelope with code `USAGE_INVALID`, and the help it names stays off stdout. A word that names none of a command's\nsubcommands and is near none of them is a usage error that lists the subcommands the command takes.\nA command without a JSON output refuses `--json` as an unknown option.\n\nA subcommand's help has its own `structured output` line, and only the forms that show one write a\ndocument. Bare `cotal ext` and `cotal ext list` do. `ext add`, `ext remove`, `ext root` and\n`ext seed` report as text, refuse `--json` and `--plain` with a usage error, and exit 1 on any\nfailure.\n\nA command whose `structured output` line also names `plain` takes `--plain`, which writes the same\nreport as plain lines: no color, ASCII labels, and no marks other than the `!` that begins a warning\nor failure line on stderr. Plain writes these punctuation marks in ASCII wherever they appear: `\xB7`\nand long dashes as `-`, `\u2026` as `...`, `\u2192` and `\u21B3` as `->`, and `\xA7` as `section`. Other characters,\nsuch as the letters of a name or a message, are written as they are.\nUnder a locale whose character set is not UTF-8, as `LC_ALL`, then `LC_CTYPE`, then `LANG` decide,\nreports and error lines draw their marks and punctuation as plain does, and keep their color. With\nnone of the three set, the marks are drawn in UTF-8. Windows consoles are always treated as UTF-8.\nA usage refusal under `--plain` or such a locale prints the classified report that begins\n`Request refused`, then the command's help. Every refusal line, the `\u2717` line included, shows a\ncontrol character in the text it quotes, such as a file's bytes or an argument, as an escape like\n\\u001b, so that text cannot move the cursor or change the screen. A refusal of a value that names\nits own fix prints that line and its next step without the help: an `--on` value that is empty, a\nprincipal id or not an instance id, a `spawn --on` that needs a terminal or `-d`, `attach` with no\nname and no terminal, and a `run` command given no run id.\nPass one of `--json` and `--plain`; both is a `USAGE_INVALID` refusal. A table that does not fit the\nterminal is written as one labeled record per row, so no value is cut off. A warning on stderr wraps\nto the terminal at spaces, each continuation line under its text.\n\nShared human reports for a resolved space begin with `Mesh: <space>`, including\nempty `ps` results, manager inventories, the impact a command shows before it\nstops something, and classified failures. A failure raised after the command\nresolved its space carries that space in `target.space`, even when the failing\nstep named no space. The label comes from the operation's target, not a new read\nof the current selection. Global reports do not invent a selected mesh. JSON\nretains `target.space` without a text header. A refusal for a name no space has, such as one\n`space create` refuses or one `space show` and `space forget` do not find, carries the name as\n`target.requested` and has no `Mesh:` heading. Plain watch feeds and followed\nhistory prefix each new row with the mesh name so the context remains visible as\noutput scrolls.\n\nOn a terminal, a long value, line, warning or failure wraps at spaces, and each continuation line\nstarts under the text it continues. A labeled value with a word too wide to fit beside its label,\nsuch as an identity, starts on the line under the label. A word wider than the line, such as a path,\nkeeps a line of its own and is never split. A command wraps as one word, so a copied line never holds\npart of one: a command under Next, and a command a sentence names in backticks after other words,\nfrom its `cotal` to its closing backtick, whatever words its arguments are. A command that holds a\nbacktick itself, as a quoted path can, is named between double backticks, as Markdown writes such code,\nand wraps whole to the matching pair. A line that starts with a command, such as a usage line, wraps at\nits spaces. Help wraps the same way, including the title of `cotal help`.\n\nA question that defaults to No, such as the one before `history clear`, `down`, `channel remove` or\n`persona remove`, is answered by typing: `y` or `yes`, in any case, then Enter confirms it. Enter\nalone, any other text, Esc or Ctrl-C keeps No, so a word typed at the prompt by mistake never\nconfirms a step you cannot undo. `space delete` asks you to type the space's name instead. A\nquestion that defaults to Yes is a Yes and No choice: the arrow keys move the selection, `y` and `n`\nanswer at once, and Enter takes the selection.\n\nA usage line breaks only between options, never between an option and its argument. A ` | ` stays\non the line before it whenever it fits there. A compact `|`, such as the one in\n`<persona.md>|--prompt`, is a break point too: the line ends right after it, the next line starts\nwith the following option, and no space is added or lost. A usage too wide for the line breaks after\nits own ` | ` or compact `|` alternatives first. A group too wide for the line, such as\n`(--to/-t <agent> | --channel/-c <channel> | --role/-r <role>)`, takes lines of its own and breaks\nthe same way inside, and so does a group nested in it. A group that fits, such as\n`[--json|--plain]`, stays whole. Only a single option with its argument that is wider than the line\nbreaks at its spaces. Examples are not wrapped, so each stays one line to copy.\n\nWhen the flag names leave their descriptions fewer than 30 columns, each description goes on the\nlines under its flag. A report's list of commands follows the same 30-column rule. Below 48 columns,\na record puts each label on a line of its own with its value under it. Output to a pipe or a file is\nnever wrapped.\n\nA read that could not read part of what it was asked for is `partial`: `result` holds what it read,\n`completeness` is `partial`, each part it could not read is a warning, and it exits 4. A list is\nnever presented as complete when part of it is missing.\n\nA target the command cannot resolve or reach is refused before any request, with the code for why:\n`TARGET_NOT_FOUND` (no such mesh), `TARGET_AMBIGUOUS` (several match), `TARGET_UNAVAILABLE` (its\nbroker or manager did not answer), `AUTH_REQUIRED` (it needs a sign-in or a fresh credential),\n`PERMISSION_DENIED` (it refused the credential), `DEPENDENCY_UNAVAILABLE` (trust material, the\nuser-auth service or a space catalog it needs could not be used), or `UNSUPPORTED`.\n\nOn Linux, a broker on a loopback address whose process is stopped by a signal, such as SIGSTOP,\nstill accepts connections and never answers. Once the first dial to it times out, the refusal names\nthat process's pid and gives `kill -CONT <pid>` as the remedy, without waiting out the longer\nconfirm, so a command such as `send` or `run` returns in a few seconds. `cotal status` shows the\nconnection as `broker process <pid> is stopped` and offers the same command. A broker that runs but\ndoes not answer, or one on another host, still waits out the confirm.\n\nA command with a JSON output exits by its outcome, with or without `--json`:\n\n| Outcome | Exit | Codes |\n|---|---|---|\n| `ok`, `no-op` | 0 | none |\n| `refused` before any effect, for invalid input | 1 | `USAGE_INVALID`, `SCHEMA_INVALID` |\n| `refused` at a known boundary before any effect | 2 | `PERMISSION_DENIED`, `AUTH_REQUIRED`, `TARGET_UNAVAILABLE`, `TARGET_NOT_FOUND`, `TARGET_AMBIGUOUS`, `DEPENDENCY_UNAVAILABLE`, `UNSUPPORTED`, `CONFLICT`, `LIFECYCLE_BLOCKED` |\n| `unknown`: it may have had an effect that cannot be attributed. Do not retry blindly | 3 | `OPERATION_UNKNOWN` |\n| `partial`: some of the work completed, or cleanup remains | 4 | `OPERATION_PARTIAL` |\n| `failed`: a confirmed failure with no cleanup left | 5 | `OPERATION_FAILED` |\n\nAn error such a command does not classify is `unknown`, exit 3. Other commands still exit 1 on any\nfailure. A command run from a folder that no longer exists refuses with `USAGE_INVALID`, exit 1,\nbefore it does anything, and says to `cd` to an existing folder. It prints that line and the `cd`\nremedy without the command's help, because the command itself was not mistyped. A command whose folder has a\n`.cotal` that is a file rather than a directory, such as a notes file, refuses with `CONFLICT`, exit\n2, before it does anything, names that file and says to rename or move it. `help`, `version` and\n`completion` still work in both. `docs` and `telemetry` also work from a folder that no longer\nexists, and there `help` says the folder is gone instead of showing a space.\n\nA long-running command that SIGINT stops ends cleanly first. It stops observing, closes the\nsession it holds and writes its last record, then exits 130. SIGTERM ends it the same way and exits\n143. This holds for `watch`, `chat`, `console`, `history --follow`, `channel members --follow` and\n`join`, and for `service logs --follow`, the `--wait` follows and a foreground `up`. The JSON\nstreams of `watch`, `chat --stdio`, `history --follow` and `channel members --follow` end with an\n`end` record whose reason is `interrupted` for SIGINT or `terminated` for SIGTERM. Inside a terminal\nview a key that leaves, such as `q` or Ctrl-C, is not a signal, and the view exits as it does when\nyou leave it.\n\nOutput that cannot be written, for example to a full disk, is reported in one line on stderr, and\nthe command stops with exit 3, or with the non-zero status it had already set. It may have stopped\npartway, so check what it did before you retry. A reader that closes the output early, as `| head`\ndoes, ends the command quietly with the status it had already set, or 0. A command that follows a\nstream, such as `watch`, `history --follow`, `console` or `channel members --follow`, notices the\nclosed reader at its next line, so on a quiet mesh it runs until a message arrives or it is\ninterrupted. Ctrl-C ends it at once with exit 130, as it ends the commands above.\n\n## Commands\n\n| Section | Command | Purpose |\n|---|---|---|\n| Setup | [`setup`](#setup) | Prepare a space, application and runtimes; opt into startup separately |\n| Setup | [`help`](#running-it) | List the commands, or show one command's help |\n| Setup | [`version`](#running-it) | Print this binary's version and each installed extension's |\n| Setup | [`ext`](#ext) | List, install and remove operator CLI extensions |\n| Setup | [`config`](#config) | Show and set your preferences: the defaults your commands use |\n| Setup | [`telemetry`](#telemetry) | Show or set the anonymous usage telemetry level (`off`, `minimal`, `regular`) |\n| Setup | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Setup | [`completion`](#completion) | Report, print or install shell completion |\n| Setup | [`docs`](#docs) | Read this version's protocol and CLI docs, offline |\n| Mesh | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Mesh | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Mesh | [`backup`](#backups) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Mesh | [`clean`](#clean) | Legacy, hidden from `cotal help`: purge history or wipe a stopped store; `reset`, `history clear` and `backup cleanup-fallback` replace it |\n| Mesh | [`reset`](#reset) | Preview or reset one stopped local store or space identity |\n| Mesh | [`meshes`](#mesh-registry) | Legacy, hidden from `cotal help`: list, register and drop meshes; `space`, `space add` and `space forget` replace it |\n| Mesh | [`space`](#selecting-a-space) | List the spaces this machine can reach, or show one in full |\n| Mesh | [`sync`](#mesh-registry) | Refresh the signed-in account's advertised spaces |\n| Mesh | [`status`](#mesh-registry) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Mesh | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Mesh | [`use`](#selecting-a-space) | Select the space commands target when none is named, or show the selection |\n| Mesh | [`join`](#join) | Legacy, hidden from `cotal help`: join a space as your own presence; `chat` replaces it |\n| Mesh | [`topology`](#manifest-deploys) | Legacy, hidden from `cotal help`: view a manifest's access graph; `team check --graph` replaces it |\n| Messaging | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging | [`chat`](#chat) | Open a live session under its own identity: terminal view, `--plain` lines, or `--stdio` for programs |\n| Messaging | [`channel`](#channel) | Read channel metadata and observed members, or change a channel's settings |\n| Messaging | [`history`](#history) | Read retained messages, or clear them |\n| Messaging | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Messaging | [`pending`](#pending) | Inspect one DM durable's native counts and bounded candidate IDs |\n| Agents | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents | [`app-identity`](#plumbing) | Plumbing for a Claude app conversation's identity on a local mesh; you never run it directly |\n| Agents | [`start`](#spawn) | Removed: `cotal start` was merged into `cotal spawn --detach` |\n| Agents | [`stop`](#managed-seats) | Ask the manager to stop a managed agent |\n| Agents | [`ps`](#managed-seats) | List managed agents and their mesh status |\n| Agents | [`attach`](#managed-seats) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents | [`input`](#input) | Type one line into a managed agent's terminal without attaching |\n| Agents | [`persona`](#persona) | List, show, edit, create, or remove local personas |\n| Agents | [`personas`](#persona) | Legacy, hidden from `cotal help`: the earlier persona parser; `persona` replaces it |\n| Observe | [`endpoints`](#endpoints) | Legacy, hidden from `cotal help`: list every endpoint in the live roster; `who --all` replaces it |\n| Observe | [`who`](#who) | The live roster of the selected space, and whether a manager runs each seat |\n| Observe | [`whoami`](#whoami) | The identity this CLI acts under, and what it may read, post and do |\n| Observe | [`describe`](#endpoint-control) | Legacy, hidden from `cotal help`: read a native endpoint's contracts; `endpoint describe` replaces it |\n| Observe | [`invoke`](#endpoint-control) | Legacy, hidden from `cotal help`: invoke one native command; `endpoint invoke` replaces it |\n| Observe | [`watch`](#watch) | Follow authorized activity without starting, stopping or attaching agents |\n| Observe | [`console`](#console) | Legacy, hidden from `cotal help`: the protocol TUI; `watch` replaces it |\n| Manager | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Manager | [`reconcile-gate`](#reconcile-gate) | Hidden from `cotal help`, which lists `manager reconcile`: unfreeze an issuance gate left frozen by a crashed restart when the successor cannot boot-heal it (holder gone, complete CONNZ sweep) |\n| Manager | [`deregister-instance`](#endpoint-deregister) | Hidden from `cotal help`: the old spelling of `endpoint deregister`, which refuses and names it |\n| Manager | [`seats`](#seats) | Hidden from `cotal help`, which lists `manager seats`: list the pty seat custodians an earlier Linux manager left, and drain the ones whose agent has exited |\n| Manager | [`supervise`](#supervise) | Deprecated and hidden from `cotal help`: run a manager in this terminal; use `cotal manager start` |\n| Manager | [`feedback-intake`](#server-daemons) | Deprecated and hidden from `cotal help`: run a feedback intake server; use the `feedback-intake` service preset |\n| Manager | [`deliver`](#deliver) | Deprecated and hidden from `cotal help`: run the delivery daemon; use the `delivery` service preset |\n| Manager | [`run`](#run) | Operate durable workflow runs: start, resume, list, inspect, answer a checkpoint, check an edited program with migrate |\n| Manager | [`agent-bearer`](#plumbing) | Hidden from `cotal help`: plumbing that prints a fresh bearer for a spawned user-auth agent; you never run it directly |\n| Manager | [`auth-service`](#server-daemons) | Deprecated and hidden from `cotal help`: run a user-auth space's auth service by hand; use the `auth-service` service preset |\n| Manager | [`daemon`](#daemon) | Linux: serve the local machine daemon's control endpoint, read its status, sign it in, check and request its own registration with a space |\n| Services | [`service`](#service) | Run Cotal's built-in services and your own programs: list, add, start, stop, status, logs |\n| Services | [`manager`](#manager) | Start, stop and check the manager that runs your agents, and its maintenance |\n| Identity | [`mint`](#auth-mint) | Moved: credential minting is `cotal auth mint` |\n| Identity | [`actor`](#actor) | Legacy, hidden from `cotal help`: the actor ledger; `auth grants`, `auth grant` and `auth revoke` replace it |\n| Identity | [`service-endpoint`](#service-endpoint) | Decide and inspect service endpoint registrations on the space's auth service |\n| Identity | [`login`](#login) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Identity | [`logout`](#login) | Revoke the IdP session and clear the cached login |\n| Identity | [`auth`](#auth-mint) | Mint credentials, inspect account metadata, manage actor grants and export a protected account signer |\n| Teams | [`team`](#manifest-deploys) | Inspect teams, boot a fresh mesh from a manifest, or add its channels and agents to a running one |\n| Endpoints | [`endpoint`](#endpoint-authoring) | Save endpoint references, author contracts offline, inspect or invoke native endpoints, and deregister a departed instance |\n\nThe table has one row per command, including the hidden ones `cotal help` leaves out, under the\nsection help lists it in.\nInstalled extensions add their own: `@cotal-ai/linear` adds the [`linear`](#linear) command, and\n`@cotal-ai/web` adds the [web dashboard](#web) as a service preset.\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--destination <local|self-hosted>] [-H|--harness <connector>] [--runtime <name>]... [--project <dir>]\n [--space <name>] [--connection-file <bundle.json>] [--configure-only | --start [-d|--detach]]\n [--on <manager-instance>]\n [--demo|--full] [-y|--yes] [--json|--plain]\ncotal setup --skills\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--destination` | asked | `local`: a space this machine starts. `self-hosted`: a space registered here that runs on a server, including a Cloud space `cotal login` discovered. `cloud` is refused before anything is written or contacted, and the refusal names that route: `cotal login`, then `--destination self-hosted --space <name>`. |\n| `--harness`, `-H` | asked | The connector your agents run in. Setup runs its own setup actions and records it as the space's `harness` preference, so `cotal spawn` in the space launches it. When you have no `harness` preference of your own, it becomes yours too. |\n| `--runtime` | asked; none in a script | A runtime to set up besides the built-in `pty`: `tmux`, `cmux`, `orca`, `herdr` or an installed third-party one. Repeat it for each. |\n| `--project` | asked | The project folder: the space's root, whose `.cotal/agents` holds its personas. A script's folder must exist; a terminal offers to create it. |\n| `--space` | asked | The space to prepare (local) or the registered space to use (self-hosted). |\n| `--connection-file` | none | Self-hosted: register the space from a connection bundle exported where it runs, with the checks `space add --file` makes. |\n| `--configure-only` | on | Save preparation and start nothing. Refused with `--start`. |\n| `--start` | off | After successful preparation, start the local stack when needed and launch the default persona. Self-hosted setup uses its registered manager and starts no replacement local stack. |\n| `--on` | none | Select one self-hosted manager instance for startup. Local setup pins the manager it starts and refuses this flag. In a script, use it with `--start`. |\n| `--detach`, `-d` | off | With `--start`, leave the managed agent running without opening a terminal view. Required in scripts, JSON and plain mode. |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--full` | off | The guided flow with the guided team (implies `--demo`) |\n| `--yes`, `-y` | off | Ask nothing: every input comes from the flags |\n| `--skills` | off | Reconcile Cotal skills only through installed connector providers, plus `~/.agents/skills`. Refused with the four inputs, `--runtime`, `--connection-file`, `--configure-only`, `--start`, `--on`, `--detach`, `--full`, `--demo`, `--json` or `--plain`. |\n\nSetup saves preparation before a separate startup decision. Configure-only is the default;\n`--yes` never turns it into startup. On a terminal it asks for each input no flag gave: destination,\napplication, runtimes, project folder and space.\nIt shows the answers before it writes: Save writes them, Change asks every question again with the\nanswers kept, and Cancel saves nothing. Cloud is listed but cannot be chosen. Its hint says to sign\nin with `cotal login` and choose Self-hosted, because a Cloud space is registered here when you sign in.\n\nWithout a terminal, or with `--yes`, `--no-input` or `--json`, every input is a flag. A missing one\nis `USAGE_INVALID` naming each missing flag, before anything is written. The refusal prints the\nscripted form for the destination and not the full command help. The local form, filled in:\n\n```bash\ncotal setup --destination local --harness claude --project ~/research --space research --no-input\n```\n\nThe `self-hosted` form also takes `--connection-file <bundle.json>`. A space name `cotal up` cannot\nstart is refused with the name respelled in letters, digits, `-` and `_` as the remedy, and an\nunknown `--harness` with the known ones and `cotal ext add <npm-package>`.\n\nA local space is prepared as `cotal space create` prepares it. A self-hosted space is one already\nregistered with `cotal space add` or a signed-in catalog, whose root must be `--project`, or one\n`--connection-file` registers. Setup then seeds the `default` persona into\n`<project>/.cotal/agents`, the catalog `cotal spawn` reads for that space, records the application as\nthe space's `harness` preference, runs the application's setup actions, and reconciles Cotal's\nskills. When you have no `harness` preference of your own, setup also records the application as\nyours, so a space you prepare later with `space create` uses it. A value you already set stays.\nA preferences folder another account could write is refused with `PERMISSION_DENIED`, exit 2, before\nanything is written (see [Preferences](config.md#preferences)).\nThe result names the next command: `cotal up` in the project for a local space, or\n`cotal spawn --space <name>`. `--json` writes one envelope whose `result.written` lists every file\nsetup wrote.\n\nSetup needs an installed connector. With none, a `--harness` that names no official connector is\nrefused with `USAGE_INVALID`, exit 1, and otherwise setup refuses with `DEPENDENCY_UNAVAILABLE`,\nexit 2. Its remedy names `cotal ext seed` on a released install. A source checkout does not seed\nconnectors unless `COTAL_ALLOW_CHECKOUT_SEED=1` marks a sandboxed release test, and the remedy says\nso.\n\nAfter successful preparation, the terminal asks separately whether to start, and only a typed `y`\nor `yes` starts it. An explicit\n`--configure-only` suppresses that question. `--start` skips it; scripts must also pass `--detach`.\nMissing application/runtime prerequisites keep preparation saved and do not launch anything.\n\n```bash\ncotal setup --destination local --harness claude --project <dir> --space <name> --start --detach --json\n```\n\nFor a local space, setup invokes the current native `up` in the prepared project, then pins the\nagent request to that root's recorded manager. Native up returning successfully is not reported as\nwhole-stack health. The managed launch must return its actual joined identity. Self-hosted startup\nuses the registered manager's persona catalog; `--on` pins its manager instance\nthrough credential issuance and launch. It uploads no local persona file. Without detach,\nthe terminal opens that allocated incarnation's native view. If it disappears before attachment,\nsetup reports the confirmed join and an incomplete view, not a healthy attached terminal. Leaving\nthe view sends no stop request.\n\n`result.startup` separates the requested self-hosted manager pin, native stack-command\nreturn, joined-agent receipt and any classified failure. A declined Start keeps the\npin in the suggested spawn command rather than silently selecting another manager. A joined result includes its full manager identity and a usable exact `attach`\ncommand when its returned tokens are safe to put in a shell command. Preparation remains saved\nafter a denied or uncertain start. Unknown outcomes are not retried; the setup heading is not\nshown as healthy and the next command inspects the original startup before attachment. JSON stdout has\none envelope. A confirmed local startup refusal keeps its code and diagnostic in\n`result.startup.failure` and the setup report. Interrupted startup or an incomplete child result\nremains unknown. Native startup output is not parsed or echoed as readiness evidence. Fresh prepared\nstatic-auth, open and plain-loopback user-auth roots use the controller stack described under `up`.\nManifest startup retains its native launch path.\n\nRuntimes are where agents run. `pty` is built in and always set up. The terminal lists the official\nruntimes and every installed third-party one, each with whether it is set up and whether the host\ntool it drives is on PATH, or named by the driver's own variable (`COTAL_ORCA_BIN`,\n`CMUX_BUNDLED_CLI_PATH`); installed ones and ones whose tool was found start selected. Setup\ninstalls each selected runtime as `cotal ext add` does, at this cotal's version, then asks its\nprovider whether it answers, as `cotal runtimes` reports. A selected runtime whose host tool is\nmissing is not installed, and one that does not install or answer leaves the rest saved and the\nresult `partial`. A script sets up only the runtimes `--runtime` names; an unknown name is\n`USAGE_INVALID` and a missing host tool is `DEPENDENCY_UNAVAILABLE`, both before anything is\nwritten. Setup never removes a runtime you leave out; `cotal ext remove` does. `result.runtimes`\nlists each one with its package, its host tool and whether setup found it set up, added it or left it\nout.\n\nA rerun with the same answers changes nothing and is a `no-op`; a space already prepared, started or\nregistered at that root is reported, not refused. A space name or root another space holds is\n`CONFLICT`. An application whose host tool is not on PATH leaves the rest saved and the result\n`partial` (exit 4), naming the tool; an application action that does not finish does the same.\n`cotal status` reports the machine's state, and it points\nstale Claude skills and out-of-date `.agents` skills at `cotal setup --skills`. See\n[Getting started](getting-started.md) and, for maintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self] [--space <s>] [--server <url>] [--creds <path>]\ncotal update --check [-j|--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n| `--space`, `--server`, `--creds` | resolved mesh | Select the running manager whose continuity state is reported |\n| `--check` | off | Read version availability and recorded extensions without installation or restart; takes no mesh selector or `--self` |\n| `--json`, `-j`, `--plain` | off | Choose the finite check report; requires `--check` |\n\nUpdate reads the recorded mesh inventory without refreshing account space catalogues. An expired\nlogin to a remote mesh does not block the software update. Local manager continuity checks still\napply before changing the installation.\n\nAfter an upgrade, `up`, `down` and the `service` verbs still control the stack members and\nfirst-party service instances an earlier release recorded, as long as their adapter's declaration\ndid not change. A third-party adapter's instances stay pinned to the package version that started\nthem.\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it verifies each of the six built-in connectors and the web dashboard against the payload\nthe binary ships, byte for byte, and reinstalls one that differs in any way (a missing, changed or\nextra file, a link where the installed copy should be, an entry at another version, or an\nextensions prefix whose lock no longer matches what is on disk), brings what a\nconnector installed in its harness to this release (the `cotal` plugin copy Claude Code sessions\nload, and its Claude Desktop entry), then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. A built-in that passes is reported as verified;\n`cotal ext seed --force` remains the unconditional reinstall. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nBefore disk reconciliation, `update` reads the selected running manager. A machine with no recorded\nmesh has no running manager to observe, so that read is skipped and the command completes. The same\nholds when every recorded mesh is down and none is selected. A remote user mesh, registered with\n`cotal meshes add --mode user`, is named and skipped: its manager runs under another install, so\nthere is no custody on this machine to preserve, and a `legacy` verdict still comes only from a\nmanager this machine read. A stopped mesh that this machine runs, chosen with `--space` or by running\ninside its project, has no running manager either when no `cotal supervise` process on this machine\nmay be serving its space and nothing listens at its recorded broker address, so the read is skipped\nthere too. A controller stack that completed ordinary `cotal down` is eligible after its retained\nservice records prove every process gone. Missing instance records, live or unresolved processes,\nand an unfinished down still refuse. That is the cold path past a `legacy` verdict: `cotal down`,\nthen `cotal update`. A space prepared on this machine that never started, whether this folder's,\nselected or named, has no broker and no manager, so it is named and skipped the same way. Any other\nselected mesh whose manager cannot be read is still a refusal: a named space that is not\nrecorded, a mesh registered with `cotal meshes add`, a `--server` override, a `cotal supervise`\nprocess still running from any folder whose command line names that space or no space, or a broker\naddress where anything accepts the connection or the dial does not finish in time. With several\nmeshes running and no `--space`, `--server` or `--creds`, the install is machine-wide, so every\nrunning manager is reported in turn, each under its space name, before anything is written; a\n`legacy` verdict on any of them makes the whole run not a hot update. A folder that holds several\nregistrations, such as two remote spaces registered there with `space add --from`, is read the same\nway, one registration at a time. A selector flag still reports\none manager. A manager without a\ncustody generation is reported as `legacy`: it cannot preserve its manager-owned PTYs, so the\ncommand says that this is not a hot update and prints `exact`, `fork`, `fresh`, or `drain-only`\nfor every seat. This report sends no stop, preservation-commit, or replacement command.\nIt does not preserve a running PTY on a legacy manager. The built-in pty runtime spawns\nin-process on every platform and reports `legacy`. On Linux it still adopts seats that an earlier\nmanager left under a detached custodian, but it starts no new custodian. An incompatible native\n`@lydell/node-pty` or ConPTY ABI break remains an explicit per-seat maintenance cut.\n\nWith `--self`, the selected running manager is reported before any global install. When a newer\nrelease exists, Cotal then installs the exact version it validated, resolves and verifies that\npackage in npm's global root, then launches that binary with the same `--space` / `--server` /\n`--creds` selection to reconcile connectors and first-party extensions to the new generation. An npx\nor dev-clone invocation therefore installs and continues through a separate global copy; it never\nclaims the already-running process changed. From a source checkout, the `update --self` hint in\n`update` and `update --check` says so. If the binary is current, `--self` performs the normal\nlocal reconcile without reinstalling it. Each npm install it runs starts with 022 added to your\numask, so a umask of 0002 does not leave the installed tree group-writable.\n\nOnce global replacement starts, Ctrl-C waits for installation, verification and reconciliation to\nfinish before returning exit code 130. SIGTERM waits the same way and returns 143. The command\nexplains the wait, keeps its update lock and does not send terminal interrupts into the child\nprocesses that are replacing packages. An install or reconcile failure still reports its own\nnonzero result. Force-killing the process or closing its terminal is not a supported cancellation.\n\nRun through the installer's `cotal` launcher, `update` first removes group and other write from the\nfolders and files you own in Cotal's install root, without following links. An installer from before\nit set its own umask, run under a umask of 0002, left that tree group-writable. Running the installer\nagain repairs it the same way.\n\n`update` also removes group and other write from the folders and files you own in Cotal's config\ndirectory, `$XDG_CONFIG_HOME/cotal` or `~/.config/cotal`, which holds the extension prefix and the\nseed store. An earlier seed run under a umask of 0002 left those folders group-writable, and the web\nseed's check for other local writers refused them. Each repair prints how many entries it changed\nand where. A folder above the config directory, such as `~/.config`, is not Cotal's, so `update`\nleaves it as it is. The seed still refuses it and names `chmod g-w <path>` as the next step, and\n`update` exits nonzero.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--foreground|--detach] [--open] [--space <s>] [--root <prepared-dir>] [--server <url>] [--channels <path>] [--runtime <name>] [--manager-name <name>]\ncotal up --user-auth --idp <url> [--exchange-public-port <n> --exchange-public-url <https://\u2026> [--exchange-trusted-proxy]]\n [--advertised-server <url>] [--agent-provisioning-url <https://\u2026>] [--auth-timeout <seconds>]\ncotal up --tls-cert <cert.pem> --tls-key <key.pem> # serve broker TLS (both, or neither)\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL: one `nats://` or `tls://` URL with an IP address and a port, without credentials, path, query or fragment. `up` records and compares it with the scheme in lower case and without a bare trailing `/`, so `NATS://127.0.0.1:<port>/` names the same address as `nats://127.0.0.1:<port>`. A URL with no port is not given the default 4222. Any other value, a host name such as `localhost`, and an IPv6 address, which the stack's own clients cannot reach, refuse `USAGE_INVALID` before anything is written. That refusal gives an example on `127.0.0.1` with the port you passed, and prints no help. With no `--host` the broker binds `127.0.0.1`, so a `--server` naming another IPv4 address refuses `USAGE_INVALID` the same way and names the `--host` to add. A `tls://` address records TLS as required, so without `--tls-cert` and `--tls-key`, or a recorded TLS policy, `up` refuses it `USAGE_INVALID` before anything starts. On a prepared root it refuses `UNSUPPORTED`, exit 2, instead, as the `--tls-cert` row says. A controller stack's first `up` records it, and a later `--server` naming another address refuses `CONFLICT`, naming the recorded address and the bare `cotal up` that starts the stack there. A running space started again from its own project, that folder's subfolders included, at another address refuses `CONFLICT` too: it names the address the space runs at, the bare `cotal up` that reaches it, and the `cotal down` in its root. It does not offer a separate space in the folder, which is that space's own project. No command moves a stack that ran to another address. When another program holds the recorded address, bare `up` refuses too, so this refusal names that address and any process of this account that listens there, and says to stop it before bare `cotal up`. A different `--store-dir`, `--max-file-store` or `--host` refuses the same way, naming each recorded value. A controller stack's broker also needs an address that is not a wildcard, which `up` checks before it writes the stack journal. With `-f`, it overrides the manifest's `broker.servers` and follows that field's rules instead: a valid URL with no embedded credentials |\n| `--host <host>` | none | Bind host override for a **fresh** broker boot: an IP or hostname only, never a URL (that is `--server`) and never `host:port` (the port comes from `--server` or its default); a URL or port-bearing value is refused pointing at the right flag. 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. The broker's plaintext websocket and the manager's plain HTTP console bind the same host, so a wildcard bind opens all three listeners on every interface. Each of these refusals, and a concrete IPv6 address, refuses `USAGE_INVALID` (exit 1) before anything is written. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#managed-seats) working. A live refresh (`\u2713 mesh already running`) does not rewrite `.cotal/auth/server.conf` or rebind nats; stop the broker, then re-run `up --host` |\n| `--space <s>` | the folder's space | Space name, at most 64 characters; a longer one refuses `USAGE_INVALID` before anything is written |\n| `--root <prepared-dir>` | current folder's workspace | Start the project in `<dir>` instead of the folder you run `up` from. `<dir>` must already have its own `.cotal`, which `space create <space> --root <dir>` or an earlier `up` there made. An absent, inherited or symlinked `.cotal`, or the machine's home store, refuses before startup. Relative file, restore, channels, TLS and store paths remain relative to the invoking folder. The native `up` selection rule still applies after startup. |\n| `--store-dir <dir>` | none | JetStream store directory (recorded; a repair up reuses it). Also accepted with `-f`. A path that is a link refuses `USAGE_INVALID`, naming the directory it points to. A store folder belongs to one space: a folder another space uses refuses `CONFLICT`, naming that space |\n| `--max-file-store <bytes>` | nats-server's dynamic cap | JetStream file storage cap in bytes (a positive integer, no unit suffix). At least 67108865: the space's membership bucket reserves 67108864 (64 MiB), and nats-server creates each of the other streams only while one more byte is free. A smaller value refuses `USAGE_INVALID`, naming 67108865, before anything starts. Without it nats-server sizes the store at start as three quarters of the free space on its filesystem. The cap is fixed at broker start: a running broker cannot change it (`cotal down` first), and a stopped stack's next `up --max-file-store` replaces the recorded cap. A controller stack keeps it across `cotal down`, because it refuses `down --preserve-state` in this release. On a legacy root, `down --preserve-state` keeps it for the resume, and a resume with a different value refuses `CONFLICT`, exit 2. When a broker `up` started cannot create the space's streams for want of storage, the failure names the cap and `cotal up --max-file-store <bytes>`. Once the space holds more than the cap, the broker refuses every write: `send` refuses naming the cap, and `status` shows the store full. Not accepted with `-f` |\n| `--auth-timeout <seconds>` | nats-server's 2 seconds | User-auth only: how long the broker waits for the auth callout's answer before it refuses a connect, a whole number from 1 to 60. `up` renders it as `authorization { timeout: <seconds> }` in `.cotal/auth/server.conf` and starts the space's auth service with the same value as its callout deadline, so the two never differ ([callout deadlines](identity-and-auth.md#callout-deadlines)). It is recorded in `.cotal/broker-policy.json`, so a later `up` on the root renders it again without the flag, after `cotal down` or a preserved resume included. It is fixed at broker start: a running broker refuses a different value (`cotal down` first), and a stopped stack's next `up --auth-timeout` replaces the recorded one. Refused `USAGE_INVALID` with `--open`, without user auth, with `-f` or with `--restore`. A resume renders the recorded value and refuses another one |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON), in the [channel seed file](#channel-seed-file) format. An explicit path that is missing, unreadable or not JSON refuses `USAGE_INVALID`, naming the file, before anything is written. Content the registry would not take refuses `SCHEMA_INVALID` before anything starts, naming the file and the entry. `up` reads the file once, so a pipe or a FIFO such as `--channels <(\u2026)` works. A default `.cotal/channels.json` that is not a regular file, such as a FIFO, refuses `USAGE_INVALID` the same way |\n| `--restore <dir>` | none | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--accept-stale-checkpoint` | off | Explicit consent to resume a seat whose checkpoint was captured outside its recorded recency horizon |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs. A root whose first `up` recorded another mode refuses it |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger. A root whose first `up` recorded another mode refuses it. A stopped root whose space has user-auth state refuses a start without it, `--open` included, as `CONFLICT`, exit 2, before anything is written; add `--user-auth` to start it |\n| `--idp <url>` | none | With `--user-auth`: the IdP auth base URL to pin on first enable. A later run may omit it or pass any spelling that normalizes to the pinned URL, such as one with a trailing slash; a different IdP is refused. With `-f`, overrides the manifest's `broker.idp` under the same rules: https (or http on a loopback IP literal), with no credentials, query or fragment |\n| `--exchange-public-port <n>` | none | With `--user-auth`: add the public exchange face on this loopback port, for an HTTPS reverse proxy to forward to |\n| `--exchange-public-url <https://\u2026>` | none | With `--exchange-public-port`: advertise the reverse proxy's HTTPS URL in discovery |\n| `--exchange-trusted-proxy` | off | With `--exchange-public-port`: attribute public failure buckets to the last `X-Forwarded-For` hop. Enable only when the listener is reachable solely through a trusted proxy; otherwise the socket address is used |\n| `--advertised-server <url>` | `--server` | With `--exchange-public-port`: the broker address the public discovery bundle tells participants to dial, such as a `wss://` address behind your proxy. Must be a `nats://`, `tls://`, `ws://` or `wss://` URL |\n| `--agent-provisioning-url <https://\u2026>` | none | With `--exchange-public-port`: the deployment's agent-provisioning endpoint the public bundle advertises. A participant's `cotal spawn` on a remote user-auth mesh sends its login bearer there to provision the agent; without one, that spawn needs a one-time enrollment ([Run a mesh](run-a-mesh.md#remote-supervised-seats-by-enrollment)). HTTPS only |\n| `--service-endpoints` | off | With `--user-auth`: the auth service also hosts service endpoints: the public registration door and the authenticated host route that `cotal service-endpoint` drives |\n| `--detach`, `-d` | on | Run in the background, which is also the no-flag default |\n| `--foreground` | off | Keep native stack lifetime in this terminal. Required when a systemd/launchd unit supervises `up` itself. Cannot combine with `--detach` |\n| `--tls-cert <path>` | none | PEM certificate to serve TLS with. Must be given together with `--tls-key`. Before starting the broker, Cotal checks readability, private-key mode, key/certificate match, the validity window, and host coverage. `nats-server` accepts an expired certificate and leaves the failure to clients, so Cotal performs these checks first. The decision is recorded; a later bare `cotal up` keeps serving TLS. Cotal's own clients, `up` included, check the certificate against Node's trusted CAs and the address they dial. For a self-signed certificate or a local CA, set `NODE_EXTRA_CA_CERTS` to that certificate or CA file (PEM) for `cotal up` and every other `cotal` command of the space; the stack's delivery daemon and manager inherit it from `up`. The stack's address is `tls://`, so every dial checks the certificate against the address it names: an address `up` chooses, the default or the one a stopped folder last ran at, becomes `tls://`, and the `TLS: serving` line of the receipt names it. A root prepared with `cotal space create`, or one whose stack the service controller owns, has no route that serves TLS: there `--tls-cert`, `--tls-key` or a `tls://` `--server` refuses `UNSUPPORTED`, exit 2, before anything starts. On a prepared root that refusal names `cotal up -f <cotal.yaml> --tls-cert <cert.pem> --tls-key <key.pem>` with a `tls://` address in the manifest's `broker.servers`, which serves TLS there. Elsewhere a `--server nats://` with `--tls-cert` refuses `USAGE_INVALID` before anything starts and names the `tls://` form, and so does `up -f` whose manifest `broker.servers` names a `nats://` address. A `--server nats://` on a root whose recorded policy serves TLS refuses the same way, naming `.cotal/broker-policy.json` and the `tls://` address to pass, because the broker it would start serves TLS while the address it would record is dialed in cleartext. When this client refuses the certificate in the handshake, `up`, with or without `-f`, stops the broker, records nothing and fails `OPERATION_FAILED`, naming the native error and the step to take |\n| `--tls-key <path>` | none | PEM private key for `--tls-cert`. Refused if group- or other-readable (tighten to `600`) |\n| `--file <cotal.yaml>`, `-f` | none | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing. Without `-f` it refuses `USAGE_INVALID` before anything starts or is written. A preview from an unprepared folder under the machine home reads that folder's empty policy rather than inheriting the home store; applying there requires `space create` first. |\n| `--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. Without `-f`, that refusal is `DEPENDENCY_UNAVAILABLE` (exit 2) before anything is written, and for a known runtime the remedy names the `cotal ext add <pkg>` to run. With `-f`, overrides the manifest's runtime |\n| `--max-sessions <n>` | 64 | Live-session ceiling for the mesh manager. Each console pane and each `cotal attach` is one session, so size for agents \xD7 panes, not agent count. Recorded on the mesh, and for a controller stack in its saved manager definition, and reused by every later manager launch, so a bare `up` after `down`, a repair or a resume does not silently drop back to 64. A running manager cannot change it: `cotal down` first, then `cotal up --max-sessions <n>` |\n| `--seat-cap <n\\|unlimited>` | `unlimited` | Live seats this root's manager admits for one owner. Recorded in this root and read by the manager at each start, so a bare `up` after `down`, a repair or a resume keeps it. A running manager cannot change it: `cotal down` first, then `cotal up --seat-cap <n>`. Cannot be combined with `--restore`, `--file` or `--no-manager` |\n| `--manager-name <name>` | none | Name this root's manager. `who --all` and the manager choice show the name, and on a user-auth mesh `--on` takes it. It is recorded in the root, so a restart or repair keeps it. A running manager keeps the name it started with, so a different name is refused until `cotal down`. The name is one line of at most 128 characters with no `/` or `\\`, and it may not look like an instance id (26 to 32 lowercase letters and digits). Cannot be combined with `--no-manager`, `--file` or `--restore` |\n| `--no-manager` | off | Broker-only boot: start the broker and, in auth mode, the delivery daemon, and no local manager. A refresh under the flag of a mesh whose manager is live refuses rather than keeping or stopping it. For a controller stack, use `cotal service stop stack-manager --space <s> --yes` first. A legacy stack has no command that stops only its manager: `cotal down` stops the whole stack, then `cotal up --no-manager --space <s>` starts it broker-only. Cannot be combined with `--runtime`, `--max-sessions`, or an agent-declaring manifest |\n| `--rotate-sys` | off | Rotate the space's system account and re-mint its two `$SYS` creds. Needs a stopped mesh; refused with `--open`. A controller stack takes it once every member is stopped, and a first start on a prepared root refuses it |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs. It runs in the background by default and records the mesh so `cotal spawn` from\nany directory can find it; `--detach` explicitly names the same mode. `--foreground` keeps the\nnative stack in this terminal. A contradictory mode pair refuses before setup or seed work. `up`\nseeds the [built-in connectors](#built-in-connectors-are-seeded-extensions) only when it first starts\na process, or when `up -f` checks the connectors its manifest's agents use, so a refusal before then\nleaves the operator-global seed store as it was. When a start fails after that seed moved the store to this version, the failure\nsays so and names the store. In a\nfolder whose only `.cotal` above it is the machine's home one, such as a new folder in your home\nfolder, a bare `up` makes the folder's own `.cotal` and starts the stack there, as it does in a folder\nwith no `.cotal` above it, and a run refused before it writes there removes that `.cotal` again.\nWhen the home holds a stack an earlier release started there, the folder starts its own stack beside\nit. The home's stack keeps running and keeps its records, and commands in the folder reach the\nfolder's own stack from then on. In such a folder,\n`up -f` and `up --restore` refuse `CONFLICT` before anything is written: prepare the folder with\n`space create <space> --root .`, then run `up` again from it. In the home folder\nitself, whose `.cotal` holds this machine's own records, `up` refuses `CONFLICT` and names a project\nfolder, as `space create` does there. In a folder that is, or is in, a control directory, `up`\nrefuses `CONFLICT` before anything is started or written and names the project folder to run it from. A home that already holds a stack an earlier release started\nthere keeps starting it in place, and `up --root <dir>` from the home starts that prepared project.\nWhen `--root` names another prepared project, run bare\n`cotal down` from that project folder: bare down\nstops the folder's stack, and `down --space` is not a remote replacement for that scope. With no\n`--space`, `up` runs the folder's space: the one its auth account names, else the one its stack or\nrunning mesh records, else the one prepared there. A folder whose records name no space is a new\nproject, and `up` names its space after the folder, the name `cotal up --space <folder name>` would\ngive it. A folder name that is not a valid space name, such as one with a space in it, refuses\n`USAGE_INVALID`, exit 1. A name another folder on this machine registered or prepared, running or\nstopped, refuses `CONFLICT`, exit 2, and is not taken over. Both refusals write nothing and name\n`cotal up --space <name>`. A space named after its folder keeps its registration after `cotal down`,\nso the name stays the folder's while it is stopped. `cotal up` there starts it again on the same\nlauncher, and `cotal space forget <name>` frees the name. A second `up` in the same folder\nruns the same space again. A folder whose stack and registrations name more than one space refuses\nwith `CONFLICT` and asks for `--space`. With no\n`--server`, a fresh local stack selects a free port if the default address is occupied or\nreserved by another registered space, including one whose broker is stopped. It saves the chosen\naddress. A later `cotal up` of\nthe same space from the same folder starts from the URL that mesh recorded; an explicit `--server`\nstays fail-loud on collision. An address that a space registered for another folder keeps while it\nis stopped refuses `CONFLICT`, exit 2, naming that space and folder, and nothing starts, unless this\n`up` takes that space's name over. `up -f` reads its address from the manifest's `broker.servers`\nand is refused there by the same rule, with the field to point at another address in place of the\n`--server` to pass. Each refusal also names `cotal space forget <space>`, which frees that address\nwith the name. A second stack started on a held address answers for both registrations, which\nleaves the other space's `down` and its reads reaching this one. When the named space runs from another folder on this machine,\n`up --space` refuses `CONFLICT`, exit 2, naming that folder, with or without `--server`, `--json` or\n`--plain`, and writes nothing in the folder it ran from. A bare `up` whose folder names a space that\nruns from another folder refuses `CONFLICT` as well. Its Next names\n`cotal up --space <name>` to start a separate space in this folder, and the `cotal down` that stops\nthe other one in its own folder. A background boot also brings up the control plane (delivery daemon in auth\nmode, then the manager). `--no-manager` is the broker-only mode: it boots\nthe broker (and the delivery daemon in auth mode) and starts no manager, so there is no manager\npidfile to leave stale. A refresh under the flag of a mesh whose manager is live refuses rather\nthan keeping or stopping it. Stop a controller stack's manager first with\n`cotal service stop stack-manager --space <s> --yes`. A legacy stack has no command that stops only\nits manager: `cotal down` stops the whole stack, then `cotal up --no-manager --space <s>` starts it\nbroker-only. For a split topology with a manager, wait for `.cotal/manager.<spaceKey>.log` to contain `\u2713 manager up`, then stop that manager the same way on that\nhost and run [`manager start`](#manager) against the remote broker; see\n[Run a mesh](run-a-mesh.md). `cotal up --detach` prints `\u2713 running in the background:` with\n`manager` listed (pidfile liveness, not a teardown boundary); with `--no-manager` the line lists\nonly what actually started. Its receipt begins with `Mesh: <space>` and ends with a Next that names\n`cotal spawn` when a manager runs, and the stop. A start refused for its space, such as one at\nanother address of a space that runs from this project or one whose name another folder holds,\nprints no receipt: its refusal names the space under its own `Mesh:` heading on stderr. With no `--space`, in a new project, it also says\nthat the space is named after the folder. For a stack an earlier release started in\nthe machine home, the line names `cotal down --preserve-state`,\nbecause bare `down` refuses the home stack. With `--no-manager` it names `cotal up` first, because\n`--preserve-state` needs a manager. On POSIX, a foreground broker has its own session and process group.\nTerminal Ctrl-C reaches the CLI owner, which stops the manager through the same stop as bare\n`cotal down` (see [`down`](#down)), then the rest of the stack, and reports managed agents under\nthe same rule. When the manager stop is refused, Ctrl-C prints the refusal with the reap route and\nleaves the stack running, including the manager, broker, delivery and any auth service. A second\ninterrupt during teardown does not start another stop. Windows keeps its existing console signal\nbehavior. The `-f` form is a\n[manifest deploy](#manifest-deploys).\n\nAn `up` that starts or refreshes a space on this machine writes the `default` persona into\n`<root>/.cotal/agents` when the root has none, so a bare `cotal spawn` has a persona to launch. A\nspace prepared or started before it had one gets it on its next `up`, and a persona you edited is\nleft as it is. A controller stack lists the write under `result.written` and in its report, and the\nretained launcher prints the path it wrote. If the write fails, the stack stays up and the failure\nis a warning. A controller recovery or restore leaves the persona to the next `up`.\n\nA space name belongs to one registered mesh. `up` refuses a name that another live mesh holds. It\ntakes over a name whose broker is not running, and when that registration belonged to another root\nit prints that root and its broker address. It refuses instead when that root holds a restore or a\npreserved cut in its maintenance journal: finish that attempt from its root, or start this stack\nunder another `--space`. It also keeps the name, and names that root, when the root or its journal\ncannot be read, for example when access to a parent folder is denied, or when the recorded root is\nnot an absolute path. A recorded root matches this one only by the folder it resolves to on disk, so\na path such as `<this root>/link/..` whose link leads through an unreadable folder keeps its name\nlike any root that cannot be read. A root counts as gone only when it no longer exists or is not a\ndirectory. A readable root with no maintenance journal also gives up its name. Each of these\nrefusals ends `refused` with exit 2, on a controller stack and on the retained launcher, and shows\nits reason and the next step, such as `cotal meshes rm <space>` for a root that is not an absolute\npath. A bare `up` in a copy or a moved folder, which holds that space's records, leaves the name\nwith the folder it is registered for: it refuses `CONFLICT` and names that folder, as the stack\njournal section below describes.\n\nA foreground `up` holds the root's maintenance lock only while it starts the stack. A bare\n`cotal down` from another terminal can then stop the stack; the foreground `up` sees the completed\nstop or preserve, prints `Foreground stack released` and exits 0. On a controller stack, Ctrl-C or\n`SIGTERM` in that terminal runs the same guarded stop as bare `cotal down` and exits 130 or 143. A\nsignal while the stack is still starting lets the start settle, then runs that stop. When the start\nfails, the stop still runs before the start failure is printed. A hangup, such as a closed terminal,\nruns no stop: the command exits 129 and the stack keeps running until `cotal down` stops it. While\na foreground `up` holds the stack, it records its process in `.cotal/stack-foreground.hold`, and a\nsecond foreground `up` on that stack refuses `CONFLICT`, exit 2, names that process, and starts\nnothing. A foreground `up` on a stack that is already running without such a holder, because a\nbackground `up` started it or the holding terminal closed, takes over its lifetime. Before it\nblocks it says that the stack is already running and that Ctrl-C or `SIGTERM` there stops the\nwhole stack. A\nsystemd unit that runs `up --foreground` should set `KillMode=mixed`, so the stop signal reaches\nonly `cotal up`, which runs the guarded stack stop, and a `TimeoutStopSec` long enough for that stop.\nThe default `control-group` mode signals every member at the same time, and a manager signalled\ndirectly stops on its own, without the request-bound stop's receipts.\n\nA detached `up` interrupted by SIGINT, SIGTERM or SIGHUP stops where it is and exits 130, 143 or\n129. A member it was launching, with the launch recorded and its child not yet recorded, is recorded\non the way out: `failed` when no child was spawned yet, or with the child's process when one was, so\nthe next `up` or `down` settles it from evidence. Members it already started keep running, and it\nnames them and the next step: `cotal up`\nfinishes the start, and `cotal down --yes` stops them. When only its broker started and the space\nwas not registered yet, it names that broker's pid, `cotal down --yes` and then `cotal up`. An\ninterrupted `up --no-manager` names `cotal up --no-manager` in each of these, because bare `up` adds\nthe manager, and the `cotal down --yes` that settles such an `up` names it as its Next. An `up`\ninterrupted while it writes a new space's trust material leaves the space new, so the next `up`\nwrites all of it again. A bare `down` interrupted the same way exits with the same codes and names\nthe members still running; `cotal down --yes` finishes the stop. When it had signalled a member that\nhad not yet exited, it also names that stop's request: `cotal down --yes` stops the member again\nwhile it runs, and once it has exited, `cotal down --abandon-stop <request-id> --yes` finishes the\nstop. A bare `down` on the retained launcher, whose stop lines name each process and its pid, stops\none member at a time. A signal that lands while a member's stop is in flight lets that stop finish\nand print its line first. The `down` then exits with the same codes and names the members it\nstopped and those still running, and `cotal down --yes` finishes the stop. A second signal ends it\nat once and names the member whose stop is not confirmed, which `cotal down --yes` stops again\nwhile it runs. Any command interrupted while the CLI is still loading, before the command begins, exits with\nthe same codes and says it did nothing. `up` keeps that notice until its own takes over, so no moment\nof a detached start exits without one.\n\n`up` runs the `nats-server` on PATH when one answers `--version`, and the bundled one otherwise. A\nbroker start refuses a `nats-server` file that another local account could replace, or that sits in\na folder another account could write, as the folder of a Homebrew install on macOS is. So when the\none on PATH is like that and the bundled one is not, `up` runs the bundled one and never runs the\none on PATH. The `Broker executable` line of `status` names the one it passed over, and `--json`\ncarries it as `machine.natsServer.skipped` with the reason. A file that fails this rule is never\nrun, not even for its version. When the bundled one is missing or fails the rule too, the one on PATH\nstays the choice: `status` shows why on that line, and `up` and `setup` refuse `PERMISSION_DENIED`\n(exit 2) before anything runs, naming each folder and the `chmod` that would fix it. A bundled one\nthat fails the rule, with none on PATH, is refused the same way. One\non PATH must report version 2.12 or newer. An older one, or one that reports no version, refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2) before the broker starts, naming its path and version; upgrade it\nor take it off PATH. When `up` starts the broker, its receipt names the file, where it was found and\nits version on the `Broker executable` line, and `--json` carries them as `result.brokerExecutable`.\n`status` shows the same under This machine, and `setup` refuses the old one the same way. When the\n`nats-server` a detached start on the retained launcher ran exits before it answers, `up` fails\n`OPERATION_FAILED`, exit 5, naming that file, its exit code or signal, and its log `.cotal/nats.log`.\nThe next step is to read that log, then fix the file or take it off PATH, and run `cotal up` again. A\nbroker still running when the wait ends leaves the outcome unconfirmed, `OPERATION_UNKNOWN`, exit 3.\nA foreground `up` whose broker exits on its own stops its members and exits 5, or 0 when the broker\nexited 0, as it does after an external `down`. Its line names the exit code or signal, and says the\nmesh stays recorded only when the registry holds that broker's record. A broker that exits before\n`up` recorded the mesh leaves it unrecorded, and the line says so.\n\nThe generated broker configuration is owner-private (`0600`) in open and authenticated modes,\neven with a group-writable umask. `up` creates `.cotal`, the broker store and the persona catalog\n`.cotal/agents` owner-private (`0700`) too, as do `space create` and `setup`. An existing directory\nkeeps its mode. A `.cotal`, or a root without one, that this account cannot write refuses with\n`PERMISSION_DENIED` (exit 2), naming it and `chmod u+w`, before anything is started or written. A\nbroker store this account cannot write refuses the same way, naming the store's `jetstream` folder,\nor the store itself before a broker has made one or when this account cannot open it, and\n`chmod -R u+w`. If that unwritable folder also lacks read or search access, the step names\n`chmod -R u+rwX` instead, since `u+w` alone leaves it closed. A writable store root without read\naccess refuses before startup too, naming that root and `chmod -R u+rwX`; a writable `jetstream`\nfolder without read access does not trigger this additional root check. The step quotes the path when a shell\nwould split it or read a character in it as syntax, such as a space or a backtick. Without that check the broker\nexited and named the cause only in its log. `status` names the same step for a stopped space whose\nstore `up` would refuse. A service record folder of the space, under `services/` in the Cotal home,\nthat this account cannot write refuses `up` the same way, naming the folder and `chmod u+w`, before\nany member starts. The broker log `.cotal/nats.log` and the auth service,\ndelivery and manager logs are created owner-private as well, and the process records `up` writes, such as\n`.cotal/nats.pid` and its identity pin, are created `0600` under a fresh name that is never opened\nthrough a symlink, then renamed into place. The pid record and identity pin of a native service\ninstance are published the same way. A file Cotal writes in place under `~/.cotal` or a\nproject's `.cotal`, such as a generated configuration, a credential, a process marker, a persona,\na seat record or one of those logs, is never written through a symlink.\n\nEvery command that takes `--store-dir` refuses an empty value `USAGE_INVALID` (exit 1) before it\nruns, rather than reading it as the default store. A value that starts with `~` refuses the same\nway: no shell expanded it, so it would name a folder called `~`, and the refusal names the absolute\npath to pass instead.\n\nAn explicit `--store-dir` on `up`, `up --restore` or `backup restore` must name a directory or a\npath to create in one. A regular file there refuses `USAGE_INVALID` (exit 1) and names it. The\nexisting part of the path follows the [`space create`](#selecting-a-space) rule for other local\nwriters, so a group- or world-writable directory in it refuses `PERMISSION_DENIED` before anything\nis written. A path still to create needs its nearest existing directory to be writable by this\naccount, or it refuses `PERMISSION_DENIED` (exit 2) before anything is written. On `up`, a\nnon-empty directory is accepted, since it may hold an existing store. When it holds anything besides a\nstore's `jetstream` directory, its `.cotal-store-id` marker and the `ocsp` folder nats-server makes\nthere when it reloads its configuration, `up` warns and names those entries,\nbecause `clean store`, `reset store`, `backup cleanup-fallback` and a restore into that directory\nrefuse it while they are there. A restore creates its store\ndirectory itself, so `up --restore` and `backup restore` refuse a path that already exists, empty or\nnot, other than the preserved store, with `USAGE_INVALID` (exit 1) and restore nothing. A control\ndirectory refuses `USAGE_INVALID` too, before anything is written: a root's `.cotal` or a path in it,\nother than that root's own `.cotal/nats`, and the machine home's `.cotal`. A store there would mix\nwith credentials and records, or with another root's store, and `reset store` could not remove it.\n\nA store folder belongs to one space. nats-server takes no lock on its store folder, so a broker of\nanother space starts on it, and a reset or clean of the folder would remove that space's data too.\nA `--store-dir` on `up`, with or without `-f`, refuses `CONFLICT` (exit 2) before anything starts\nwhen another space uses the folder: its registration, its prepared stack journal or the broker\nconfiguration its root last rendered names the folder, or the folder's `jetstream` tree holds its\n`CHAT_`, `DM_` or `TASK_` streams, whether or not its broker runs. The refusal names each such\nspace, the root a record names for it and its streams. A store an earlier release let two spaces\nshare still starts from its stack's recorded store, from a legacy root's last served store, or as\nthe default store, and `up` warns `STORE_SHARED`, naming the other space; `--json` carries the\nwarning. `clean store`, `clean all`, `reset store` and `reset space` refuse such a folder, as\n[clean](#clean) and [reset](#reset) describe. A controller stack whose recorded store folder is\nmissing, as after it was removed by hand, starts its broker on a new empty store there and warns\n`STORE_MISSING`: the space's history starts empty. A legacy root whose last broker served a missing\nfolder warns the same. When a controller stack's broker logged damage in a stored file since it\nstarted, as `status` shows in its `Store` row, `up` warns `STORE_DAMAGE_LOGGED` with the broker's\nwords. It still starts the stack and repairs nothing.\n\nA file you own is set to `0600` through the open file. A symlink, a file another user owns or a name\nthat is not a regular file is refused and left unchanged. A private directory is set to `0700`\nthrough its path, which follows a symlink; this step does not refuse a symlinked `~/.cotal` or\n`.cotal`.\nOn Windows the owner and symlink checks do not\napply, because POSIX modes are not that platform's protection model; the file is opened with mode\n`0600` and private secrets are locked down by their access list. Local records under `~/.cotal`\nand a project's `.cotal`, such as the stack journal, service records and deployment attempts, use\nthe same model there. A store directory Cotal creates gets the private access list, and a record is\nwritten like a private secret. Reading a record or checking its path does not check an owner, a mode\nor an access list, and does not refuse a link: a symlink or junction is followed. Size limits, the\nrecord schema, the regular-file check and the refusal of a link inside a record store's own\ndirectories still apply. When a service path check refuses a path, the refusal names the option\nand each path that failed once, with its reason and one `chmod` that fixes every writable one. A\nwhole-stack failure shows that sentence as its native detail. On POSIX, a credential file a command\nis given, such as a `<private-file>` argument, and a saved private record must be a regular file this\naccount owns with no group or other access. One that is not, or that this account cannot open,\nrefuses `PERMISSION_DENIED` (exit 2) and names the file. When only its owner or mode is wrong, the\nNext is the copy or `chmod 600` that fixes it. A credential file that does not exist refuses\n`TARGET_NOT_FOUND`, and one whose path cannot be checked refuses `DEPENDENCY_UNAVAILABLE`; both\nname the file.\n\nA fresh prepared static-auth, open or plain-loopback user-auth root starts through the service\ncontroller. It records its mode, definitions and full instance IDs in the private\n`.cotal/stack-controller.json` journal. The root keeps the mode its first `up` recorded: a later `--open` or\n`--user-auth` that names another mode refuses `CONFLICT`, exit 2, whether the stack runs or not.\n`reset store`, `reset space`, `clean all` and `space forget` all keep the journal, so none of them\nchanges the mode. To run another mode, run `cotal space create <space>` in another folder, then\n`cotal up --open` or `cotal up --user-auth` there.\nStatic-auth startup prepares trust and configuration, starts\nthe broker, initializes native streams, then starts delivery and the optional manager. Open startup\nprepares an unauthenticated broker and streams, then the optional manager; it creates no delivery\nservice or credential. Each member reports its own readiness; the result does not prove agent or\ntask readiness. A prepared root never starts the legacy launcher: an option the controller stack has\nno route for, such as `--tls-cert` or a `tls://` `--server`, refuses `UNSUPPORTED`, exit 2, before anything starts, as it\ndoes on a root whose stack the controller already owns. `--rotate-sys` on a first start refuses the\nsame way, because a first start mints the space's `$SYS` creds. The refusal names each option, and\nnames `cotal up -f <cotal.yaml>` when the manifest route takes them all. A root with no prepared\nrecord and no stack journal starts on the legacy launcher whatever `up` names, one whose space its\n`up` registered included, so `up` never writes a stack journal there. A prepared user-auth root that\nthe legacy launcher started and that has no stack journal keeps that launcher for such an option.\n`--json` returns one\nstack receipt and `--plain` returns a line-oriented report. Partial startup keeps confirmed members\nand reports them; unknown operations are retained and never retried automatically. A failure while\ninitializing streams or recording the broker registration is attributed to the broker, not the next\nunstarted member. The failure target names that stage. Typed JetStream API errors retain their\nnumeric code and status without copying the server's description or changing the unknown outcome.\nThe journal records the absolute root it was written in, and the stack runs only from that folder.\nIn a copy of a controller project, or after the folder is moved, commands that read the journal\nrefuse `TARGET_UNAVAILABLE`, exit 2, and name the recorded folder, with another space selected too,\nwhile `up` and `down` there\nrefuse `CONFLICT`: a copy is told to use the stack from the original, and a moved folder to move\nback there first. A folder is moved when its recorded folder no longer holds the journal. A second Cotal home that reaches the original over a shared filesystem is\ntold the same, and it can adopt the copy only after the original's stack is stopped there and that\nfolder no longer holds its journal. When this machine records neither the space nor an instance of\nits stack, as on another machine, moving back would not start it, so a moved folder is told only to\nadopt the journal with `space create --adopt`. `status`, `send` and `history` there name the same\nnext step. `status` there reports under the journal's space, as the reads there refuse under it, and\nsays the folder holds a copy of that space or its records moved from the recorded folder. Bare\n`cotal` says the same, with that step. `clean store` and `clean all` there refuse `TARGET_UNAVAILABLE`, exit 2, naming the\nrecorded folder, and remove nothing, so `status` there does not offer `clean store`.\nCommands that reach a static-auth space from a moved folder, such as `send`, `history`, `ps` and\n`service stop`, refuse the same way. `status` there says the folder was moved and names the way back\nas its next step. The broker names a store inside the project folder relative to that folder and\nruns from it, so a broker still running when its folder is renamed keeps writing into the renamed\nfolder and does not create the old path again. A broker an earlier release\nstarted does, so when something is found at the recorded path, the refusal says to move it aside\nbefore moving the folder back. When the space is prepared or registered here and\n`space create --adopt` could take the journal over instead, the refusal names that command too. A\nfolder whose `.cotal` is a link to another project's `.cotal` is told that it is a link and to use\nthe stack from that project. `up` and `down` there refuse `CONFLICT`, exit 2, before anything reads\nthrough the link, naming the link and where it leads. When it leads to another project's `.cotal`,\nthey name that project; otherwise the next step is to remove the link and prepare the folder with\n`space create`. When the `.cotal` it leads to holds this folder's own records, because its stack\njournal was recorded here, as after moving `.cotal` away and linking it back, they name the way back\ninstead: `rm` the link, which removes only the link, then `mv` that `.cotal` back in its place, which\nkeeps the history. Nothing moves it for you. In the folder the records were moved to, commands say\nthey were moved there from this folder and name the same step. A `.cotal` link that leads nowhere makes them refuse `TARGET_NOT_FOUND`, exit 2,\nnaming where it leads. `status` there does not read the folder's boot and additive attempt\ninventories through the link. It warns `COTAL_DIR_LINKED`, or `COTAL_DIR_DANGLING` for a link that\nleads nowhere, with the same next step, and exits 4. For a linked folder, its selected space names\nthe same refusal, and Next gives that step in place of starting the space with `cotal up`. `space create` in a copy whose records name\nanother root says so and names that root, and in a linked folder it names the link.\n\nA project the legacy launcher started writes no journal, so a copy of one, the folder a later `up`\ntook its name over from, and one moved away from its recorded path are recognized from the\nregistration instead. While this machine registers that space for another folder, reads there refuse\n`TARGET_UNAVAILABLE`, exit 2, and name that folder, with another space selected too: the next step is to use the stack from it, or,\nwhen it no longer exists, to move this folder back there first. A bare `up` or `down` there refuses\n`CONFLICT`, exit 2, and names the same step, because the folder holds the space's records and no\nstack of its own, so a bare `up` does not take the name over from that folder. When that folder no\nlonger exists and the space's broker does not answer, the `up` refusal also names\n`cotal up --space <space>`, which takes the name over to run the stack from this folder.\n`status` there reports under that space and says the folder holds its records, naming the folder it\nis registered for, or that the folder was moved from there. A copy and the folder a later `up` took\nthe name over from hold the same records, so both read that way. Bare `cotal` says the same, with\nthe step the reads name. `clean store` and `clean all` there refuse `TARGET_UNAVAILABLE`, exit 2, as the reads do, and\nremove nothing, so `status` there does not offer `clean store`. A legacy root whose space this machine registers for no folder keeps its own\n`cotal up`, which starts it again at the address its last start configured.\n\nA journal that is not valid JSON, as an interrupted write or a full disk can leave it, makes `up`,\n`down`, `clean`, `reset` and `space create` refuse `SCHEMA_INVALID`, exit 1, naming its path, and\n`status` warns about it and gives the same step under Next, in place of `cotal up` and\n`cotal clean store`. When no member of the stack runs, the Next says to remove the file, and the\nnext `cotal up` starts the stack again from its saved definitions. When no space is recorded at the\nroot, as after `space forget`, there are no saved definitions, and the Next says to remove the file\nand run `cotal up`. When members still run, it names the `cotal service stop` for each one first,\nmanager first and broker last.\n\nA service instance record under `services/` in the Cotal home that is not valid JSON, cut short the\nsame way, is left as it is. `up`, `down`, `service` and `reset store` refuse it `SCHEMA_INVALID`,\nexit 1. `service` names its path and the `mv` that moves it aside, and so do the others for a stack\nthat has run. Once it is moved, Cotal no longer tracks the process that record names, so stop that\nprocess yourself if it still runs. For a stopped space, `status` warns\n`SERVICE_INSTANCES_UNAVAILABLE` and gives the same step under Next, in place of `cotal up`. The\ndefinition of a service you added that is not valid JSON is left as it is too. `service` and its\nverbs, such as `service show`, `service add` and `service stop`, refuse it `SCHEMA_INVALID`, exit 1,\nnaming its path, the `mv` that moves it aside, and `cotal service add <name> --space <space> --file <declaration>`,\nwhich adds the service again. An instance of it that still runs can then be stopped by its full\ninstance ID. Any other saved record that is not valid JSON, such as a native install, uninstall or\nunit-action attempt, a supervisor receipt, a stack member's definition or an endpoint reference, is\nrefused `SCHEMA_INVALID` with no step: an attempt or receipt can be what holds a native step back,\nso nothing says to move it. Other commands that read the definition of a service you added keep\nthat plain refusal, and `status` shows the saved definitions as unavailable.\n\nA service record folder that this account cannot read, such as its `instances` folder under\n`services/` in the Cotal home, is left unchanged. When `service` or `service logs` cannot list it,\nthey refuse `PERMISSION_DENIED`, exit 2, naming the folder and a quoted `chmod u+rwx <folder>`\nstep. A command that reads one record in it, such as `service show`, names the folder and\n`chmod u+rx <folder>`, which makes it readable only. A record file it cannot read is named with\n`chmod u+r <file>`. Each path is quoted when a shell would split it. A command that then writes a record there or\nholds one while it acts, such as `service add` or `service start`, refuses `PERMISSION_DENIED`,\nexit 2, naming the folder and `chmod u+w <folder>`, and nothing is written. No permissions change\nautomatically. `status` keeps its partial result and warns that the saved definitions or instance\nobservations could not be read, with that code.\n\nA controller stack opens three listeners. The broker serves clients at its `--server` port, and a\nwebsocket for the browser console's mesh session on a port `up` picks, which is always plaintext.\nThe manager serves its console over plain HTTP on a port it picks when it starts. All three bind the\n`--host` address, and loopback without it, so `--host 0.0.0.0` opens each of them on every interface.\nThe receipt's `Listeners` section names each one with its address, how far it reaches and whether it\nis encrypted, and says when a plaintext listener is reachable from other machines.\n`cotal service status stack-broker` and `cotal service status stack-manager` name their own while\nthey run, and `--json` carries the same list as `listeners`. The console's port is recorded nowhere,\nso it is named by its host.\n\nA journal root always keeps that controller route and its recorded mode and broker address, even\nwhen a completed stop removed its mesh registration. Bare `up` reuses that address; an explicit\nconflicting `--server` or `--open` refuses before effects. With no journal and no registration, as\nafter a damaged journal was removed, `up` starts at the address the saved `stack-broker` definition\nnames and warns `STACK_ADDRESS_RECOVERED`. With no definition either, a root that holds the space's\ntrust or a store from an earlier start refuses `USAGE_INVALID`, exit 1, and asks for `--server`.\nOnly a fresh prepared root starts at the default address. A journal whose stack never launched a\nmember pins neither, as when its first `up` was refused because another broker answers at the\naddress or another program holds it. The next `up` plans it as a first start, with that address or\nanother `--server`, and `down` reports that nothing runs there. That refusal names the address and\nany space this machine registered there. Its next step for a first start is another `--server`, or\nstopping that space or program. For a stack that ran, it is stopping that space or program and\nrunning bare `cotal up`, because the stack keeps its address. A stack whose broker does not listen on its recorded address,\nbecause a bind host recorded beside it names another address, or because no bind host was recorded\nand the address is not `127.0.0.1`, cannot be reached and pins no bind host. `up` there refuses\n`CONFLICT` before anything starts and names `cotal up --host <host>` with the address's own host,\nwhich replaces the recorded bind host once the broker is stopped. Legacy-only options refuse rather than switching launchers.\n`up -f`, recovery and existing legacy roots retain their native paths; those paths refuse the\nnew JSON/plain flags. `up -f` checks its manifest before it refuses them. A manifest that does not\nvalidate is refused `SCHEMA_INVALID`, exit 1, naming each problem, and a file it cannot read is\nrefused `USAGE_INVALID`, exit 1. A valid manifest is refused `UNSUPPORTED`, exit 2, and the remedy\nnames only the flag to omit. A legacy root is one with no stack journal that is neither prepared nor\nregistered for the controller, such as a folder an earlier release started and a normal `down` then\nunregistered, or a new project a bare `up` started. Plain `cotal up` there starts it again on its\nretained launcher, with its identity, store and history. Its `down` drops the registration that kept\nthe broker address, unless the space is named after its folder: that registration stays, and it does\nnot move the folder to the controller stack. Without a registration, or with only that kept one, and\nwith no `--server` or `--host`, it starts at the address in the broker configuration its last start\nrendered there, and says so in one line. The mode is a separate decision, because `--server` and\n`--host` name where the broker listens and not whether it authenticates: a root whose last start was\nopen starts open again whatever address this one names, and the receipt says the mode it started\nin. A mode flag still decides for itself, so `--open` or `--user-auth` starts the mode it names.\nAn open root holds no account to name its space, so the broker configuration its last start rendered\nnames it, and plain `cotal up` starts that space again. A configuration an earlier release rendered\nnames none, and a root it deployed with `up -f` starts again as the space its newest open fresh team\nsummary names, while that configuration still serves its address.\nAfter `clean all` that configuration is gone. A root deployed with `up -f` then keeps only its fresh\nteam summary, and plain `cotal up` refuses `CONFLICT` and names `cotal up -f <manifest>` when the\nsummary names another address, mode or space. `space create` in such a folder refuses `CONFLICT`\nand names that `cotal up`.\nA controller `up` checks that the space is prepared or locally started at its root before it writes\ntrust, configuration, a definition, an instance or the journal; a refusal there leaves the folder as\nit was. When a completed stop and `space forget` removed that record,\n`cotal space create <space> --root <root>` prepares the same space at its root again, and `up`\nstarts it from its journal. Schema apply is not supported. Fresh `team boot` uses the controller\nroute described below. Recovery re-entry keeps its recorded mode.\n\nA whole-stack `up` or `down` takes the root's maintenance lock before it reads the journal. While\nanother whole-stack or maintenance command holds it, the command refuses `LIFECYCLE_BLOCKED` and\nnames that command's pid. A lock whose holder is no longer running, as a command killed by\n`SIGKILL` leaves it, is taken over by the next command that needs it, which warns\n`MAINTENANCE_LOCK_TAKEN_OVER` with the pid and start time of the process that left it. Taking the lock makes `.cotal/maintenance` when the folder has none. A\ncommand that then refuses or fails, such as `up -f` with a manifest that does not validate or `up\n--json` on the retained launcher, releases the lock and removes those folders again while they are\nempty, so a fresh folder is left without a `.cotal` that later commands would read as a project. A crashed member, or an `up` or `down` killed before it recorded its\noutcome, is settled by the next bare `up` or `down` from process evidence. A member whose recorded\nprocess (pid and start time) is gone is recorded as failed, the earlier operation is recorded as\nfailed, and the command continues with an `OPERATION_SETTLED` warning. A member that is still\nrunning is kept: `up` reuses it and `down` stops it. An `up` stopped after its broker started and\nbefore it registered the space leaves a broker whose stream setup and registration only that `up`\ncould finish, and trust is never prepared again under a running broker. The next `up` refuses\n`LIFECYCLE_BLOCKED`, names the broker's pid and `cotal down --yes`, and changes nothing; after that\n`down`, `cotal up` starts the stack. A member whose process cannot be established,\nor one with a stop that was requested and never confirmed, refuses `LIFECYCLE_BLOCKED` and names\n`cotal service status <definition> --instance <id> --space <space>`. A member whose launch record\nnames no process, because the `up` that launched it was killed after it recorded the launch and\nbefore it recorded the child, also names `cotal down --abandon-launch <instance-id> --yes`. That\ncommand refuses `TARGET_NOT_FOUND` when no member has that instance, and `LIFECYCLE_BLOCKED` when\nits record names a process or a settled state; either way nothing changes. Otherwise it records the\nmember `failed` and stops the rest of the stack with a `LAUNCH_ABANDONED` warning. A process that\nlaunch started without recording it is not excluded, and that `down` does not stop it; a broker\nthat still answers at the stack's address makes the next `up` refuse `CONFLICT`. It needs `--yes`\nand refuses `--dry-run`. A manager stop whose request\nwas published and not confirmed before `down` stopped waiting keeps the manager's record `stopping`\nunder that request, also after its process exits. The next bare `up` or `down` reads that request's\nown receipts first. When the manager recorded the stop as completed and its exact process is gone,\nthe record becomes `stopped` with that receipt, the request's reservation is released once the\n`down` that took it has exited, and the command continues. When the manager's only receipt is a\nrefusal, which it writes when it started no stop, for example because that `down` had already\nexited, the request is removed, its reservation is released, and the record returns to what its\nprocess shows, so `down` stops a running manager and `up` reuses it. Otherwise it refuses\n`LIFECYCLE_BLOCKED` and names the instance and the original request. `down --dry-run` writes\nnothing, so it does not settle such a stop and refuses the same way. `service start`, `restart` and\n`stop` refuse it `LIFECYCLE_BLOCKED`, exit 2, before any readiness probe, stop request or record write.\nThe earlier stop's outcome stays unknown. When the launch's\nrequest file does not read as its own, the refusal reports that it is unknown whether a stop was\npublished. A manager record that an earlier release left `unknown` while its launch still holds a\nstop request is recorded `stopping` under that request when its process is gone. Its exit is never\nrecorded as a crash, and no `up`, `down` or `service` command relaunches it, removes its record or\nremoves the stack's registration. A stop that has neither a completed nor a refused receipt, such\nas one whose manager was killed before it read the request, is cleared only on request:\n`cotal down --abandon-stop <request-id> --yes`, with the request ID the refusal names. It refuses\n`LIFECYCLE_BLOCKED` while the manager or the `down` that sent the request still runs, and\n`TARGET_NOT_FOUND` when no member retains that request; either way nothing changes. Otherwise it\nremoves the request, the stop reservation and a `--with-agents` shutdown intent that the same stop\narmed, records the manager `failed`, and stops the rest of the stack with a `STOP_ABANDONED` warning.\nWhether that manager stopped or released its agents' seats is not confirmed, and a seat that runs\noutside the manager process may still run. It needs `--yes` and refuses `--dry-run` and\n`--with-agents`. A `down`, or a foreground `up` running its stop, that exits while it stops a member,\nbecause it was killed or its terminal closed, leaves that member `stopping`. When that member is the manager and its request was not yet\npublished, the manager was handed nothing: the next bare `up` or `down` returns the record to what\nits process shows, and the next stop releases the reservation that `down` took once its pid has\nexited. When it is the broker or the delivery daemon, whose stop is a signal to its recorded\nprocess, the refusal names the same `--abandon-stop` command once that process is gone. That\ncommand records the member `failed` and stops the rest of the stack with a `STOP_ABANDONED`\nwarning. Whether the stop caused the exit is not confirmed. Such a stop records the member `stopping`\nbefore it signals, so a process that still runs may never have been signalled: the next bare `up` or\n`down` returns its record to `running`, `down` signals it again and `up` reuses it. When `up`\nredefines a member because its launch options changed, the journal records the\nreplacement's instance and launch request before the old instance is retired, and the new\ndefinition's revision before the replacement launches. After an `up` cut off in between, the next\nbare `up` or `down` adopts the replacement only when its record carries that launch request and\nrevision for the same operation; `down` stops it and `up` reuses it. Any other instance record\nrefuses `CONFLICT`, and nothing is adopted. A stack definition saved for another folder under the\nsame space name refuses `CONFLICT`, naming that folder and every stack definition saved for it;\n`space forget` removes them once that folder's stack is stopped. A preserve or resume that\nnever recorded its outcome stays with its own maintenance attempt. A member that exits before\nreadiness is recorded as failed with its exit code, and the refusal quotes the last line it logged\nduring that launch and names its log with\n`cotal service logs <definition> --instance <id> --space <space>`. When the broker this `up` started cannot\ncreate the space's streams, `up` stops that broker again, so the next `up` starts a fresh one. It\nfails `OPERATION_FAILED`, exit 5, and names the cause when the broker's log shows it ran out of\nopen files, with raising `ulimit -n` as its next step. Otherwise its next step names that log. When\nanother member, such as the manager, exits before readiness and the broker's log shows it ran out of\nopen files, `up` names that log and that cause. A running broker keeps the open-file limit it started\nwith, so its next step is `cotal down --yes`, then a higher `ulimit -n`, then `cotal up`. When such a\nmember exits before readiness while the space's store holds more than its recorded\n`--max-file-store` cap, `up` names the store and the cap, because the broker refuses every write\nuntil there is room, the delivery daemon's lease included. Its next step makes room with\n`cotal history clear --yes`, which purges the space's channel history, then runs `cotal up` again.\nTo keep that history, it names `cotal down --yes` and `cotal up --max-file-store <bytes>` with a\nlarger cap, after `kill -TERM <pid>` for a manager that still runs, as a full store refuses the\nmanager's stop.\n\nWhen the stack's broker process is stopped by a signal, such as SIGSTOP, it accepts connections and\nnever answers, so `up` refuses `TARGET_UNAVAILABLE` before it writes anything or records the broker's\nreadiness again. It names that pid and gives `kill -CONT <pid>`, as `down` does.\n\nBefore it launches a broker, the controller binds the broker address once, and so does the legacy\nlauncher that an unprepared folder or `up -f` starts. When another process holds it, `up` refuses\n`CONFLICT`, names the address and the bind error, and launches nothing. A\nfirst start names another `--server` as its next step, and a stack that ran keeps its recorded\naddress. On Linux, when nothing listens there and a connection's local end holds the port, the\nrefusal says so in place of another process. For a closed connection in TIME-WAIT, its next step is\nto wait up to a minute and run `up` again. When no member started, the refusal says the stack\nstarted no member. A text `up` prints one `Starting <definition>` line\non stderr before each member it starts, with the readiness timeout it waits for. A started stack's\nNext offers `cotal spawn --space <space>`, or `cotal up` to add the manager after `--no-manager`,\nand `cotal down`. The spawn carries `--space` because `up` keeps an existing selection, so a bare\n`cotal spawn` can target another space. Run from another folder, as `up --root <root>` is, the Next\nsays to run `cotal down` from the root, because `down` stops only the stack of the folder it runs in,\nand it offers `cotal up --root <root>` to add the manager. When another space stays selected, the\nreceipt says so and names `cotal use <space>`, as a legacy `up` does. When no registered or prepared space is\nselected, `up` selects the space it started and says so.\nThe receipt also names each service saved for the space that `up` does not start, such as one\n`cotal service add` saved, and `--json` carries their names as `result.savedServices`. Saved\nservices belong to the space name, so a space forgotten under that name leaves its services to the\nnext one. The receipt names those it left from another folder, which do not start here, with\n`cotal service remove <name> --space <space> --yes`, and `--json` carries them as\n`result.inheritedServices`. `cotal service show <name>` shows what one runs.\n\nOn a controller root, `down --preserve-state` refuses `UNSUPPORTED` (exit 2) in this release, in\nevery stack mode. It refuses while validating its arguments, before the confirmation and before any\nstop, write or manager request. Plain `down` stops the stack and keeps its store, channels and\nhistory for the next `up`. On a full store `down` refuses first, and [down](#down) says how to stop\nthe stack there with or without purging that history. Preserving a controller-owned stack returns\nin a later release.\n`backup create`, `backup restore` and `up --restore` need a preserved cut, so they refuse the same\nway on a controller root (see [Backups](#backups)).\n\nUser-auth controller startup requires a plain loopback IP and an auth provider with read-only\nserver preflight. A first enable checks the required IdP and its JWKS before creating maintenance\nstate, a stack journal, trust or credentials. Missing or invalid inputs are usage refusals; an\nunavailable JWKS is a dependency refusal. The check follows no redirect: a `3xx` answer at\n`<idp>/jwks` is refused as an unavailable JWKS, and the place it points to gets no request.\nMalformed IdP inputs are not echoed. An explicit `--idp`\nis also compared with the recorded pin before maintenance state changes, even while the broker is\nrunning. A different IdP refuses without refreshing members or rewriting their journal. The controller\nchecks again under its lock, and the provider repeats its authoritative checks during preparation,\nbefore any member launches. Later races or failures retain their records and are not rolled back.\nIt starts the broker, initializes streams, records provisional public trust metadata, then starts\nthe `stack-auth` service. Only that exact process's provider readiness supplies the final endpoints.\nDelivery uses the same hosting-root authority as native startup. No user caller is replaced by a\nstatic or borrowed caller.\n\nThe manager requires an existing signed-in, granted CLI login. First enable can therefore start\nthe other members and return partial `OPERATION_PARTIAL` with native cause `AUTH_REQUIRED`, while\nleaving the manager unattempted. Sign in with `cotal login`, obtain the required grant, then run\nbare `cotal up` to continue that known partial operation. `cotal manager start` refuses the same\nway, with `AUTH_REQUIRED` and exit 2, before it saves or launches anything. Stopping the manager\nneeds no sign-in in the folder that hosts the space: `cotal down` and `cotal manager stop` read its\nstop impact from the manager's own local receipt. `--no-manager` omits that member and its\nlogin requirement. Unknown outcomes are never continued or replayed. Public exchange faces and\nTLS-required or remote user-auth brokers still require the retained native route.\n\nThe journal reads earlier three-member auth/open records with an unattempted auth-service row.\nIts next write records all four roles. It neither adopts legacy processes nor converts a mode.\n\nA repair `up` on a mesh whose broker died reopens the store its record names, and refuses a\ndifferent `--store-dir` rather than silently opening a second store. `down` drops that record, so a\nlater start without `--store-dir` reads the store its last broker served from the broker config `up`\nwrote. A space named after its folder keeps the record, so its next start reopens the store the record\nnames. When that store is not the default `.cotal/nats` and still holds JetStream data while the\ndefault store holds none, such as after `backup restore --store-dir`, the start refuses `CONFLICT`\n(exit 2) and names it. Pass `--store-dir <dir>` to reopen it, or remove it with\n`cotal clean store --force`. While that store holds data, a\n`--store-dir` that does not exist is taken for a mistyped path, and the start refuses `CONFLICT`\n(exit 2) and names both. Create the directory first to start a new empty store there.\n\nWhen the broker or manager of a static-auth stack that `up` started in a folder exits, its pidfile\nstays. `cotal status` in that folder shows the row as `stale pidfile` and puts `cotal up` first under\nNext, which starts it again on the recorded address and store. `up` does not record `--runtime`, so\npass it again when the manager ran on one.\nWhile such a stopped folder's broker store remains, Next also names the step for a stale store:\n`cotal reset store <space>` when that reset reaches this folder's store, and `cotal clean store --force`\nwhen no registration lets a reset reach it, as after `down` in a legacy root started under another name.\n\nThe generated `.cotal/auth/server.conf` is written on a real broker boot and is not an\noperator-owned config. `--host` changes that file only when nats is actually started. A unit\nrestart that leaves an answering listener in place is a refresh, not a rebind. When a symlink or a\nfolder stands in its place, `up` leaves it as it is and refuses `PERMISSION_DENIED` (exit 2) before\nit starts anything, naming the path and the `mv` that moves it aside; the next `up` writes the file\nagain. A trust record under `.cotal/auth` that this account cannot read, such as one at mode 000, is\nrefused the same way, and its next step makes it readable again.\n\nOn an existing mesh, `cotal up` reconciles the presence and lease bucket TTLs. It writes a reserved\ncanary and waits for the bucket to expire it before reporting success. If the broker accepts the\nstream update but the backing store does not persist or enforce it, `up` exits nonzero with a TTL\npersistence error instead of trusting the value returned by stream info. A refresh that restores a\nmissing manager waits for that process to come up, then says so with its pid (`\u2713 restored in the\nbackground: manager (pid N)`). A manager that `up` starts and that exits at boot instead is reported\nas `! control plane degraded:` with the path of its log, and `up` then exits nonzero, whether it was a\nrefresh, a first `--detach` boot or `up -f`. The degraded message preserves the failure's own\nrecovery instructions. Spawn and retirement require a functioning control plane; follow the\nreported next step before retrying. The same happens when another\n`cotal supervise` takes over the manager pidfile before the started process comes up. A legacy\nrefresh that finds everything already running reports it like any other start: the `Mesh: <space>`\nheading, the `\u2713 mesh \"<space>\" already running` line, and a Next that names the launch and the stop,\nwith `cotal spawn` when a manager runs. A controller refresh prints its members under `Controller\nstack already running`, and its JSON result is a no-op unless it wrote the default persona. Neither\nlists a role the stack's mode does not use, such as auth on a static-auth stack. A first boot starts\nits manager without the restore line.\n\nA manager or delivery daemon that is killed, or a daemon that exits because its broker went away,\ncannot release its lease. The row stays until the bucket TTL removes it: 10s for the manager and\n30s for delivery. A repair `up` inside that window waits out the row when it can show the holder is\ngone, then starts the replacement, so one run restores the control plane. For the manager, the row\nmust name this workspace root and a pid that is not running on this host; the new manager logs\n`which is not running on this host; waiting` and the seconds left. For delivery, no daemon may be\nrecorded running in this folder and the row must be held by the credential this folder gave its\nprevious daemon; `up` prints `shard 0 is still leased to this folder's previous delivery daemon`.\nThis holds on a controller stack too, both for an `up` right after the crash and for one after a\n`down` that recorded the members as already stopped, because that `down` keeps the credential.\nThe wait never writes the lease. A row that is written again while `up` waits has a live holder, so\nthe start is refused and `up` exits nonzero. A row held by any other process is refused at once,\nas before.\n\nA delivery daemon that `up` launched and that exits before it is ready fails the start. The error\nquotes the failure the daemon logged during this launch and names its log,\n`.cotal/delivery.<spaceKey>.log`. It describes a lease held elsewhere only when the daemon logged\nno failure. A daemon start that fails on a root whose maintenance journal holds a restore or a\npreserved cut never suggests `clean store`, because that store holds the restored or preserved\ndata. Check the root with `cotal status` first.\n\nA full restore recreates the delivery daemon's durables from their checkpoints, at their recorded\nstart sequence. The daemon binds such a restored durable when it otherwise matches its own\nconfiguration, so a restored store starts. A durable that differs in any other setting still\nrefuses.\n\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker: the NATS\nauth callout plus its capability-gated local exchange, and optionally the closed public exchange\nface configured by the three `--exchange-*` flags above. The service is torn down with `cotal down`,\nand a re-run of `cotal up` heals a dead service on a running broker. `up` waits for the service to\nfinish binding: while the daemon it launched (or found running) stays alive, the wait extends past\nthe base 15s up to 60s; a daemon that exits is refused at once with \"exited before becoming ready\",\nand one alive past 60s is refused as \"alive and still starting\" (wedged), naming the pid record and\nthe service log. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n`--rotate-sys` renews the two `$SYS` credentials (`membership-observer`, `connection-evictor`).\nThey carry a 30-day expiry and nothing re-signs them in place, because the trust record never\ncarries the system-account seed, so they are renewed by issuing a **new system account** under the\nsame broker operator and minting fresh creds against it. The root keeps that seed in its own secret\nstore, written at space creation and replaced here, so an auth service started with\n`--service-endpoints` can mint the short-lived observer credentials service caller issuance needs. A plain re-`up` does **not** do this: it reuses the\nexisting trust record, and its `$SYS` creds along with it.\n\nThe rotation is safe to run on a real space, with one operational cost. The data account, the account\nsigning key, every agent credential minted from it, and the JetStream store are all untouched; what\ndies is the retired system account, and with it any out-of-band copy of the old `$SYS` creds, on every\nbroker that loads the rotated config. The cost is that **earlier full backups stop being restorable**\n(see below), so this is not a no-consequence operation. It needs the broker to restart on the rewritten\nconfig, so it runs as part of a boot:\n\n```bash\ncotal down\ncotal up --rotate-sys --detach # agents reconnect; nothing is re-provisioned\ncotal doctor auth # both $SYS creds healthy again, 30 days out\n```\n\nA controller stack rotates the same way. Outside a terminal its `down` needs `--yes`. The `up`\nreceipt adds a `System account rotated` section with the new generation and the creds' expiry, and\nits JSON carries them as `result.rotatedSystemAccount`, which is `null` on an `up` that did not\nrotate.\n\nA rotation is a stopped, fresh boot, and anything that is not one refuses it, all for the same reason\n(the on-disk material and the broker it runs on must never end up on different generations):\n\n- a live mesh, because the running broker would keep serving the retired account. On a controller\n stack any running member refuses it `LIFECYCLE_BLOCKED`, and the refusal names\n `cotal down --yes`;\n- an open mesh, whether that comes from `--open` or from `broker.auth: false` in a manifest, which\n has no system account at all;\n- `--restore`, because reinstating a trust root and superseding it in one command leaves no way to\n say which authority the mesh came up on;\n- an unfinished restore or resume attempt on this root, including one `cotal up` would recover on\n its own, because those paths can adopt a live listener and return without booting a broker;\n- a root that hosts more than one space, because the system account lives in the shared broker\n record and a rotation would retire every tenant's, while the root holds one `$SYS` cred pair\n pinned to one data account.\n\nTwo things to know before you run it:\n\n- **The retirement is config-load-bound.** Old `$SYS` creds are refused by any broker that loads the\n rotated config. A stale `nats-server` still running the *previous* config in memory would keep\n honouring them, so stop every broker for this root first. `--rotate-sys` refuses if this root's\n mesh is recorded as running, if anything unidentified is answering at the address it was given, or\n if the root's pid file names a live (or unreadable) process. Those are Cotal's own ownership\n records, not a scan of the process table: a `nats-server` you started by hand against this root's\n `server.conf` on some other port writes none of them and will not be seen. Do not run one.\n- **It invalidates earlier full backups.** A full artifact binds to the trust chain it was taken\n against, and that commitment covers the operator JWT and the system account. Every full backup\n taken before a rotation refuses to restore afterwards, so take a fresh `cotal backup` once the\n rotated mesh is up. `cotal up --restore` names this case when the data account still matches.\n\nThe commit is not atomic (a trust-record write plus two credential writes), so an interrupted\nrotation leaves the record ahead of the creds. That split is detected rather than silent: every\n`cotal up` on an auth mesh, and every `cotal doctor auth`, compares each `$SYS` cred's issuer against\nthe persisted record and names the retired account. `up` warns rather than refusing, because these\ncreds power the membership graph and live eviction, both of which degrade fail-soft; the mesh is not\nworth taking down over them. Re-running the rotation heals it, at the cost of one generation.\n\nWhile those creds are expired the mesh keeps delivering messages, but the\n[membership feed](delivery-daemon.md) and live connection eviction stay down; `cotal doctor auth`\nand the manager's log both name the credential and this repair.\n\n### Channel seed file\n\nA start seeds the channel registry from the `--channels` file, or from the root's\n`.cotal/channels.json` when you pass none. The file is one JSON object with two optional keys, in\nUTF-8. A byte-order mark at its start, which some Windows editors write, is ignored, and a file\nsaved as UTF-16 is refused with `USAGE_INVALID`, saying to save it as UTF-8:\n\n```json\n{\n \"defaults\": { \"replay\": false, \"replayWindow\": \"6h\", \"deliveryClass\": \"durable\" },\n \"channels\": {\n \"news\": { \"description\": \"Daily news\" },\n \"ops\": { \"deliveryClass\": \"live\", \"instructions\": \"Keep posts short.\" }\n }\n}\n```\n\n`defaults` takes `replay` (`true` or `false`), `replayWindow` (a number followed by `s`, `m`, `h`\nor `d`) and `deliveryClass` (`live` or `durable`). Each key under `channels` is one concrete channel\nname, as `channel set` takes it, and its object takes those three fields plus `description` (up to\n200 characters) and `instructions` (up to 2000). Each field in the file overwrites that field in the\nregistry, and fields the file leaves out keep their values. Any other key, a wildcard or invalid\nchannel name, or an invalid value refuses `SCHEMA_INVALID` before anything starts, naming the file\nand the entry. A default file that is invalid refuses the same way, with a remedy to fix or remove\nit. A manifest start (`-f`) seeds from the manifest's own channels instead. Because the next start\nwrites the default file's values back, a `channel set` or `channel defaults` of a field that file\nsets, and a `channel remove` of a channel it names, warn `CHANNEL_SEED_REAPPLIES` and name the file.\n\n## down\n\nEvery `down` runs to its end when the reader of its output goes away, as `| head` does. It\nkeeps stopping, writes nothing more, and exits with the stop's own status.\n\n`down <service-name>` routes one saved owned definition through the\nsame selection, confirmation, reservation, native manager-impact and stop controller as\n`service stop`. Use `--instance <full-id>` when selection needs a full instance pin. `--yes`,\n`--dry-run`, `--space`, `--json` and `--plain` keep that controller's meanings; structured receipts\nname `down` while retaining the service-stop result and actual identity. Unknown definitions do not\nfall back to legacy component pidfiles. A controller stack names its members `stack-broker`,\n`stack-auth`, `stack-delivery` and `stack-manager`. A preset name reaches the member built on that\npreset, as in [`service`](#service), so `cotal down manager` stops `stack-manager` and\n`cotal down delivery` stops `stack-delivery` through the service stop, and the receipt's target\nnames the member and the name you gave as `requested`. Several names in one `down` resolve each the\nsame way, so `cotal down manager delivery` stops `stack-manager` and `stack-delivery`, and each\nmember of the target carries its own `requested`. A name that is no preset, such as `nats`,\nrefuses `TARGET_NOT_FOUND` there and names the `stack-` member to use. Among several names it\nrefuses the whole set, so no service stops. A stop of `stack-broker` refuses\n`LIFECYCLE_BLOCKED` while a legacy daemon record under the root may front a process, or another\nservice instance of the space is live or unresolved. For those instances it names bare `cotal down`,\nor `cotal service stop <name> --instance <id> --space <space> --yes` for each one. When one of them\nis not a member of the stack, such as one added with `cotal service add`, bare `cotal down` refuses\ntoo, so the refusal names that service's stop first, then bare `cotal down`, or the stops of the\nstack's own members and the broker stop again. Its dry run warns with that refusal. Completion offers saved definitions without imports or a network request. No hidden connector repair or shared catalog preparation precedes this route.\n\nMultiple names resolve as one selected-space set. All owned targets and process roles are checked\nbefore effects. After confirmation, the controller reserves every definition in stable order and\nrechecks all records and native impact before the first stop. Stops follow the displayed input\norder, with each native guard rechecking at effect. A later failure stops the sequence; one receipt\nretains completed, failed/unknown and unattempted members. Reservation/preflight refusals also\nretain the full selected set. Release is attempted for every acquired definition hold; a reported\nrelease error makes an otherwise known outcome partial without erasing known stop results. An\nalready unknown stop stays unknown. Lock cleanup is not independently proved. This is not atomic and no rollback or cleanup is claimed. Dry-run reads metadata without reservations, provider imports or native impact\nqueries. Duplicate names and `--instance` with multiple names are usage errors. Named `--with-agents` refuses and points to\n`cotal service stop <manager-name> --instance <full-id> --with-agents`; it is never dropped.\n\nBare `down --dry-run` reads local process records for one registered space at the current\nproject root. A root with no `.cotal/` directory, or with no stack registered at it, refuses\n`TARGET_NOT_FOUND`, exit 2, because there is no stack there to preview. Its next step is the folder\nwhere `cotal up` started a stack, or, when this machine has no stack started or registered at all,\n`cotal up` in a project, as `cotal status` says. A bare `down` in such a folder refuses the same\nway. A root whose stack is\nalready stopped shows the no-op described below instead. In the machine home, a stack an earlier\nrelease started there is read the same way, and the Next names `cotal down --preserve-state`, its\nstop, or says that none of it is running. Add `--space <name>` to\nconfirm that exact space belongs to this root;\n`--json` and `--plain` produce a partial, finite inspection receipt. It lists pidfile\nliveness, readable additive deployment IDs, retained additive attempts and\nunreadable paths under this root, and saved service definitions, instance IDs, recorded states and\nhosts, supervisor receipts, and retained install, uninstall and unit-action\nattempts in the selected space's workstation store. Saved records are not attributed to this project root, and their recorded\nstates do not prove live process or supervisor status. A submitted native attempt may have\nchanged state; the preview neither reconciles nor retries it. For each saved service instance it\nalso shows whether it blocks bare `down`: one that still runs or cannot be verified blocks it, with\nthe reason, and one already stopped is skipped. When one blocks, the preview warns with the refusal\nthe real stop would give. That check observes processes on this host and changes nothing. Other local registrations with\nthe exact recorded broker address appear as possible sharing. No match does not exclude\naddress aliases, remote tenants or live attachments. The preview does not establish process\nownership, the complete service set, exclusive broker use or agent impact.\n`--with-agents` only marks the requested policy. It does not reserve or signal\na process, and the receipt is not an authorized stop plan. A fresh `team show`\nsummary points to this read-only command from the recorded root.\n\nOn a root with a controller journal, bare `down` reads only its recorded full instance IDs. On an\ninteractive terminal it shows the member set and the live manager's native agent impact and asks to\nconfirm, then stops manager, delivery and broker in that order. `--yes` acknowledges that scope in\nscripts and prints no plan. The receipt names the mesh once, lists each member with its result in\nstop order, and names `cotal up` as the next step. `--with-agents`\nrequests the manager's native retirement policy. Another live service instance in the space\nrefuses `LIFECYCLE_BLOCKED` before the first stop; the Next names the\n`cotal svc stop <name> --instance <id> --space <space> --yes` command for each one. So does a\nprocess record under the root that may front a process, such as a daemon an earlier release\nstarted; the message and the Next name each record's path and pid. A record whose process is gone,\nor whose pid now runs another start than its identity pin records because the pid was reused, fronts\nnothing. It refuses neither bare `down` nor a stop of `stack-broker`, `down --dry-run` names it, and\na complete bare `down` removes it with its pin and names it in a `STALE_PROCESS_RECORD` warning. The\nprocess that holds a reused pid is never signalled. The broker scope\nis checked again after its dependants stop. A failure stops the sequence and preserves completed and\nunattempted members. A member stopped by signal that has not exited 15 seconds after SIGTERM gets\nSIGKILL. Its row then reads `stopped with SIGKILL`, and a `STOP_ESCALATED` warning names its pid.\nOnly a complete confirmed stop removes the delivery credential and local registration. After the\ndelivery daemon stops, `down` reads its lease. When the daemon was already gone, or the lease still\nnames the daemon that credential gave, as after a kill or a release a full store refused, the\ncredential stays, and the next `up` uses it to recognise the row and wait it out before it mints a\nnew one. When the lease cannot be read, the credential stays too, and a `DELIVERY_LEASE_UNREAD`\nwarning says so. The JSON `cleanup` field then reads `registration-removed-delivery-credential-kept`. Saved\ndefinitions and the journal remain for the next `up`, and the space stays prepared at its root. A\nselection that names it stays, so the next command run without `--space` refuses with\n`TARGET_UNAVAILABLE`, exit 2, and names the root to start it in, rather than reach another space.\nNo descendant, lease or authority cleanup is\nimplied. A dry run reads records and, with a live manager, how much the space's store holds. It takes\nno lock, does not establish native impact and exits 0;\nits JSON marks `completeness` as `partial`. It lists members in stop order, says when the stop would\nfirst settle an earlier operation, and names a scope check the real stop would refuse as a warning.\nA member whose recorded process is already gone is recorded as stopped without loading its adapter\nor credential, so a second `down --yes` stops nothing and is a no-op. Before it stops a live\nmanager, `down` reads the workflow runs it hosts and records the unfinished ones, with the steps\nthey wait on, in the stack journal, where `reset store` lists them. A read that fails is recorded\nwith its reason and does not stop the `down`; the JSON receipt carries the record as\n`operation.runs`. When the manager is\nlive but the broker is not, `down` cannot read the manager's agent impact and refuses before any\nstop, from a managed seat's shell too. Run `cotal up --space <space>` to restart the broker from its recorded inputs while the\nmanager keeps running, then run `cotal down`. On Linux, a broker process stopped by a signal, such\nas SIGSTOP, is read from its process state, and `down` refuses `TARGET_UNAVAILABLE`, exit 2, at\nonce, naming its pid, and stops nothing. When the broker's process runs but does not answer, the\nmanager read waits out its bound and `down` refuses the same way. The broker stops last and\nis never force killed, so `down` does not signal it. Resume a suspended broker with\n`kill -CONT <pid>` and run `cotal down` again; end one that stays unresponsive, then take the route\nabove. The manager's stop rides a control credential whose issuance record the broker stores first,\nand a store over its cap refuses that write. With a live manager, `down` then refuses\n`DEPENDENCY_UNAVAILABLE`, exit 2, before any stop, naming the store and the `--max-file-store` cap\nthis machine recorded. Make room with `cotal history clear --yes`, then run `cotal down`. That\npurges all of the space's channel history. To keep it, end the manager on the stack's host with\n`kill -TERM <pid>`, using the pid the refusal names. A plain SIGTERM stops every agent that manager\nruns by its default policy, as [supervise](#supervise) describes, and it needs no credential. Then\n`cotal down` stops the other members, and `cotal up --max-file-store <bytes>` with a larger cap\nstarts the stack again with its history. The signal reaches only a process on that host.\n`cotal service stop stack-manager` and `restart` refuse the same way before submitting a stop, and\nname `cotal history clear --space <space> --yes` and then the same command again. A stop also\nnames `kill -TERM <pid>` as the way to keep that history. A dry run\nnames that refusal as a warning when the space holds more than the recorded cap.\n\nnats-server disables JetStream when its store refuses a write for permission or for space, and\nonly a restart brings it back. While the broker's log on this machine shows that, the manager's\nstop cannot record its credential either, so `down`, `service stop stack-manager` and `restart`\nrefuse `DEPENDENCY_UNAVAILABLE`, exit 2, before any stop, and name the logged failure. `up` refuses\nthe same way, because it cannot check the space's streams on that broker. A dry run names the\nrefusal as a warning. Each names the way out: end the manager on the stack's host with\n`kill -TERM <pid>`, which stops every agent it runs by its default policy, then run `cotal down`,\nfix the logged cause, and run `cotal up`. Fixing the cause alone does not bring JetStream back.\n\nController roots refuse\npreservation and manifest flags rather than switching to the legacy launcher. Outside a terminal,\na bare `down` that would stop a running member refuses without `--yes`, exit 1, and prints that\nline and its remedy without the command help. A stop records each step in the stack journal in the root's\n`.cotal`. When this account cannot write that folder, `down` refuses `PERMISSION_DENIED`, exit 2,\nbefore the plan, names the folder and the `chmod u+w` that fixes it, and stops nothing. Each\nmember's stop is also recorded in the space's service instance records, under `services/` in the\nCotal home, and a folder there this account cannot write refuses the same way. A dry run names\neither as a refusal the real stop would hit.\n\nA controller stack's members are stopped through the registration `cotal up` wrote for it. When an\nolder `meshes rm --force` dropped it, or an older `meshes add --force` replaced it with one added by\nhand at the stack's own root and broker, `down` rebuilds it from the stack journal for the stop and\nsays so with a `REGISTRATION_REBUILT` warning, as `--dry-run` does. It does that only while the\nstack's broker is the launch the journal records at that root and address, and otherwise refuses\n`CONFLICT`, exit 2, before any member stops. A registration added by hand at another address or\nfolder is not replaced: `down` refuses `CONFLICT` and names `cotal space forget <space>`, after\nwhich `down` stops the stack through its journal. While another registered space names this\nstack's exact broker address, bare `down` refuses `LIFECYCLE_BLOCKED`, exit 2, and stops nothing,\nbecause one listener answers for both registrations. It names each of those spaces with the\n`cotal space show <space>` that reads one, the `cotal space forget <space>` that takes it off this\naddress once its own stack is stopped, and the `cotal down` to run again after that. A user-auth stack's registration is not rebuilt,\nso it refuses `UNSUPPORTED`. A stop that refuses or is cancelled before any member stops puts the\nregistration back as it was.\n\nA bare `down` stops only the stack of the folder it runs in. In a folder whose root holds no\nstack, when the space named by `--space`, or else the selected space, is a stack this machine\nstarted in another folder, `down` refuses with `TARGET_NOT_FOUND`, exit 2, and stops nothing. A folder whose\nown `.cotal` holds no stack, such as one a failed start left, answers for itself unless `--space`\nnames another folder's stack. In a folder with its own controller stack, a `--space` that names\nanother space refuses with `CONFLICT`, exit 2, and names this folder's stack. Each refusal names\nthe space and, when this machine started it elsewhere, its root, and its next step runs the same\n`down` from that root. `--json` and `--plain` carry that refusal as structured output, because the\nfolder has no stack for a bare stop to print its own lines about.\n\nWhen the folder of a running controller stack is deleted, its stack journal and broker\nconfiguration go with it, so no member's own stop can run. A running broker writes `.cotal/nats`\nback into the folder, which does not bring them back. `down --space <space>` from any folder stops\nthat stack from the instance records this machine keeps, and so does a bare `down` in the folder\nthe broker wrote back, or in a folder with no `.cotal` of its own while the space is selected. Each\nrunning member gets SIGTERM at its recorded process, manager first and broker last, after `down`\nchecks that the process is still the one its record pinned, and its record is marked stopped. The\nmanager stops its agents by its default policy, so `--with-agents` and the other stop flags are\nrefused. A member that does not exit within its window, 90 seconds for the manager and 30 for the\nothers, ends the stop with `OPERATION_UNKNOWN` and later members are not stopped. Once every member\nis stopped, `down` removes the stack's saved definitions, their instance records and the\nregistration that names the folder, so the space can be created and started in another folder.\nThe same stop runs for a folder moved or renamed while its stack ran, which took its journal, store\nand trust with it. Its Next names `cotal space create <space> --adopt --root <folder>` first, which\nadopts those records where the folder is now and keeps its history, then\n`cotal space create <space>` for another folder. Outside a terminal it needs `--yes`, and\n`--dry-run` lists each member with its pid and process.\n`cotal status` marks such a project folder as gone, with `selected.rootGone` set to `true` in its\nJSON, and its Next names `cotal down --space <space> --yes`. A command that connects to a static-auth\nspace, such as `send` or `history`, is refused with `DEPENDENCY_UNAVAILABLE`, because the seed went\nwith the folder, and its Next names the same `down`.\n\nA bare `down` in a folder whose stack is already stopped stops nothing. It reports that the stack\nin that folder is not running and names `cotal up`, outcome `no-op`, exit 0, and writes nothing,\nso it needs no `--yes`. `--dry-run`, `--json` and `--plain` show the same. A controller stack\ncounts as stopped when its last whole-stack operation was a completed `down` and no member and no\nother service instance of its space runs. That check reads no process record under the root; those\nare read only as described below. A stack that stopped any other way takes the full stop, which\nalso finishes its cleanup. A legacy root counts as\nstopped when no registration names it, or only the one a space named after its folder keeps, and no\n`*.pid` record remains at the top of its `.cotal`, and the root still holds the broker configuration a start rendered there or a space prepared there.\nWithout that evidence a legacy root still refuses, with one next step: `TARGET_NOT_FOUND` when\nnothing shows a stack there, and `CONFLICT` when several registrations name the root or a process\nrecord remains that no registration explains. Before either kind of root reports the no-op, `down`\nreads every record a local-process descriptor names under the root, an installed extension's\nincluded, at any path and name under `.cotal`. A descriptor whose record name holds a space is read\nfor every space its files under the root name, as well as the stack's own. A record whose process\nruns, whose liveness the kernel does not answer, or that cannot be read or attributed refuses\n`LIFECYCLE_BLOCKED`, and so does a file whose space part decodes to no space. Nothing outside the\nroot's `.cotal` is read: a link anywhere on a record's path refuses at the link, unread, as does a\nrecord that is not a regular file, is larger than a process record, cannot be opened, or changes\nwhile it is read. Each is named once, at the path where reading stopped. A `.cotal` that is itself\na link never gives the no-op: on a stopped controller stack `down` refuses at `.cotal`, and on a\nlegacy root it refuses `CONFLICT` before any record is read. A template whose `{space}`\nnormalizes away names one path for every space and is read as that path. The refusal lists each\nrecord by name, path under the root and pid, and its next step names the pids and paths to check.\nIt stops, adopts and removes nothing. `--with-agents` on a stopped stack refuses\n`LIFECYCLE_BLOCKED`, because no running manager can stop the agents; start the stack first. Its\npreview, `cotal down --dry-run --with-agents`, shows the no-op and warns with that refusal. When the\nstack's last completed `down` took `--with-agents`, or the root never started a stack, no agent of\nit can remain, and `--with-agents` reports the same no-op.\n\nThe legacy bare-stack and manifest apply paths described below are retained. They do not\naccept the new named-service JSON/plain/yes apply receipt flags, except that `--json` and\n`--plain` show the no-op of a stack that is already stopped. A bare stop of such a root asks for no\nconfirmation, and `--yes` there changes nothing, so `cotal down --yes` stops a stack on any root.\nBefore a retained bare apply\nreaches its pidfile sweep, it now requires one locally started registration under\na real isolated project root and refuses pending maintenance transitions, any valid or\nunreadable additive ledger or attempt under that root, or any other local registration at\nthe exact recorded broker address. The additive ledger refusal names each ledger with the command\nthat finishes it: `cotal down --run <id>` for a readable one, and for an unreadable one the reason it\ncould not be read and the same command once it is repaired. It reads each record its sweep stops as the stopped-stack check\nabove does. A record that check leaves unread, such as one behind a link, refuses `LIFECYCLE_BLOCKED`\nbefore the first stop and is named once, where reading stopped. No process behind it is read or\nsignalled, and no record is removed. These are known-blocker\nchecks, not a complete live broker-sharing or stop plan; aliases, remote tenants,\nagent impact and process identity remain unproved. These paths do not check full\nwhole-stack target ownership, set exclusion, confirmed agent impact or schema\nreconciliation. The retained `down --preserve-state`\nroute separately refuses a retained or unreadable additive record under its\nmaintenance lock, without adopting its effects into the preserved set.\nA validated completed restore in `active` for the registered space does not block ordinary\nbare `down`. The other root, ownership, sharing and native process guards still apply, and\nstopping leaves the restore journal and fallback store unchanged.\nThe machine home is not an isolated project root, so bare `down` refuses the home stack. Stop it\nwith `cotal down --preserve-state`, which keeps its state for the next `cotal up`. The refusal names\nthat command. `--preserve-state` takes its agent inventory from the manager that serves the space.\nWhen no manager holds the space's lease, as on a stack started with `--no-manager`, it refuses\n`TARGET_UNAVAILABLE`, exit 2, before it stops anything or writes a maintenance record. Start the\nmanager with `cotal up`, then run `cotal down --preserve-state` again.\nIt also refuses `LIFECYCLE_BLOCKED`, exit 2, before it stops anything, while a saved service in the\nspace still runs or cannot be verified, because the service depends on the broker it would stop.\nThe refusal names each one and the `cotal service stop <name> --instance <id> --space <space> --yes`\nthat stops it. Bare `down` refuses the same way.\nThe preserved state records its store by absolute path. If the project folder is moved after\n`down --preserve-state`, `up`, `backup create` and `backup restore` refuse `CONFLICT`, exit 2, name\nthe folder it was moved from, and say to move it back there first. A store missing from its recorded\npath is refused `TARGET_NOT_FOUND` with that path; only `backup restore --accept-missing-source`\nrestores over a lost store.\n\nAfter those checks, a retained bare apply refuses `LIFECYCLE_BLOCKED`, exit 2, before its pidfile\nsweep while a service instance in the selected space still runs or cannot be verified, because the\nservice depends on the broker the sweep stops. It stops nothing. As on a controller stack, bare\n`down` does not stop a service itself: the refusal names each instance with the\n`cotal service stop <name> --instance <id> --space <space> --yes` that stops it, then `cotal down`\nagain. An instance whose process is already gone is skipped, and saved definitions stay. An\nunreadable service record keeps the code of its read refusal. A retained native attempt, an\nunsettled request, a process that cannot be observed, or a record from another host, which bare\n`down` does not touch, refuses too, and the remedy names the command that inspects or stops it.\n\n\n```bash\ncotal down\ncotal down --with-agents\ncotal down --abandon-stop <request-id> --yes\ncotal down --abandon-launch <instance-id> --yes\ncotal down --preserve-state [--store-dir <dir>] [--session-store <dir> \u2026]\ncotal down --dry-run [--space <name>] [--json|--plain]\ncotal down <service-name> [<service-name> \u2026] [--instance <id>] [--space <s>] [--yes|--dry-run] [--json|--plain]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | none | Tear down this manifest's deploy |\n| `--run <id>` | none | Tear down one additive ledger, written by the retired `spawn -f` deploy, by its run id |\n| `--space <name>` | current space | With service names: the space whose saved definitions to stop. With bare `--dry-run`: confirm that space belongs to this root. A bare stop refuses it |\n| `--dry-run` | off | Print the manifest teardown, the named service stop or the bare stack preview, mutate nothing |\n| `--with-agents` | off | Bare whole stack only: also stop and deprovision every managed agent |\n| `--abandon-stop <request-id>` | none | Controller stack, with `--yes`: abandon a member's stop that no receipt settles, then stop the stack. See the settlement rules under [up](#up) |\n| `--abandon-launch <instance-id>` | none | Controller stack, with `--yes`: record failed a member launch whose record names no process, then stop the stack. See the settlement rules under [up](#up) |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | the recorded store, else `.cotal/nats` | With `--preserve-state`: the store to cut. The mesh record names the store the broker runs on, such as a `backup restore --store-dir` target, and a different path refuses `CONFLICT` before anything stops |\n| `--session-store <dir>` | none | With `--preserve-state`: a harness transcript store directory to capture with every continuation-capable retained seat. Repeatable. No default and never inferred from a connector name; a path that does not exist or is not a directory is refused before anything stops |\n\nBare `cotal down` stops the whole local stack in dependency order and leaves managed agents running\nwhen their runtime lets them outlive the manager. Before signalling the manager it verifies the spare\ncapability of the exact recorded manager, which records what that manager's stop does with its\nseats, and it reports the agents left behind plus `cotal down --with-agents` as the explicit reap.\nWhen the manager had no managed agents, it prints no report. When they cannot be listed, for\nexample on a user-auth mesh with no login on this machine, it prints\n`could not list managed agents (<reason>)` and the stop continues.\nThe built-in pty runtime keeps each PTY inside the manager process, so those seats cannot outlive\nit: every manager stop stops and deprovisions them, and `down` reports them as stopped. Every manager\nstop the CLI makes runs this one path: `down`, Ctrl-C on a foreground `cotal up`, the teardown after\nthat `up`'s broker exits, the leftover-manager stop before `cotal up -f`, and the delivery cutover.\nEach holds the manager's stop reservation, so a second stop while one is in flight is refused,\nnames the process holding it, and leaves that stop's `--with-agents` policy in place. On Windows,\nmanager and delivery stops send an authenticated local cleanup request because Windows process\nsignals terminate without running cleanup handlers. The request requires the process's private\ncapability, its creation identity and the live stopper's identity and reservation. Other platforms\nuse `SIGTERM`. A `cotal supervise` that receives `SIGTERM` or `SIGINT` while it is still starting\nlets the start settle, then runs the same stop and exits without printing `manager up`, so its\nliveness lease is released and an immediate restart is not refused. When that start fails, the stop\nstill releases the lease before the process prints the start error and exits 1. A process still running after 20s on the Windows cooperative path, or 15s after\n`SIGTERM`, receives `SIGKILL`. Ctrl-C stops the manager first; when that\nstop is refused or the manager's exit cannot be confirmed, Ctrl-C signals nothing else, prints the\nrefusal with the reap route, and leaves the stack running; end it with `cotal down --with-agents`.\n`--with-agents` is a one-shot destructive policy bound to the exact verified manager process\nand the exact live `down` stop reservation; a stale, malformed, crashed, or different stop attempt\ncannot turn a later bare shutdown destructive. If a managed agent cannot be proven stopped within\nthe manager's stop timeout, the manager logs which one, still closes its broker connections and\nconsole listener, and exits with code 1. It does not release its pidfile, liveness lease or\nservice registration in that case, so no successor is handed authority while that agent may still\nrun; the lease lapses on its TTL. A named `down` is the service stop above and never stops a\nlegacy component by its pidfile, so `cotal down manager` or `cotal down web` on a legacy stack is\nrefused unless a saved definition has that name. Only bare `cotal down` stops a legacy root's\ncomponents, all together, under the folder you run it in. It takes no `--space`. Its stop lines\nfollow a `Mesh: <space>` heading, and its Next names `cotal up` to start the stack again. A bare `down` or\n`down --dry-run` whose `--space` names a space whose stack is recorded in another folder refuses\n`TARGET_NOT_FOUND`, exit 2, naming that folder and the stop or preview to run there. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with service names or `--space`.\n\nA pinned manager with no spare-capability record is not signalled by bare `cotal down`. A current\nmanager always publishes the record, so a missing one means an older\nmanager: one that predates capability reporting, or one whose pty runtime reported that it cannot\ndetach its agents. Stop each managed agent explicitly, then run `cotal down --with-agents` from the\nmesh root to stop the whole stack. An older manager does not understand\nthe reap request, which is why the agents must already be stopped.\n\nAfter the known-blocker admission checks above, the retained bare `cotal down`\ninventories by pidfile. When this folder's registered broker answers and no\n`nats.pid` records it, the command does not say nothing is running. It names the space and the\nbroker address, says no pidfile records that process, says it will not stop a process it did not\nstart, and exits 1. Stop that broker with whatever started it (an init unit, a container, or the\nhand-run process). `cotal meshes rm <space>` only drops the registration. The probe runs whether or\nnot other owned components were running: they stop and clear their artifacts first, then the broker\nis named. A `--dry-run` does not run this probe.\n\n`down` reads each process record once. A component that exits and removes its own record while\n`down` runs counts as having no record, so the stop goes on. Any other failed read is an error.\nWhole-stack shutdown keeps delivery credentials until every required component has stopped.\nA refused stop preserves both the space-scoped credential and its legacy copy. Credential removal\nerrors remain failures. `down` removes the manager's delivery-aware marker, shutdown intent and\nspare capability only after every component has stopped, so a refused stop keeps them too.\n\n**Teardown verifies pinned process identity before signalling.** PIDs are recycled by every OS,\nso a recorded pid alone is not a durable target identity. `up` and the dashboard's native child record each\nprocess's creation identity in a sibling `<pidfile>.identity` pin, which holds the pid and the\nprocess start reported by the OS. Every stop path, including `down` for the broker, web and\nextension components, and the manager, delivery and auth-service stops, applies the same rule. A pin\nthat names a different start means the pid was reused, so teardown refuses and preserves it. A\ncontroller stack's bare `down` signals no such record, so it removes it, as described above. A torn\nor unreadable pin also refuses. On macOS, the reader calls `sysctl` through a bundled Node-API\nbinding for x64 or arm64 and records the process start in microseconds. A macOS pin that an earlier\nrelease wrote holds the `ps -o lstart` start date instead and is verified with that same reader. That\ndate keeps its spaces, including the padded day, in both the identity pin and the one-shot shutdown\nintent. A single record cannot span lines.\nOn Windows, the reader calls `GetProcessTimes` through a bundled Node-API binding for x64 or\narm64. It requests limited process-query access and preserves the creation FILETIME as a decimal\nstring. It launches no subprocess. Access denial leaves identity unverified; an unreadable pin\nstill refuses teardown. On either platform, a missing or damaged binding is an installation error\nand has no fallback.\n\nThe pidfile and its pin are published by renames, and the pidfile rename is the commit point. Just\nbefore it, the pin holds two lines: the old process's and the new one's. A launcher that dies\nmid-publish therefore leaves the old record or the new one, each checked against its own pin line,\nnever a pidfile without its pin. An old record with no pin is legacy, so its line holds `-` in place\nof the token and it stays legacy until the commit. A CLI older than this change reads a two-line pin\nas torn and refuses.\n\nPublishes of one pidfile are serialized by a lock file beside it, `<pidfile>.publish.lock`, because\nthe launcher and the daemon it starts both publish the same record. The next publisher reclaims a\nlock left by a crashed one. When no start token can be read for the new process, its pin line holds\n`-` in place of the token, which reads as a legacy record, and the publish ends in the legacy shape:\na pidfile with no pin. Teardown, and a daemon removing its own record on exit, take the same lock and\nremove the record only while the pidfile still names the pid they stopped, so a stop that races a\npublish leaves the new record whole.\n\nThe web dashboard service claims `web.pid` with an exclusive create, so a second dashboard for the\nsame mesh is refused, and writes its pin right after the claim. A stop that runs between the two\nreads a legacy record. Any existing `web.pid` refuses the start, whether or not its process still\nruns, and the start neither adopts nor reclaims it.\n\nThe pidfile pid and the pin pid are two coordinates. Automatic cleanup follows **proven death of\nthe pidfile target** (ESRCH on that pid): a torn sibling pin does not wedge a dead pidfile pid.\nA torn pairing where the pin names another pid, while the pidfile pid is still live or not proven\ndead, still refuses. Inspect both pids with `ps`. Do not delete `<pidfile>.identity` to force a\nstop; that weakens target-identity protection. Once the pidfile process is dead, rerunning\nteardown clears the stale record automatically.\n\nA Windows manager or delivery daemon without the cooperative capability must be stopped through\nits original supervisor before restarting with this version. Teardown refuses it without signalling\nor deleting its live record. It does not wait for a lease to expire or delete a lease by local pid.\n\nFor components using signals, the first teardown after upgrading a running pre-pin stack has a\nnarrower guarantee. A live record with no identity pin is signalled after a loud warning that it predates identity pinning. Restarting\nthe component writes the pin, so later teardowns receive full match and mismatch protection. The\nsame warning applies on platforms where no stable start token is available. For a legacy manager,\nbare `cotal down` also warns that agent sparing cannot be verified before it signals. Because the\nCLI cannot establish which SIGTERM handler that already-running binary carries, it never presents\nthe pre-signal seat inventory as confirmed spared; a genuinely older destructive handler may still\nreap those agents. `--with-agents` publishes a one-shot reduced-guarantee handoff bound to the\nrecorded manager pid and the live `.stopping` reservation's inode, then signals unconditionally.\nThat handoff cannot be replayed by a later stop attempt. A pin that exists and does not match the\nlive process still refuses before signal.\n\n`--with-agents` performs the old destructive logical teardown: managed processes stop and their\ncredentials, ACL rows, and delivery footprints are deprovisioned. `--preserve-state` is a different\nmaintenance transition: it stops retained processes while suppressing leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\nOn a mesh that runs the delivery daemon, the cut stops it first so no join or leave can change\nmembership under the manager's inventory. Before any manager-owned endpoint stops, the cut then\nreads the presence roster and refuses while an endpoint the manager does not own is live. A refusal\nleaves the delivery daemon stopped. A deleted presence key is the only row it reads as absent. Any\nother row that is not a card with an `id` equal to the presence key it was read under and a string\n`name` refuses the cut with `presence record <subject> is malformed`. A participant can write any\nvalue under its own key, so the key is the identity the cut trusts.\n\n**Seat checkpoints.** After the stack is proven down, the cut writes one checkpoint per retained\nseat under `.cotal/maintenance/v1/checkpoints/<attempt>/<seat>/`, and prints the path, the\ncontinuity class and the generation for each. The path carries the preservation attempt because a\ncheckpoint is immutable once sealed: a shared directory would make the second cut in a root refuse\non the first cut's leftovers, and clearing it would destroy an artifact a rollback still needs. The\ncapture happens only at that point because anything earlier races a harness that is still writing\nits transcript and its working tree.\n\nEach checkpoint directory is created 0700, refuses a destination that already exists, and holds:\n\n- `repo.bundle`, the seat `cwd`'s reachable history, anchored on the base commit the record names\n by full object id;\n- `repo.index.diff` and `repo.worktree.diff`, the staging state as two diffs, base to index and\n index to worktree. Two rather than one because a single combined diff restores a mixed tree with\n the right bytes and the wrong index: a source reporting `MM README` would come back as ` M README`;\n- `repo.untracked.tar`, the untracked files in scope;\n- the harness session pointer, when the seat's connector declares one, and the transcript store\n files the operator named with `--session-store`. Each records where the destination puts it back\n as an anchor (the workspace root, the account home, or the seat's `cwd`) plus a relative path,\n because the destination's root and home are its own and the source host's absolute spelling would\n either miss them or write outside them;\n- `checkpoint.json`, written last, after every digest is computed over the bytes that landed.\n\nThe record carries the manager's resume entry unchanged as its first field, then the space, the seat\nname, the recovered `lifecycleUid`, the writer generation the cut was taken at, `capturedAt`, the\nrecency horizon, the applied profile revision, the seat's `git status --porcelain` as the cut read\nit, and the continuity class. Every captured file is\nlisted with its byte size and sha256, so an operator verifies the whole artifact with `sha256sum`\nand `git bundle verify`. No secret values, no operator keys and no source-host launch material\nenter it.\n\nThe generation is the highest one this root has claimed for the seat in `.cotal/auth`. A\ngeneration entry there that is a symlink, a directory or anything else that is not a regular file\nrefuses the cut, as every other auth record does.\n\nThe continuity class is what the connector declares, capped by what the checkpoint carries. A\nconnector declaring session continuation classifies as `exact`, but reopening a session takes both\nhalves, the pointer that names it and the store that holds its transcript. A checkpoint missing\neither one cannot reopen that session, so it is recorded as `fresh` when the connector declares a\nfresh start and `drain-only` otherwise. A pointer with no store is capped the same way as a cut\ncarrying neither, because it names a session whose bytes the artifact does not contain. A class is a promise the destination is entitled\nto act on, so it never describes bytes the artifact does not contain. The transcript store stays an\noperator input: this repository does not know where a harness keeps its transcript, so `exact`\nrequires `--session-store` to name one.\n\nThe recorded status is read under the same selection rule as the untracked set, so it describes the\nstate the captured bytes can reproduce. The destination re-reads it in the promoted tree and refuses\na difference.\n\nThe untracked selection rule is recorded in the record and is\n`git ls-files --others --exclude-standard -z, excluding .cotal/`. It honors `.gitignore`, so an\nignored file the seat needs does not travel and has to be moved separately. The `.cotal/` exclusion\nis a secrecy boundary rather than a size one: when a seat's `cwd` is also the mesh root, the control\ndirectory is untracked, and without the exclusion the broker trust material, the space account, the\nmanager instance identity's private seed and the seat's own credentials would land inside the\nartifact. A checkpoint carries credential references only; the destination resolves that material\nitself.\n\nA seat whose launch options could not be resolved is refused rather than checkpointed, with the\nmanager's own wording: `imperative launch options have no non-secret durable source (<keys>)`. The\nrefusal arrives at prepare time, so the cut stops before any child does.\n\nA delegated seat (SPEC \xA713.17) is refused at prepare time too, with\n`a delegated seat is not resumed by a later manager; stop it before preserving`. A manager stop\nafter a refused cut retires that seat through its retirement path.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean <store|all> --dry-run\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force [--discard-history]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | none | Required: destructive, no prompting |\n| `--dry-run` | off | `store`/`all`: list what would be removed and remove nothing; needs no `--force` |\n| `--attempt <id>` | none | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n| `--discard-history` | off | `restore-fallback`: also delete an old store that holds the only copy of the history or workflow runs the restore left out |\n\nOne configurable cleanup verb; every target requires `--force`, except a `--dry-run` of `store` or `all`.\nIt takes one target and no extra positional arguments. A channel name after `history` refuses\n`USAGE_INVALID` (exit 1); it does not narrow the purge. The dry run quotes an explicit `--store-dir`\nvalue in its printed force command so it stays one shell argument.\nOnly `history` takes `--space`, `--server` or `--creds`. Every other target acts on the root of the\nfolder it runs in, so it refuses them `USAGE_INVALID` (exit 1) before local cleanup inspects or removes\nroot state.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle. It removes the\n store's `jetstream` directory, its `.cotal-store-id` marker and the broker's `ocsp` folder, then\n the empty store directory. A\n store directory that holds anything else refuses `CONFLICT` (exit 2), names those entries and\n removes nothing; move them out and run it again.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity. On a root deployed with `up -f`, plain\n `cotal up` then refuses and names `cotal up -f <manifest>` when the deployment ran at another\n address, mode or space. A full backup is bound to the space trust it was taken under, so full\n backups taken before `clean all` no longer restore into this root. Only the registry of such a\n backup still restores, with `backup restore <dir> --only registry`, and chat and DM history start\n empty. Its dry run and its receipt say so when it removes that trust; take a new full backup after\n the next `cotal up`. On a user-auth space it also removes the space's IdP trust and its actor\n ledger, so every actor grant is lost. Its dry run and its receipt say so, and a later `up` then\n needs `cotal up --user-auth --idp <auth base URL>` and each actor granted again with\n `cotal actor grant`.\n\n`store` and `all` move the store aside to a `.cotal-clean-<id>` folder beside it, then remove its\nentries there, as `reset store` and `reset space` do. A removal that stops partway, such as at a\nfolder in the store this account cannot write, ends `OPERATION_PARTIAL` (exit 4). It names that\nfolder and what is left in it, and its Next makes the folder removable and removes it. Nothing else\nrecords the folder, so a later `clean` or `reset` finds no store and reports nothing to remove.\n\nAn existing store path that is a symbolic link refuses `CONFLICT` (exit 2), naming the link and\nits target. Replace the link with the directory it points to, then run the command again. The\nlink and its target are left unchanged, including for `--dry-run`.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process may still be\nrunning or any same-root recorded broker endpoint remains reachable (run `cotal down` first). On a\ncontroller root, a member whose process is gone counts as stopped once a completed `down` recorded it\nas already stopped, so `down --yes` after a crash is enough. A\nrecord counts as stopped only when it is absent, empty, or names a pid the kernel reports gone, so\nunattributable content, a removal reservation, or a pid whose liveness cannot be read refuses. They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. Without `--store-dir`, `store` and `all` read the\nstore the root's last broker served, as a start does: while the default `.cotal/nats` holds no\nJetStream data and that store does, such as after `backup restore --store-dir`, they remove that\nstore, and the dry run names it. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused. A store in the project folder itself, as `up --store-dir .` makes, refuses\n`CONFLICT` (exit 2), since the folder holds the project's own files; its Next names the store's own\nentries to remove by hand once the space is stopped. Its contents are not checked, so a directory that holds only a `jetstream/`\ndirectory is removed as a store, whatever that directory holds. The one exception is another\nspace's data: when another space uses the store folder, as [up](#up) checks it, `store` and `all`\nand their dry runs refuse `CONFLICT` (exit 2), name that space, its root and its streams, and remove\nnothing. Keep the folder while either space needs its history; once neither does, stop both and\nremove it by hand.\n\nIn a folder whose `.cotal` is a link, every target but `history`, and both dry runs, refuse before\nanything reads through it: `CONFLICT`, exit 2, naming the link and where it leads, or\n`TARGET_NOT_FOUND` for a link that leads nowhere, which is not passed over to the records of a\nfolder above it. When the link leads to this folder's own records, moved away and linked back, the\nNext names moving them back, as [up](#up) does.\n\nIn a copy, in a folder moved away from its recorded path, and in the folder a later `up` took its\nspace's name over from, the store may hold that space's only history. There `store` and `all`, and\nboth dry runs, refuse `TARGET_UNAVAILABLE`, exit 2, before the liveness checks, naming the folder\nthe stack runs from and the step the commands that read there name, and remove nothing.\n\n`--dry-run` runs the same checks as `store` or `all` and prints `would remove <path>` for each\nstore, credential, record and registration the command would remove, then removes nothing and takes\nno maintenance lock. A check that would refuse the command refuses its dry run the same way.\n\nCtrl-C or `SIGTERM` during `store` or `all` releases the maintenance lock, reports what the clean\nleft and exits 130 or 143. The outcome is `failed` when nothing was removed yet, and `unknown`, naming\nthe paths removed, once removal began.\n\n`store` and `all` also refuse every maintenance journal state, with `LIFECYCLE_BLOCKED`, exit 2. The\nrefusal names the attempt and, as Next, the step `cotal status` shows for it, such as bare\n`cotal up` to resume a preserved cut; once the root holds no maintenance state and its mesh is\nstopped, the clean runs. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle. After a\nrestore that left history or workflow runs out it also requires `--discard-history`, as\n`backup cleanup-fallback` does, and its refusal names the flag without printing the command help.\nAfter an `--accept-missing-source` restore there is no old store, so it only retires the journal.\nAn attempt the root's maintenance record does not name refuses with `TARGET_NOT_FOUND`, exit 2, and\ndeletes nothing. A repeat after a completed cleanup is such an attempt, and its refusal says the\nrestore has settled and names `cotal status`. Without `--force`, `restore-attempt` and\n`restore-fallback` name the command to run with its `--attempt`. `restore-attempt` refuses the same\nway, with `TARGET_NOT_FOUND`, for an attempt the record does not name, and with `LIFECYCLE_BLOCKED`\nfor a recorded attempt past its pre-commit point, which recovers forward. Without `--attempt` both\nverbs refuse with `USAGE_INVALID`. Each refusal's Next names the attempt the root records and its\nstep.\n\n## reset\n\n```bash\ncotal reset store <space> [--dry-run] [-y|--yes] [--json|--plain]\ncotal reset space <space> [--dry-run] [-y|--yes] [--json|--plain]\n```\n\nBoth routes select a space this machine started. A space with a locally started registration is\nselected by it. A controller stack that a completed `down` stopped has no registration; its\nprepared record and stack journal at the same root name its broker address, store and mode, and a\nreset keeps both, so a later `up` starts it again in the same mode. No reset changes a stack's mode;\n[up](#up) names the way to run another one. After `reset space` of a user-auth space, that\n`up` needs `--user-auth --idp <auth base URL>`, as described below. A stack none of whose members ever started has\nno store to reset, so both refuse `TARGET_NOT_FOUND` and name `cotal up`. So does a prepared space\nno stack has run for, which also names `cotal space forget`. A stack whose last\noperation is not a completed `down` refuses `LIFECYCLE_BLOCKED` and names `cotal down --yes`. Each\nrefusal names one reason and the command that clears it. A space that may still run names\n`cotal down` from its root, and lists each process that may run once. A stack member whose\nprocess also holds its component's record, such as the manager's pidfile, is listed as that member\nwith the record's pid. A member that exited without a recorded stop, as after a crash, is\nsettled by `cotal down --yes`: it finds the member gone and records it as already stopped, and the\nreset then accepts it. Only a member that no completed `down` recorded that way needs `cotal up` and\nthen `cotal down --yes`. A legacy root has no stack journal, and its `down` drops the registration\na reset selects, so no reset reaches it once stopped. A space named after its folder keeps that\nregistration, so a reset reaches it stopped. For any other legacy root, the step after `cotal down` is\n`cotal clean store --force` from its root for `reset store`, or `cotal clean all --force` for\n`reset space`, with `--store-dir <dir>` when the registration records a custom store. Once stopped,\nsuch a space has no record here, like a mistyped name, so a reset of either refuses\n`TARGET_NOT_FOUND`, says no registered or prepared space has that exact name, and names `cotal space`\nand then that step.\n\nRemoving the store removes what the broker kept in it: workflow runs and their journals, channel\nhistory, stored messages and every other record. A run still waiting for an answer goes with it and\ncannot be answered or resumed afterwards. Only the broker reads the store, and it is stopped, so\n`cotal down` reads the stack's runs from its manager before stopping it and records them in the\nstack journal with the steps they wait on. The preview, the confirmation and the receipt list those\nruns as of the last down, and `--json` carries them as `storeContents.runsAtLastDown` and\n`removedRuns`. When that down could not read them, or no down recorded them, the preview says so.\nTo see the runs then, run `cotal up` and `cotal run`, then `cotal down --yes`.\n\nThe store dry-run is a local-impact preview that exits 0. It requires a space selected that way, a\nreal project `.cotal` and a root readable as one local tenant. It opens with a verdict: the apply is\noffered, with `cotal reset store <space> --yes` as Next, or it would refuse, with the apply's own\nreason and remedy. The JSON result carries it as `applyVerdict`, and `completeness` stays\n`partial`. Completion suggests locally started names, without proving\neligibility. The preview inspects the store path with the existing guarded\ncleanup path checker, reads the maintenance journal, legacy process records,\nservice-controller instances, supervisor receipts, retained native attempts\nand root-local additive ledgers and attempts plus fresh boot attempts, and\nmakes a credless broker probe. Readable deployment IDs, unsettled attempts and unreadable\nrecord paths remain visible; these records do not\nprove live ownership or a complete broker tenant inventory. An orphan receipt\nor attempt remains visible without an instance record; these space-scoped\nrecords are not proven owned by this root. The preview also lists other local\nregistrations at the exact\nrecorded broker address. A match blocks store apply, while no match does not\nexclude aliases, remote tenants or live attachments. A negative credless probe\ncannot prove absence for TLS-first listeners. A custom store path must be\nabsolute and one real directory. A recorded store path that is a link refuses `CONFLICT` in the\npreview and the apply of both resets, naming the path and the directory it points to, and nothing\nis removed. Replace the link with that directory to reset it. A selected root whose `.cotal` is a\nlink refuses both resets and their previews `CONFLICT`, exit 2, before anything reads through it,\nnaming the link, and nothing is removed; when the records were moved away and linked back, the Next\nnames moving them back. A store in the project folder itself\nrefuses both resets `CONFLICT` as it refuses `clean store`, naming the store's own entries to remove\nby hand. The preview takes no maintenance lock and cannot establish exclusive\ncustody or a complete identity deletion set.\n\nBare `reset store <space>` checks those conditions, displays a scoped terminal\nconfirmation or requires `--yes` in a script, and acquires the maintenance lock. While another\ncommand holds it, such as an `up` started at the same time, `reset store` and `reset space` refuse\n`LIFECYCLE_BLOCKED`, exit 2, name that command's pid and remove nothing.\nUnder the lock it rechecks the exact registration or stopped-stack records, one-tenant root, absent\nmaintenance journal, legacy process records, service instances, supervisor\nreceipts and retained native attempts for that space, additive ledgers and\nattempts plus unsettled fresh boot attempts under the root, credless broker\nnonresponse and pinned filesystem identity before calling the existing\nstore-only cleanup. A readable or unreadable additive ledger or attempt\nblocks reset before confirmation and under the lock. It refuses another\nlocal registration at the exact broker address before confirmation and\nrechecks under the lock. This does not prove\nexclusivity against aliases or remote tenants. An orphan receipt or attempt\nblocks reset even when no instance record remains; a submitted native step may\nhave changed state. A TLS-required or TLS-first target refuses\nbecause a credless probe cannot prove it is down. The route preserves trust,\ncredentials and registration. A cleanup with an unconfirmed outcome is reported\nas unknown and is not retried. Credless nonresponse and the recorded process\nchecks are not proof that no unrecorded process exists. A store directory that holds anything\nbesides the store refuses both routes and their dry runs as it refuses `clean store`, naming those\nentries. A store folder another space uses refuses both routes and their dry runs `CONFLICT` the\nsame way, naming that space, its root and its streams; the store dry-run shows it as its verdict.\n\n`reset space <space>` adds local identity cleanup through the existing native\nclean-all operation. This route currently requires Linux, a space selected as above and a\nplain-NATS loopback listener. It removes a locally started registration; a stopped controller stack\nkeeps its prepared record and stack journal. On a user-auth space it also removes the IdP trust and\nthe actor ledger, so every actor grant is lost. The preview and the receipt say so on their own\nline: a later `up` needs `cotal up --user-auth --idp <auth base URL>`, and each actor needs\n`cotal actor grant` again. TLS-first, remote, shared-root\nand non-Linux targets refuse. It applies the same maintenance, deployment and\nprocess-custody guards as store reset. It does not stop a process to become\neligible.\n\nIts `--dry-run` validates the existing local trust chain and inventories the\nnative identity paths, including provider-declared secret keys, without running\ndeprovisioning hooks. The inventory is bounded to 10,000 paths. Foreign space\nsegments, symlinks, hard-linked files, foreign ownership, group/world-writable\npaths and providers without bounded cleanup metadata refuse. The refusal names the path and its\nreason; for a group- or world-writable path it names `chmod go-w <path>`. Values and content\nfingerprints are not printed. No maintenance lock is taken by the preview. Like the store dry-run, it\nhas a Store contents section when a store is present, naming what removing it removes and the runs\nthe last down recorded, and `--json` carries them as `storeContents`. The apply's confirmation shows\nthe same section. A preview that passes every check opens with a verdict, as the store dry-run\ndoes: the apply is offered, with `cotal reset space <space> --yes` as Next.\n\nApply shows the path inventory and defaults to cancellation, or requires\n`--yes` in a script. Under the maintenance lock it repeats the eligibility\nchecks and compares the reviewed file identities. Native secret deletion is\nrestricted to the planned keys; each filesystem removal checks the remaining\nreviewed tree. Added paths and changed files do not enlarge the confirmed scope.\nThe native operation deprovisions secrets before raw identity removal and\nremoves trust last. After checking the planned targets absent, it removes only\nthe unchanged eligible local registration and releases its matching selection.\nA replacement registration is kept and reported as partial.\n\nThe receipt retains acknowledged deletion paths even if later cleanup fails, and lists each path\nonce.\n\nCtrl-C or `SIGTERM` during either apply releases the maintenance lock, reports what the reset left\nand exits 130 or 143. Before the removal was submitted the outcome is `failed` and the store and\nidentity are as they were. After it, the outcome is `unknown`, `reset space` lists the paths removed\nso far, and Next is the route's `--dry-run`.\nUnconfirmed effects are unknown and are not retried. Personas, saved service\ndefinitions and stopped-instance records, installed packages, login sessions\nand remote data remain. Local cleanup does not revoke a remote credential or\nclear Cloud data.\n\nA saved service definition whose `credentialRef` names a credential this space's account issued,\nsuch as one `cotal auth mint` wrote, would hand its child a credential the next identity rejects.\nReset reads the issuing account from each such file and clears that `credentialRef`, and both the\npreview and the receipt name the service and the file. A file it cannot read as a credential is\nleft as it is. To give the service a credential again, mint one after `cotal up`, then remove the\nservice and add it again with that `credentialRef`.\n\n## Backups\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>] [--json|--plain]\ncotal backup inspect <dir> [--json|--plain]\ncotal backup verify <dir> [--json|--plain]\ncotal backup recover <full-attempt-id> [--yes|--dry-run] [--json|--plain]\ncotal backup cleanup-fallback <full-attempt-id> [--yes|--dry-run] [--discard-history] [--json|--plain]\ncotal backup restore <dir> [--only registry] [--store-dir <dir>] [--accept-missing-source] [--json|--plain]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nOn a controller root, one with a stack journal, `backup create`, `backup restore` and\n`up --restore` refuse `UNSUPPORTED` (exit 2) in this release. Each needs a preserved cut, and\n`down --preserve-state` cannot make one there. They refuse while validating their arguments,\nbefore the connector seed or any write. Plain `down` and `up` keep a controller stack's store,\nchannels and history. On a full store `down` refuses first, and [down](#down) says how to stop the\nstack there with or without purging that history. Backup and restore of a controller-owned stack\nreturn in a later release. The\nrest of this section applies to a root without a stack journal.\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match (`--store-dir` defaults to the cut's recorded source store), no recorded process that may still be running (the same test `clean` uses), and an\nunreachable exact endpoint from the recorded cut. That endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. `backup restore`, `up --restore` and a bare `up` that resumes the cut probe the\nbroker address they will bind before they move or start anything. While another process listens\nthere they refuse `CONFLICT` (exit 2), name the address and leave the cut unchanged. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. A bootstrap broker that does not accept connections within\n15 seconds fails the command. Each maintenance-login reload waits for the broker's acknowledgement\nfor up to 15 seconds, bounded by the operation deadline. A rejection, broker exit or missing\nacknowledgement fails the command before the next phase receives its login.\nCreation returns one JSON/plain receipt after cleanup.\nA published artifact plus incomplete cleanup is partial and retains the artifact and original\nattempt; it is not a reason to repeat creation blindly. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The presence bucket is memory-backed, so it does not survive the cut and\nthe clone may lack it. Every other stream must be present. The original store is never opened by\nthe backup broker, and the stack is not restarted implicitly. Artifact destinations must not overlap\nthe preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`backup create` checks its preconditions before it claims the cut or creates any maintenance state.\nA root with no preserved cut refuses and names `cotal down --preserve-state`. A cut in another state\nrefuses and names that state and its space. A create during a restore that still runs refuses\n`LIFECYCLE_BLOCKED` (exit 2), names that restore attempt, its process and when it started, and says\nto wait for it. A create that finds the root's maintenance lock held, such as by another\n`backup create`, names the operation that holds it, its pid and since when it has held the lock.\nNeither refusal journaled anything, so it names no attempt of its own. A destination that already exists is a `CONFLICT`\nrefusal, because create writes only a new directory. A destination whose folder does not exist\nrefuses `TARGET_NOT_FOUND` (exit 2) and names that folder; create makes only the artifact's own\nfolder. A destination check that finds a non-directory parent (`ENOTDIR`) or a symbolic-link loop\n(`ELOOP`) refuses `USAGE_INVALID` (exit 1), naming the destination and its parent. Choose a destination\nwith a directory parent; no attempt was started and the preservation journal is unchanged.\nA destination inside the preserved store refuses `USAGE_INVALID` (exit 1). So does one\ninside a path `clean all` removes, such as `.cotal/auth` or the space's `.cotal/space.<hex>`\nfolder, because that cleanup would remove the backup with them. One in a\nfolder this account cannot write refuses `PERMISSION_DENIED` (exit 2) and names that folder. A `--store-dir` that is not the cut's preserved store, such as an empty folder,\nrefuses `CONFLICT` (exit 2) and names both stores. A process that listens at the cut's recorded\naddress refuses `CONFLICT` (exit 2), as `backup restore` and a resuming `up` refuse it, and its Next\nsays to stop the process that listens there and run the same `backup create` again. Any other\nrefusal or failure shows the check's own sentence as its native detail.\nA failure that removed what it wrote leaves the preserved cut ready, and its Next names the same\n`backup create` to run again once that cause is fixed.\nA create that runs out of disk space (`ENOSPC`) or quota (`EDQUOT`) after it claims the cut removes\nwhat it wrote and fails `OPERATION_FAILED` (exit 5). Its message names the error and where it was\nwriting: the store copy under `.cotal/maintenance/attempts`, or the backup in its destination. It\nalso says that no backup was published and that the preserved cut is still ready. Its Next frees\nspace on that disk and runs the same `backup create` again.\n\nA `backup create` interrupted by SIGINT, SIGTERM or SIGHUP after it claims the cut exits 130, 143\nor 129 and names its attempt, the partial artifact it left and `cotal backup recover <attempt> --yes`.\nThat claim follows the restore claim's rule below: it holds the cut until its backup process and\nbrokers are proven gone, or for 30 minutes when they cannot be. Until then `up`, another create and\n`backup recover` refuse `LIFECYCLE_BLOCKED` and name the backup process and the deadline, and\n`cotal status` names the attempt and its next step. Once it is stale, `backup recover` removes the\npartial artifact, the clone and the broker files, each matched by inode, and a create to another\ndirectory does the same before it starts, then backs up the cut's recorded store, a custom\n`--store-dir` one included. A create naming the partial artifact refuses `CONFLICT`\nand names that recovery. The release's receipt names `cotal backup create <directory>` and\n`cotal up` as its next steps.\n\n`backup inspect` reads the bounded manifest and exact declared file-name inventory. It does not\nhash snapshot or checkpoint contents. `backup verify` additionally reads each named regular file\nand compares its size and SHA-256. Both are read-only: no staging directory, broker, restore or\nsecret upload. A path through a linked folder is read at its real path, as `backup restore` reads\nit, and the report names that path. Reports omit snapshot bytes, configuration bodies and trust\nmaterial. JSON retains\nthe declared metadata; terminal file lists are bounded and state their omissions. When a declared\nfile is missing, an undeclared file is present, a declared file is not a regular file, such as a\nsymbolic link or a FIFO, or a file does not match its declared size and SHA-256, the refusal names\nthat file and the fault, and its JSON target carries `file` and `fault`. A declared name that is\nnot a plain file name in the folder, such as one with `../` or an absolute path, has the fault\n`invalid-name` and is not printed. A `manifest.json` that is not a regular file, is larger than\n1 MiB or is not a valid manifest has the fault `manifest-not-regular`, `manifest-too-large` or\n`manifest-invalid`. `backup restore` and `up --restore` refuse each of these `SCHEMA_INVALID` with\nthe same file and fault, before they stage or change anything.\nA directory with no `manifest.json` that holds files a `backup create` writes is refused as an\nincomplete artifact, the kind an interrupted create leaves, with the fault `manifest-missing`. A\nfile, or a directory that holds neither a manifest nor such a file, is not a backup artifact and\nrefuses `TARGET_NOT_FOUND` (exit 2), naming it and saying whether it is a file, an empty folder or\na folder with other files. The folder of a create in this root that was\ninterrupted before it wrote a file is still an incomplete artifact. A path that names nothing refuses\n`TARGET_NOT_FOUND` (exit 2) and says in `missing` whether its folder or the artifact is missing, as\n`backup restore` does. A full artifact of an authenticated space\nrestores only into a root that still holds the trust state it was taken under, so it cannot move to\nanother machine or a fresh root. `backup create`, `backup inspect` and `backup verify` say so in a\n`Restores into` row, and their JSON carries `trustBound`. A registry restore does not check the trust\nstate, so `--only registry` still restores such an artifact's channel settings. When a full restore\nrefuses `CONFLICT` because the trust state moved, its Next names that `--only registry` restore for an\nartifact that holds the registry.\n\nFile integrity is not a signature or a restore proof. Matching a manifest does not authenticate\nits author, verify snapshot semantics or prove existing trust will accept a restore. `backup\ninspect` and `backup verify` do not compare an artifact's trust state with this root's, and their\n`Authority continuity` row says `not checked`. A full artifact taken on this root before `clean all`\nor `up --rotate-sys` still verifies, and restore refuses it `CONFLICT`. The guarded\nread path refuses unsupported file-protection environments rather than using a weaker reader.\n\n`backup recover <full-attempt-id>` uses the original root's pre-commit restore journal, not a\nmanifest path or selected-space guess. It rolls the attempt back once it is stale: its restore\nprocess and every recorded watchdog and broker are proven dead, and either every broker this root\nstarted is proven gone or the claim's deadline has passed. Commit intent forbids rollback. The\nreceipt names `cotal up`, which resumes the space from its preserved state, and\n`cotal backup restore <directory>`, which tries the restore again, as its next steps.\n`backup cleanup-fallback <full-attempt-id>` requires the matching healthy active restore and\nremoves only its unchanged retained old-store identity; it never removes the restored target.\nIt removes that store by its store entries, as `clean store` does. When the old store also holds\nanything else, such as a file you put in a custom store directory or in the fallback folder, its\n`--dry-run` names those entries, and the cleanup refuses `CONFLICT` (exit 2), names them and deletes\nnothing; move them out and run it again. A restore into the preserved store's own path moves that\nstore aside whole, so it refuses `CONFLICT` (exit 2) the same way before anything moves.\nA restore made with `--accept-missing-source` replaced a missing source and retains no old store,\nso its cleanup removes nothing and only retires the journal. A later `down --preserve-state` then\nstarts a new backup cycle. A registry restore leaves every other stream out, chat and DM history\nand workflow runs included. A full restore of an artifact with no workflow runs leaves the runs out,\nand a full restore of an artifact made before the state it replaced was preserved leaves out what\nwas written after the artifact. In each case its old store holds the only copy of them. Its `--dry-run` warns with\n`HISTORY_ONLY_IN_PREVIOUS_STORE` and names what it left out, and the cleanup refuses\n`USAGE_INVALID` (exit 1) and deletes nothing unless `--discard-history` is given.\nThat refusal names the flag and does not print the command help.\nBoth review full paths and default to Cancel. Scripts need `--yes`; `--dry-run` reads the plan\nwithout taking a maintenance lock or writing. Apply rechecks the reviewed journal revision under\nthe native lock, preserves unknown outcomes and never retries a deletion. No live-owner override\nor process kill is supplied.\nA restore's full attempt id has the form `restore-<uuid>`. `backup recover` also takes an\ninterrupted create's `backup-<uuid>`, as described above. `cotal status` shows the attempt a root\nholds under Maintenance, with its state and next step. For a restore that left history or workflow\nruns out that step includes `--discard-history`. A refusal for the wrong state names the next step\nfor the recorded one. A completed `cleanup-fallback` retires the journal, so a repeated cleanup\nrefuses with `TARGET_NOT_FOUND` and says the root holds no restore attempt.\n\n`backup restore <dir>` is the canonical restore. It runs the same guarded restore as\n`up --restore <dir>`, with the same lock, claim, trust and space proofs and the same resume, and\nreturns one `backup restore` receipt: the restore attempt, space, root, selection, store, broker\nprocess and whether the manager, delivery and auth service started. The restore follows the\nartifact's own selection, and `--only registry` narrows a full artifact to the registry. Any other\n`--only` value is refused, because the restore could not honor it. The space, broker and runtime\ncome from the preserved cut, so no selector can rebind them. An artifact of another space refuses\n`CONFLICT` (exit 2) and names both spaces, and a full artifact taken under another trust state\nrefuses `CONFLICT` the same way. An artifact file that is missing, undeclared or not its declared\nsize and SHA-256 refuses `SCHEMA_INVALID` (exit 1) and names the file, as `backup verify` does. A\nmanifest whose space is longer than 200 characters is invalid, so restore refuses it with the fault\n`manifest-invalid` and does not print that space. In each case the preserved cut is not changed. A\nreceipt means the\nactive journal was written. Its `seatResume` field says whether retained seats were handed to the\nmanager's resume (`requested`) or dropped by a registry restore (`not-requested`). It does not\nconfirm that any seat joined. Its `restored` and `recreatedEmpty` fields name the streams the\nrestore brought back and the ones it recreated empty. A registry restore brings back channel settings only, so chat and DM\nhistory and workflow runs start empty. Its receipt says so with a `HISTORY_NOT_RESTORED` warning, and `previousStore`\nnames the store the restore set aside, which holds the only copy of them. Its next step says\nthat `backup cleanup-fallback` deletes that store only with `--discard-history`. A full restore of\nan artifact made before the state it replaced was preserved, such as a backup taken before a later\n`cotal up` and `down --preserve-state`, leaves out the chat and DM history and workflow runs written\nafter the artifact. Its receipt says so with a `NEWER_HISTORY_NOT_RESTORED` warning that names the\nstore it set aside, which holds their only copy, and its next step includes `--discard-history` too.\nWhen that restore brought the workflow runs back, it also names each run it rolled back and each\nstep that was answered after the artifact and is open again, and says that answer is only in the\nstore it set aside. A reopened step accepts an answer again. `cotal status` and the\n`backup cleanup-fallback` preview name the same runs. To find them, the restore copies the store it\nsets aside and reads its run journals on one more isolated broker, so such a restore takes longer\nand needs disk space for that copy.\n`up --restore --restore-only registry` prints the same two lines. Without `--store-dir`, a restore\ncreates its store at `.cotal/nats`, also when the cut's store is elsewhere, as after\n`up --store-dir`; only a controller root restores into its recorded store. The preserved store stays\nwhere it is until `backup cleanup-fallback` deletes it, so to keep the space on another disk, name a\nnew store there with `--store-dir`. `--store-dir` names a store the restore creates, so a path that\nalready exists, or whose parent directory does not, refuses `USAGE_INVALID` (exit 1) before anything\nis staged. Restore needs the stopped preserved cut from `down --preserve-state`. A root with no\nmaintenance journal refuses before the restore takes its maintenance lock, so the folder keeps no\nstate from the attempt, and `up --restore` refuses the same way. An artifact path that names\nnothing refuses `TARGET_NOT_FOUND` (exit 2) at the same point. It names the path, and its JSON\ntarget says in `missing` whether the folder that should hold the artifact (`folder`) or only the\nartifact (`artifact`) is missing. A path that is not an artifact, or is an incomplete one, refuses\nthere too, as `backup verify` refuses it. A path that is, holds or sits inside the preserved store\nor `.cotal/maintenance`, such as `.` in this folder, refuses `USAGE_INVALID` (exit 1) there, naming\nthat store or folder. The restore checks the path again once it holds the maintenance lock, because a\nlink in it may name another folder by then, and refuses the same way. A restore that fails after it\nbegan names its attempt in the text and in the JSON target, which also carries `journalState`, and\nits remedy gives the next step for that state. `up --restore`\nremains as the equivalent startup form. After its startup lines it names the restore attempt and\ngives the cleanup step that retires it, with `--discard-history` for a restore that left history or\nworkflow runs out. Unlike the other\nbackup modes, `backup restore` runs the connector seed first, as `up` does, because it resumes seats.\n\nThree commands refuse an active restore. `down --preserve-state`, in each of its forms, refuses\n`LIFECYCLE_BLOCKED` (exit 2) and stops nothing: a new cut begins only after the restore journal is\nretired, so it names the attempt and `backup cleanup-fallback` as the next step, with\n`--discard-history` after a restore that left history or workflow runs out. `team boot`\nresumes only a stopped stack whose manager never launched, and a restored stack's manager already\nran, so start it with bare `cotal up` instead. `cotal up -f` boots a fresh mesh, so it starts only\nonce the restore is retired. While the restored broker runs, it refuses `CONFLICT`, exit 2, naming\nthe broker's PID, and its Next is `cotal down`, then `backup cleanup-fallback <attempt> --yes`, then\n`cotal up -f` again. After a restore that left history or workflow runs out, that cleanup step\nincludes `--discard-history` and says it deletes the previous store. Once that stack is stopped, it refuses `LIFECYCLE_BLOCKED`, exit 2, and names\nthe cleanup. Nothing is started either way. While the restored broker runs, any other `cotal up`,\nbare or with `--server`, refuses `CONFLICT` too, naming its PID, and leaves the restore journal as it\nwas. Its Next is `cotal down`, then `cotal up`, which starts the restored stack again.\n\nStopped client-managed KV ordered consumers are ephemeral read residue, not backup state. Backup\nignores only the pinned client's exact stopped shapes: ordinary last-value watchers, a watcher of one\nsubtree of its bucket such as the manager's goal index, and the whole-bucket scanner that uses\nall-history delivery to collapse concurrent tombstones. A bound consumer or any lookalike with a\nfilter outside its bucket, another inbox, lifetime, or other config is still refused.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, on\na static-auth or open mesh the workflow run stores (the WFJ step journal, the records store and the\nrun admission store), and validated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. The other endpoint stores are nonportable control state: endpoint\nsubmissions, facts, events, timer requests and fires, work pools, contract artifacts, and the\nauth, session and issued-authority stores. Restore recreates those streams empty with their\ncanonical configs before exposing the normal listener, so endpoint sessions do not cross a backup. A\nworkflow run that waited at a checkpoint or a timer at the cut is back after a full restore, and the\nmanager takes it over as after a restart. The records store also holds the endpoint authority rows,\nsuch as lifecycle heads, obligations and policy versions, and a full restore rolls them back to the\ncut with the runs. A user-mode full backup holds no workflow run store, because the records store's\nlifecycle heads name credential rows in the auth store, which a backup does not carry; its restore\nrecreates the run stores empty and says so. An artifact in the earlier `cotal-space-backup/v1`\nformat still restores; it holds no workflow run, so the run stores come back empty and the restore\nsays so. `backup create` says so too when its artifact holds no runs, as a registry or user-mode\nartifact does. After a restore that left the runs out, the store it set aside holds their only copy:\nits receipt says so with a `RUNS_NOT_RESTORED` warning that names that store, and\n`backup cleanup-fallback` deletes it only with `--discard-history`, as for a registry restore. Each\nrestore lists what it restored and what it recreated empty. Artifacts are exclusively created `0700`;\nsnapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting. So\n`backup verify`, `backup restore` and `up --restore` apply the\n[`space create`](#selecting-a-space) rule for other local writers to the artifact directory, each\nfile in it and each directory above it. An artifact another account could write refuses\n`PERMISSION_DENIED`, exit 2, before a restore stages or changes anything, and the refusal names each\nsuch path and one remedy such as `chmod g-w <paths>`. `backup create` refuses the same way, before it\nwrites anything, when a folder that would hold the artifact breaks the rule. `backup inspect` does\nnot check this, and a copy that other accounts can only read is accepted.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space. A full restore also requires the trust state the artifact was taken\nunder; a registry restore, `--only registry` of a full artifact included, does not check it. The\nwhole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, and a deadline 30 minutes after the restore\nbegan). While the claim is live, ordinary `up`, a repeated restore, `backup create` and\n`backup recover` refuse `LIFECYCLE_BLOCKED` (exit 2) and name the restore process, when it started\nand the deadline. A command that finds\nthe root's maintenance lock held refuses the same way and names the pid that holds it. The attempt\nis stale once the restore process and every recorded owner are proven dead on this host, and\neither the deadline has passed or every broker the root started is proven gone. A broker's\nwatchdog stops it when the restore process exits, so a restore stopped by Ctrl-C is stale within\nseconds. A retried restore rolls a stale attempt back and continues; an operator can also roll it\nback with `cotal backup recover <full-attempt-id> --yes`. Nothing ever rolls back a live attempt.\nA restore interrupted by SIGINT, SIGTERM or SIGHUP before it commits exits 130, 143 or 129 and\nprints its attempt id and that recovery command. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store, file store cap, runtime and broker address. A different\n`--store-dir`, `--max-file-store`, `--runtime` or `--server` refuses `CONFLICT`, exit 2, before\nanything starts, naming the value given and the recorded one. The Next resumes with `cotal up` without that flag, then `cotal down` and\n`cotal up` with it.\n\n**Admitting a seat checkpoint.** An ordinary `up` from a preserved cut admits that cut's seat\ncheckpoints before it journals the resume attempt and before any process starts, so a refusal costs\nnothing. Three gates run in order, each naming what it saw.\n\n1. *Integrity.* Every file the record names must be present, a regular non-symlink file, the\n recorded byte size and the recorded sha256, re-stat'd after the read so a file that moved is a\n refusal. Failure here consults no other gate.\n2. *Identity.* The recorded space must match, the recorded `lifecycleUid` must not belong to a live\n incarnation, and the profile revision must match this host's or be resumed under deliberately\n this host's. A differing revision is refused with both digests and the remedy, and there is no\n override: the checkpoint carries the recorded digest and not the config bytes, so nothing could\n run the seat under the recorded revision, and the manager re-digests the same file and refuses\n drift on its own. This gate has no blanket override, which is the only reason the next one may\n have one.\n3. *Recency.* `capturedAt` is compared to this host's clock against the horizon the record carries.\n Inside it, the seat resumes. Outside it, `up` refuses and prints the capture instant, the clock\n reading and the horizon; `--accept-stale-checkpoint` admits it anyway and the exercised consent\n is printed with the actual age. An unreadable `capturedAt` is refused with no override, because a\n freshness gate that fails open is not a gate.\n\nCustody transfers only after all three pass. The destination claims the recorded generation plus one\nby exclusive create, before it launches anything. A lost create means another destination is already\nclaiming that seat, and it refuses with `seat-writer-generation-create-lost` rather than adopting\nthe winner and becoming a second writer. The recorded `lifecycleUid` is reused and never minted, so\nthe resumed seat binds the same lifecycle-keyed durables.\n\nAdmission is reconciled against the inventory the resume is about to hand the manager, and that\nreconciliation finishes before the restore moves a single tree. A checkpoint whose recorded\n`lifecycleUid` is not the one the retained inventory carries describes a different incarnation of\nthat seat, and it refuses with both uids while every live working tree is still untouched and no\ngeneration is claimed. A retained\nseat with no admitted checkpoint refuses the resume by name: an absent checkpoint directory and an\nabsent record are indistinguishable from a seat that was never checkpointed, and a seat that starts\nwithout passing the gates has claimed no generation. `--accept-stale-checkpoint` is recorded in the\nresume journal with the seat, the capture instant, the admitted age and the horizon, so the consent\nsurvives the terminal it was typed into.\n\nThe whole admission is all or nothing. Coverage is settled first, then every gate runs over every\ncheckpoint, and only then is any generation claimed. A refusal at any point leaves every generation\nunclaimed, including a lost exclusive create during the claim itself: the claims that attempt made\nare removed before the refusal is raised, by the exact paths it wrote, so a generation another\ndestination holds is never touched. A claim is a create that can never be made again, so a refusal\nthat left one behind would consume the retry over the same checkpoint set.\n\n**Restoring a seat checkpoint.** Once every gate has passed over every checkpoint, and before a\nsingle generation is claimed, `up` puts each admitted seat's captured bytes back. A refusal here\ncosts nothing for the same reason a gate failure does: no claim has been made and nothing has\nstarted.\n\nA restore never moves or replaces the destination's own control directory. A checkpoint excludes\n`.cotal/` by design, so a seat whose `cwd` holds one, which is the layout an operator gets by\nrunning `up` and `spawn` in a single directory, is refused before anything is staged: promoting a\ntree that cannot contain `.cotal/` over that `cwd` would carry this host's live trust material and\nmaintenance state away with the superseded tree. The refusal names the control directory it found\nand the remedy, which is to give the seat a working tree that is not a workspace root.\n\nEach seat is staged beside its own `cwd`, in `<cwd>.incoming`:\n\n1. every recorded digest is verified again over the files as they are now;\n2. the bundle is cloned into `<cwd>.incoming`, which is refused when that path already exists;\n3. the recorded base commit is verified in the clone and checked out detached, so a bundle that does\n not contain it stops the resume instead of continuing against a different history;\n4. the index diff is applied with `--index` and the worktree diff without it, both `--binary\n --allow-empty`. That order is what puts staged content back in the index rather than only in the\n worktree, and `--allow-empty` is why a seat with a clean tree is still restorable;\n5. the untracked archive is extracted.\n\nEvery seat stages before any seat is promoted. Promotion moves an existing `cwd` aside to\n`<cwd>.superseded.<timestamp>` and renames the staging directory into place, then puts the session\npointer and store files where the destination's connector reads them, then re-reads\n`git status --porcelain` in the promoted tree and compares it to the status the checkpoint recorded.\nA restore that applied without error and produced a different index is a refusal, not a warning. The\ntwo renames are the only steps that touch the path the seat will use, so a failure anywhere leaves\nevery seat's live `cwd` as it was.\n\nThe rename itself claims the superseded name, and a taken name gets a numeric suffix. The timestamp\nhas one-second resolution, so two promotions of the same seat within one second compute the same\npath; a rename onto a name that already holds a tree fails on every platform, and that failure is\nread as taken. Nothing creates the name ahead of the move, because Windows refuses to rename onto an\nexisting directory at all. A superseded tree is the thing that rename exists to keep.\n\n`git` and `tar` run as child processes with argument arrays, never a shell string.\n\nA leftover `<cwd>.incoming` refuses the resume by name. A staging directory from a failed run is the\nonly record of what failed, so nothing removes one automatically: inspect it, remove it by hand, and\nresume. A pre-existing `cwd` is renamed rather than deleted, so a wrong checkpoint costs a rename\ninstead of a tree. When a promotion fails, the renames that attempt made are undone and the staging\ntree is left where it is, as the evidence for what did not verify.\n\nA session pointer whose recorded `sessionId` is not the one the retained inventory reopens is\nrefused before anything is cloned. A session file already present at its destination is judged by\ncontent: bytes equal to the recorded digest are already restored, and different bytes under the path\nthe connector is about to read are refused with both digests rather than clobbered.\n\n`up --restore <dir>` reaches the same admission and the same restore, after the store is restored\nand validated and before commit intent is journaled. A registry-only restore resumes no seat, so it\nadmits and restores nothing.\n\nOne limit is worth stating plainly. The writer generation is claimed by exclusive create inside one\nworkspace root, so it fences two resumes on the same host and does not fence two independent\ndestinations: copy a checkpoint to two roots and both claim the same successor. A real cross-host\nfence needs a coordinate neither root owns.\n\nAuthenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. Because the system account is part of that\ncommitment, a [`cotal up --rotate-sys`](#up) makes every full artifact taken before it unrestorable\nagainst this root: take a fresh full backup after each rotation. [`cotal clean all`](#clean) removes\nthe space trust itself, so it does the same. Such an artifact still restores its registry with\n`--only registry`, and chat and DM history start empty. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Without it, the restore refuses `TARGET_NOT_FOUND`\n(exit 2), names the missing path and changes nothing. A default `.cotal/nats` that is a symbolic\nlink to the recorded source is that source, so the restore runs in place at the real path and the\nlink keeps naming it. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode, including open, mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. On a user-auth mesh\nthese manager calls run as the logged-in operator's `cli` actor, the caller the preserve cut used, so\nthat actor needs a current `admin` grant. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. A new launch hands the token to the next manager as\na protected file: the CLI journals its path, writes it at mode 0600 in\n`.cotal/maintenance/v1/handoff/`, then journals its inode and digest before the child starts. The\nchild reads it once with `--resume-commit-file` and `--resume-commit-sha256` and removes it. The\ntoken never appears on a new launch's command line. Before retirement or activation the CLI settles\nthat file. A file it did not record, or a recorded path that now holds another file, holds the\nattempt and is never deleted. `cotal supervise --resume-commit-token` stays accepted for older\ncallers; while such a process runs, its token is visible in its command line. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. A retained agent the lost manager already launched can still be running, for example\nin a tmux window, while the journal reads `resume-intent`. On a static mesh the replacement manager\ncloses that seat through the reference the lost manager recorded on the agent's slot, waits for the\nprincipal to leave presence, and launches it again. A live principal with no such record, or one that\nstays live after the seat is closed, is refused. Every normal restore listener has an unguessable\nattempt-bound NATS server name. The CLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. Before the\ndaemons start, re-entry recreates the memory-only buckets on an adopted restore or resume listener,\nbecause the interrupted `up` may have stopped before it made them. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## Selecting a space\n\n```bash\ncotal space [--json|--plain]\ncotal space show [<name>] [--json|--plain]\ncotal space add <name> --server <url> [--root <dir>] [--mode auth|open] [--tls] [--allow-unencrypted-overlay] [--force] [-y|--yes]\ncotal space add [<name>] (-f|--file <bundle.json> | --from <https url>) [--root <dir>] [--server <url>] [--force] [-y|--yes]\ncotal space create <name> [--root <dir>] [--adopt]\ncotal space forget <name>\ncotal space delete <name> [--dry-run] [-y|--yes] [--json|--plain]\ncotal use [<space> | --clear] [--json|--plain]\n```\n\n`space` lists the spaces this machine can reach: each one's broker state, server, mode, origin\n(`started here`, `registered` or `discovered`) and root, with the selected one marked. Inside a\nproject whose root has one running registered space other than the selection, that space is marked as\nthis folder's, since a command without `--space` targets it there. It probes\neach broker that this machine started or that was registered by hand, and reports one that does not\nanswer as `unreachable`; its registration stays. A discovered space is never dialed by a listing and\nreads `not checked`. `space show [<name>]` shows one space in full and probes only that broker.\nWith no name it is about this folder: the space it has registered, else the one it holds prepared or\nstopped, which `show` names although every other command there targets the selection, else the\nselected one. Neither writes anything. A registry record that cannot be\nread refuses both with `SCHEMA_INVALID`, exit 1, naming its file.\n\n`--json` writes one document. `space` gives `current` (the selected space or null),\n`currentRecorded`, `folder` (this folder's space when it is marked, else null), `items` and\n`prepared`; each item is the row `cotal status --json` gives under\n`meshes.items`: `space`, `server`, `mode`, `root`, `origin`, `tlsRequired`, `eventsRequired`,\n`modeChecked`, `current`, `check` (`reachable`, `unreachable` or `not-checked`) and `catalog`. `space show` adds\n`recordedAt`, `attachHost`, `maxSessions`, `storeDir`, `unencryptedOverlay`, the login pins of a\nuser-auth space under `userAuth` and the catalog's `id`, `slug`, `kind` and `role`. It leaves\ncredential paths out. A name that is not registered is refused with `TARGET_NOT_FOUND`, exit 2, and\nso is a bare `space show` outside a project with nothing selected. Each entry under `prepared` has `space`, `root`,\n`preparedAt`, `current`, `rootMissing`, which is true when its project folder no longer exists,\n`state` and `lastOperation`. `state` is `prepared` for a space that never started, `stopped` for one\nwhose stack ran and was stopped, and `unsettled` when its last `up` or `down` recorded no outcome.\n`lastOperation` names that operation and how it ended, such as `cotal down, completed`, or is null.\n`space show` of a prepared space gives those with the kind `prepared-space`. The listing marks\nsuch a folder `(no longer exists)` and names `cotal space forget <space>` for it instead of `cotal up`.\n\n`space add` registers a space that runs elsewhere, so `--space` and `use` can reach it. A name\n`space create` refuses, such as one that starts with `-` or holds a `*`, given or named by a bundle,\nrefuses with `USAGE_INVALID`, exit 1, before anything is written. With\n`--server`, the trust comes from `--root`'s `.cotal/auth`, and the mode is `auth` when that folder\nholds the space's trust, `open` otherwise. `--root` defaults to this project. Outside one it is\nrequired, and leaving it out is a one-line `USAGE_INVALID` refusal whose remedy says what to pass:\nan open space keeps no credentials there, so any folder will do. With `--file`, a user-auth connection bundle exported\nwhere the space runs supplies the login pins and the server, unless `--server` names another\naddress for that broker; `--from` fetches the space's discovery\ndocument instead, after you agree to the fetch, and needs a terminal. Before anything is written,\n`space add` checks that the address may carry credentials, that the trust composes, that the\nbroker enforces the mode, and that the credentials open the space; a user-auth space's exchange\nmust answer as the issuer it pins. An open broker answers for any name, so an open space must also\nbe one the broker serves, as its streams show. A name it does not serve is refused with\n`TARGET_NOT_FOUND`, exit 2, naming the spaces it does serve. On a terminal it then shows what it will trust and asks; `--yes`\nskips that question, but not the confirmation of fetched pins. It never changes the selection.\n\nA refusal writes nothing and carries its code: `USAGE_INVALID` or `SCHEMA_INVALID` (exit 1) for\ninput, and `CONFLICT`, `TARGET_UNAVAILABLE`, `TARGET_NOT_FOUND`, `DEPENDENCY_UNAVAILABLE`,\n`UNSUPPORTED`, `AUTH_REQUIRED` or `PERMISSION_DENIED` (exit 2) for what the checks found. A name that is already\nregistered is a `CONFLICT`, and its remedy offers `--force` only for a registration added by hand.\nA broker that does not answer is a `TARGET_UNAVAILABLE`, and its remedy offers `--force`.\nA broker that requires auth, with a `--root` that holds another space's trust, is a\n`DEPENDENCY_UNAVAILABLE` that names the space the folder holds.\nWhen no broker answers at `--server`, or at a bundle's server, the `TARGET_UNAVAILABLE` refusal says\nwhether its host name resolves and whether anything took a connection at its port. When a broker\nanswers there under another scheme, such as `nats://` for a `tls://` address, the remedy names that\naddress, and says when it has no TLS. A port that takes no connection gets the port check as its\nremedy. For `--server`, the `--force` route follows either one.\n`--force` replaces a registration added by hand, never one this machine started or a catalog lists,\nand adds a `--server` space without checking its broker, which suits a\nspace that is down right now; a user-auth space is always checked. Such a registration records that\nits mode was not checked: `space` and `space show` show the mode as not checked, with\n`modeChecked: false` in `--json`, and `whoami` does not state what that mode implies as fact. Declining the question is a\n`no-op` with a `USER_CANCELLED` warning. `--json` gives the registration under `space`, in the\nshape `space show` gives, with `verified` and `replaced`.\n\n`space create <name>` prepares a space to start on this machine. It records the name and the root,\nwhich is `--root` or this folder, and makes `<root>/.cotal/`; `cotal up` in that root then starts the\nspace under that name and drops the prepared record. It also writes the `default` persona into\n`<root>/.cotal/agents` when the root has none, as setup does, and leaves one you edited as it is.\n`--json` lists the files it wrote under `result.written`. The project and its ancestors must be physical\ndirectories owned by you or root, with no symlinks. World write refuses. Group write is allowed only\non a directory you own whose group is your user private group, the Debian and Ubuntu default, and\nthat has no extended ACL. On Linux, `getent` must show that the group is your primary group, lists\nno other member and is no other account's primary group, and `getfacl` must show no ACL entries\nbeyond the base ones. That account list counts only when `/etc/nsswitch.conf` names `files` and\n`systemd` alone for passwd and group. A directory source such as LDAP or SSSD can list part of its\naccounts, so with one configured, group write refuses. `getent` and `getfacl` run from your PATH\nwith a 2 second limit each, so the first check in a process can take a few seconds. A proven group\nholds for the life of the process. When a read is unavailable, group write refuses; install the\n`acl` package for `getfacl`, or remove group write. Root-owned sticky temporary directories are\nallowed. New control directories, the persona catalog `.cotal/agents` included, use mode 0700,\neven with a group-writable umask. An unsafe existing path refuses before the\nprepared record is written, without changing its permissions. The refusal names every unsafe path\nin the chain at once, the reason and one remedy such as `chmod g-w <paths>` that lists each path.\nA path that its group and every account can write gets `chmod go-w`, since `chmod o-w` alone would\nleave the group write that refuses next, and the other group and world gaps join that one command.\nWhen a symlink is the gap, the remedy names the physical path the folder resolves to.\nIt starts nothing and leaves the selection as it is. `cotal up` applies the same rule to the root\nit starts in, before it writes anything, so it never starts a stack that `down` would refuse to\nstop from that root. `cotal up` starts a space only under a name of 1 to 64 letters, digits,\nunderscores or hyphens that starts with a letter, digit or underscore, because a name that starts\nwith `-` cannot be passed as a flag value. `space create` refuses any other name, such as `caf\xE9` or\n`-x`, with `USAGE_INVALID`, exit 1, before anything is written, with a `remedy` that says which\ncharacters a name can use, and `cotal up` refuses a `--space`\nthat starts with `-` the same way. The limit is 64 because each local file that carries a space spells its name twice over,\nand a file name holds at most 255 bytes. `cotal up` refuses a longer name that an earlier release\nprepared the same way, before it starts anything, and names `cotal space forget` and a shorter name.\n`space add` and `meshes add` refuse a longer name with `USAGE_INVALID`, exit 1, and write no\nrecord, because a space added from elsewhere is carried by the same files.\nIt refuses with `CONFLICT` a name that is registered or prepared, a folder that is already a\nspace's root, and the home folder, whose `.cotal` holds this machine's own records. It refuses a\nfolder that is, or is in, a control directory, which is a project's `.cotal` or the Cotal home,\njudged by the path given and by the path its links lead to, with `CONFLICT` and nothing written. A\nhome `COTAL_HOME` redirects may hold project folders beside its records, so under it only the home\nitself and its record entries (`meshes`, `prepared`, `services` and the like) are control paths. The\nnext step names the project folder that `.cotal` belongs to. A project folder inside another project,\noutside its `.cotal`, is a project of its own. For a folder\nanother space uses, the next step names creating it in another folder with `--root <dir>`, and\nstarting, selecting or forgetting the space that uses the folder. A folder this\naccount cannot write, such as `/`, refuses `PERMISSION_DENIED`, exit 2, with nothing written, and so\ndoes a Cotal home (`~/.cotal` or `COTAL_HOME`) it cannot write. A `.cotal` that links to a folder that\ndoes not exist refuses `TARGET_NOT_FOUND`, exit 2, naming the link and its target. A folder whose\npath contains a control character, such as a tab, a newline or an escape character, or a bidirectional\nformatting mark, which can show one path as another, refuses `USAGE_INVALID`, exit 1, with nothing\nwritten, in `space create` and in `cotal up`. The refusal names the character and shows the path with\nit escaped. `space` lists prepared spaces under their own heading, below a line that says no space\nhas been started or registered yet when that is so, and `space show`, `use` and `whoami` accept one.\nOnce one of them has run, the heading gets a `State` column, and the line says no space is running\nor registered. A space whose stack ran and was stopped reads as stopped in `space`, `space show` and\n`use`, with its last operation, as `status` reads it.\nA command that\ntargets a prepared space refuses with `TARGET_UNAVAILABLE`, exit 2, naming the root to start it in,\nand so does `cotal up --space <name>` run from another folder. Its Next names\n`cotal up --root <root> --space <name>`, which starts it in that root from where you are, and\n`cotal space forget <name>`, which lets you start it in the current folder. When that root's last `up` or `down`\nwas cut off before it recorded an outcome, as an `up` interrupted before it registered the space\nleaves it, the refusal says so and names `cotal status` in that root, which names the step that\nfinishes or settles it. `cotal up --space <name>` in a folder\nthat is already another space's root refuses with `CONFLICT`, exit 2. `space add` refuses a prepared\nname. A prepared space records its folder by path, and moving the folder leaves nothing in it that\nnames the space. A bare `cotal up` in a folder that names no space, while the selected space is\nprepared in a folder that no longer exists, refuses `TARGET_UNAVAILABLE`, exit 2, rather than start a\nspace named after the folder there. Forget the space with `cotal space forget <name>`, prepare it again in its new\nfolder with `cotal space create <name>`, then run `cotal up`. Until then, `space create` of that name\nsays its recorded folder no longer exists.\n\n`space forget <name>` removes a registration this machine keeps. It stops nothing, deletes nothing on\nthe broker, and keeps the files under its root. It refuses a space this machine is running with\n`LIFECYCLE_BLOCKED`, because `cotal down` stops it, and a discovered space with `CONFLICT`, because\nits catalog keeps it. A stack `cotal up` started stays a prepared space at its root after that\n`down`, so the refusal names `cotal down` and then `space forget` again. A legacy root's `down` drops\nthe record, and the refusal says so, unless the space is named after its folder: that `down` keeps the\nrecord, and the refusal names `space forget` again. A space whose root holds its stack journal counts as\nrunning while any stack member's process may still run. The refusal names one such member and\nwrites nothing: the registration, the selection and the root's files stay. A member whose process\nis gone without its stop receipt does not block, and the result warns with `STACK_MEMBER_UNSETTLED`\nthat it was never stopped cleanly. A prepared space is forgotten the same way, and the `.cotal/`\nthat `space create` made stays. A prepared record that cannot be read names no folder, so forgetting\nits space removes only that file, and the result's `root` is null. Forgetting the selected space clears the selection. Forget also\nremoves the per-space preferences saved under that name, so a later space with the same name starts\nwithout them, and the receipt lists each removed key and value so you can set them again. If that\nremoval fails after the record is gone, the run is `partial` and names the `config unset` to finish it,\nafter the `chmod` when the preferences folder is one another account could write.\nForget also removes the stack definitions `cotal up` saved for the space at its root, with their\nstopped instance records, so another folder can start a stack under the same name. The result lists\nthem. While a member was not stopped cleanly, or the root holds preserved state, they stay and the\nresult warns with `STACK_DEFINITIONS_KEPT`. For a member that was not stopped cleanly, the warning\nnames `cotal down --yes` in the root and then `space forget` again. When the stack's folder is\ngone, it names `cotal down --space <space> --yes`, which stops the stack and removes them.\nForget keeps the other services saved under the name, such as one `cotal service add` saved, with\ntheir instance records. The result names them as `keptServices`, and a later space with the same\nname inherits them: its `cotal service` lists them and its `up` names them. A later space in another\nfolder does not start one, as [Running a service](#running-a-service) describes. `cotal service remove\n<name> --space <space> --yes` in that space forgets one. While no space has the name, `cotal service`\nfinds no space for it, so remove a service before you forget its space to drop it at once.\n`status` in a forgotten space's root then selects no space and probes no broker. When nothing left\nin that root's `.cotal/` names a space, its next step is `cotal space create <name> --root <root>`,\nbecause a bare `cotal up` there would start a space named after that folder. A command run\nthere that finds no broker at the default address says that no space is registered or prepared for\nthe folder and names the same step. That refusal has no `Mesh:` line, and its `target` names only\nthe folder, because no space was resolved there.\n\nIn open or static-auth mode, an unanswered `--server` address that differs from the registration\nretained by target resolution gives `TARGET_UNAVAILABLE`, naming the address that did not answer.\nIts Next names the recorded broker and says to drop `--server` to reach it. If resolution retained\nno registration, the Next says to check the address instead. The request is not sent to the\nrecorded broker in its place. Target resolution given `--server` probes only records at that address,\nincluding when `--space` is omitted. `status --all` still checks every recorded address because\n`--all` asks for that scope. This refusal wording does not describe the infrastructure report from\n`status`.\n\n`space delete <name> --dry-run` reads the registered broker in open or static-auth mode.\nIt requires the broker's mode to match the registration, and a static connection must\nconfirm its current account and user identity. It reports the known first-party\nstreams and bucket backing streams as present or absent, the broker-observed\ntransfer-stream targets under a second exact-name grant in static mode, and the\nentries in the presence bucket. Both static connections must confirm their own\nidentity and the same broker server ID. A failed or incomplete native read refuses\ninstead of showing missing resources as absent. The snapshot is non-atomic and presence is not an\nauthoritative liveness or membership check. User-auth deletion inventory and apply\nare not supported. The dry-run removes no live or local state, and a read that covers its whole scope\nexits 0. A space that is prepared and not running, including one stopped with `cotal down`, has no\nlive broker to read or delete from: `space delete` refuses it with `TARGET_UNAVAILABLE`, exit 2.\nFor a stopped stack it names `cotal reset store <space> --yes`, which removes the stored streams, and\n`space forget`, which removes only its local record. For a space that never ran it names only\n`space forget`, because no streams were stored.\n\nThe dry run also reads the space's workflow runs from its records bucket and lists each run that\nhas not finished, with its recorded state. Deleting the streams removes those runs, and a run still\nwaiting for an answer cannot be answered or resumed afterwards. To read a run's open request first,\nrun `cotal up`, then `cotal run show <run>`. The typed confirmation says how many unfinished runs go\nwith the space, and the receipt lists the runs it removed.\n\nOn a space this machine started, the apply refuses `LIFECYCLE_BLOCKED` while the space's own stack\nstill runs here, before any broker connection. On a controller root that is any service instance of\nthe space other than its broker, and on a legacy root any recorded daemon other than the broker.\nDeleting the streams under them leaves a stack that can neither start nor stop. The refusal names\nthe running services and `cotal down`, then `cotal reset store <space> --yes` on a controller root or\n`cotal clean store --force` on a legacy one to remove the stored streams. When a service that is not\none of the stack's members runs, such as one added with `cotal service add`, `cotal down` refuses\ntoo, so the refusal first names `cotal service stop <name> --instance <id> --space <space> --yes`\nfor each one. With only the broker left after that, it names the delete again in place of\n`cotal down`. The dry-run opens with a verdict, as `reset store`'s does: the apply would refuse,\nwith that refusal's reason and remedy, or it is offered, with `cotal space delete <space> --yes` as\nNext. `--json` carries the refusal as `applyBlockedBy` and as a `LIFECYCLE_BLOCKED` warning.\n\nBare `space delete <name>` displays that impact on an interactive terminal and requires\ntyping the exact space name. Its question, and the message under a wrong answer, wrap to the\nterminal with each continuation line under the prompt's bar. A script supplies the exact\npositional name with `--yes`;\n`--json` without `--yes` refuses before a connection. After confirmation the command\nreads the broker again and refuses if the stream states, transfer names, presence\nidentities, registration, trust or broker identity changed. After a typed confirmation it also\nrefuses `CONFLICT`, exit 2, when a stream's message count differs from the impact it showed, as\nwhen a message is posted while the confirmation waits; the refusal names each changed count and\ndeletes nothing. It opens a separately\nminted exact-target connection, checks each stream state again before the first delete,\nand submits one deletion at a time. A native acknowledgement is reported as deleted;\nprior absence is separate, and the receipt lists only the groups that have a name. It then checks each target's stream info and enumerates\ntransfer names again. A surviving or newly observed resource produces a partial\noutcome if deletions were acknowledged; an incomplete post-read stays unknown.\nA refused or unconfirmed delete stops the sequence without retry, reporting\nacknowledged names and refused, unknown or remaining targets. This is not atomic:\na live process can recreate a stream after the final read. The result\ndoes not claim that processes stopped, or remove registrations, credentials or local\nfiles. Open-mode impact and receipt label that any broker client can delete the space.\n`space forget` is not a substitute for live deletion.\n\nCtrl-C or `SIGTERM` during the apply reports what the command left and exits 130 or 143; with\n`--json` the report is the envelope on stdout. Before any delete was submitted the outcome is\n`failed` and the streams are as they were. Once a delete was acknowledged it is `partial`, listing\nthe acknowledged streams and any delete still unconfirmed, with `cotal space delete <name> --dry-run`\nto see what remains. After every delete was acknowledged and before the check that nothing remains,\nit is `unknown`.\n\nThe receipt's Next names the route back. On a space this machine started, the broker keeps running\nwithout the streams. Until they are created again, `cotal history`, `cotal send` and `cotal run`\nrefuse `TARGET_UNAVAILABLE`, exit 2, on an auth or user space and on an open space this machine\nstarted: the streams are not on the broker, and the remedy names `cotal up` in the space's root.\n`cotal status` shows `Space streams: not on the broker` and puts the same step first in its Next.\nOn a controller stack whose other members are stopped, a bare `cotal up` in its root creates them\nagain, empty, on that broker, the way a broker start does, and starts the other members. Its receipt says\n`Space streams: created again, empty, on the running broker`, and `--json` sets\n`result.streamsRecreated`. It does so only when every one of the space's streams is gone. A broker\nthat lost only some of them keeps them as they are, and `up` refuses `TARGET_NOT_FOUND`, exit 2,\nnaming `cotal down --yes`, then `cotal up`, whose broker start creates the missing ones. While the\nother members run, as when another machine deleted an open space under the stack, a bare `cotal up`\nfinds the stack running and changes nothing. The refusals and `status` then say the space was\ndeleted under its running stack and name `cotal down --yes`, then `cotal up`. They say so only when\nthe broker answers that the chat stream is not found. A broker that answers no JetStream request\nhas not lost its streams: nats-server stops JetStream after a store write it could not make and\nkeeps the streams in its store. `send` and the other commands that read the space first then refuse\n`DEPENDENCY_UNAVAILABLE`, naming the write failure the broker's log records and its repair, and a\nbroker restart brings the history back. To drop\nthe space instead, run `cotal down --yes` in its root, then `space forget`. On a legacy root, run\n`cotal down`, then `cotal up` to start it again with empty streams.\n\n`use <space>` selects the space commands target when none is named, outside another space's\nproject folder. It changes only the selection:\nit starts nothing and does not check the broker, so a stopped or prepared space can be selected and\nthe command that targets it reports that. Selecting the selected space again is a `no-op`. A name that is not\nregistered or prepared is refused with `TARGET_NOT_FOUND`, exit 2, naming the known spaces, and the\nselection stays. That refusal has no `Mesh:` heading, and its remedy names `cotal use --clear` only\nwhile a space is selected. Bare `use` reports\nthe selection. Inside a project whose root has one registered space other than the selection, `use`\nalso says that commands in the folder target that space while it runs, and `--json` gives it as\n`folder`; `use` reads no broker, so it does not say whether that space runs. `cotal space` and\n`cotal status`, which read its broker, give `folder` and `meshes.folder` as null when it does not\nanswer, and the listing does not mark it as this folder's. A selection that names a space no longer registered is reported with a\n`SELECTION_NOT_REGISTERED` warning by `use` and `space`, and stays until `use` changes it.\n`use --clear` removes the selection, so a command targets its folder's space again. Its receipt\nnames the space that was selected, and `--json` gives `current: null` with that `previous`. With\nnothing selected it is a `no-op`, exit 0. It takes no space name.\nA space stays selected through its `cotal down`: `use` then reports it as stopped, with its last\noperation, such as `cotal down, completed`, and names `cotal up` in its root. It stays selected until\n`use` changes it. On a controller root, `up --json` gives\n`result.selection`, with `current`, the space selected after the start, and `changed`, which is true\nwhen this `up` selected its own space because no space was selected.\n\nA command run without `--space` targets the selection. When the selected space's broker is down it\nrefuses with `TARGET_UNAVAILABLE`, exit 2, and when the selection names a space no longer registered\nit refuses with `TARGET_NOT_FOUND`. Neither reaches another mesh.\n\nA folder's space captures the commands run there only while it runs. Inside a project whose root has\none registered space other than the selection, and whose broker answers, the command targets that\nspace, as it does with nothing selected, and the note under its `Mesh:` heading, a refusal's\nincluded, says the selection applies outside the folder. A space `cotal space create` prepared there\nand no `cotal up` started, and one whose stack there is stopped, is the target of the commands about\nthe folder itself only: `cotal up`, `cotal down`, `cotal space show` and the folder part of\n`cotal status`. Every other command run there targets the selection, and the note under its `Mesh:`\nheading says what this folder holds and the `cotal up` that starts it. A refusal whose own next step\nnames `cotal up`, as for a selection whose stack is stopped, leaves that `cotal up` out of the note,\nso it names one. `--json` carries the same note in\n`warnings` as `FOLDER_SPACE_NOT_RUNNING`, whose `target` names the folder's `space`, its `state`\n(`prepared` or `stopped`), its `root` and the `selected` space, and `cotal chat`, which prints no\nheading, names the selection it joins with that note on stderr. `cotal space show` there calls a\nregistered folder space whose broker does not answer this folder's stopped space. A folder that holds\nthe records of a stack that runs from another folder or under another Cotal home, as a copy, a moved\nfolder or one whose space name a later `up` took over, is not released to the selection: commands\nthere refuse `TARGET_UNAVAILABLE` and name that folder or home, as they do with nothing selected.\nFor a stack that runs from another folder, the refusal heads with `Mesh: <space>` and carries\n`target.space`, naming the space this folder's records name, as `up` and `down` there do.\nNor is a folder whose own stack's last `up` or `down` ended before it recorded an outcome while a\nmember still runs, as an `up` cut off before it registered the space leaves its broker. That stack\nruns in part, so every command there targets it, as with nothing selected, and the note under its\n`Mesh:` heading says the selection applies outside the folder. `cotal status` there reports it as\n`Half started` or `Half stopped` with its Next, which for that `up` is `cotal down --yes` and then\n`cotal up --root <root>`.\nInside a project whose root\nhas several running spaces, none of them the selection, the command targets the selection. Before it\nacts, it warns on stderr and in its JSON `warnings` with `SELECTION_NOT_FOLDER_SPACE`, naming those\nspaces and `--space <space>`.\nAn empty `--space` value, such as `--space \"$S\"` with `S` unset, is refused with `USAGE_INVALID`,\nexit 1, by every command that takes `--space`, before anything is read or written. It never falls\nback to the selection.\nA `--server` URL with a user name or password in it, such as `nats://<user>:<password>@<host>:<port>`,\nis refused with `USAGE_INVALID`, exit 1, by every command that takes `--server`, before anything is\nread, written or dialed. The refusal does not repeat the value. The NATS client uses only the host\nand port of that URL, so the command would otherwise connect with the credential it resolves. Pass\nthat credential with `--creds`, a login or the space's own trust instead. `up` refuses such a URL\nwith its own message. The manager reads `COTAL_SERVER` when `--server` is unset, and refuses a\n`COTAL_SERVER` with a user name or password the same way, naming `COTAL_SERVER`. `space add` and\n`meshes add` refuse such a server in a connection bundle, and the `meshes add` prompt refuses one\ntyped there, in the same words and with the same next step.\n\nA prepared space whose project folder was deleted cannot start. A command that targets it, `persona`\nincluded, refuses with `TARGET_UNAVAILABLE`, exit 2, saying the folder no longer exists and naming\n`cotal space forget <space>`. `cotal space create <space> --root <dir>` then prepares it again.\n`use`, `space show`, `up --space` and `space forget` also say the folder no longer exists, and\n`space show` names the same forget and create steps in place of `cotal up`. Run from the folder it\nwas moved to, whose stack journal names the old folder, such a command says the folder was moved and\nnames the next step that `up` and `down` name there.\n\nWith nothing selected, a command run in a project targets the space registered for that project's\nroot. A root with no registration whose `.cotal/` holds the stack journal `cotal up` writes is\nrefused with `TARGET_UNAVAILABLE`, exit 2, naming the broker the journal recorded and\n`cotal space create <space> --root <root>`, which prepares the journal's space there again so that\n`cotal up` starts it. That is the state after an `up` that failed to start or a `space forget`, and the\ncommand never dials the default address instead. `status` there reads the machine, selects no space\nand probes no broker. A legacy root after its `down`, which drops its registration, is refused the\nsame way, naming the space its account names, the address in the broker configuration its last start\nrendered and the bare `cotal up` there that starts it at that address again. A space named after its\nfolder keeps its registration, so a command there is refused `TARGET_UNAVAILABLE` naming that root\nand `cotal up`, and `status` names the same step. A selection does not change these refusals: a root\nthat holds such a journal or configuration and no registration, as a moved project does, is refused\nthe same way and never reaches the selected space.\n\nA copied or moved project folder keeps a stack journal that names the folder it was recorded in.\n`space create <name>` there refuses `CONFLICT`, exit 2, and names `cotal space create <name> --adopt\n--root <root>`, and `status` names the same command. `--adopt` takes the journal over: it rewrites it\nto name this folder, moves a broker store that lay under the recorded folder to the same place under\nthis one, and records a stack that never ran here, so the next `cotal up` starts it from this folder\nwith the copied store. It refuses with `CONFLICT`, writing nothing, when the folder's `.cotal` is a\nlink, when the recorded folder still holds the journal, because this folder is then a copy of a stack\nthat runs from there, when this machine records an instance of that stack, because the folder was\nthen moved on this machine and moves back, and when the space is prepared or registered on this\nmachine, where its Next names `cotal space forget` first. A copy brought from another machine and a\nfolder moved after its record was forgotten are adopted. A folder renamed from a path that `cotal up`\nrefuses, such as one with a tab in it, is told only to adopt, since moving it back would not start\nthe stack. Without `--adopt`, nothing rewrites a journal.\n\nWith no `--space`, `--server` or `--creds`, nothing selected and no project in the folder, a command\nthat several running spaces could serve asks which one when it runs in a terminal. The answer is the\ntarget of that one run, and the selection stays as it is; cancelling the question does nothing and\nexits 0. A script, a `--json` caller and `--no-input` are not asked: the command refuses with\n`TARGET_AMBIGUOUS`, exit 2, naming the running spaces. Every refusal of a target puts what to run next\nin `error.remedy`, apart from `error.message`, and names the space and root it concerns under\n`target` when they are known, as for a prepared space that is not started.\n\n`--creds <file>` presents that credential to `--server`. Without `--server`, it goes to the broker\nits space is registered at and keeps that registration's TLS requirement. The space is `--space`,\nelse the selected space, else the space of the project the command runs in, else `main`. Inside a\nproject whose root has one space other than the selection, it is that space. A command never picks the only running space for a credential. A space not registered here is refused with\n`TARGET_NOT_FOUND`, exit 2, before anything is dialed. The refusal says whether it is the selected\nspace, this folder's space or the default space, and a space other than the default is also under\n`target`. The default address is never assumed for a credential.\nThe file is read and checked with the NATS client's own parser before anything is dialed, and the\nrefusal names the file. A missing file is `TARGET_NOT_FOUND`, exit 2. A directory, an empty file and\na file the client cannot parse are `SCHEMA_INVALID`, exit 1, with the cause: no complete JWT block,\nno seed block, an invalid seed, or Windows (CRLF) line endings. A file in a folder that another\naccount can write, or below one, is refused `PERMISSION_DENIED`, exit 2, as `auth mint` refuses to\nwrite one there: the refusal names the folder, and its Next is one `chmod`, such as `chmod go-w <dir>`.\nA file that other accounts can read is still used, with a warning on stderr that gives the\n`chmod 600` command. `join --creds` reads its file the same way. A credential the broker rejects is `PERMISSION_DENIED`, exit 2. When this\nmachine holds the space's account and that account revokes the file's key, the refusal says when it\nwas revoked with `auth revoke-credential`. Its Next is a mint with a new key, which for an agent file\nnames the read and post channels and the role the file held. When the\ncredential's own permissions name one other space, the refusal names that space, such as\n`credentials rejected at <server>: <file> is a credential for space \"<owner>\", not \"<space>\"`, and\nits Next is `--space <owner>`. When `--server` named another broker than the one that space is\nregistered at here, the Next says to use `--space <owner>` without `--server`, naming that broker.\nKeeping `--server` with `--space <owner>` refuses again, saying the space is registered at another\naddress, and the Next is to drop `--server` or point it at that broker.\n\nA manifest's `space:` is the target of native `up -f` and the new team\ncheck/boot/deploy routes. A `--space` that names another space is refused before\nanything is written. The old `spawn -f` manifest spelling no longer deploys;\n`team deploy` applies all-new live channel-only manifests or bounded\nstatic/open agent and mixed manifests. Reapply remains unsupported.\n\n`cotal meshes` still lists, registers and drops meshes. `space`, `space add`, `space create` and\n`space forget` replace it, and its `--json` lines are the old shape. A bare `meshes add` on a terminal is still the\nguided form.\n\n## Mesh registry\n\n```bash\ncotal meshes [--json]\ncotal meshes add # guided, on a terminal\ncotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open|user] [--tls] [--allow-unencrypted-overlay] [--force]\ncotal meshes add <space> --mode user (--user-auth-file <bundle.json> | --from <https url>)\ncotal meshes rm <space> [<space> \u2026] [--force]\ncotal sync [--idp <auth base URL>] [--json|--plain]\ncotal status [--json|--plain] [--all] [-v|--verbose] [--space <s>] [--server <url>]\ncotal status --components [--space <s>] [--server <url>]\n```\n\n`cotal meshes` is a legacy command. It still runs, and `cotal help meshes` still prints its help,\nbut `cotal help` no longer lists it and completion does not offer it. `cotal space` lists the\nspaces, `space add <name>` registers one that runs elsewhere, including a user-auth space with\n`--file` or `--from`, and `space forget <name>` drops a registration.\n\nEveryday command forms use positional agent names: `cotal attach <name>` and `cotal stop <name>`.\nFor one-shot messages, choose one destination with `--to/-t`, `--channel/-c` or `--role/-r`:\n`cotal send -t reviewer \"Please inspect the patch\"`. Message receipts do not repeat the body.\nThey distinguish storage from delivery and say that no reply listener remains active.\n\nBare `cotal` shows a compact list of useful actions. `cotal help` shows the same list, then every\ncommand by group. `cotal help --all`, the spelling from before help listed every command, prints the\nsame. A hidden command, such as a deprecated daemon entry, is left out and still runs. `cotal cfg` is the shorthand for `cotal config`. Its default view\nshows effective values and sources; `config --verbose` adds the storage path and revision.\n\nStatus starts with This machine, lists the spaces this machine knows under Spaces, then shows the\nselected space, this folder's maintenance journal under Maintenance when it holds one (its state,\nattempt and next step; `maintenance` in JSON), unsettled fresh and additive attempts in this folder\nwhen present, owned service records and useful commands under Next. Connection state, local process\nstate and service replies remain separate observations.\n\nThis machine shows what is set up and what needs attention. The version line is always there. Agent\napplications is one line: the applications whose required executables are on PATH, and a count of the\nothers, which `cotal setup` adds. An application's own rows, such as the Claude plugin, Claude\nDesktop and Claude skills, appear for an application that is set up and only when they are not\ncurrent. The broker executable appears when it is missing or when `up` refuses it. The dashboard\nappears when its record cannot be read. The extensions are one line, `11 extensions at 0.103.3`, and\nan extension at another version is the only one named.\n\nSpaces puts the space this report is for first, with its connection state, then one line per space:\nits name, its connection state and how it signs in. Only the space being reported is dialed, so the\nothers carry no state and one note says that unselected spaces are not dialed and\n`cotal status --all` dials each. The broker address is\nnot a group title here; `cotal space show <name>` names it. An account whose space catalog could not\nbe refreshed is one line naming that account.\n\n`status --all` and `status --wide` report every row in full: one row per known application, every\nextension with its version, the spaces grouped under their broker addresses with their origin, and\nthe broker executable and dashboard rows. `--all` also dials every recorded broker; `--wide` dials\nno more than the default report. Use `status --verbose` for installation paths, extension records and\nthe detailed local diagnostics.\nThe default report does not include the agent roster; use `ps` or `who`.\nThe deployment-attempt section lists at most 100 fresh and 100 additive\nlocal IDs, their returned/unsettled state and unreadable paths, with full\nreadable coordinates in JSON.\nIt appears only when there is something to inspect. A missing fresh summary\ndoes not prove the original broker never started; an additive intent does not\nprove which resources changed. This read grants neither retry nor stop\nauthority. A fresh summary that names another folder, as one does after its folder was moved or\ncopied, is listed with its recorded path and is not counted as unreadable. If the folder inherits\nthe machine's home `.cotal`, `status --verbose` says that `up` there makes the folder its own project\nroot. When the home holds a stack an earlier release started there, it also says that commands in the\nfolder reach that stack until then.\n\nA prepared space whose controller stack ran and was stopped reads as stopped, with its last\noperation and how it ended, such as `cotal down, completed`, and Next names `cotal up` in its root.\nA space that never started still reads as prepared but not started. When the stopped stack recorded\na file store cap below 67108865, the least the space's streams start on, the selected space shows it\nin a `Store cap` row, and Next names `cotal up --max-file-store 67108865` or more instead, because a\nbare `up` would fail on that cap. `--json` carries the cap as `selected.storeCap`, and\n`meshes.currentStopped` says whether the selection is such a stopped stack. A stack whose last `up`\nor `down` has no recorded outcome is not stopped, even when a member it launched still runs: the\nselection reads as prepared but not started, and the selected space names that operation.\n\nOwned services use the saved controller definitions and instance records for the selected space.\nInstances are listed by definition name, then instance ID, in the text and in\n`result.services.instances`, so their order holds from one start to the next.\nFull instance IDs and execution hosts stay visible. Process observations and historical readiness\nare separate columns; neither a saved serving state nor a supervisor receipt becomes a fresh\nendpoint response or OS unit-state check. Legacy PID-file rows are labeled separately. On a root\nwhose controller stack serves the selected space, its members are the `stack-` rows here, the\nlegacy rows and the stored-state hint are omitted, and when the stack does not answer or a member's\nprocess is gone, Next offers `cotal up --root <root>` first, with `<root>` quoted when a shell would\nsplit it. On a stack whose last `up` was `--no-manager` it adds `--no-manager`, and a manager record\nan earlier `up` left does not count as a stopped member. When a service of the space outside the stack runs one of this root's records, such as a\n`delivery` preset started after `cotal down stack-delivery`, `up` refuses until it stops, so Next\nfirst names that service's `svc stop` with its instance, as the refusal does, and then\n`up --root`. In open or static-auth mode, an unreachable `--server` address other than the recorded\none says nothing about whether the stack answers. Next offers `up --root` only for a member whose\nprocess is gone, and names the failed address the way other commands' `TARGET_UNAVAILABLE` refusal does.\nWhen an earlier `down`, or a foreground\n`up` whose terminal closed, ended after it signalled the broker or the delivery daemon and before it\nsaw that process exit, `up` and `down` refuse until that stop is abandoned. `status` then says so\nunder the selected space, names the member, and offers `cotal down --abandon-stop <request-id> --yes`\nwith the request ID their refusal names, and the member's `service status`, in place of `up --root`.\n`--json` carries the same as `folder.stackBlock`. A manager stop is decided by the manager's own\nreceipts, which only `up` and `down` read, so `status` does not name it. An `up` or `down` that was\ninterrupted before it recorded an outcome, and left some members running and others stopped, is\nnamed under the selected space as `Half started` or `Half stopped`, with the members on each side.\nNext then offers the command that finishes it first: `cotal up --root <root>` for an `up`, with\nthe root shell-quoted when needed and `--no-manager` kept when it was chosen, or\n`cotal down --yes` for a `down`, followed by `cotal up --root <root>` to restart instead.\nThe restart root is also shell-quoted after an interrupted `down` or when recreating deleted\nspace streams. `--json` carries it as `folder.stackUnfinished`, with `kind`, `id`, `running` and `stopped`. An `up` cut off\nbefore it registered the space leaves the selected space prepared and not started, and `status`\nnames its stack's block or unfinished operation and the same Next under it. When that `up` left\nmembers running, the next `up` cannot finish it, so Next offers `cotal down --yes` and then\n`cotal up --root <root>`. With another space selected, `status` in that folder still reports this\nstack, under `This folder's space`, and so does every other command there while a member runs.\nWhile the stack is blocked, the `Delivery responder` row says that `up`\nrefuses until the block is cleared. The next bare\n`up` or `down` settles the interrupted operation with an `OPERATION_SETTLED` warning. When the\nstack reached that operation's end anyway, every member running after an `up` or stopped after a\n`down`, the warning says it was interrupted and where the stack stands, without calling it failed.\nA running `stack-manager` gets one\nprobe of its manager endpoint, and the selected space's `Manager` row reads `answering`,\n`not answering \xB7 stack-manager pid <pid>`, or `endpoint not checked` when the probe could not be\nmade or another manager instance answered. A manager that does not answer puts `kill -KILL <pid>`\nfirst in Next, because `cotal down` cannot stop it; once that process is gone, `cotal down --yes`\nrecords it stopped. A folder this account cannot read is one warning that names the folder and\n`chmod u+rx` on it, and nothing in it is read. Unreadable\nservice record families make the report partial instead of becoming empty lists. Warnings retain\na classified cause code, such as unsupported host observation, without native diagnostic bodies. These reads\nimport no provider and start, stop, adopt or repair nothing. `--all` extends broker checks; this\nlocal owned-service section retains its explicit selected-space scope.\n\n`meshes` lists the meshes this machine knows; `(default)` after a name marks the `current` default\na bare `cotal spawn` joins. A terminal too narrow for its columns gets one labeled record per mesh,\nas other tables do. Entries learned from a signed-in account are marked `discovered`. Their\nregistration trust is stored under the account's private auth state, and the registry contains no\nsession token or sentinel credential bytes. Commands resolve the catalog `slug`; a different human\n`name` is rendered only as a label.\n\n`meshes --json` prints one JSON object per recorded mesh per line: `space`, `server`, `mode`,\n`root`, `default` (the row marked `(default)`), and `origin` (`up`, `manual`, or `catalog` for a\ndiscovered entry). A local or hand-registered entry also carries `offline`. A discovered entry is never probed, so it has\nno `offline` field. `tlsRequired`, `events: \"required\"` and a discovered entry's `catalogName` appear\nonly when the record has them. An empty registry prints nothing and exits 0. The note about a default\nthat matches no record goes to stderr, so stdout carries only rows, on a first run too. The table is\npresentation and is not a stable parsing target. `meshes add` and `meshes rm` refuse `--json`.\n\nA registry record this build cannot use is refused by name, never rendered and never skipped. One\nthat does not parse, or is missing a field every consumer reads (`server`, `mode`, `root`, `ts`,\n`space`), makes every command that reads the registry refuse with `SCHEMA_INVALID`, exit 1, before\nit acts. The refusal names the file under `target` without repeating its stored values; `meshes`\nalso says what is wrong with it. `status` is the exception: it lists the readable records, names the\nfile and exits 4. Remove the file or restore the record; nothing repairs or invents a field for you.\nA registry directory that exists but cannot be read, such as one without read permission or a\nregular file in its place, refuses the same way, naming the directory, and `meshes` gives the read\nerror. Only an absent directory is an empty registry. The folder of prepared spaces,\n`~/.cotal/prepared`, refuses the same way when it exists and cannot be read, on every command that\nreads it, such as `space`, `space create`, `use <name>` and `up`. A file in its place is named as\n`<path> is a file, not a folder`, with the step that moves it aside. A record in it that does not parse\nor is not a prepared space record refuses only a command about the space its file is named for, such\nas `use <space>`, and names `cotal space forget <space>`. Other commands skip it with the warning `PREPARED_RECORD_SKIPPED`, which names the\nfile and `cotal space forget <space>`, the step that removes it, and `status` lists it under\n`meshes.unreadable` and exits 4. A record removed between listing and reading\nis omitted from that listing. A keyed read also treats a record removed during its read as absent;\nparse, shape and other read failures still refuse by name. Recording or removing a mesh also sweeps\nthe directory for an older-format record of the same space. Any error listing the directory for\nthat sweep, other than an absent directory, fails the operation with that error.\nAn unknown `--space` names the missing registration and points to `cotal space`, which also\nlists prepared spaces.\n\nAn IdP may advertise a same-origin space catalog during login. Cotal reads the complete snapshot and\nadds every valid registration without a separate `meshes add`. A snapshot younger than five seconds\nis used without a request. After that, commands that resolve a mesh target conditionally refresh the\nsaved catalogs. An operation targeting a discovered space refreshes only that space's account and\nrefuses if that account fails. Operations targeting local or manually registered meshes refresh every\naccount and continue. They warn once when an account's catalog starts failing and stay quiet while\nit keeps failing; the next failure after a successful refresh warns again. `cotal status` refreshes every account,\nnever refuses on a refresh failure, and lists each account as `fresh`, `updated`, `not-modified`,\n`no-catalog`, or `failed` with its error. With no cached sign-in, catalog preparation reads without\ncreating a control directory or lock. An explicit IdP without a cached login refuses with\n`AUTH_REQUIRED`. An unreadable sign-in or catalog cache refuses with `DEPENDENCY_UNAVAILABLE`\nbefore refresh or registry application. Both exit 2 and leave the cache untouched; diagnostics\nname the path without printing cache contents or parser excerpts.\n`cotal sync` bypasses freshness and reports added, changed, removed, unchanged, and name collisions. `--idp` limits it to one signed-in account. It never connects\nto a broker. `--json` gives each account under `accounts` with its `idp`, `state`, `catalog`, the\nfive name lists, `selectionCleared` and `error`. An account whose catalog could not be refreshed keeps\nits last snapshot and is named in a `CATALOG_UNAVAILABLE` warning, so the result is partial, exit 4;\nwhen no account could be refreshed, `sync` refuses with `DEPENDENCY_UNAVAILABLE`, exit 2.\n\nThe registry is updated under the same lock that guards the catalog cache, so a command never lists\na discovered space set that another command is still writing. The cache records a fetched snapshot\nas not yet applied before the first registry write and as applied after the last. If a command dies\nor is stopped in between, the next command applies that snapshot again before it can use it, with\nno request inside the freshness window.\n\nThe shared dispatcher applies this preparation to every command that declares both `--space` and\n`--server` as mesh-target flags, including commands registered by other packages and commands that\ndeclare their own equivalent flag objects. Daemon and startup commands that use those names only as\nconfiguration explicitly opt out. Registry-local `meshes add` and `meshes rm` never refresh a catalog.\nWhile the registry holds a record this build cannot use, the preparation neither refreshes nor\napplies a catalog, so the command's own checks run first. A snapshot left unapplied is applied by the\nnext preparation after the record is restored or removed. A command that resolves its target through\nthe registry still refuses the record by name.\n\nRun on a terminal with the space or `--server` missing, **`meshes add` is guided**: it asks for the\none thing that cannot be derived (the broker URL), probes it, and tells you what answered - open or\nrequiring credentials. It then offers the spaces your `--root` already holds credentials for, and\non an open broker the spaces that broker serves, states\nthe mode as a fact about that broker rather than asking, and shows the exact record before writing\nanything. A broker that does not answer, or a space name already registered, becomes a choice rather\nthan an error. For a name added by hand, the first choice is another name. Replacing a registered\nrecord then asks you to type `y`; Enter keeps No. Nothing is written before that last question, and\na cancel at any question says nothing was registered. A name prepared or started on this machine, or\nlisted by a catalog, is not offered for replacement. Anything you pass on the command line is taken as given and not asked again. Without\na terminal - a script, an agent, CI - nothing prompts and the flag form's errors stand\n(`COTAL_NO_PROMPT=1` forces that too).\n\n`cotal up` and `cotal down` maintain their own records. `meshes add` registers a mesh they cannot\nspeak for: one running on another machine, a shared broker, a hosted space. Like `space add`, it\nrefuses a space name `space create` refuses, exit 1, with nothing written, even with `--force`. A\nmesh an earlier release registered under such a name still lists, shows and removes as before. `--root` is the folder\nwhose `.cotal/auth` holds that mesh's credentials and whose `.cotal/agents` holds its personas.\nThe default is the project you run it in. The registry stores that path, never a secret. `--mode`\ndefaults to `auth` when the root holds the space's account record and to `open` otherwise. The\nbroker is probed before anything is recorded, so a wrong address, or credentials that mesh will\nnot accept, fails here instead of at the first `spawn`; `--force` records without verifying (and\nreplaces a record added by hand). A name that is prepared on this machine, or that this machine\nstarted or a catalog lists, is refused with `CONFLICT` (exit 2), with or without `--force`, as\n`space add` refuses it: `cotal down` finds a running stack through that record.\n\nA hostname or public address is registrable only when the connection will **require TLS**. Pass\n`--tls`, or use a `tls://` URL. Without either, the refusal's next step is the `tls://` form of the\nsame address, or `wss://` for a `ws://` one. The scheme is recorded as enforced intent, so every later dial\nthrough the record demands the handshake (and `meshes add tls://\u2026` against a plaintext broker is\nrefused at registration). Without required TLS the fence admits loopback literals, and\nprivate-overlay literals only with `--allow-unencrypted-overlay`, which records your acceptance that\nthe overlay is protected only while its tunnel is up ([Run a mesh](run-a-mesh.md#which-addresses-you-may-register)). RFC1918 addresses are refused in both modes because a cafe LAN is private but does not belong to you.\n\nA **user-auth** mesh registers from supplied pinned trust, never guessed: `--user-auth-file`\ntakes the bundle exported where the mesh runs; `--from` asks before it dials the address at all,\nthen fetches the `/.well-known/cotal-mesh` discovery document under that address (HTTPS only; a URL\nthat already ends in that path is fetched as given), displays the pins, and asks again before\nadopting them. The document must parse as JSON under any content type the host serves it with; an\nanswer that does not is refused with the URL and the content type that came back. Neither fetch follows redirects: a 302 can move a pinned fetch\nonto plaintext or onto another host, so it is refused rather than followed, and the pinned\nexchange must itself be an `https://` URL, except for an exchange on this machine, where plain\n`http://` is accepted for a loopback *literal* (`127.0.0.1`, `::1`, any spelling of them) but not\nfor `localhost`, which is a name rather than an address. Registration verifies that the exchange\nanswers `/health` and `/jwks` as the pinned issuer. It also verifies that the broker refuses a bare\nconnect; that auth-required refusal is the pass. The sentinel credentials land in a 0600 file under\nthe entry's root; the registry records only the path.\n\n`meshes rm` drops records. It never stops a mesh. For a mesh running on this machine `cotal down`\nis the right verb, and `rm` refuses and says so, with or without `--force`, because `cotal down`\nfinds a running mesh by its record. A stack `cotal up` started stays a prepared space at its root\nafter that `down`, which `meshes rm` does not list or remove, so the refusal names `cotal space\nforget <space>` for it. `--force` drops a record only when its folder cannot be checked\nfor a running mesh. A broker record that cannot be proven\nstopped counts as running, as it does for `clean`. A hand-added record is removed by\n`meshes rm`, by an `add --force` replacement, or by a `cotal up` that actually starts the broker for that same space, server and root, which becomes that\nmesh and so takes the record over (a `cotal up` for that space anywhere else refuses instead).\nNothing that merely *infers* a record is stale from a dead broker touches it: an\nunreachable broker is listed `offline` and stays, whether `cotal up` or `cotal meshes add`\nwrote the record; a foreground `up` whose broker exits unexpectedly keeps its record the same way. A broker that answers at a record's address and refuses the connection is read the same way\nwhile another registered space names that address: the broker there is that space's, so the record\nis kept and the refusal is `CONFLICT`, exit 2, naming the space that holds the address, the\n`cotal space forget <space>` that frees it and the `cotal up --server <url>` that moves this one.\n`cotal status` shows that space as `address held`. Deleting the record would leave its folder\nnaming no space, after which nothing there says which space its commands meant.\nA bare command does not treat that offline record as a running mesh;\nname it with `--space` to restart it. An operator command whose target is the selected space, or\nthe space registered at its project's root, probes no other recorded broker. When it picks among\nthe running meshes instead, it probes the recorded brokers once, and its mode check and its\nconnect both read that one result. Managed-seat control resolution and commands that inspect all\nspaces keep their existing probe behavior. `cotal down` / `cotal clean all` still drop an `up` record for the project\nthey tear down; a hand-added one they leave alone even when it shares a root, because nothing\non this machine could write it back.\n\nA discovered entry belongs to the normalized IdP origin and proved subject that supplied it. Local\nteardown, cleanup, and liveness pruning do not remove it. A manual or locally started entry with the\nsame name wins and remains untouched; that discovered name is reported as a collision. Logging out\nremoves only the discovered entries owned by that account.\n\n`cotal meshes` and `cotal status` print `events: required` for a registration carrying\n`policy: { events: \"required\" }`. On that space, foreground spawn, detached spawn and manager\nstarts that host a native harness cannot opt out or launch without an event plane. `--no-events` is refused with\nthe space named. A connector without an event plane is refused with both the space and connector\nnamed. A session whose own grant omits `events.<owner>.<actor>` is refused before joining and the\nmessage names a full-row `actor grant` repair. A running seat whose event plane stops keeps running\nand keeps its mesh connection on that space as on any other; the connector re-establishes the plane\nitself and reports every failure with the delay before the next attempt. A seat the manager resumes\nfrom a preserved cut meets the same rules as a fresh\nspawn: a seat retained with `--no-events`, or on a connector without an event plane, is refused, and\na resumed seat carries the requirement in its launch material.\n\n`join`, one-shot sends and passive protocol views host no native harness turns. They publish no\nAG-UI runs, regardless of who drives their input. Required events do not turn terminal messages\ninto agent runs or copy channel and DM text into an event stream. This launch policy is not an\nadditional broker authorization boundary. The broker still enforces each endpoint's grants.\n\nThe selection `cotal use` sets applies from every directory except the project folder of a running\nspace, where the command targets that folder's space. There `status` reports that space under its\n`Mesh:` heading and a `This folder's space` section, and says the selected space applies outside the\nfolder. `--json` names it in `target.space` and in `meshes.folder`, which `cotal space --json` gives\nas `folder`. In the folder of a space that is prepared or stopped, `status` reports the selection\ninstead, and the line under its heading names what the folder holds and the `cotal up` that starts\nit; `meshes.folder` is then `null`. A folder whose stack is half started or half stopped, with a\nmember still running, counts as a running space's folder here. In a copy or a moved folder, where the commands refuse under the\nspace the folder's records name, `status` reports that space under its `Mesh:` heading, marks it in the\nSpaces list when it is registered, and says what the folder holds in a `This folder` section, with the step those commands\nname under Next. `--json` names that space in `target.space` and `selected.space`, and the recorded\nfolder in `selected.recordedRoot`. `status` is a read-only report in this order: this machine\n(the installed `cotal-ai` version, NATS and each named integration), the installed extensions and\ntheir versions, this folder's `.cotal/`, the recorded meshes, the selected mesh (its connection, the\ndelivery responder, its channels and membership feed), then the hints. It lists no roster; `cotal ps`\nlists managed seats. Stale Claude skills and out-of-date `.agents` skills recommend\n`cotal setup --skills`, not unscoped `cotal setup`. `status` takes `--space` / `--server` to pick the\nmesh to inspect; it starts nothing.\n\nStatus displays broker addresses without URL user information, query parameters or fragments,\nincluding component facts. Saved addresses and connection inputs are unchanged. An address that\ncannot be displayed safely is `null` in JSON, `unavailable` in text, and names a\n`BROKER_ADDRESS_INVALID` warning with a partial result. Its broker component is refused.\n\nOnly the selected mesh is checked. Its row under Spaces dials its address with its recorded TLS\nrequirement, and every other row carries no connection state, under the note that says what was\ndialed. When `--server` names another address, that address is the only broker dialed, and the\nselected mesh's row carries no state either. In `--json` each of those rows is `not-checked`. `--all` checks every recorded mesh, including discovered ones, each with its own\nrecorded TLS requirement. Each row then shows the check of its own address, the selected mesh's\nrow included when `--server` names another.\n\n`status --json` writes one envelope whose `target` is `{space, scope}`, where `space` is the selected\nspace or `null` and `scope` is `selected` or `all`. Its `result` is the report the text draws:\n\n| Field | Holds |\n|---|---|\n| `kind` | `infrastructure` |\n| `machine` | `version`, `install` (`kind` and `root`), `nats` (`path`, `bundled` or `missing`), `integrations` (each `{name, state, text}` a connector's setup reports), `connectors` (each `{name, requires, missing}`), `skills` (`state`: `current`, `not-installed`, `behind` or `unreadable`), and `web` (`installed`, `url` and the error of each when it could not be read) |\n| `extensions` | `root` and `items`, each `{package, label, version}` |\n| `folder` | `root`, `auth` (`state`: `space`, `spaces`, `none` or `unreadable`), `personas` (`root`, `default`: `valid`, `invalid`, `missing` or `unreadable`, `demo` and `error`), `launches` (whether a bare spawn launches from this folder's catalog; `null` when no mesh resolves), `processes` (each `{name, state, pid, detail}`, `state` one of `running`, `stopped`, `not-serving`, `unknown` or `unreadable`), `storedState`, `noManager` (whether this root's controller stack was started with `--no-manager`) and `managerService` (the service of the space, outside that stack, whose process runs this root's manager, as a `manager` preset started there does: its `definition` and `instanceId`, or null), `heldServices` (the services of the space, outside that stack, whose processes run this root's records, as `up` names them before it refuses: each `definition`, `instanceId`, `record` and `pid`; null when those records could not be read) |\n| `meshes` | `current`, `folder` (this folder's running registered space when a command there targets it, else null), `unreadable` (the registry directory, a record, the current-mesh pointer, or the prepared spaces folder or a record in it, that could not be read, or a pointer that is empty, each as `{file, error}`), `items` (each with `space`, `server`, `mode`, `root`, `origin`, `tlsRequired`, `eventsRequired`, `current` and `check`: `reachable`, `unreachable` or `not-checked`) and `catalogs` (each `{idp, account, state, error}`, where `account` is the IdP subject the error line names) |\n| `selected` | `{resolved: false, code, message}` when no mesh resolves, where `message` states why and `hints` carries the step (`code` is `registry-unreadable` when the registry directory or a record could not be read, and a `not-started` prepared space adds its `space` and `root`); otherwise the target's fields, `connection` (`ok` or the reason it failed; on a user-auth mesh `reachable` or `unreachable`), `login`, `deliveryResponder` (`bound`, `unbound`, `stale` or `unknown`, and `null` on an open mesh with no delivery lease), `snapshot`, `channels`, `membershipFeed` and `spaceStreams`. `spaceStreams.state` is `present`, `absent` or `not-checked`; it is read for an auth or user space and an open space this machine started. When `absent`, `root` is this machine's stack of the space and `stack` is `running` or `stopped`, each `null` when unknown. `storedBytes` is what the space's account holds in the broker's JetStream file store, `null` when it was not read |\n| `services` | Selected-space local owned records: `space`, `state` (`read`, `partial` or `not-checked`), non-atomic `observedAt`, public definition/instance projections and explicit scope. An unreadable family is `null`; stored readiness is historical. Older serialized snapshots may omit this observation, which is not an empty inventory. |\n| `stackManager` | The selected space's running stack manager: `instanceId`, `pid` and `endpoint`, which is `answered`, `not-answering` or `unchecked`. Absent when no stack manager runs |\n| `store` | What the selected space's running local broker logged about its JetStream store: `state` `write-failed` with `pid`, `log`, `at`, `cause`, `path` and `jetStreamDisabled`, true when the broker then disabled JetStream; `full` with `used`, `cap` and `path` when the space holds more than its broker's recorded cap; `damaged` with `pid`, `log`, `at` and the broker's `warning` when it logged damage in a stored file; `none-recorded` with `pid` and `log`; or `not-checked` with a `reason`. Absent when no space resolves |\n| `hints` | The hints, in the order the report prints them |\n\n`store` covers only a broker whose log is on this machine: a controller stack's `stack-broker`\ninstance, or the broker a legacy root records in `.cotal/nats.pid` with its log `.cotal/nats.log`.\nnats-server reports a stream's write error natively only through `healthz`, which needs a\nsystem-account user, so `status` reads a bounded tail of that log instead and keeps only lines tagged\nwith the running broker's process ID since its last start. When the broker logged a store write\nfailure, such as a full disk, the selected space shows a `Store` row with its time, cause and file.\nThe broker keeps that error until it restarts, so messages sent meanwhile are not stored. The first\nNext is `cotal down`, to run once the logged cause is fixed: write access to the store again for a\npermission error, or free space for a full disk. Then `cotal up` starts the broker again. When the\nbroker also disabled JetStream, which `store.jetStreamDisabled` shows, the manager's stop cannot be\nrecorded, so the Next starts with `kill -TERM <pid>` for the stack's manager on this host, then\n`cotal down --yes` and `cotal up`.\n\nWhen the broker logged damage in a stored file since it started, the `Store` row reads\n`damage logged` with its time and the broker's own words. The recognized warnings cover a message\nblock whose records do not parse (`indexCacheBuf corrupt`), which the broker rebuilds without the\nmessages from the damage on; a stream whose recovery rolled back past what a consumer had delivered;\nand a stream metafile that fails its checksum, which the broker does not load. It keeps serving what\nit can still read, so `history` holds only that. These warnings do not necessarily populate the\n`STREAM.INFO` lost-data field. For a controller stack the Next is `cotal service logs stack-broker`.\n`status` repairs nothing.\n\nWhen the space holds more bytes than the `--max-file-store` cap this machine recorded for its\nbroker, the broker refuses every write, so the `Store` row reads `full` with the bytes stored, the\ncap and the store's path. The first Next is `cotal history clear --space <space> --yes`, which makes\nroom by purging the space's channel history, followed by `cotal up --root <dir>` when a member of the\nstack stopped. To keep that history, the Next goes on as `up` does: `kill -TERM <pid>` for the stack's\nmanager on this host, as a full store refuses its stop, then `cotal down --yes` and\n`cotal up --max-file-store <bytes>` with a larger cap. From another folder, the Next names the stack's\nfolder to run `cotal down --yes` in, and `up` carries `--root <dir>`. The `Delivery responder` row\nthen says that the full\nstore refuses the daemon's lease, so `up` starts the daemon only once there is room. The bytes are\nthe space account's own, as\n`$JS.API.INFO` reports them; the cap counts every account on the broker. A broker started without\na cap sizes its store from the free disk, and `status` does not compare that.\n\n`deliveryResponder` is `stale` when the ready row in the delivery lease belongs to a daemon that is\ngone. That includes this folder's own daemon once its recorded process has exited without releasing\nthe row, as after a kill. The row expires on its own within 30 seconds. The delivery responder line\nthen says the delivery daemon died and its lease has not expired yet, that spawn, retirement and the\ndurable backstop wait until a new daemon binds while joins, DMs and live posts can still work, and that\n`cotal up` restarts it, since `up` waits out the lease first.\n\nWith no delivery daemon serving, as after `cotal down stack-delivery`, `deliveryResponder` is\n`unbound`. The delivery responder line says spawn, retirement and the durable backstop wait until a\ndaemon binds, while joins, DMs and live posts can still work, and that `cotal up` starts it.\n\nWhen a controller stack's delivery member runs but holds no delivery lease, `deliveryResponder` is\n`unbound`, and the delivery responder line names the member's pid and says it is starting or not\nresponding. `status` cannot ask the daemon which. Next names `cotal service status stack-delivery`,\nthen `cotal down --yes`, which kills a member that does not exit, and `cotal up`.\n\nAn unreachable broker, a stopped process or an unbound responder is a state the report names, and\n`status` exits 0. An open mesh runs no delivery daemon, so when it records no delivery lease its\ndelivery responder row says the responder is not used and suggests no command. A controller stack\nstarted with `--no-manager` gets a Manager row saying it runs none by choice and that bare `cotal up`\nadds it. A part it cannot read is named on its row and as a warning, and the run is\n`partial` with exit 4: a PID record, the folder's trust material, the delivery-aware marker, the\nconnectors, the skills bundle, the web records, a persona catalog, the mesh\nregistry or one of its records, the current-mesh pointer, a user-auth login, or the selected mesh's\nchannels or its owned service definition/instance records. Only an absent catalog, registry or pointer reads as empty or unset; an empty pointer is\nnamed too. A registry directory or record that cannot be read also leaves the selected mesh\nunresolved, because resolution refuses it, and the readable records are still listed. A corrupt or\nunreadable extensions manifest is refused while loading the command surface, before this report\ncan run. The manager\nrow asks the service endpoint once: a live process that does\nnot answer is `not serving`, and a probe that could not be made leaves the row\n`running \xB7 service unchecked`. A broker that takes the connection and does not answer, as a\nstopped one does, leaves it unchecked too, and `status` still exits.\n\nThe local auth reader uses a stripped signer only when both split trust records are absent.\nThe load refuses dangling and looping links at either record even when an older stripped signer is present.\nA process row whose PID record exists but cannot be read, or exists under both its current and a\npre-upgrade name, reads `pidfile unreadable` with the error, and the other rows still print. A live\nmanager whose delivery-aware marker cannot be read keeps its row and names the failure as\n`delivery-aware marker unreadable` with the error. The `Web process` row\nprints the address the selected mesh's dashboard recorded once it was listening, while the PID in\nits `web.pid` is alive. The dashboard that holds `web.pid` picks the record. A dashboard started with\n`cotal service start` writes its session record beside its service startup receipt, and status finds\nthat record through the web service instance whose recorded process is that PID. Any other holder is\nread from `web.session`. Neither record is read in place of the other. A `web.pid`, session record or\nservice instance record that exists but cannot be read is named on the row with the error.\nOtherwise it reads `down`, or `not installed` without the web\nextension. Status and the `cotal setup` card count the web extension as installed only when a\npackage in the extensions manifest provides `web` and that package is on disk. A web package record\nthat cannot be read, is not JSON or has no `version` is named on the `Web extension` row and on the\ncard's web row, after the dashboard's address when it is listening and beside an unreadable\n`web.pid` or session record. The `Web process` row still prints, and reads `down` unless the\ndashboard is listening.\n\nIf a refresh fails, `status` may still show the kept catalog bytes for diagnosis. It labels them\nstale with the last successful snapshot timestamp and the refresh error. It never calls that state\nsynchronized or online. If a selected discovered space vanishes from a successful snapshot, the\nselection is cleared and the command reports that no default is selected.\n\nFor a user-auth mesh the selected-mesh section reports the login `status` works as: the signed-in\nsubject when this machine holds a cached session for the entry's pinned IdP, or the exact `cotal\nlogin --idp <url>` line when it does not, with no network round trip either way. A locally\nprovisioned space also shows the actor grant row; a discovered or registered remote entry reports\nthe grant as not checkable on this machine, because the ledger runs where the space was\nprovisioned. `--components` on a user-mode target probes as that same signed-in login (`ps`'s\ncredential), never a static mint; when the login cannot supply a credential, the row says why\ninstead of printing the broker's refusal of an unauthenticated probe.\n\nPersona rows name the catalog they describe. If this folder and the selected mesh use different\ncatalogs, status names both and marks which one spawn launches from. A green `default` means the file\npasses the same agent-file loader spawn uses; a present but invalid file is reported as invalid.\n\n`cotal status --components` prints the report, then a fail-loud per-component health pass. It takes\nno `--json`, `--plain` or `--all`. It reads **each\ncomponent's own control surface**, rather than treating a PID, a lease, or a successful probe of a\nsibling as proof that the component serves. It prints one of `serving`, `absent`, `not-serving`, or\n`refused` for each component and exits `0`, `1`, `2`, or `3` respectively (the highest observed\nstate wins). An optional component that is not installed prints `not-installed`, which exits `0`.\nThat exit replaces the report's own, so a partial report followed by a clean pass exits 0:\n\n- **manager**: local PID record, its liveness-lease holder and PID, then the manager's own typed\n `status` service reachability from this host. A manager that answers without reporting static\n reconciliation says `static reconciliation not reported by this manager build`; the line stays\n visible even when the manager is otherwise `serving`. A row that reached no manager does not\n carry it.\n- **delivery**: local PID record, its ready lease (`ready` is the daemon's own bound-control\n signal), and the latest `renewal.<spaceKey>.json` adoption verdict, the record of the space the\n command was asked about, keyed per space the way the pidfiles are. A re-signed credential and a\n broker-accepted adoption stay distinct facts. A root-only `renewal.json` left by an older build\n names no space and is never read as any space's verdict (`doctor auth` names it as a leftover).\n- **web**: `not-installed` when the dashboard extension is not installed and no PID record exists.\n Otherwise, the local PID record, then the `/api/meta` response at the address the dashboard\n recorded once it was listening, which must name the same PID. The holder of `web.pid` picks the record, as\n on the `Web process` row: a service-run dashboard's own session record, found through its web\n service instance and named on the row as `service instance <id>`, or `web.session` for any other\n holder. The probe presents the readiness nonce recorded beside that address, the one credential\n the dashboard accepts on `/api/meta`. The probe follows no redirect. An answer with a status\n outside 2xx, a 3xx included, is `not-serving`, and the row names that status, such as `HTTP 401`.\n A live PID with no recorded address (the dashboard is still writing it, or an earlier build\n started it), or an unrecognizable process record, is `refused`, not a green default-port guess. A\n session record or service instance record that exists but cannot be read is also `refused`, and\n the row names the read error.\n- **broker**: the registered mesh URL dialed from this host with its recorded TLS requirement.\n\n`absent` means Cotal has no live local component record (or has a stale record); `not-serving`\nmeans the component record is live but its service/readiness surface did not answer or is not ready.\nThose are intentionally separate exit cases. A failed or unreadable probe is `refused`, never an\nabsent component or a clean zero. A broker that takes the connection and does not answer, as a\nstopped one does, refuses the manager and delivery probes, and the pass still exits. A PID record that exists but cannot be read, or exists under both\nits current and a pre-upgrade name, refuses only its own row. A record that its component removes\nwhile the pass runs reads as `absent`.\n\nEach component prints on one line. A control character inside a reported reason, such as a newline\nin a stored row's field name, prints as its `\\uXXXX` escape.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--file <persona-file>] [-d | --foreground] [--dry-run] [options]\n```\n\nA bare `spawn` runs the application in this terminal, on every kind of mesh, and no manager is\ninvolved unless you pick one when it asks where to run, as described below. Its exit, or the terminal's, may end the agent. A manager runs the launch only when you\nask for one. `--detach` (`-d`) hands it to a manager, waits until the agent joins, and returns\nwithout attaching. `--on <instance>` hands it to that manager and attaches this terminal to the\nagent (see [Managed launch view](#managed-launch-view)), when that manager's runtime streams a\nterminal. The built-in `pty` runtime streams one. The `tmux`, `cmux`, `orca` and `herdr` runtimes\nstart the agent in a surface they do not own and stream none, so a launch on one of those managers\nis not attached: it returns once the agent joins, with a receipt that names the runtime and what\nreaches the agent instead. Attaching needs usable input and output\nterminals and refuses `--json` and `--plain`, before an identity is allocated. A spawn in this\nterminal writes no structured receipt, so `--json` and `--plain` need `-d`, except with\n`--dry-run`. `--foreground` names the default and is kept for scripts. A spawn in this terminal\nchecks its persona and connector before it starts the application: a missing persona is refused\nwith `TARGET_NOT_FOUND` and a missing connector with `DEPENDENCY_UNAVAILABLE`, both with exit 2,\nand the connector refusal names the `cotal ext add` that installs it. `--file` (`-f`) names a\npersona file, never a manifest: manifests deploy with `cotal team deploy <file>`, and a manifest\npassed to `--file` is refused with that command.\n\nOn a terminal, a bare `spawn` on a user-auth mesh where several of your managers are live asks\nwhere the agent runs before it does anything else. The managers listed are the ones the exchange\ncounts for a manager selection, so the question comes up only when `spawn -d` would ask which\nmanager to use. Each manager's row gives its instance id, the host it reports, the runtime it\nspawns through and whether a terminal attaches to its seats (`pty \xB7 terminal attach`, or\n`tmux \xB7 no terminal attach`), so the choice is made before anything is launched. A manager whose\npresence does not carry it reads as `tmux \xB7 terminal attach not reported`.\n`This terminal` is the default and runs it as above. Picking a manager launches the\nagent there and attaches this terminal, as `--on` does, and a `Manager chosen` receipt names the\n`--on` that skips the question. No manager credential is held while you answer. `--foreground`,\n`--file` and `--live-only` keep the spawn in this terminal without asking, and so does a command\nthat cannot ask: no terminal on stdin and stdout, `--no-input`, or `COTAL_NO_PROMPT=1`. Cancelling\nthe question does nothing and exits 0.\n\nA managed launch on a terminal, picked at a question or named with `--on`, asks which folder the\nagent works in when you give no `-C` and the manager is not known to run on this host. The host is\nthe one that manager reports, the name `cotal ps` prints as its reported host, so a manager that\nreports this machine's host name is never asked about and one that reports no host is. The mesh and\nthe pin are read before anything is asked, so a stopped mesh or an `--on` that names no running\nmanager is refused first and no question comes up. Enter keeps `Manager's workspace (default)`: no\nfolder is sent, and the manager uses its workspace root, as a launch without `-C` does. A folder you\ntype is sent as typed to that manager, which resolves it on its own host with `resolve-cwd` before\nanything is launched. This terminal does not read or rewrite the path, so a Windows manager takes a\ndrive or UNC path from any client. A folder the manager cannot use is shown with its reason and\nasked again, with your text left to edit. A check that could not be made at all refuses the launch\nand names the command again with the `-C` that sends the folder with it and asks nothing. The\nquestion holds no manager credential: the folder is checked under a view of that manager acquired\nafter you answer, and the launch acquires its own. A\n`Folder chosen` receipt names the folder and the host, and for a typed folder it gives the command\nwith the `--on` and `-C` that skip the questions. Cancelling does nothing and exits 0. `spawn -d`\nwith no `--on`, which the exchange gives to your only live manager, asks about that manager too:\nthe CLI reads which manager the exchange picked, keeps none of that view, and pins the launch to\nthat manager. If it has stopped by the time you answer, the launch is refused and goes to no other\nmanager. A command that cannot ask, or one with `--json`, does not ask.\n\nOn a user-auth mesh that runs elsewhere, a spawn in this terminal provisions its agent with your\nlogin, through the agent-provisioning endpoint the mesh advertises. A mesh that advertises none is\nrefused in one line. The line names the remedies: ask the mesh operator to advertise one, run the\nagent where the mesh runs, or add `-d` to launch it through one of your managers. A one-time\nenrollment, described below, also stands in for the endpoint. An answer from the endpoint other\nthan a 401, which names `cotal login`, or a held name, described next, says whether the agent was\nprovisioned is unknown (`OPERATION_UNKNOWN`, exit 3).\n\nA spawn whose name is already taken checks what holds it. When a live manager of yours runs that\nagent, or presence or the mesh reports it online, the spawn launches nothing, says where it runs,\nand exits 0 with the outcome `no-op`. In a terminal it then attaches to that agent. With `-d`,\n`--json`, or no terminal it prints the `cotal attach <name> --on <manager>` command instead. When\nthe mesh answers that the name is held by a lifecycle that does not run\n([Held agent names](identity-and-auth.md#held-agent-names)), the spawn releases that one lifecycle\nby its UID with your login and asks for the name again, for a managed launch and a spawn in this\nterminal alike. Nothing is adopted: the agent starts under a new lifecycle. While the mesh says the\nname is still retiring, the spawn asks again every 5 seconds for up to 2 minutes, then refuses\n`LIFECYCLE_BLOCKED`. A release the mesh cannot confirm is `OPERATION_UNKNOWN` and is not repeated.\nA lifecycle that a live manager of yours holds without reporting it running is not released: it may\nstill be starting there, so the spawn refuses `CONFLICT` and names that manager. Any other held-name\nrefusal stands as the manager or the mesh gave it, as `CONFLICT`.\n\nOn a user-auth mesh, an ordinary spawn in this terminal without `--name` allocates the first free actor\nin the persona's series: `default_agent`, `default_agent_2`, `default_agent_3`, and so on. Held\nlocal material, existing grants and a name whose agent is online are occupied; local material and\ngrants count even when no presence is visible. A name held only by a lifecycle that does not run is\nreleased and reused, as above. Allocation preserves the existing session and its credentials. An explicit `--name`, a\nsigned enrollment, and a managed handoff pin an identity and never rename it. Authorization,\nnetwork and malformed-provisioning failures are not reasons to try another name.\n\nFor a foreground spawn onto a remote user-auth mesh, a one-time enrollment may stand in for a cached\nhuman login. Prefer a private file:\n\n```bash\ncotal spawn --foreground --file ./seat.md --space main \\\n --enrollment-file /run/secrets/cotal-enrollment\n```\n\nThe file contains only the enrollment URL, ending with at most one line terminator, and must be\nmode `0600` on POSIX. A launcher may name the same file in `COTAL_ENROLLMENT_FILE` instead, and run\n`cotal spawn --config ./seat.md --space main`. An orchestrator that cannot mount a file may set\n`COTAL_ENROLLMENT_URL`; that value is redeemed byte for byte, so a trailing newline in it is refused.\nNaming more than one source is refused, and so is `--enrollment-file` with `--detach`, `--creds` or\n`--on`. Enrollment input requires `--space` and applies only to a foreground persona spawn. If the\nmesh is not registered yet, the enrollment response must carry the stock user-bundle fields and the\ncommand needs a persona file (`--file`, or `--config` from a launcher) because there is no local\nremote-mesh persona catalog to read. The client\nredeems the URL once, registers the returned mesh material, exchanges the returned actor token at the\npinned auth service, and removes both enrollment variables before starting any child process. A\nregistered mesh whose record pins no exchange URL is refused before the URL is redeemed, so the\nenrollment stays unspent; re-register the mesh with `cotal meshes add <space> --from <url> --mode user`.\n\nA cached login for the same IdP and an enrollment are conflicting proofs, so the command refuses\nrather than choosing one. An invalid enrollment never falls back to login provisioning. Unknown,\nexpired, revoked, and already-used enrollments all produce one response: ask the owner for a fresh\none. See [Enrollment redeem](identity-and-auth.md#enrollment-redeem) for the HTTP contract.\n\nA runtime that starts a managed seat outside the manager's filesystem hands the child a managed\nhandoff instead: one `0600` file named by `COTAL_MANAGED_HANDOFF_FILE`, carrying the lifecycle the\nmanager already enrolled, alongside `COTAL_MANAGER_INSTANCE` naming the launching manager. The runtime builds the command with `delegatedSeatCommand`:\n\n```bash\nCOTAL_MANAGED_HANDOFF_FILE=/run/seat/handoff.json COTAL_MANAGER_INSTANCE=<instance-id> \\\n cotal spawn --config ./seat.md --space main --name <actor> --agent claude \\\n --expect-owner <owner> --expect-lifecycle-uid <uid>\n```\n\n`cotal spawn` reads the file, deletes it and drops the variable before it parses flags, prints\nhelp or loads extensions, so every outcome leaves no file. Every other command drops the variable\nwithout reading the file and leaves the file in place. The variable is read under any letter case;\nspellings that name different files are refused after every one of them was deleted. A file spawn\ncannot take, such as a missing one, is refused `USAGE_INVALID`, naming the variable and the path,\nwith `unset COTAL_MANAGED_HANDOFF_FILE` as Next.\n\n`DelegatedSeatLaunch.managerInstanceId` is required. A newer CLI child under an older manager or\nruntime that omits it refuses every delegated launch, so managers and the CLI must be upgraded\ntogether. The spawn refuses an absent `COTAL_MANAGER_INSTANCE`\n(`COTAL_MANAGED_HANDOFF_FILE requires COTAL_MANAGER_INSTANCE`), a malformed value\n(`COTAL_MANAGER_INSTANCE \"<value>\" is not a valid lifecycle token ([a-z0-9]{26,32})`),\na malformed handoff, or one whose space, owner, actor or lifecycle UID differs\nfrom `--space`, `--expect-owner`, `--name` and `--expect-lifecycle-uid`, before any broker\nconnection or exchange request. Every refusal on this path names the field and never a value from\nthe handoff. The registration's server, exchange and enforcement checks, the local state this\nmachine keeps for the space (its mesh record, user-auth state and agent secret files), target\nresolution, the policy refresh, the broker preflight and the agent auth preflight quote the space,\nthe server, the exchange URL, the actor or a path named for one of them in their own diagnostics and\nin the filesystem errors under them. For a handoff each prints one fixed sentence that names the\nfield and the phase instead, whether its check fails or an error is thrown. When the agent auth\npreflight's rollback then fails to remove a secret or file, that sentence is followed by the names of\nthe cleanup steps that failed, without their errors. The event-plane policy\nrefusals name the handoff's space field. An actor outside `[A-Za-z0-9_]` and a space that cannot\nname local state, such as `..`, are refused as malformed before any plane. A handoff conflicts with\nthe enrollment variables, `--detach`, `-f` and `--creds`, and needs `--config <persona-file>` and `COTAL_MANAGER_INSTANCE`. From\nthere it runs the enrollment consumer above without redeeming anything. See\n[Delegated seats](embedding.md#delegated-seats-outside-the-managers-filesystem).\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | none | Control-caller creds for an off-registry manager. Managed launches only, with `-d` or `--on`; refused for a spawn in this terminal and from a managed seat's own shell |\n| `--name <n>`, `-n` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--file <persona-file>`, `-f` | none | Persona file to launch, on the execution host. A value with no path separator that does not end in `.md` is read as `./<value>`. Refused together with a positional persona, and when the file is a Mesh manifest |\n| `--config <persona-or-path>` | none | Reserved for internal launchers that set `COTAL_MANAGED_HANDOFF_FILE`, `COTAL_ENROLLMENT_FILE` or `COTAL_ENROLLMENT_URL`; any other use is refused and names `--file` |\n| `--harness <connector>`, `-H` | persona's `agent:`, else the [`harness` preference](config.md#preferences), else `claude` | Connector type (`claude`, `opencode`, `jcode`, `hermes`, and so on). `--agent` is the legacy spelling; giving both is refused |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>`, `-m` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>`, `-C` | this cwd; on a managed launch, the serving manager's workspace root | Working directory to root the agent at. On a managed launch, a relative path resolves against the manager's workspace root, not your shell, and a directory that does not exist on the serving manager's host is refused before launch. Without it, a terminal asks for the folder when the manager is not known to run on this host. |\n| `--prompt <text>`, `-p` | none | Initial prompt auto-submitted at start |\n| `--opt <k=v>` | none | Connector-specific launch option, repeatable. Wins per key over the persona's `launchOptions:`; a connector with no option surface refuses it |\n| `--resume <id>` | none | Fork an existing session id into the mesh; only connectors that declare resume support accept it (see [the matrix](connectors.md)). The manager records the source session id, and `ps --wide` shows it. On a managed launch with `--on <instance>`, a Claude session held on this host is carried to that instance first ([Resume a session](connect-claude.md#resume-a-session)); carrying one needs `--on` |\n| `--no-events` | event plane on where supported | Opt out of the session's structured event plane (`--events` only restates the default) |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override; `\"\"` clears it |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override; `\"\"` sets it to the read set |\n| `--allow-publish <a,b>` | deny | Post-ACL override; `\"\"` clears it |\n| `--detach`, `-d` | off | Launch through a manager, wait until the agent joins, and return without attaching (reattach with `cotal attach`) |\n| `--foreground` | on without `-d` or `--on` | Run the application in this terminal, as a spawn without `-d` or `--on` does; kept for scripts. Its exit, or the terminal's, may end the agent. Refuses `--detach`, `--creds`, `--on`, `--restarts`, and `--json` or `--plain` except with `--dry-run` |\n| `--on <instance>` | none | Launch through one manager instance and attach to the agent, or with `-d` return once it joins. Takes the instance id (the whole id, as `ps` prints it), or on a user-auth mesh a manager's name or host, as [`ps`](#managed-seats) takes them. Refused with `--foreground` and when empty. On a static or open mesh an id that names no running manager is refused `TARGET_NOT_FOUND` before anything is sent, naming the managers that run, or `TARGET_UNAVAILABLE` as an unpinned launch is when none runs. A pinned manager that does not answer is refused `TARGET_UNAVAILABLE`, exit 2, after the reply deadline, because the launch was never sent |\n| `--runtime <name>` | the serving manager's runtime | The terminal runtime for this one managed seat (`pty`, or an installed extension such as `tmux`). It needs `-d` or `--on`. A spawn in this terminal refuses it, because that spawn has no manager runtime to choose. `auto` is refused `USAGE_INVALID` before anything is sent. Which host checks any other name is described below this table. Other seats on that manager keep the runtime they already have |\n| `--restarts <n>`, `--restart-window <ms>` | none | Managed launches only, with `-d` or `--on`, and given together: the manager restarts the agent at most `<n>` times within `<ms>`. Each must be a positive integer |\n| `--live-only` | off | A spawn in this terminal only: skip the durable backstop |\n| `--enrollment-file <private-file>` | none | With `--space`, for a spawn in this terminal: a private file holding one seat's enrollment URL (see above) |\n| `--dry-run` | off | Preview the launch without allocating an identity, provisioning anything or starting an application |\n| `--expect-owner <u_\u2026>` | none | With `COTAL_MANAGED_HANDOFF_FILE` only, and required there: the owner the handoff must carry |\n| `--expect-lifecycle-uid <uid>` | none | With `COTAL_MANAGED_HANDOFF_FILE` only, and required there: the lifecycle UID the handoff must carry |\n\n`spawn` refuses `--allow-stale` with \"manifest deployment options belong to cotal team deploy <file>, not spawn\".\n`--runtime` is a per-seat choice on a managed launch. Omitted, the seat uses the runtime that manager\nwas started with. `auto` names a manager's own runtime, so it is refused for one seat. Any other\nruntime has to be installed and reachable on the host the manager runs on, and the CLI can check\nthat only when that host is this machine. When the launch can reach only this root's own manager,\nbecause `--on` names it or it is the only manager running, the CLI resolves the runtime through the\ninstalled-extension lookup `up --runtime` uses. An unknown, uninstalled or unreachable runtime is\nthen refused `DEPENDENCY_UNAVAILABLE`, exit 2, before the launch is sent, and for a known runtime\nthe remedy names its `cotal ext add` package. A launch that may reach a manager on another host\nsends the name unchanged. That manager refuses a runtime it cannot run, the CLI reports\n`LIFECYCLE_BLOCKED`, and nothing starts. No manager substitutes `pty`.\n\nEach session uses its connector's **event plane** by default: a stream of structured events\ndescribing what the agent did, rather than the prose it wrote, on a channel of its own. The channel is named after\nthe agent's principal, `events.<owner>.<actor>`, never after its display name, because two live\nagents are allowed to share a display name and would then share a stream. The launch grants publish\nrights on that channel alone, foreground and detached alike. On an open mesh, which issues no\ncredentials, the launch still allocates the agent an id, so the channel names a stable actor.\n`--no-events` is the explicit opt-out unless the selected registration says\n`policy: { events: \"required\" }`. Required policy makes the\nevent arm and grant mandatory, so `--no-events` and connectors without an event plane are refused.\n\nThe launch decision and the grant are separate on purpose. Holding publish rights on a channel is\nnot a request to publish to it, so writing an event channel into an agent file's `allowPublish`\ndoes not override `--no-events`.\n\nThe persona (`--file` or the positional name, else the [`persona` preference](config.md#preferences),\nelse `default`) is loaded from the target mesh's `.cotal/agents/` when it is a bare name. A reference that contains a path separator or\nends in `.md` is loaded from that file. A relative path resolves against the mesh root, except that\na managed handoff, and an enrollment onto a mesh this machine has not registered yet, resolve the\npersona file against the working directory. A missing\npersona is refused in one line with the catalog directory or the file that was checked, and the\nline names `--file <persona.md>` for a persona file elsewhere. A missing `default` persona also\nnames the command that writes one: `cotal up` in the root of a space this machine started, or\n`cotal persona create default` for any other space. `--dry-run` refuses a missing persona with\nthe same text, as `TARGET_NOT_FOUND` (exit 2). When a managed launch is refused because the chosen\nmanager's catalog has no such persona, the refusal names that manager, its host and the catalog file\nto create there, and the Next gives the spawn again with `--on` for each of your other live managers\nwhose catalog lists it. The launch flags\noverride the file. On a user-auth mesh the\neffective name is also the agent's actor token, so it must match the token grammar (no `-`); the\nspawn is refused with that explanation before any request is sent. A spawn runs the agent attached\nto your terminal; `--detach` and `--on` hand the launch to a manager. Every mode gets the durable\nbackstop on a mesh that runs the delivery daemon; `--live-only` skips it for a spawn in this\nterminal (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. On a static-auth mesh, a spawn that\nfails after its footprint exists, for example because the creds directory cannot be written,\nretires that footprint the same way before it exits with the error. The creds and the footprint are\nremoved as separate steps, so a creds file that cannot be deleted does not leave the broker durables\nbehind, and the failed steps are reported together. On a user-auth mesh the two arms\ndiffer: a spawn against a mesh this machine provisioned revokes the actor row on exit, while a\nremote spawn (an enrollment or the advertised provisioning endpoint) removes only this machine's\ncredential files; its grant stays until the mesh operator revokes it, and the launch line says\nwhich arm you are on. A spawn through the advertised provisioning endpoint against a record that\npins no exchange URL is refused before the grant is requested, so no credential lands on this\nmachine. With no sign-in the spawn is refused with `AUTH_REQUIRED` (exit 2) before any request is\nsent. Any provisioning failure after the request, an endpoint 401 or 403 included, is reported as\n`OPERATION_UNKNOWN` (exit 3): the endpoint belongs to the deployment, and nothing says what its\nrefusal left behind. A foreground `--share-tools` selection that names a server the config does not declare is\nrefused before anything is provisioned. A managed spawn is an\n**action**: the manager accepts it and allocates the identity, and the CLI waits for a real outcome\n(the agent joins, exits, or about 30 seconds pass) before it reports or attaches (see\n[the control surface](control-surface.md)). On a user-auth mesh the manager enrolls the agent with\nthe host first. When the host refuses that enrollment, for example for a role or a channel your grant\ndoes not carry, and the manager removes the secrets it staged for the agent, the spawn is refused\nwith the host's reason and `PERMISSION_DENIED` (exit 2). A host that fails or does not answer leaves\n`OPERATION_UNKNOWN`. See [Connect Claude Code](connect-claude.md) and\n[Agent files](agent-files.md). (`cotal start` was merged into `cotal spawn --detach`.)\nA managed spawn onto a manager from another Cotal release is refused before any request is sent\nwhen the manager's contract does not declare a field this CLI sends. The refusal names the field,\ncalls it version skew, and gives this CLI's version. A field you leave unset is not sent, so it\nnever causes that refusal.\n\nA manager has 50 seat slots, and each seat counts once. A slot is held by a managed seat (a row in\nthat manager's `cotal ps`, including a seat still joining), by a reserved launch the manager accepted\nbut has not started a process for, or by a cooling hold. A seat that ends within 10 seconds of\nstarting leaves its slot cooling until those 10 seconds pass, unless an operator stopped it. Such a\nseat holds only that cooling slot, even while its launch is still reporting the failure. A spawn\nrefused at the limit states that split and whether waiting can free a slot:\n\n```text\nat capacity (50 of 50 slots: 49 managed, 0 reserved, 1 cooling); waiting frees a cooling slot in 7s, or despawn one\n```\n\nA cooling slot frees at the stated time. A launch that has not settled frees its slot only if it\nfails, and a managed seat frees its slot only when it stops. The refusal counts a launch as pending\nonly while it holds a slot, so a launch whose seat already ended is not counted. The roster counts\npresence, which also includes peers no manager owns, so its total is a different number.\n\nThose 50 slots bound the host. A manager started with `--seat-cap <n>` also bounds how many live\nseats it holds for ONE owner, so a manager shared by several owners cannot be filled by the first\none to ask. The default is `unlimited`, which leaves the 50 slots as the only bound. The cap counts\nthe seats that manager supervises for the owner the launch belongs to, plus its launches that have\nnot started a process yet; it does not count cooling slots, or any other owner's seats. A launch\nover the cap is refused before a name is reserved or anything is minted, and the seats already\nrunning are untouched:\n\n```text\nowner <owner> holds 4 of the 4 seats manager <id> admits per owner; stop one of that owner's seats, or ask the operator who runs this manager for a higher seat cap\n```\n\nThe CLI exits 2 with `CONFLICT` and a remedy naming `cotal stop --name <name>` and `--seat-cap`.\nUnder `--json`, `error.cause.code` is `resource-exhausted` and `error.cause.detail` carries\n`owner`, `seats` and `cap`, so a caller can decide without reading the sentence. `cotal_spawn`\nshows the agent the same sentence. Stopping one of that owner's seats admits the next launch.\n\nRun from a managed seat's own shell on a static or open mesh, a managed `cotal spawn` (with `-d`\nor `--on`) launches as that seat when it targets the seat's own space. A bare `spawn` there runs in\nthat shell, as anywhere else. Without `--space` it picks that target the way the\noperator path does, so a recorded mesh that is not running is skipped. The CLI reads the seat's\nlaunch identity (`COTAL_NAME`, `COTAL_ID`, `COTAL_LIFECYCLE_UID`, `COTAL_SPACE`, and on a static\nmesh the seat's own credential), so the manager records the seat as the spawner, the same as for\nthe seat's `cotal_spawn` tool. On a static mesh that credential also proves the seat's space, so a\nlaunch without `COTAL_SPACE` still runs as the seat, and a target space holding no credential for\nthe seat is refused. An open mesh acts as the seat only when `COTAL_SPACE` names its space. The\nseat can then stop the child with `cotal_despawn`, and the manager stops the child when the seat\nexits. On a static mesh a seat whose agent file lacks `capabilities: [spawn]` is refused, because\nits credential holds no spawn subject.\n`--on <instance>` keeps its pin: the seat's own credential has no instance route, so on a static\nmesh the CLI mints a one-shot `manager-caller` view for the seat, pinned to that instance and\ncarrying the spawn subject only when the seat's credential holds it. On an open mesh the call keeps\nthe TLS requirement the mesh records. A managed spawn from a seat's shell never falls back to the\noperator: `--creds` is refused there, and so is a target the seat cannot act on as itself, such as\n`--server` with an unregistered `--space`, a user-auth mesh, or an open mesh that `COTAL_SPACE` does\nnot name.\n\nForeground user-mode launches serialize their local actor material. A duplicate refuses\nwithout replacing the first launch's files. A new local-user foreground launch also refuses\nan existing grant before rotating it. Departure cleanup is conditional on the launch's token\nand lifecycle generation, so an earlier launch cannot revoke a successor's grant. Remote\nprovisioning remains the mesh operator's authority and can reissue material before the\nclient observes an existing same-host consumer.\n\n## models\n\n```bash\ncotal models [--json | --plain] [-H | --harness <connector>] [--refresh] [--on <manager-instance>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--harness <connector>`, `-H` | all registered applications | Application whose catalog to list |\n| `--on <manager-instance>` | native selection | Ask only the named manager instance, or on a user-auth mesh the manager a name or host picks, as [`ps`](#managed-seats) takes them. An unavailable or unauthorized pin refuses without choosing another. On a static or open mesh an id the registry lists for no manager is refused `TARGET_NOT_FOUND` at once, naming the managers that run, as `spawn --on` does. |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n| `--json` / `-j` | off | One JSON document: `result.items` holds each catalog as returned; the requested manager ID, answering ID and answering epoch are separate, and `result.notAskedManagerInstanceIds` lists the live managers an unpinned read did not ask, or is `null` when no registry was read |\n| `--plain` | off | The same report without color |\n\nAsks the selected manager for each application's model catalog (model ids plus their variants)\nfor connectors that expose one. OpenCode and Codex query harness/provider surfaces; Jcode reads\nproviders that enable `model_catalog = true` in the operator Jcode `config.toml`. Jcode's listed\neffort tiers render as `<tiers> (declared, not provider-verified)`, and launch can still refuse one.\nA connector without a catalog says so with `supported: false`, and one whose install has no\ncatalog it can list, such as OpenCode 2.x, gives the reason in `unavailable`; neither is a failed\nread. A connector whose catalog could not be read, or that the manager could not load, makes the\nread `partial` (exit 4) with one `MODEL_CATALOG_UNREADABLE` warning naming its cause. The\nscreen points to that warning without repeating it inline. The remedy is to correct the stated\ncause before retrying; a refresh cannot repair missing connector configuration. A manager-level\nrefusal is reported separately. When the space's service registry lists no live manager, an\nunpinned operator `models` refuses `TARGET_UNAVAILABLE` (exit 2) at once, as `ps` does, and names\nbare `cotal up` as the next step. An unpinned operator read reads the registry first and goes to\nthe one manager the native selection picks. With several live managers, the report names the others\nin a `Not asked` row, and its Next asks each with `--on`. A manager lists a catalog row for every\napplication its install names, so an empty list says the answering manager has no agent\napplication installed. A connector whose harness the manager did not find at boot\nreports the reason boot recorded, as a spawn does, so restart the manager after installing it. Pick\na result with `cotal spawn --model <id> --variant <v>`, where `<id>` is the model id as the catalog\nprinted it. OpenCode and Codex ids are the full\n`provider/model`; Jcode ids are bare (`opus-5`, not `cliproxy/opus-5`), because the provider is\nselected by the operator's Jcode config and a prefixed id is refused at launch with the bare form\nnamed.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager. The Participant column holds the name\nthat `endpoint describe` takes, and the role has its own column.\n\n## who\n\n```bash\ncotal who [--waiting] [--all] [--json | --plain] [--space <s>] [--server <url>] [--creds <path>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--waiting` | off | Only rows that are waiting or report a condition |\n| `--all` | off | Also list infrastructure endpoints such as the manager |\n| `--json` / `-j` | off | One JSON document: `result.items` holds one row per participant |\n| `--plain` | off | The same report without color or marks |\n\nReads the live presence roster of the selected space. Each row has the participant's name, role, kind, status and condition,\nactivity, attention mode, harness, model and host, as its presence reports them. Infrastructure\nendpoints are hidden unless `--all` asks for them; the report says how many it hid. A `cotal chat`\nsession is a participant a person drives, so it is listed with the agents and never counted as\ninfrastructure. The report adds a Kind column that names it `chat session`, and its JSON row carries\n`sessionKind: \"chat\"`. In a user-auth\nspace, `--all` shows each of your managers as a row whose id ends in `manager_<instanceId>`, the id\n`--on` takes, with its host and its runtime in the activity, such as `supervisor (tmux)`. A manager\nrow reads `manager <name>`, with the name the manager was started with, or `manager <host>/<runtime>`\nfor one started without a name.\n\n`managed` says whether a manager runs the seat. With no manager on the roster it is `false` for\nevery row and no manager is asked. Otherwise every registered manager is asked for its seats, and a\nseat is matched on its lifecycle UID. In a user-auth space each of your live managers is asked, and\neach answers only for the caller's own seats, so other seats show `managed: null` with a\n`MANAGED_SCOPE` warning. An\nidentity the broker does not let ask the managers, such as a user whose grant has no `admin`, sees\nevery seat `null` with a `MANAGERS_DENIED` warning. So does a credential passed with `--creds`: it\ncarries no caller the managers accept from this command, so they are not asked. These two warnings\nsay how far the identity's\ngrant reaches; the roster itself was read in full, so they do not make the run partial. A manager\nthat does not answer leaves every unmatched seat `null` with a warning. In a user-auth space the\nNext names those managers, as `ps` does.\n\nA part `who` could not read makes the run `partial` (exit 4): a roster snapshot that did not finish\n(`ROSTER_INCOMPLETE`), a roster that has gone stale (`ROSTER_STALE`), an error on the roster watch\n(`ROSTER_ERROR`), or a manager census it could not read in full (`MANAGERS_UNREAD`, and the warnings\n`ps` names). A credential that may not read the roster is refused with `PERMISSION_DENIED` (exit 2),\nnever shown an empty roster.\nA mesh where nobody is present is a complete roster: `who` says so and exits 0.\n\n`who` watches the roster and the channel registry on every mesh so it can report those states. On a\nstatic-auth or user-auth mesh the credential `who` connects with may not delete its watch consumers,\nso each one stays on the broker for five minutes after `who` exits. A script that runs `who` in a\ntight loop there can reach the broker's limit of 1000 consumers per stream.\n\n## whoami\n\n```bash\ncotal whoami [--json | --plain] [--space <s>] [--server <url>]\ncotal whoami --creds <file> [--json | --plain]\n```\n\nReports the identity this CLI acts under in the selected space and what it may read, post and do.\nIt reads the mesh registry, the login cache and, when it is on this machine, the user-auth grant\nledger. It does not refresh the remote space catalog or contact the IdP. A one-shot command has no\nsession, so there is no unread count or connection state to report.\n\n| Mesh | `identity.kind` | What it may do |\n|---|---|---|\n| Open | `open` | Everything; the broker authenticates nothing |\n| Static auth, signer on this machine | `space-signer` | Everything; each command mints its own short-lived credential for its task, so there is no standing principal |\n| Static auth, no signer here | `no-credential` | Nothing without `--creds <file>` |\n| User auth | `user` | The signed-in user's `cli` actor: `read`, `post` and `capabilities` are its ledger row |\n| A `--server` the space is not registered at, whether or not this folder holds its trust | `unknown` | Unknown: `mode` and the access fields are `null`, and the run is `partial` (exit 4) with `BROKER_NOT_PROBED` |\n| A space `space add --force` registered without checking its broker | the kind its recorded mode gives | What that mode allows, if the broker enforces it: `modeChecked` is `false`, and the run is `partial` (exit 4) with `MODE_NOT_CHECKED`, naming how to add it again with the check |\n\nA user who is not signed in has no principal. A signed-in user with no ledger row (`grant:\n\"not-granted\"`) may read and post nothing. When the grant ledger is on another machine the grant\ncannot be read here: `read`, `post` and `capabilities` are `null`, the run is `partial` (exit 4) and\nthe `GRANT_NOT_LOCAL` warning says so. A remote registration can hold a cached login without the\nsecret that derives its owner and principal. Those fields stay `null`; the report calls the\nprincipal unknown on this machine, rather than calling the user signed out. The cached expiry is\nadvisory and the command does not verify the login with the IdP. `dms` says whether the identity\nmay read DM history: on an open mesh, or with the space signer's audit credential on an authenticated mesh. The report\nreads local configuration and does not contact the broker to check its authentication requirements.\nSo an address no registration names is reported as unknown, never as open; adding the space with\n`cotal space add` checks the broker. The same holds for the space's own folder: its signer does not\nsay whether a broker at another address serves the space, and the remedy names `cotal whoami --space\n<space>` for the address it is registered at.\nA space that is prepared and not started has no broker yet. `whoami` reports the identity its folder's\ntrust gives it: `space-signer` when the root holds the space's signer, as after `space forget` and\n`space create` again, and `no-credential` before its first `up`. `server` and `mode` are `null`, the\n`target` names the root, and the report says to start it with `cotal up` there. With `--server`, or\nwhen the prepared folder no longer exists, it still refuses with `TARGET_UNAVAILABLE`.\nIn a folder whose `.cotal/` names no space, as after `space forget`, the held default `main` is\nassumed. The report keeps `Mesh: main` and says under it that the default was assumed, and the JSON\n`target` is `{\"root\": <folder>, \"assumed\": \"main\"}`.\n`--creds <file>` reports what a credential file may do instead, with `kind: \"credential\"`: the space\nits permissions name, the profile it was minted with, its principal, the agent lifecycle its\ndelivery and history grants are keyed to (none for the `observer` and `admin` profiles), when it was\nissued and when it expires, the channels it may read and post to, and whether it may read every DM\nin the space, only its own, or none. `revokedAt` is when the space's account on this machine revoked\nthe file's key with `auth revoke-credential`, and `null` when no revocation of it is recorded here. It reads the file as every `--creds` file is read and decodes\nthe claims without checking the signature or contacting the broker, which enforces them. When the\npermissions name no single space, `space`, `read`, `post` and `dms` are `null`, and the run is\n`partial` (exit 4) with `CREDENTIAL_SPACE_UNKNOWN`. `--creds` takes no `--space` or `--server`.\nAn unregistered `--space` is refused even with `--server`. Server addresses in the identity report\nand connection refusals omit URL user information, query strings and fragments, and escape terminal\ncontrols. Addresses need a host and a supported broker scheme. TCP addresses cannot have a path;\nWebSocket routes and bare host addresses remain supported. Invalid addresses make `whoami` refuse\nrather than print the raw value. Recovery instructions use a `<url>` placeholder rather than a\nsecret-stripped connection address.\n\nBoth reads refuse an unusable local mesh registry with `SCHEMA_INVALID` (exit 1) before\nconnecting. The diagnostic names the file without repeating its stored field values.\n\n`endpoint invoke <target> <command> --cast` uses the native one-way verb. Only an ephemeral\ncommand can be cast; journaled commands and `--wait` combinations refuse before publication.\nThe receipt records the original request, resolved binding and broker flush, not a responder or\nconfirmed execution. A class cast is not retried or repaired if another instance refuses its\nbinding. A broker publish denial remains a refusal. A lost publication outcome remains unknown.\n\n## service-endpoint\n\n```bash\ncotal service-endpoint approve <proposalId> [--caller-ceiling a,b | --request-id <hex32>] [--space <s>] [--json | --plain]\ncotal service-endpoint refuse <proposalId> [--request-id <hex32>] [--space <s>] [--json | --plain]\ncotal service-endpoint proposal <proposalId> | operation <operationId> | name <endpoint> [--space <s>]\ncotal service-endpoint name-bind <endpoint> (--owner <u_\u2026> --claim <ref> | --request-id <hex32>) [--space <s>]\n```\n\nDecides and inspects service endpoint registrations on a user-auth space whose auth service runs\nwith `--service-endpoints`. A static-credential or open space has no service endpoint host, so\nthere every subcommand refuses `UNSUPPORTED` (exit 2), says so, sends nothing and records no\nrequest. A root that does not hold the space's user-auth service refuses the same way and names the\nroot that started it. Every subcommand is one request to the service's\n`/service-endpoint-host` route as the signed-in user: the route verifies your IdP token and derives\nthe granter itself, so no command names one. `approve` and `refuse` are the owner's: the command\nreads the proposal and its name first and decides at those revisions, so a proposal or name that\nmoved in between is a conflict the host reports, never a decision on something else.\n`--caller-ceiling` names capabilities the proposed commands declare, such as `read`. Each one\nbecomes a ceiling entry, pinned to the proposed instance, for every proposed command that declares\nit, and callers can later be granted only commands inside that ceiling. A name that no proposed\ncommand declares is refused with nothing decided, and so is one a targeted command declares,\nbecause a capability name does not choose a target mode. Without the flag the ceiling is empty. `proposal`,\n`operation` and `name` read as the owner: another owner's record reads absent. `name-bind` is the\noperator's: it needs the auth service on this machine and runs on its loopback face under the\nexchange capability, with your signed-in subject as the audit id. The host verifies no domain\ncontrol, so `--claim` is your own reference for the namespace you checked. The answer is the host's\nown. A decision or a binding is recorded under its request id at\n`.cotal/auth/space.<key>/service-endpoint-requests/<id>.json` in this root before it is sent, and\nthe command prints that id, or names the exact resend command in the failure when no answer came\nback. A run interrupted before any answer prints nothing: the record's file name is the id.\nRun the same subcommand and subject again with `--request-id <hex32>` to send the recorded request as it was,\nwhich the host replays with the same approval or binding; a fresh read would carry the revisions\nthe first attempt moved, so the replay never rebuilds the request, and it takes no\n`--caller-ceiling`, `--owner` or `--claim`. An id this root never recorded, or one recorded for a\ndifferent command or subject, is refused with nothing sent. A run without `--request-id` is a new\nrequest, and a proposal already being approved answers `approval-in-progress`.\n\n## Handle inspection\n\n```bash\ncotal auth handle inspect <private-file> [--space <space>] [--json|--plain]\n```\n\nThis reads one protected native handle artifact or a bounded array of artifacts once. Without\n`--space`, it shows unverified public scope, holder and declared expiry claims. Signatures,\nissuer authority, parent links, holder currency and revocation remain not checked. No broker\nis contacted or credential issued. Signature bytes and original signed artifacts are never printed.\nA handle is a signed JSON grant, so a file that is not JSON, such as a NATS credential file, refuses\n`SCHEMA_INVALID` and says what it is. For a credential file, the Next names\n[`cotal whoami --creds <file>`](#whoami), which reports what it may do. A path that names nothing\nrefuses `TARGET_NOT_FOUND` (exit 2) and names the path.\nThe human report shows at most 50 scope rows and names omissions; JSON retains public metadata.\n\nAn explicit `--space` selects online inspection on a registered static-auth space whose root\nholds matching native operator authority. One ephemeral provisioner reader is minted without\npersisting it. Its broker account and identity must match before leader-served signer and\nissuer-namespaced revocation reads. The inspector checks signatures, issuer scope, presented\nparent digests and containment. Every required sturdy-handle status must be explicitly unrevoked;\nmissing, malformed, unreadable or stale spec/status pairs fail closed. Reads share one bounded\nchain budget. No records are created or repaired, and no alternate signer or broker is tried.\n\nThese non-atomic observations are signature inspection, not a usable presentation context.\nDeclared expiry, presenter identity, holder/process currency and contract-dependent use authority\nremain separate. No holder credential is conferred and no handle is redeemed. Open and user-auth\nspaces refuse this static reader rather than borrowing an operator identity. Reader-close\nuncertainty is reported separately.\n\n## Account signer export\n\n```bash\ncotal auth signer --out <private-file> [--space <space>] [--force] [--json|--plain]\n```\n\nThis exports the selected space's existing data-account signer through the native stripped-signer\nformat. It does not create a caller, rotate a key, start a service or export the operator root.\nA cached login is not signing authority. The selected root must hold validated static trust;\nconflicting current-folder and selected-root account authorities refuse before publication. A space\nregistered as open refuses `UNSUPPORTED`: it issues no credentials, so it has no signer to export.\nA per-user-auth space refuses `UNSUPPORTED` as `auth mint` does there, naming the login or\nenrollment path, and nothing is written.\n\n`--out` is required. Its parent must exist and pass the protected physical-directory checks. A\nparent that does not exist refuses `TARGET_NOT_FOUND`, naming the folder, before anything is\nwritten.\nManaged `.cotal` and workstation state are not valid explicit export destinations. The file is private, and secret contents\nnever appear on stdout, in the receipt or in diagnostics. Keep this account-wide signer out of\nlogs and support bundles; it is not a least-privilege endpoint credential.\n\nAn existing path refuses by default. `--force` first inspects an owned private regular file and\naccepts only the stripped format for the same space and account. It refuses full trust bundles,\nother accounts, symlinks and concurrent changes. An identical export is a no-op. New files use\nnative create-only private publication; replacement uses the inspected snapshot under a writer\nlock. An unconfirmed publication remains unknown and is not retried. Platforms without the\nprotected file policy refuse rather than substituting a weaker writer.\n\n## Endpoint authoring\n\n```bash\ncotal endpoint init reviews [--dir <directory>] [--json|--plain]\ncotal endpoint check <contract-file> [--json|--plain]\n```\n\n`init` creates `contract.json` and an executable `handler.mjs` template. The default directory is\nthe supplied name; `--dir` chooses another one. Its parent must exist. Files are created\nexclusively, without following a pre-planted symlink or replacing an existing file. A partial\nwrite retains the named created material for review rather than deleting it automatically.\n\nA simple scaffold name such as `reviews` declares `com.example.reviews`. A full reverse-DNS name\nis kept. Choose a namespace you control before requesting serving authority. Scaffolding creates\nno service definition, reference, credential, registration or process. The handler accepts one\nJSON object with `command` and `args` on stdin and writes one JSON result on stdout.\n\nThe version-1 local contract file has `schemaVersion`, `endpoint` and `cluster`. The cluster has\nnative `urn`, `revision`, `attributes`, `commands` and `events` fields. Each command carries its\nnative declaration plus `input` and `output` schema bundles, each `{root, members?}`. The installed\nnative compiler derives their closure digests; authors do not hand-maintain a second digest next\nto an edited schema. `members` maps native digest references to supplied JSON documents. Unknown\nwrapper or command fields are refused. This file is local authoring input, not a new wire kind.\n\n`check` reads at most 1 MiB, validates the native cluster declaration, compiles every input/output\nwith the installed schema profile and reports the derived cluster closure. All schema references\nmust resolve inside their bundle; no URL or adjacent file is fetched. Attribute and event\nsemantics remain opaque to the native parser, so a file declaring them produces a partial result,\nexit 4, rather than a claim that those semantics were checked.\n\nNeither command contacts a broker, loads the handler, starts a service or proves serving\npermission. Authoring takes no space, broker, caller or invocation flags.\n\nServe the edited contract through the independent service controller:\n\n```bash\ncotal service add reviews --preset exec-endpoint --space <s> --root <absolute-root> --contract <absolute-contract.json> --handler <absolute-node-executable> --handler-args '[\"<absolute-handler.mjs>\"]'\ncotal service start reviews --space <s>\ncotal service status reviews --space <s>\ncotal service logs reviews --space <s>\ncotal service stop reviews --space <s>\n```\n\nThis preset supports Linux and an existing registered static-auth root. The executable is an\nabsolute protected regular file, with argv supplied separately. A symlink on the root, contract or\nexecutable path refuses, because a link could be repointed after the check, and the refusal names\nthe physical path to use instead, such as `/usr/bin/python3.14` for `/usr/bin/python3`. There is no shell, PATH lookup,\nimplicit installation, account switch or open/user-auth credential fallback. For a Node handler,\nselect the Node executable and pass the script path in `--handler-args`. The runtime Node in\nCotal's install root qualifies; if an earlier installer left its folders group-writable, the\nrefusal names `cotal update`, which removes that write. That JSON array has at\nmost 64 fixed nonsecret strings of at most 4096 characters each, and its serialized form has at\nmost 8192 characters. A longer string refuses `USAGE_INVALID`, exit 1, naming its position and that\nlimit. The handler\nruns in the contract directory with an empty environment. It is trusted local code with the\noperator's OS permissions, not a sandbox; it must not daemonize or leave independent children.\n\nStart recompiles one protected contract snapshot and pins its cluster closure and the executable\nfile identity across the controller handoff. Changes before launch refuse; changes to the\nexecutable before a later call also refuse. Only ordinary untargeted ephemeral commands are\nsupported. Targeted, journal, trait, attribute and event declarations refuse before registration.\nThe host publishes the schema roots, members, closure manifests and cluster to the native\ncontract store, registers one fresh native instance, and uses the native issuance gate and\nbranded serve grant. The handler receives no signer, executor credential or broker environment.\n\nThe native serving boundary validates input before spawning and output before replying. Each\ncall receives one `command`/`args` JSON object on stdin and must return one UTF-8 JSON value on\nstdout with exit code zero. Calls and casts need a native deadline; the handler budget never\nexceeds it or 120 seconds. At most 32 handlers run concurrently. Input is bounded to 64 KiB,\nstdout to 256 KiB and discarded stderr to 64 KiB. Deadline, pipe, process, JSON and schema failures\ndo not replay effects. Handler payloads and stderr are not forwarded to service logs.\n\nInitialization is recorded only after the real serve subscriptions flush and the native ready\nstatus returns. Controller readiness remains a process-bound initialization observation, not an\nongoing endpoint health probe. Logs and private launch records name the full native instance;\nuse that instance for an attributed `endpoint describe` or `endpoint invoke`. Normal renewal\nuses the same issuer, identity and registration, with one immediate same-broker reconnect. An\nunconfirmed renewal withdraws the service rather than starting a replacement.\n\nStop first prevents new handlers, requests termination of its owned handler groups, waits for\nprocess/pipe closure, drains serving and closes the connection. Native deregistration pins the\noriginal registration revision and preserves successor or in-flight governance state. Private\ncreate-only intent, registration and removal records retain the original coordinates. A later\nlaunch of the same local instance refuses an unsettled or malformed attempt instead of silently\nrepeating it. Unconfirmed initialization or cleanup remains unknown; neither process disappearance\nnor an initialization receipt proves native cleanup. When the earlier host process is gone, as\nafter a `kill -9`, the refusal names its native registration and gives the way out: remove it with\n`cotal endpoint deregister <endpoint> <instance> --yes`, then start a fresh instance with\n`cotal service start <name> --new-instance --space <s>`. A `service restart` of that instance\ngives the same commands. The separate `endpoint deregister` route retains its broker-gone and\nrevision guards.\n\n## Endpoint control\n\n```bash\ncotal describe <endpoint> [--on <instance>] [--space <s>] [--server <url>] [--creds <path>]\ncotal invoke <endpoint> <command> [--args '<json>'] [--timeout <ms>] [--space <s>] [--server <url>] [--creds <path>]\ncotal invoke <endpoint> <command> --name <agent> [--admin] [--space <s>]\n```\n\nThe generic v0.4 service surface. `describe` resolves a registered endpoint's command set off the\nwire - the reserved `describe` command answers the registered contract digests, the schemas are\nfetched from the space's content-addressed contract store, recompiled, and verified against those\ndigests - and prints each command with its capability class and targeting shape. `--on <instance>`\npins `describe` to one manager instance's rail (the whole id, as `ps` prints it under its\n`manager <id>` headers), so an operator can read what that instance serves in a multi-manager space;\nunpinned, the class queue answers and the attribution line names whichever instance did. `invoke`\ncalls one command by name: `--args` is a JSON object validated against the fetched input schema\n*before*\npublish, and `--timeout` sets the reply deadline in milliseconds (default 10000). `--server` overrides the\nregistry's broker URL and `--creds` names a creds file for an off-registry connection. A targeted command takes `--name <agent>` (resolved to the agent's current principal through\n`inspect`) or `--self`. `--admin` uses the admin instrument credential, whose cross-agent reach rides\nthe operator-only `any` authorization mode. Neither command has compile-time knowledge of any\nendpoint's schemas - this is the same trust chain every built-in control command now uses. Needs an\nauth mesh: the manager registers its service on both static and per-user meshes (a signed-in user\nrides their bearer; each visible or invoked command still requires its existing grant, and cross-agent\nreach needs the `admin` scope). An open mesh has no service registry.\n\nOn a static-auth root the operator's own credential sends requests to the manager endpoint only, so\nthe broker refuses its `invoke` of any other endpoint before the call runs. When the call and the\nendpoint's whole command surface are untargeted synchronous ephemeral commands, the refusal names\n`cotal auth mint <name> --endpoint <endpoint> --space <space> --out <private-file>`, with the space\nthe call resolved and the `--server` it was given, if any, so the caller is issued for the space that\nrefused even when another space is current. Run the same `invoke` again with `--creds <private-file>`. An `invoke --on <instance>` of another endpoint is refused the same\nway and names no remedy: with `--on` the operator's credential still carries manager rows only, and\na paired caller file takes no instance pin.\n\n## Managed seats\n\n```bash\ncotal ps [--json | --plain] [--on <instance>] [--wide] [--slots] [--space <s>]\ncotal stop <name> [--force] [--on <instance>] [--space <s>]\ncotal stop --self [--force]\ncotal attach [<name>] [--on <instance>] [--no-reconnect] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `<name>` / `--name <n>` | none | Managed agent to stop or attach, named positionally or with `--name`. `stop` needs one unless `--self` is given. `attach` without one lists your agents to choose from on a terminal, and refuses `USAGE_INVALID` where it cannot ask |\n| `--self` (`stop`) | off | Stop only the verified managed seat running this command (see [Stopping an agent](#stopping-an-agent)) |\n| `--force` (`stop`) | off | Request the native hard despawn (`graceful:false`); it does not bypass authority |\n| `--on <instance>` | class anycast (`ps`: class scatter) | Pin to one manager instance id (multi-manager space); takes the whole id as `ps` prints it, not a prefix. On a user-auth mesh it also takes a manager's name, its host, or the `<host>/<runtime>` an unnamed manager shows as (its runtime alone when its presence names no host), when only one of your live managers matches it; otherwise it refuses as `TARGET_AMBIGUOUS` or `TARGET_NOT_FOUND`, lists your live managers and gives the command again on each. On a static or open mesh a manager's presence does not carry its instance id, so a name is refused as `USAGE_INVALID` and the Next asks for the instance id. An empty value (`--on \"\"`, an unset shell variable) is refused, never treated as absent. A roster principal id (`local.\u2026`) is refused with a message naming the instance id `ps` prints |\n| `--wide` (`ps`) | off | Print each seat as a labeled record that adds the operational facts the manager records: the provider the connector reported serving the model, `cwd`, `pid`, spawner, lifecycle uid, the owning manager's instance id and host, and for a `--resume` seat the session it forked (`forked from <id>`, with the source title and transcript SHA-256 once a Hermes or Jcode seat has recorded its fork; a carried Claude session prints `forked from <host>:<id>` with its title, SHA-256 and `carried <time>`, the time its bytes reached the manager). Model and requested variant stay in the identity row rather than printing twice. A fact the manager did not record (for example a runtime with no real process, a Windows seat whose process ConPTY has not created yet, or a connector that reported no provider) prints nothing, never a placeholder |\n| `--json` / `-j` (`ps`) | off | One JSON document (see below). Mutually exclusive with `--wide` and `--plain` |\n| `--plain` (`ps`) | off | The same report without color or marks |\n| `--slots` (`ps`) | off | List the durable static slot rows this manager owns instead of live seats, through the `slots` command. Mutually exclusive with `--wide`. A row that is not in the live roster still prints, with `LIVE` false; a retired row never prints |\n| `--no-reconnect` (`attach`) | off | End the attach when its session ends, instead of re-establishing it. For scripts that want one run and one exit code |\n\nOn a controller stack started with `--no-manager`, `ps` refuses `TARGET_UNAVAILABLE`: no manager\nruns, so there are no managed agents to list. Bare `cotal up` adds the manager. A `manager` preset\nservice started at the root answers instead, and `status` names it on its Manager row. Bare `up`\nthen refuses `LIFECYCLE_BLOCKED`, exit 2, naming that service and the\n`cotal svc stop <name> --instance <id> --space <space> --yes` that stops it; once it is stopped,\nbare `up` starts the stack's own manager. A stack manager whose process died stays registered, so\n`ps` waits out the manager's answer deadline first, then refuses the same way and names bare\n`cotal up`, which starts it again. While an `up` is still starting the\nstack's manager, `ps` refuses `TARGET_UNAVAILABLE` saying the manager is starting, and its Next is\nto run `cotal ps` again once `up` reports it started. That `up` holds the root's maintenance lock;\nonce it has exited, `ps` gives the refusal above.\n\nA raw `--creds` file is refused by `ps`, `stop`, `attach` and the other control commands, because\nthat route mints no endpoint-caller triple; the project folder, or `--space` against the registry\nentry, is the route that does.\n\n`ps` shows the instance id `--on` takes for each manager that answered, with or without agents. One\nmanager is listed under `Manager` as its `instance`, and with several each gets a `manager <id>`\nheader. With no agents its Next is `cotal spawn`, or `cotal setup` when no agent application is set\nup here, as `status` reports it.\n\nWhen `ps` asks every manager, it then asks each manager that answered for the host name in its own\nstatus reply. The host shows as `\xB7 reports host <host>` after the id, or as a `reported host` row\nunder `Manager`. It is that manager's own claim, and `ps` does not check it. Two managers on one\nhost report the same name, and `--on` never takes it on a static or open mesh. These asks share one\n2-second limit. A manager that has not answered by then is listed without a host, and the read\nstays as complete as the census found it. `ps` does not show a manager's name. On a user-auth mesh\nwith several of your managers live, the host is the one each manager's presence reports.\n\nThe human `ps` report is presentation text and is not a stable parsing target. Scripts use `--json`,\none envelope whose `result` is:\n\n| Field | Meaning |\n|---|---|\n| `kind` | `seats`, `slots` with `--slots`, or `presence` when no manager of yours answered on a user-auth mesh |\n| `items` | Each seat (or slot) row as its manager returned it, unchanged |\n| `managers` | Each manager instance asked: `{instanceId, state}`, where `state` is `answered`, `silent` (registered, no answer within the deadline, with the `liveness` this command established), `refused` (with its `error` and `code`) or `contract-mismatch`. An `answered` manager that gave a host name in time also has `reportedHost` |\n\n`target` is `{space, scope: \"all-managers\"}` for the class scatter,\n`{space, scope: \"your-managers\"}` for a user-auth mesh with several of your managers live,\n`{space, scope: \"presence\"}` when none of them answered, and\n`{space, scope: \"manager\", managerInstanceId}` for `--on` or a user-auth mesh with one. A manager that is not\n`answered` makes the read `partial` (exit 4), with a `MANAGER_SILENT`, `MANAGER_REFUSED` or\n`MANAGER_CONTRACT_MISMATCH` warning naming it. A `silent` manager whose `liveness` is `gone` is a\nregistration its host never removed. On an auth space its row names `cotal endpoint deregister\nmanager <id> --yes`, run from the space's own root. On any other space only that manager's own clean\nstop removes it, so the row names starting it again and stopping it if its host is still available,\nand says no supported command removes the record if the host is gone. The row stays and the read\nstays `partial`.\n\n`--slots --wide` is refused: `--slots` lists durable static slot rows, `--wide` prints live seat facts, and the two answer different questions. Across a multi-manager scatter, `--slots` prints each manager's rows under its own instance header, the same way the plain `ps` scatter does.\n\nA revision-32 manager also supports a bounded native `slots` query through\n`endpoint invoke manager slots --on <instance> --args '{\"names\":[\"worker\"],\"includeRetired\":true}'`.\nIt point-reads up to 100 distinct aliases and can return their retired slot/head and\ncleanup observations. The default `ps --slots` query is unchanged and excludes retired\nrows. Omitted aliases, live-map absence and stop acknowledgements are not retirement\nproof. Match the returned principal and lifecycle UID before interpreting any terminal.\n\nThese are operator clients over the running manager's control plane. The default row includes the\nconnector, model pin, optional requested variant, and runtime as operational descriptors for the\nmanaged row. They do not make a shared display name a unique protocol identity; use `--json` when\nunambiguous owner+actor attribution is required. An omitted variant means no override was requested;\nCotal does not invent an effective provider default it cannot observe. `ps` also prints two state\nfacts per managed agent, because they answer different questions: the process fact from the manager's\nown runtime handle (`running` with its uptime, or `exited` with how long it ran), and the mesh fact\nfrom the roster (`idle` / `working` / `waiting` / `mesh offline`, or `not in roster` when the seat has\nno presence row at all: a seat that has not joined yet, or one that never did). When the seat's\nconnector relays a harness-reported condition, the mesh fact carries its code and how long it has\nheld, so a seat whose turn died on a provider rate limit reads `waiting (rate_limit for 40m)` rather\nthan a bare `waiting`, and `--json` carries the whole `condition` object. When the connector reports\nthe seat's last work event (presence `activeAt`), the mesh fact ends with its age, such as\n`\xB7 active 3s ago`, and `--json` carries `activeAt`. A seat whose turn stopped advancing keeps\nheartbeating, so its presence row stays fresh and this age is what shows the stall. A seat can be\n`running` and `mesh offline` at once: the process is alive and its presence has lapsed. That row says\nhow long, as in `mesh offline for <age>` with an age such as `3.5h`, counted from the seat's last\npresence heartbeat, which `--json` carries as `offlineSince` (epoch ms). The age is read only from\nthe seat's own presence record, matched on its principal and lifecycle uid, so a same-named peer or\nan older lifecycle never dates it. The manager log names each managed seat that is offline on the\nmesh while its slot is held\n(`seat offline on the mesh: <name> - last heartbeat <time>; process <state>`), including one its\nwatch first sees offline after a reconnect, and each one that comes back\n(`seat back on the mesh: <name>`), so a watchdog that only checks process liveness has a line to\nact on. The manager does not reap or re-key such a seat. The mesh fact is only a verdict while the\nmanager's own presence watch is fresh: when that watch has been silent past the liveness window, or\nhas not replayed the bucket yet, every row prints `mesh unknown` with the reason instead (`--json`\ncarries it as `meshView: stale | unpopulated`), because `offline` and `not in roster` would then\ndescribe the manager's watch rather than the seat. The manager rebinds a watch that goes\nsilent under a live connection on its own, so `mesh unknown` normally clears within a liveness window.\nOn a user-auth mesh `ps` also renders each managed agent's last credential-refresh outcome, fail-closed.\n\n**Mode split (chosen up front, never try-scatter-then-degrade):**\n\n- **Static / open mesh.** Bare `ps` is a **class scatter**: it freezes the live manager class from\n the records registry, merges every registered instance's agents grouped and attributed per\n instance, and a non-answering instance is shown as `registered, no answer within the deadline`\n (never silently omitted). A refused list or a missing answer makes the census incomplete: rows\n from other instances remain visible, but `ps` prints an incomplete-census warning on stderr and\n exits 4 (`partial`), including with `--json`. Those rows are not a complete seat count. A contract\n mismatch prints one plain comparison of the requested and served input/output digest pairs and\n advises aligning manager versions. The no-answer label means only that the instance is registered\n and did not answer. It does not say the host is down, because a dead host never deregisters itself and a\n live one can be slow; if it is gone, deregister it.\n `--on <instance>` pins the read to one exact instance id instead. A wrong pin fails loud\n rather than falling through. On a static or open mesh a well-formed id that the registry lists for\n no manager is refused `TARGET_NOT_FOUND` at once, naming the managers that run, as `spawn --on`\n does; a registered one that does not answer is reported as `manager instance <id> did not answer`\n (nothing else is asked). A credential without that\n instance's rail is reported as refused by the broker, not as an unresponsive manager. A manager\n that answers with a refusal is shown with its own cause; \"no manager reachable\" is said only when\n nothing answered at all. If the scatter's own registry read fails (the freeze or the reconcile),\n `ps` says the manager registry could not be read rather than pronouncing on the managers, which\n may all be up. When nothing answered and this root's stack manager process still runs, as a hung\n one does, `ps` refuses `TARGET_UNAVAILABLE` naming its pid, and its Next ends that process so bare\n `cotal up` starts the manager again.\n\n**The verdict is scoped to the endpoint rail the request rode.** An issued caller rides the\nversioned `ep.v1` rail, a separate subject space from the legacy `ep` rail, and an endpoint serves\nboth (SPEC 13.15). A manager older than the versioned rail serves `ep` alone, so it can be running,\nregistered and answering while an issued caller's request reaches nobody. Silence on `ep.v1` is\nreported as `no manager answered on the <rail> rail` with `ep.v1` as the rail, and names both causes\nit is consistent with: no manager running, or one older than the rail. The CLI cannot tell them\napart, because the service registry records no package version, so check whether a manager is\nrunning and, if it is, its version. The same scoping applies to `cotal run`'s hosted verbs, which\ndrop the `--local` suggestion there, since `--local` drives the run from the calling process and\nnames the caller as its answerer. A hosted verb that no manager answered refuses\n`TARGET_UNAVAILABLE`, exit 2, leading with `the manager registered for this space did not answer`,\nand its Next is `cotal status`, which names a stack manager that runs but does not answer, with its\npid.\n\n**`stop` and `attach` route by seat locality.** A seat can only be stopped or attached by the\nmanager actually running it, and the class queue does not know which one that is. So on a\nstatic/open mesh both verbs first ask every registered instance which one hosts the named seat, then\naddress that instance directly. This happens by default; you do not need `--on`. When every instance\nanswers and none hosts the name, `stop`, `attach` and `input` refuse `TARGET_NOT_FOUND` (exit 2)\nbefore anything is sent, as they do with `--on`, naming the managers that answered, with `cotal ps`\nas Next. When an instance did not answer for itself, the seat may run there, so they refuse\n`TARGET_UNAVAILABLE` (exit 2) and name that instance. When more than one instance runs the name,\nthey refuse `TARGET_AMBIGUOUS` (exit 2) and name each of them. `--json` carries each refusal. On a\nterminal the last two ask instead, listing only the managers that hold the name or did not answer,\nand the refusal's Next gives the command again with `--on` for each.\n\n`--on <instance>` remains the override, for when you already know where the seat lives or the\nlookup itself is degraded. On a static or open mesh, `stop --on`, `attach --on` and `input --on`\nwith an id the registry lists for no manager are refused `TARGET_NOT_FOUND` at once, naming the\nmanagers that run, as `spawn --on` is.\nOn a **user-auth mesh**, the exchange selects one authorized manager\nfor a short-lived `manager-caller` view. `--on` requests a specific instance. Without it, a command\nrun from a managed seat's shell selects the manager that launched the seat (`COTAL_MANAGER_INSTANCE`),\nand any other caller's selection must be unique. Discovery and the command use that instance route.\nThe caller gains no registry read or scatter permission. An absent, ambiguous or unauthorized\nselection refuses before sending the command. With several of your managers live, the refusal lists\neach one on its own row with its name, instance id, host, activity and the time its process started.\nA manager started without a name is listed as `<host>/<runtime>`. A manager started by a cotal\nbefore 0.100.0 puts no host in its presence, so its host reads\n`not reported (started by an older cotal)` and it is listed by its runtime, such as `tmux`.\nRestarting it with the current cotal shows its host. Its Next gives your command again with `--on`\nfor each, using the name, `<host>/<runtime>` or runtime when that picks the manager out, and the\ninstance id otherwise. The code stays\n`PERMISSION_DENIED` and the exit 2. `who --all` lists the same managers at any time. By default, when every manager you registered has\nexited, the refusal says to start one with [`manager start`](#manager); the command is not sent to the local manager.\n\n`ps` and `status <agent>` do not refuse: they read each of your live managers, as the user-auth\nmode below describes. `stop`, `attach` and `input` with an agent name read them the same way and\nrun on the manager that holds the name, without asking. A name held on several of them, or found on\nnone that answered, is settled as on a static mesh above, among those managers only, and a manager\nwhose read fails counts as one that did not answer. A name every manager answered without is\nrefused `TARGET_NOT_FOUND`, naming the managers searched. On a terminal, `spawn -d`, `models` and\n`persona` ask instead of refusing. Every manager the exchange counts is one of yours in the space the command\nresolved, from the folder or `--space`, so the question lists those managers with the most recently\nstarted first, and that one is the default. The command then runs on the manager you pick, as the\nNext would run it. The question and a `Manager chosen` receipt, with the manager, its instance id\nand the `--on` that skips the question, go to stderr. Cancelling the question does nothing and\nexits 0. Without a terminal on stdin and stdout, with `--no-input`, with `--json`, which implies\n`--no-input`, or with `COTAL_NO_PROMPT=1`, the command refuses as above. A `--json` caller passes\n`--on`, and the result's `target.managerInstanceId` names the manager that answered. A bare `spawn` asks where the agent runs\nbefore any of this, and a managed launch on a manager that is not known to run on this host then\nasks for its folder (see [spawn](#spawn)).\n\nA trusted hosted composition may opt into an [interactive local primary](embedding.md#interactive-manager-primary).\nUnder that policy, unpinned human control selects the genuine local Manager up front, even when your\nown remote Manager is live; an unavailable local Manager refuses without remote substitution. This\naffects all interactive Manager verbs, not only `ps`. Select your remote Manager explicitly for its\nspawn, run or terminal operations. Explicit pins and managed-seat launching bindings keep their rules.\nThere is no user flag for the policy, and stock `up` and recovery defaults do not enable it.\n\nA connection that the broker refuses, or that does not open within 5 seconds, sends nothing. A manager\ncommand then connects again, as do `send` and the other commands that open a one-shot connection, up\nto 4 attempts 1 second apart. A view the auth service refused for a moment, or an auth service that\nanswered late, then just works. When every attempt fails the command says nothing was sent and is\nrefused, exit 2: `PERMISSION_DENIED` when the broker refused each attempt, `TARGET_UNAVAILABLE` when\nnone opened in time. The broker's own words stay in the native detail. On a user-auth mesh the space's\nauth service logs why it refused.\n\nA seat is reported as **not found** only when every reachable instance answered for itself. An\ninstance that stayed silent past the deadline, or that refused the read rather than answering, said\nnothing about which seats it hosts, so the seat may be running on it. That case reports that the\nlocation could not be established, names the instances that did not answer, and states outright\nthat it is not a report that the seat is gone. Read it as unknown and retry with\n`--on <instance>`; a retry loop that treats it as \"already gone\" stops looking for a seat that is\nstill running. A single manager cannot tell \"hosted elsewhere\" from \"does not exist\": it answers\n`not-found` for both, which is why the search asks all of them and why an incomplete search\nconcludes nothing.\n- **User-auth mesh.** With one of your managers live, `cotal ps` reports what that manager knows\n about your agents (an instance-addressed read against its in-memory roster, owner-filtered). With\n several live, `ps` asks nothing: it reads each manager the exchange counts live through its own\n one-instance view, one after another, and lists each under its own `manager <id>` header, as the\n class scatter does. A manager that does not answer is listed as silent and makes the read\n `partial` (exit 4), and the Next names each one by instance id: a manager that ended without a\n clean stop stays registered under its id. Managers of other owners are not asked, so completeness\n across the space is not claimed. `status <agent>` and `who` read your managers the same way.\n When none of your managers answers, or none is live, `ps` lists your agents as presence shows\n them, read-only, with their state, application and host, and exits 0. It says that no manager\n answered, and its Next starts one: bare `cotal up` on the root of the space's own stack, as for a\n static stack above, and `cotal manager start` anywhere else. `--on` and `--slots` keep refusing\n `TARGET_UNAVAILABLE`, exit 2, when their manager does not answer. Your ledger row needs the `admin` scope to\n reach `ps` at all; `spawn` alone is refused by the broker (the ep tier boundary). A broker-refused\n read keeps that refusal and adds the documented scope requirement and `cotal auth grants` remedy.\n The broker does not identify whether the missing permission is for the route, command or target;\n the CLI does not substitute another caller or retry on a different route.\n\n`attach` streams and drives an agent's terminal on the `pty` runtime; detach with the escape key\n(Ctrl-] by default; see the [`detach-key` preference](config.md#preferences)). The key is recognised\nas the legacy control byte and as the kitty keyboard protocol and xterm modifyOtherKeys encodings of\nthe same press, so a terminal with either protocol enabled detaches too. It does so over a one-use, holder-bound\nmesh session ([SPEC](../SPEC.md) \xA713.6): the manager replies with a signed session grant (never a\n`127.0.0.1` URL), the CLI redeems it once over the broker, and the browser console (`cotal console`)\ndrives the same session. `stop` and `attach` need a running manager to talk to. On a static mesh\nthey are cross-agent admin operations. On a user-auth mesh, your own agents (any agent under your\nowner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger row\n([identity & auth](identity-and-auth.md)). Launch detached agents with [`spawn --detach`](#spawn).\n\n`pty` is the only runtime that streams a terminal. A seat started by the `tmux`, `cmux`, `orca` or\n`herdr` runtime runs in a surface the manager does not own, so there is no stream to give and\n`attach` refuses once with `UNSUPPORTED`, exit 2, carrying that runtime's own guidance for watching\nthe agent where it runs, and a Next that names `cotal send` and `cotal watch`. The reconnect loop\nbelow does not retry that refusal, because asking again cannot change it.\n\n**`attach` reconnects when the link dies.** A session lives on a network link, and a laptop that\nsleeps, a VPN that drops or a wifi handover kills it. When that happens `attach` prints\n`[cotal: connection lost, reconnecting]` on stderr and starts asking the manager for a new session:\na fresh grant, a fresh per-session credential, a fresh connection, so every attempt re-runs the same\nauthorization the first attach did. On success it prints `[cotal: reconnected]`, the manager repaints\nthe seat's current screen the way it does for any attach, and you carry on in the same terminal.\nRetries wait 1s, 2s, 5s, 10s, then 30s, for as long as the seat exists. The detach key is read the\nwhole time the loop runs, the waits and the attempts alike, so a reconnect never traps you: press it\nwhile a session is being established and the attach ends there, and a session that lands behind the\npress is handed back to the manager rather than left holding a slot. Everything else you type while\nthere is no session is dropped rather than queued, so keystrokes aimed at a terminal that turned out\nto be frozen, Ctrl-C included, are not delivered to the agent by a reconnect you did not know had\nhappened. That starts before the first session, not at the first reconnect: at a terminal, `attach`\nreads and drops what you type while it is still resolving the mesh, so a key struck at a prompt that\nhas not come up yet does not reach the agent when it does.\nThe terminal is in raw mode for the whole reconnect, including when the link died before the first\nsession finished opening, so the detach key works there too instead of echoing as `^]`.\n\nA **pipe** carries script input. For example, `printf 'ls\\n' | cotal attach --name web` is\nbuffered until the session opens. Buffering continues across reconnects, so\n`tail -f log | cotal attach --name web` does not lose the part of its feed written while the link was\ndown. Only a terminal gets the reader; `--no-reconnect` keeps the old behaviour on both.\n\nIt stops on its own when reconnecting cannot help, and says why: a manager that refuses the attach\nexits non-zero with the manager's own message, and a reconnect that finds the seat no longer there\n(despawned, or its agent exited while the link was down) exits cleanly with `seat <name> is gone`.\nIt stops the same way on a refusal the CLI raises itself, such as a broker that denies the request.\nA local connect refusal that retrying cannot fix, such as a static-auth mesh whose seed is now\nmissing, also exits non-zero with the refusal's own sentence. A broker that is still unreachable,\nor a user-auth service that does not answer, keeps the loop trying in silence.\nA refusal that could still pass, such as a manager at its session ceiling, is relayed in the\nmanager's own words while the loop keeps trying, once per refusal rather than once per attempt.\nPressing the detach key ends the view with `Detached`. A remote terminal-session end, including\nprocess exit or manager shutdown, prints `Attach session ended`. Neither report claims the agent\nis still running or that its cleanup completed. Use `cotal ps` to check its current state.\n`--no-reconnect` restores the single-session behaviour.\n\nEach reconnect also hands the abandoned session back to the manager, over the first link that can\ncarry the message, so an attach that flaps does not eat the manager's session slots one outage at a\ntime. If that message never gets a link, the attach says so when it ends. The live-session ceiling\ndefaults to 64 concurrent sessions (`--max-sessions`); the browser console opens one session per\npane, so a dashboard over a large mesh should size for agents \xD7 panes. Hitting the ceiling refuses\nbefore a credential is minted and names `--max-sessions`.\n\nWhich mesh `attach` resolves also decides **how it redeems the grant**. On a registered open mesh\nthere is no local seed. The CLI connects bare, the same way other control commands already do, and\nthe session rail is the caller rail that a real open-mode connection already reaches. Telling the\noperator to re-register the root is false: the registered root is already the contract. On a\nstatic-auth mesh the grant is still redeemed by minting a short-lived\nsession-scoped credential from the seed at the root the mesh resolved to, never from a `.cotal`\nfound by walking up from whichever directory you happen to be standing in. The difference is not\nhypothetical: `~/.cotal` exists on every install because the mesh registry lives there, so a command\nrun anywhere under your home directory but outside a project used to mint from your home\ndirectory's trust and present it to a broker that trusts a different chain, which surfaced as a\nbare authorization failure that named nothing. A directory that does hold another chain for the\nsame space is now reported on the way past, and not obeyed:\n\n```text\n! this directory resolves to /Users/you, whose .cotal/auth holds a DIFFERENT trust chain for space \"team\".\n attach used /Users/you/projects/app, the root this mesh resolved to. The other one is not being used, and is worth a look.\n```\n\nWhen a **static-auth** mesh holds no seed at the resolved root, `attach` refuses and names what it\nresolved, the broker and the root, instead of describing a directory it did not use and instead of\ntaking the open-mode path. An authenticated registry entry with a missing seed is still\nauthenticated. On a USER-AUTH mesh `attach` reads no seed. It sends your login and the session grant to\nthe auth service, which issues a `session-caller` bearer only if your owner and actor hold that\nsession. The connection it opens expires with the session grant.\n\nTerminal bytes stream over the mesh; the manager's own HTTP/WS face serves the console. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you reach the browser console (`cotal console`) for an agent whose manager runs on another machine.\n`attach` does not use that face: it redeems a signed mesh session grant over the broker instead (see above), so it reaches a\nremote manager regardless of the bind address. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it, including a same-root `cotal up` repair\nand an adopted preserved or restored listener. A manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face mints terminal read and write authority for every managed agent's browser session, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## input\n\n```bash\ncotal input --name <n> --text <text> [--no-enter] [--on <instance>] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | | Managed agent to type into (required) |\n| `--text <text>` | | The text to type, taken verbatim (required) |\n| `--no-enter` | off | Type the text and stop there, without pressing Enter |\n| `--on <instance>` | class anycast | Pin to one manager instance id using the same rules as [`attach`](#managed-seats) |\n\nTypes one line into a running agent's terminal, as if you had typed it there, and returns. This is\nthe half of [`attach`](#managed-seats) that a program wants: `attach` is a live stream that holds a\nsession open and expects a terminal on your side, so a script, a cron job or a web UI cannot use it\nto send a single line. `input` is one authorized call.\n\nWhat it is for is **harness commands**. A line beginning with `/` is not chat and not a message: it\nis something the agent's own harness handles, and the only way in is the keyboard.\n\n```bash\ncotal input --name reviewer --text \"/compact\" # ask the harness to compact its context\ncotal input --name reviewer --text \"/model opus\" # switch its model\ncotal input --name reviewer --text \"hold on that PR\" # ordinary typing works too\n```\n\n**Quoting.** `--text` takes a value, so a payload starting with `/` survives as written. A payload\nstarting with a dash needs the `=` form, because the shell-style `--text --foo` is ambiguous and is\nrefused rather than guessed:\n\n```bash\ncotal input --name reviewer --text=--verbose # dash-leading text: use --text=<value>\n```\n\nEnter is pressed by default, since a command typed but never submitted has not been delivered.\n`--no-enter` types the text and leaves it sitting at the prompt, which is how you stage a line and\nsend it later.\n\nNothing comes back but a `Terminal input written` receipt. It names the agent, space and manager,\nthe bytes the manager reported writing (counting the trailing carriage return) and whether Enter was\nrequested. It confirms the write to the terminal only. Whatever the agent does next shows up where\nits output already goes: the mesh, its transcript, or an `attach`.\n\nA write the manager cannot confirm, such as a terminal write that failed partway, exits 3 with\n`Outcome unconfirmed` and is not typed again. Part of the text may already be at the prompt, so check\nthe seat before you send it again.\n\nWith no manager running, as after `cotal service stop stack-manager`, `input` refuses\n`TARGET_UNAVAILABLE` (exit 2) before it sends anything and names bare `cotal up`, as `ps` does.\n`stop` and `attach` find their seat the same way and refuse the same. A manager that stays\nregistered but does not answer, such as one that crashed, leaves `input` and `stop` waiting out its\nanswer deadline. They then refuse `TARGET_UNAVAILABLE` too, since nothing was sent, with\n`cotal status` as the next step.\n\n**This one is operator-only, and more narrowly than `stop` or `attach`.** Those two are granted to\nanything holding `spawn`, so an agent can stop and attach to seats under its own owner. `input` is\nnot: it is granted only to operator credentials, which on a user-auth mesh means your ledger row\nneeds the `admin` scope, the same scope [`ps`](#managed-seats) already needs there. The reason is\nthat a write into a terminal is control of whatever is running in it, and on a user-auth mesh the\nown-owner rule covers every seat under you, not only the ones you launched: a `spawn`-scoped agent\ncould otherwise type into a sibling it never started. Seat locality is still resolved for you.\n\nOnly the `pty` runtime can be typed into. The external terminal runtimes (`tmux`, `cmux`, `orca`,\n`herdr`) attach to a process they do not own, so they have no input stream for it and the command\nrefuses by name rather than dropping the keystroke.\n\n## persona\n\n```bash\ncotal persona [--json|--plain]\ncotal persona show <name> [--json|--plain]\ncotal persona create <name> --file <persona.md>\ncotal persona create <name> --prompt <text> --subscribe <a,b> [--allow-subscribe <a,b>] [--allow-publish <a,b>]\ncotal persona edit <name>\ncotal persona remove <name> --yes\n```\n\n`cotal personas` is the earlier parser for the same catalog and is a legacy command. It still\nruns, and `cotal help personas` still prints its help, but `cotal help` no longer lists it and\ncompletion does not offer it.\n\n`p` aliases `persona`; `persona rm` aliases `persona remove`. These commands manage the\nselected space's local `.cotal/agents` catalog without connecting, refreshing a remote catalog\nor seeding connectors. `--space` and `--server` select the same root used by launch. A prepared\nspace has an offline catalog before it is started. Failed target resolution never falls back\nto the current directory: a `--server` that no registration names reads the project in this\nfolder, under the space prepared there, as launch does, and outside one it refuses with `TARGET_NOT_FOUND` and writes nothing. With\nno space selected and no project here, the refusal says so. With `--on <manager>`, list, show and\ncreate work on that manager's catalog instead, and edit and remove refuse there, as\n[Personas on an exact manager](#personas-on-an-exact-manager) describes. `--announce <channel>`\nbelongs to `persona create --on`; without `--on` it refuses with `USAGE_INVALID` and changes no file.\n\nA persona name uses letters, digits, `_` and `-`, up to 128 characters, and a name refusal says\nwhich rule it broke.\nCreation accepts one of `--file/-f` (a complete [agent file](agent-files.md)), `--prompt`, or\n`--from` (body file, or `-` for explicit stdin). Complete files validate all supported fields,\nmust name the requested persona and keep their original bytes. A file that names another persona is\nrefused with `SCHEMA_INVALID`, naming the file's own name and the `persona create` that keeps it.\nA file the agent-file rules refuse is `SCHEMA_INVALID` naming the file, the field and the reason,\nsuch as a missing frontmatter block, the renamed `channels` key or a `subscribe` channel outside\n`allowSubscribe`. A YAML error names only its line and column, not the parser's text.\nThey cannot be combined with\n`--role`, `--model`, `--harness`, `--subscribe`, `--allow-subscribe` or `--allow-publish`. Body\ninput requires an explicit `--subscribe` declaration, including `--subscribe \"\"` for no channels,\nand accepts `--role`, `--model` and `--harness`. `--allow-subscribe` sets the channels the persona\nmay read, and an omitted or empty one reads as the `--subscribe` set. `--allow-publish` sets the\nchannels it may post to, and without it the persona posts nowhere. Both take a comma list, as the\n[agent file](agent-files.md) fields `allowSubscribe` and `allowPublish` do. Both refuse control\ncharacters with `USAGE_INVALID`. An empty `--model` is refused with `USAGE_INVALID` too, and so is a\n`--role` or `--model` with a control character, before any other rule reads it. A refusal that quotes\na flag's value escapes its control characters. A role is an address `send -r` reaches, so a role\nthat is not one\ntoken of letters, digits, `_` and `-` is refused, `USAGE_INVALID` from `--role` and `SCHEMA_INVALID`\nfrom a file's `role`. Each entry of `--subscribe`, `--allow-subscribe` and\n`--allow-publish` must be a valid channel name, written without `#`, and each `--subscribe` channel\nmust be within `--allow-subscribe` when it is given, or creation refuses with `USAGE_INVALID`,\nnaming the flag and the entry. `--harness` must name a harness this machine knows, as\n`config set harness` requires, or creation refuses with `USAGE_INVALID` and writes nothing.\nCreate and edit refuse a definition over 1 MiB with `SCHEMA_INVALID`, the bound a create on a\nmanager applies to its input, and an edit keeps that draft and names it.\nCreation refuses an existing definition, and with `CONFLICT` a name that differs from an existing\none only in case, since a case-insensitive file system holds the two as one file. Mutation receipts contain names and paths, not bodies.\nA persona file that is a symbolic link is listed with a `PERMISSION_DENIED` warning that names the\nfile it links to. `spawn` reads that file, but `persona show`, `edit` and `remove` refuse the link\nwith `PERMISSION_DENIED`, and the next step names the file to copy in its place.\nA `.cotal/agents` that is a file or a link that loops holds no personas, so the listings, `show`,\n`create`, `edit` and `remove` refuse it with `CONFLICT`, naming it and what it is, and the next step\nis to move it out of the way.\nShow and edit accept a definition whose `name` is not its catalog name. The seeded `default`\npersona is one: its file is `default.md` and its agents launch as `default_agent`. That name is\nreserved, so a create under it is refused with `USAGE_INVALID`.\n\nEditing opens a private draft using the editor preference (`VISUAL`, then `EDITOR`, then saved\npreferences). A value that is the path of an existing file runs as that program, spaces and all.\nAny other value runs as a shell command. With no editor set, or one that cannot be found or run,\nediting refuses with `DEPENDENCY_UNAVAILABLE` (exit 2)\nand names the setting to fix. The draft's private folder is made in the temporary directory\n(`TMPDIR`). One this account cannot write refuses with `PERMISSION_DENIED`, and one that is missing\nor not a directory with `TARGET_UNAVAILABLE`, both exit 2 and naming it before anything is made. A\nsuccessful editor exit and valid definition are required before saving. A draft that fails\nvalidation is refused with `SCHEMA_INVALID` and the reason, keeping the draft and naming its path,\nso the edit is not lost. An editor that exits non-zero, as vim's `:cq` does, cancels the edit: it\nexits 0 with a `USER_CANCELLED` warning, saves nothing, and keeps and names a draft it changed. An\neditor ended by a signal fails with `OPERATION_FAILED` and keeps the draft. A SIGINT, SIGTERM or\nSIGHUP that reaches the CLI while the editor is open is passed to the editor, and the CLI waits for\nit to exit. SIGTERM and SIGHUP then stop the edit, as does a Ctrl-C that ends the editor: nothing is\nsaved, an unchanged draft is removed, a changed one is kept and named, and the CLI exits 130, 143 or\n129. An editor such as vim that takes Ctrl-C as a key keeps the edit going. A draft left as it was\nsaves nothing and says the persona is unchanged. A changed\nor removed source refuses with `CONFLICT` (exit 2), leaving the current definition intact. The\nrefusal says which one happened and keeps your edited draft in its private folder, naming it, so\nthe edit is not lost. Local\nCLI create, edit and remove share a per-persona lock during comparison and commit, not while an\neditor or confirmation is open. Manager and external writes do not participate in that lock.\nCreate, edit and remove refuse a catalog folder you cannot write, such as one set to mode 0500,\nwith `PERMISSION_DENIED`, and leave it and its files as they were.\n`--json` and `--no-input` refuse editing before an editor opens. That refusal, and a removal\nrefused for want of `--yes`, give the fix without the command's help.\n\nA catalog file that is not a regular file, such as a FIFO, is not opened: show, edit and remove\nrefuse it with `PERMISSION_DENIED`, and the list marks it unreadable and names it in a\n`PERMISSION_DENIED` warning, exit 4.\n\nList, show and mutation receipts support one-document `--json` and line-oriented `--plain`.\nShow explicitly returns the full definition and source. List does not return the body or launch\noptions. A malformed entry produces a partial inventory (exit 4); an unreadable catalog refuses\nrather than reporting it empty. Remove asks with cancellation as the default in a terminal.\nRemoving the seeded `default` persona says so in the question and the receipt, since a bare\n`cotal spawn` launches it, and names what writes a fresh copy: a local `cotal up` in its root,\n`cotal space create` or `cotal setup`.\nPlain confirmation uses numbered choices. Scripts and JSON removal require `--yes/-y`.\n\nThe legacy `personas` parser remains available during migration: `list [-v] [--running]`,\n`show`, `edit`, `new` and `rm --force`. Its `new` requires `--subscribe`, reads `--from -` from\nstdin, and overwrites an existing persona only with `--force`. Its local writes share the same\ndraft validation and commit fence. Its `list` escapes control characters in the names, fields and\nbodies it prints, as `persona show` does, and shows each persona's description or first prompt line\nwhole, wrapped to the terminal's width. Its `show` does the same on a terminal, under the file's\npath, and writes only the file, byte for byte, when its output is piped or redirected, so\n`personas show <name> > <file>` copies it. It refuses a missing persona, an invalid definition and a\nname no persona can have with the codes `persona show` uses. Only its explicit `--running` overlay\nconnects to the mesh.\nThat overlay heads the list with the mesh it read and marks each persona `running` or `not running`.\n\n## manager\n\n```bash\ncotal manager start [--runtime <name>] [--roster <file>] [--name <name>] [--foreground|--dry-run] [--space <s>]\ncotal manager stop [--with-agents] [--yes|--dry-run] [--space <s>]\ncotal manager restart [--with-agents] [--yes|--dry-run] [--space <s>]\ncotal manager status [--space <s>]\ncotal manager logs [--tail <n>] [--follow] [--space <s>]\ncotal manager install [--dry-run] [--space <s>]\ncotal manager uninstall [--yes|--dry-run] [--space <s>]\ncotal manager reconcile ...\ncotal manager seats [--drain]\n```\n\nThe manager starts and keeps your agents running. `cotal up` starts one with the rest of the stack;\n`cotal manager start` starts one on its own, for this folder's space or the one `cotal use`\nselected, with the `pty` runtime and the broker the space is registered at. Nothing has to be added\nfirst, and no other service is started for it. `mgr` is an alias.\n\n`cotal manager <verb>` is `cotal service <verb> manager`: every [`service`](#service) rule applies,\nand its flags and the manager preset's options (`--runtime`, `--roster`, `--name`, `--server`,\n`--console-port`, `--console-host`, `--ws-port`, `--max-sessions`) are taken on the line. The first\nstart saves the space's `manager` service from the preset's defaults and says so on stderr; `cotal\nservice show manager` reads it. A start that names another `--runtime` or other option for a\nstopped manager saves it again with that option, and says so. A running one refuses\n`LIFECYCLE_BLOCKED` and names `cotal manager stop` first.\n\nOn a user-auth space this folder hosts, `start` and `restart` need your signed-in, granted login,\nas `cotal up` does for its manager. Without one they refuse `AUTH_REQUIRED` (exit 2) before\nanything is saved or launched, and Next names `cotal login`, then the command again. `stop` and\n`cotal down` need no sign-in there: the broker, the records and the process belong to this folder,\nso the stop reads the manager's own local receipt.\n\nThe manager these verbs act on is the space's one manager service: `manager` when `cotal manager\nstart` saved it, or `stack-manager` when `cotal up` did. Stderr names the saved service when it is\nnot `manager`. So after `cotal up`, `cotal manager status` and `cotal manager stop` act on the\nstack's manager, and `cotal manager start` reports it running and starts no second one. Two saved\nmanager services in one space refuse `TARGET_AMBIGUOUS`, naming each with its `cotal service`\ncommand. A manager that `cotal up` or `cotal supervise` started without a saved service is reported\nby `start` and `status` with its PID, and starts nothing beside it. `logs` reads the log `cotal up`\nopened for it, `<root>/.cotal/manager.<spaceKey>.log`, and changes nothing. It takes `--tail`,\n`--follow` and `--json` as a service's `logs` does, and a follow ends when that manager's process is\ngone. After `cotal down` stops that manager, `logs` still reads the log it left: the text says the\nmanager is stopped, with its PID while its record still names one, and names `cotal up` under Next,\nand `--json` carries `recordedProcess` with `state` set to `stopped`. With no such log, `logs`\nrefuses `TARGET_NOT_FOUND`, naming the file and `cotal up`. `cotal service logs stack-manager` reads\nthe same file when no `stack-manager` is saved. A\nmanager that `cotal supervise` runs in a terminal writes to that terminal instead. `stop`, `install`\nand the other verbs refuse it while it runs, because no saved service owns it; it stops with its\nstack through `cotal down`, or where `cotal supervise` runs it.\n\n`--foreground` keeps the manager in this terminal until Ctrl-C, which stops it through its native\nstop. A unit or container you write that runs a manager uses `cotal manager start --foreground`.\n`cotal manager install` refuses `UNSUPPORTED` in this release, because a Cotal-written unit runs\nonly a direct-process service. It names the `ExecStart=` line for a unit of your own instead, whether\nor not a manager was started, with one exception: while a manager that `cotal up` started with no\nsaved service runs, `install` refuses it as `stop` does and names `cotal down`.\n\n`manager start` and `manager restart` run the first-run connector seed, as `cotal supervise` does,\nso a fresh machine or container has its connectors before the manager boots; `status`, `logs`,\n`stop` and the maintenance verbs install nothing.\n\n`reconcile` and `seats` are the [manager maintenance](#manager-maintenance) commands. Bare\n`cotal manager` lists every verb and starts nothing.\n\n## Start a manager for a hosted space\n\nA hosted space runs its broker elsewhere, so nothing on your machine starts a manager for it. Start\none:\n\n```bash\ncotal manager start --space <space> --runtime <orca|tmux|pty>\n```\n\nIt needs you signed in to the space (`cotal login`) and the space registered on this machine\n(`cotal meshes add`). With the space selected by `cotal use <space>`, a bare `cotal manager start`\nis enough: it uses the `pty` runtime, which every platform has built in. `orca` and `tmux` need\ntheir extension first, such as `cotal ext add @cotal-ai/orca`. The command returns once the manager\nanswers; `--foreground` keeps it in this terminal instead. `cotal ps` then lists it with the agents\nit runs, and `cotal spawn -d <persona>` launches an agent through it.\n\n## supervise\n\n```bash\ncotal supervise [--execution runtime|none] [--runtime <name>] [--space <s>] [--server <url>] [--name <name>] [--spawn <names>]\n```\n\n`supervise` runs a manager in this terminal until Ctrl-C. It is deprecated, hidden from help and\ncompletion, and removed in `1.0.0`, with the other [deprecated daemon\nentries](UPGRADING.md#deprecated-daemon-entries): every run prints one stderr warning naming\n`cotal manager start`, and `cotal manager start --foreground` for a terminal or a unit. On a\nterminal it wraps like any other warning. A run given\n`--execution`, `--spawn`, `--seat-cap` or a resume flag, which `manager start` does not take yet,\nsays to keep `supervise` for that run instead. It keeps its flags and its run for existing units,\nscripts and containers. The manager that `cotal up` starts\nruns the same code through a hidden entry. Without `--space` it\nsupervises the space other commands target here: `COTAL_SPACE`, else the space this folder hosts\nor is registered for, else the one `cotal use` selected. A space it did not get from `--space`,\n`COTAL_SPACE` or a folder that hosts it keeps its manager records in the folder that space is\nregistered at, so a start from any folder is the same manager, with the same instance and name.\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | `COTAL_SPACE`, this folder's space, or the `cotal use` selection | Space to supervise. On a root that hosts several spaces, name one of them here |\n| `--server <url>` | hosting mesh, or matching registered mesh | Broker URL. A registered mesh supplies it when omitted; a different explicit value is refused before anything is dialed. |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--execution <runtime\\|none>` | `runtime` | `none` starts a control instance for a hosting user-auth root, with no runtime, connector inspection, workload or terminal execution |\n| `--console-port <n>` | none | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets the browser console reach this manager from another machine. `cotal attach` does not use this face: it redeems a mesh session grant over the broker |\n| `--max-sessions <n>` | 64 | Live-session ceiling. Each console pane and each `cotal attach` is one session, so size for agents \xD7 panes, not agent count. A capacity refusal names this flag. `cotal up --max-sessions` records the same number on the mesh so a later `supervise` started by repair keeps it |\n| `--seat-cap <n\\|unlimited>` | `unlimited` | Live seats this manager admits for one owner. `unlimited` leaves the manager's own 50-slot ceiling as the only bound, so a mesh with one owner keeps every slot. Set a number where several owners share a manager, and one owner cannot take the host. A launch over it is refused before anything is reserved, naming the owner's count, the cap and the remedy. It is recorded in this root, so a later start without the flag keeps it |\n| `--name <name>` | none | The manager's name. Its presence card carries it, `who --all` and the manager choice show it, and `--on` takes it on a user-auth mesh. It is recorded in this root, so a later start without `--name` keeps it. Without a name, the manager shows as `<host>/<runtime>` |\n| `--roster <file>` | none | Declarative roster to boot at startup. See [Roster files](define-a-team.md#roster-files) |\n| `--launch <spec>` | none | Resolved manifest launch spec (from `up -f` / `team boot`) |\n| `--spawn <names>` | none | Comma-separated personas to pre-spawn at startup |\n\n`--ws-port`, `--resume-attempt`, `--resume-commit-file` and `--resume-commit-sha256` are passed by\n`cotal up` when it starts the manager, for the console session client and for resuming preserved\nseats. They are not for direct use. `--resume-commit-token` is the deprecated argv form of the\nresume commit, still accepted for older callers (see [Backups](#backups)).\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, `@cotal-ai/cmux`, or `@cotal-ai/herdr`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\nBoot inventory decides whether this process takes unpinned `spawn`/`launch` on the class rail:\nif every declared connector is unavailable, those commands stay on this instance rail only\n(`status` reports `classSpawn: false`). `describe` still answers on the class rail, so an\nunpinned spawn can bind-fence against a skip member; re-issue, or pin `--on`. With no member\non the class rail, an unpinned spawn is refused and names the agent application to add. A partial\ninventory keeps the class rail and names `--on` on a harness refusal, because sibling\ninventories are not readable from the serve credential. See [control surface](control-surface.md#instance-routing).\nThe manager prints what its boot inventory found once it is up, so a start that is refused prints\nno boot line.\n\nUse `--execution none` only for a fresh control instance in an existing hosting user-auth root.\nIt requires that space's signing trust, provider pin and user-mode mesh record, without remote\nmanager authority. It rejects `--runtime`, startup workload flags and participant registrations\nbefore resolving a Runtime or connector. The mode needs no connector seeding. An existing\nunclassified identity or retained workload refuses admission; a recorded NONE instance cannot\nrestart as runtime. Do not delete identity or capability files to change modes.\n\nNONE retains native registration, status and the selected Manager's empty managed-seat ps.\nIt does not adopt foreground agents, roster peers or HTTP-hosted sessions. Workload operations\nstill need an explicitly selected execution-owning participant Manager and its existing authority.\nStarting a control instance does not enable an auth service's interactive local-primary policy.\nNONE also refuses model inspection and caller-driven persona, purge, transcript, preservation,\nresume, cancellation, turn-yield and self-stop writes. It starts no transcript receiver or sweep.\nIts native registration, lease, renewal, session-ledger and goal maintenance can still write.\n\nLocal runtime startup now records identity and execution mode during admission. A later failed\nauthentication or registration can leave those files classified as runtime. Invalid supervise\narguments refuse before connector seeding, with one error line rather than seeding first.\nA refusal names the caught value's `message`, or the value itself when it has none, so an auth\nprovider that rejects with `null` or a string still gets its refusal line and exit status 1.\nWhen the broker refuses the credential the manager presents, such as one this folder's signer\nminted for a space another folder hosts, `supervise` exits 2 with a `PERMISSION_DENIED` refusal\nunder a `Mesh:` heading that names the credential and the broker, and its Next names the folder\nthat hosts the space. Before it connects, `supervise` refuses the same way, with exit 2 under a\n`Mesh:` heading, and writes nothing in this folder. A space whose own stack in this folder is\nstopped or not started gets `TARGET_UNAVAILABLE` and `cotal up` there, and with `--server` also the\nfolder whose `cotal up` started that broker. A user-auth space that another folder hosts gets\n`PERMISSION_DENIED` and the folder that hosts it.\n\nOn a normal `SIGINT`/`SIGTERM`, the manager stops every seat and requires the selected runtime to\nprove the seat is gone before it releases the manager lease or service registration. A stop that\ncannot prove exit fails loud and keeps manager authority instead of reporting a clean shutdown while\nan orphan still holds broker rails. When the runtime refuses the stop itself, the shutdown fails at\nonce and its error names the refusal as `stop failed: <message>`. A manager that a controller stack\nor `service start` runs stops this way on a plain `SIGTERM` that carries no stop request, such as\none from `kill`, a systemd stop or a system shutdown, with the default agent policy. Its log shows\n`received SIGTERM with no stop request`, it writes no stop receipt, and the next `cotal down`\nrecords it as already gone. It ignores a `SIGINT` that carries no stop request and logs one\nwarning line, because Ctrl-C reaches it together with the CLI that publishes the request. A\nmanager that does not answer its endpoint, such as a hung or stopped process, cannot report its\nstop impact, so `cotal down`, `service stop` and `service restart` refuse it `LIFECYCLE_BLOCKED`,\nexit 2, submit no stop and name its pid. Once that process is ended, as with `kill -KILL <pid>`, the\nsame command finds it gone and goes on. After an abrupt manager death, the same logical successor\nterminalizes only its own durable static slots, verify-evicts the predecessor's broker principal,\nrecords that result in the lifecycle's caller-readable audit detail, reaps the predecessor's seat\nprocess through the runtime's custody reference recorded on the slot (the pty runtime verifies the\nprocess start identity in its seat record, so a reused pid is never signalled), and only then\nretires the lifecycle and frees the alias. A seat whose spawn carried `supervise` with restarts left\nis restarted instead of retired: the successor reaps any seat the slot still names, relaunches it\nunder the same name, lifecycle uid, credential, persona and cwd, and counts the restart against the\npolicy. A seat on a runtime that can adopt a live seat (tmux) and still running when the successor\nstarts is adopted in place: the successor checks that the recorded tmux server, window and pane are\nthe ones the slot names and that the pane's process still runs, then manages that seat again under\nits existing credential, without a restart. Both paths read the record the manager writes to\n`.cotal/seat-records/<space>/<name>.json` once a supervised or adoptable seat is ready, rewrites at\nevery restart and credential renewal, and removes when the seat is freed. A spent budget, a missing\nrecord, an expired credential, a persona file or credential that no longer matches the record, or\nan adoptable seat whose pane has exited retires the lifecycle as above. A runtime that custodies its seats reserves that\nreference before it launches one, and the manager records it on the slot's first durable row, so a\nmanager that dies part-way through a spawn also leaves a seat its successor can address. A\nsame-lifecycle restart or a resume records the new seat's reference on the slot the same way, and\nwhen the slot does not take it the restart or resume fails and stops any seat it started, so the\nslot never names a seat that has already exited while its replacement runs. A resumed seat keeps\nits retained credentials, so the resume frees it only once its exit is proved; a seat whose stop\ncannot be proved stays managed, and the resume's error says so, naming a stop the runtime refused\nas `stop failed: <message>`. A spawn\nthat launched its seat and then failed is rolled back by the manager that launched it, and that\nrollback reaps the seat through the same reserved reference before the lifecycle retires. Missing or unverified broker evidence keeps the slot\nterminalizing, and so does a runtime that cannot reap by reference. While the name stays held, a\nsame-name spawn or a resume re-drives that teardown with the same recorded reference, so it reaps\nthe seat again or keeps the name held.\n\nA `meshes add --mode user` entry is a **participant** registration, not hosting authority. A\nparticipant may run `supervise` only when the host advertises the remote manager authority service\nand the signed-in actor has the dedicated `supervise` ledger scope. The CLI obtains the closed,\nloopback-only `manager-service` view; `spawn` and `admin` do not substitute for that scope. The\nhost issues the manager's public-nkey JWT material through its lifecycle-bound prepare \u2192 activate\n\u2192 renew protocol, never by handing the participant a signer or static provisioner credential.\nThe host also performs instance-scoped eviction and guarded gate reconciliation. A remote manager\nrefreshes its short-lived registration executor before clean deregistration, so a long-running\nprocess removes its service row on `SIGINT` or `SIGTERM`. After an unclean stop, the same instance\nverify-evicts its superseded family and advances the process epoch. If an abandoned frozen gate\nholds the manager governance slot, a different supervise-scoped manager asks the host to reconcile\nthat holder after a complete gone verdict, then retries its registration once.\n\nA remote supervise never uses local signing trust: with host-issued authority in hand, the\nmanager mints from that authority alone and consults local records only to refuse a conflict,\nnamely the supervised space's own trust records under the cwd root. A root that hosts another\nstatic space beside the sign-in is a normal configuration and is never read as this space's\ntrust.\n\nThe broker URL in the registry entry decides the transport. A remote broker is often published\nover a `wss://` edge rather than a raw `nats://` port, and `supervise` dials whichever scheme the\nrecord holds, starting with the manager-authority registration it runs before the manager exists.\nThe record also decides whether that registration requires TLS, so a participant never downgrades\nthe credential exchange to a plaintext connection the registry did not describe.\n\nWithout that advertised host service or scope, `supervise` refuses before it starts a manager.\nRun `cotal spawn` without `--detach` to launch a foreground agent, or ask the space host to enable\nthe authority service and grant `supervise` for detached agents. If a running remote manager loses\nrenewal, it reports degraded state and refuses unsafe new starts and restarts; live agents are not\nsilently replaced. Do not run `cotal down` or `cotal up` on a participant machine to repair this\ncondition.\n\n## daemon\n\n```bash\ncotal daemon serve [--daemon-home <dir>] [--json|--plain]\ncotal daemon status [--daemon-home <dir>] [--json|--plain]\ncotal daemon login --idp <auth base URL> [--client-id <id>] [--daemon-home <dir>] [--json|--plain]\ncotal daemon connect-check --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon register --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-connect --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-disconnect --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-status --space <s> [--daemon-home <dir>] [--json|--plain]\n```\n\nThis release serves a local endpoint for status, sign-in, a connection check, a registration request\nand the daemon's own service connection. It does not start a Manager, serve any command on its\nservice, connect to Cloud, or install a system service. It supports Linux only. On other\nplatforms, an unset `XDG_RUNTIME_DIR` produces\n`TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`); with it set, the platform check refuses `UNSUPPORTED`.\n\n`serve` runs in the foreground and prints `daemon ready at <socket>` once the endpoint is bound\nand checked. That success line is plain text; `--json` shapes its refusals. `status` prints the\nstate, version, protocol, process ID, start time and uptime. `state: \"ready\"` means only that the\nlocal control listener is bound and verified, not that a mesh or Cloud connection exists.\n\nThe endpoint is `$XDG_RUNTIME_DIR/cotal/daemon.sock`. Without `XDG_RUNTIME_DIR` both forms refuse\n`TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`). Both refuse `PERMISSION_DENIED` for a runtime directory\nthat is a symlink, not a directory, not owned by you, or not mode `0700`. `status` also refuses\n`PERMISSION_DENIED` for an endpoint entry that is not a socket, not yours, or not mode `0600`.\nThe daemon drops connections from other users. The kernel separates users, not processes of one\nuser: any process running as you can use the local controls, including sign-in and cancellation.\n\n`status` with no daemon running refuses `TARGET_UNAVAILABLE` (`NOT_RUNNING`). `serve` refuses\n`CONFLICT` (`ALREADY_RUNNING`) for any existing socket, including one left after a crash, and\n`PERMISSION_DENIED` (`SOCKET_NOT_SOCKET`) for a non-socket entry. It removes neither.\nSIGTERM and SIGINT shut down cleanly and remove only the daemon's own socket. SIGKILL or SIGHUP,\nincluding closing the terminal, leaves a stale socket. To recover, run `cotal daemon status`.\nIf it reports `NOT_RUNNING`, confirm no daemon process is running, then remove the named socket\npath by hand and start again.\n\n`--daemon-home <dir>` runs or reaches a separate daemon whose own state lives in `<dir>`: its\nsign-in session, its per-space identity, register request and service store, all at the paths\nbelow with `<dir>` in place of `<COTAL_HOME>`. Spaces are still read from the shared Cotal home's\nmesh registry, which this daemon never writes. Its socket is\n`$XDG_RUNTIME_DIR/cotal/home-<16 hex>/daemon.sock`, derived from the directory, so it runs beside\nthe default daemon. `<dir>` must already exist as an absolute directory you own with mode `0700`,\nand cannot be the Cotal home. A relative path or the Cotal home refuses `USAGE_INVALID`, a missing\ndirectory `TARGET_NOT_FOUND`, and a symlinked, foreign or loose one `PERMISSION_DENIED`. Nothing is\ncreated or repaired.\n\n`serve` holds the lifetime lock `<dir>/daemon.lock` while it runs. A second `serve` for the same\ndirectory refuses `CONFLICT` (`LOCK_HELD`), naming the live holder. A start that fails for any other\nreason releases the lock again. A killed daemon leaves its socket behind, which still refuses\n`CONFLICT` (`ALREADY_RUNNING`) and is never removed, so recover as above. Every client command with\n`--daemon-home` sends nothing unless the process at that socket is the lock's live owner (same pid\nand process start). Otherwise it refuses `PERMISSION_DENIED` (`PEER_NOT_LOCK_OWNER`).\n\nThis daemon is a different machine identity. Nothing is copied from the default daemon: it needs\nits own `login`, its own `register`, and the space owner's approval of its own instance. Without\n`--daemon-home` every command behaves as before.\n\n`login` signs the running daemon in with device approval. The daemon runs the same sign-in as\n`cotal login` and keeps the session in the machine's Cotal home, where `cotal login` keeps it. The\ncommand prints only the approval link, code and expiry to stderr, then the signed-in account. No\nsession token crosses the endpoint or reaches the terminal. `--idp` takes an HTTPS auth URL, or an\nHTTP loopback IP URL; `--client-id` defaults to `cotal-cli`.\n\nOne sign-in runs at a time: a second `login` refuses `CONFLICT` (`LOGIN_IN_PROGRESS`) without\ncontacting the provider. Ctrl-C, or the command's connection closing, requests cancellation on this\nmachine. If cancellation takes effect before persistence, nothing is saved, even if device approval\nalready happened. An already-completed attempt remains completed; cancellation revokes nothing\nat the provider. A cancelled attempt reports `USAGE_INVALID` (`CANCELLED`). A denied or expired\napproval refuses `PERMISSION_DENIED` (`DENIED` or `EXPIRED`), and an unreachable provider refuses\n`DEPENDENCY_UNAVAILABLE`\n(`UNREACHABLE`); none of these saves a session. Any other provider answer fails `OPERATION_FAILED`\n(`REFUSED`). Such a failure can come after the session was saved, so run `login` again. A failure\nis reported with a fixed sentence; the provider's own error text is never relayed. `status`\nkeeps answering while a sign-in waits.\n\n`connect-check` asks the running daemon whether it could register with a space as its own service.\nThe daemon is not a Manager and never borrows a Manager's identity or authority. The check reads\nthe space's registration on this machine and the daemon's sign-in, then the space's public account\nidentity from its auth service (`GET <exchange>/service-endpoint-authority`, no redirects). It sends\nno registration and holds no credential. A space whose auth service does not host service endpoints\nrefuses `UNSUPPORTED` (`UNSUPPORTED_BY_SPACE`). The daemon does not fall back to anything else.\n\nThe other refusals are `TARGET_NOT_FOUND` (`NOT_REGISTERED`) for a space this machine has not\nregistered, `UNSUPPORTED` (`NOT_USER_AUTH`) for a space without user sign-in, `AUTH_REQUIRED`\n(`NOT_SIGNED_IN`) when the machine is not signed in to the space's provider, and\n`DEPENDENCY_UNAVAILABLE` (`UNREACHABLE` or `REFUSED`) when the auth service cannot be reached or\nanswers with something unusable.\n\nOn success it reports `ready-to-register` with the space's account, the daemon's service name\n`ai.cotal.machine` and the daemon's own instance ID. Nothing has been registered. The space owner must\nstill approve the daemon, and the service name needs the space operator's binding. The first\nsuccess creates the daemon's identity for that space at\n`<COTAL_HOME>/daemon/<space segment>/service-identity.json` (mode `0600`). It holds the instance ID\nand the account, and no credential. Later checks reuse it. If the space later answers with a\ndifferent account, the check refuses `CONFLICT` (`ACCOUNT_MISMATCH`) and changes nothing: confirm\nthe space was not replaced before you remove that file by hand.\n\n`register` asks the running daemon to request its own service registration with a space. It first\nruns `connect-check` and returns any outcome other than `ready-to-register` unchanged, without\ncontacting the space again. Otherwise it sends one registration request for the daemon's instance\nwith this machine's sign-in, through the space's stock service authority. The daemon never sees or\nrelays that sign-in proof. The request ID is saved before the first send, at\n`<COTAL_HOME>/daemon/<space segment>/register-request.json`, and every later `register`, including\nafter a restart, replays the same request unchanged. The stock writer does not guarantee durability\nacross power loss.\n\nThe fixed service name binds to one owner per space. Different owners in a shared space need distinct\noperator-approved endpoint names; this command does not yet select one. The operator must verify or\ndelegate the namespace before binding it.\n\nThe space answers in one of three ways, each reported with exit 0:\n\n- `approval-required`: the space owner must approve this daemon and the space operator must bind its\n service name `ai.cotal.machine`.\n- `pending`: the space is processing an approved registration.\n- `registered`: the registration is recorded. It is not activated or serving, so the machine is\n still not connected.\n\nNo release ships a command or page that lets an owner approve the daemon or an operator bind its\nname. A refusal by the space is `PERMISSION_DENIED` with the space's reason as the native code, such\nas `PROPOSAL_REFUSED`. A reason the daemon does not recognize is reported as `UNRECOGNIZED`, and the\nspace's own text is never shown. A new proposal after a refusal needs a new request, which `register`\ndoes not send. A sign-in the provider rejects (expired or revoked) is `AUTH_REQUIRED`: run\n`cotal daemon login` again. A failure before anything was sent is `DEPENDENCY_UNAVAILABLE`. Only a\nrequest whose outcome is unconfirmed is `OPERATION_UNKNOWN`: run the same command again\nto replay it. A changed registration for the same instance is refused `CONFLICT` and nothing is sent.\n\n`service-connect` connects the running daemon to the space as its own registered service. It replays\nthe saved register request unchanged to read its registration, gets its service credential from the\nspace's auth provider, verifies the bearer signature and witness against the pinned authority's\nissuer keys, checks the serving snapshot against its embedded contract, and connects to the broker\nas that service. It binds the registered\nresponder before reporting ready. It succeeds, exit 0, only after the broker accepts the connection,\nthe responder subscriptions are flushed and the space accepts the daemon as ready. The renewal and ready request IDs are kept in\n`<COTAL_HOME>/daemon/<space segment>/service-store/`, mode `0600`, and reused after a restart.\n\nEvery other answer is a refusal and nothing is connected. Not yet approved is `LIFECYCLE_BLOCKED`\n(`APPROVAL_PENDING`). A refused registration is `PERMISSION_DENIED`, and so is a space that ended the\ndaemon's authority, with its code as the native code, such as `APPROVAL_REVOKED`; connecting again\ndoes not reconnect it. A space or broker that cannot be reached is `DEPENDENCY_UNAVAILABLE`, and\nconnecting again retries the same requests. Without a register request the answer is\n`TARGET_NOT_FOUND`.\n\nWhile the daemon runs, the connection renews itself and reconnects on its own. The access bearer\nlives at most 60 seconds, so the broker drops the connection when each one expires and the daemon\nreconnects with the next. While the broker connection is down, for that reason or any other, the\nanswer is `connecting`, not connected; it is connected again only after the broker is back and the\nspace accepts the daemon as ready again. This is not a persistent service: a restarted daemon starts\nwith no service connection and stays disconnected until you run `service-connect` again, and an\nended connection is remembered only until the daemon stops.\n`service-status` reads the daemon's current answer without contacting the space. `service-disconnect`\ndrains the responder and closes the connection, and stopping the daemon closes all of them.\n\nThe service answers its existing `machine-status` command with `null`. This is a liveness response;\nit contains no vitals, hostname or machine metadata. Its stock Describe answer is explicitly public:\nit identifies the endpoint and derived owner token and includes the protocol and command schemas.\nCallers still need broker-enforced invoke access to the selected instance and command. Connecting the\ndaemon grants no caller access. Owner approval, operator name binding and caller grants remain\nseparate actions. An authority that does not publish its issuer identity cannot support this\nserving connection; the daemon refuses rather than trusting a decoded token or using Manager\ncredentials. The registration contract is unchanged.\n\n## service\n\n```bash\ncotal service [--space <s>] [--json|--plain]\ncotal service show <name>\ncotal service add <name> --exec \"<command>\" [--cwd <dir>]\ncotal service add <name> --preset <preset> <preset-options>\ncotal service add <name> --file <definition.json>\ncotal service start <name> [<preset-options>] [--instance <id>|--new-instance] [--foreground|--dry-run]\ncotal service stop <name> [--instance <id>] [--with-agents] [--yes|--dry-run]\ncotal service restart <name> [--instance <id>] [--with-agents] [--yes|--dry-run]\ncotal service status <name> [--instance <id>]\ncotal service logs <name> [--instance <id>] [--tail <n>] [--follow]\ncotal service install <name> [--instance <id>] [--dry-run]\ncotal service uninstall <name> [--instance <id>] [--yes|--dry-run]\ncotal service remove <name> [--instance <id>] [--yes]\n```\n\n`svc` and `services` are aliases. Every form takes `--space`, `--json` and `--plain`.\n\n| Flag | Meaning |\n|---|---|\n| `-s, --space <s>` | The space whose services to use, resolved as in [Selecting a space](#selecting-a-space). A space that is prepared but not started can be named |\n| `--exec <command>` | add: your own program and its arguments, quoted as in a shell but run without one |\n| `-C, --cwd <dir>` | add `--exec`: the folder it runs in (default: this folder) |\n| `--preset <name>` | add: save a declared preset. Its options become flags of `add` |\n| `-f, --file <definition.json>` | add: import a version-1 definition file |\n| `--instance <full-id>` | Select one instance by its full ID, not a prefix |\n| `--new-instance` | start: allocate another instance of a process definition |\n| `--foreground` | start: keep the process in this terminal |\n| `--with-agents` | stop, restart: request the manager's native agent-stop policy |\n| `--dry-run` | start, stop, restart: preview the effect. install, uninstall: inspect the unit |\n| `-y, --yes` | stop, restart, remove, uninstall: confirm in a script |\n| `--tail <1..1000>` | logs: how many records to read (default 100) |\n| `--follow` | logs: keep reading |\n\nA service is a saved definition in one space plus the instances started from it. Bare `service`\nlists every service you can name here with its state and the one command that moves it on: the\nbuilt-in services (the presets), whether started or not, and the ones you added. Nothing starts a\ndependency for you: starting the manager does not start its broker.\n\nA built-in service needs no `add`. `cotal service start <preset>` saves a definition of that name\nfrom the preset's defaults and starts it; stderr says what it saved. A preset that needs something\nonly you can give, such as the `web` dashboard's credential file or the intake's keys, refuses with\nthose flags named, and takes them on the start line: `cotal service start web --creds <file>`. A\nstart refused before anything launched forgets the definition it saved. A preset name acts on the\nspace's one service built on that preset, so after `cotal up` the name `delivery` reaches\n`stack-delivery`. The manager has its own [`manager`](#manager) commands. `--json` writes one envelope for every form,\nand `logs --follow --json` writes one record per line. Without `--json`, a follow's last line says\nin a sentence why it ended. `logs` refuses a log file this account\ncannot read with `PERMISSION_DENIED` (exit 2), naming the file and `chmod u+rw`, quoted when a\nshell would split the path. The failed read changes no permissions or service state. With `--follow`\nthe refusal is the stream's `error` record, which carries that step as `remedy`, and the stream ends.\n\n### Adding a service\n\nYour own program is one line:\n\n```bash\ncotal service add reviews --exec \"node /srv/reviews/server.mjs --port 8080\"\ncotal service start reviews\n```\n\n`--exec` splits its text into the program and its arguments as a shell quotes words, but runs no\nshell. Single and double quotes and backslashes work as in a terminal. A pipe, a redirection, a\n`;` or `&`, a variable, a glob or a `~` outside quotes refuses `USAGE_INVALID` (exit 1), because it\nwould not mean here what it means in a terminal; for those, run a shell yourself with\n`--exec \"sh -c '<command line>'\"`. A bare program name is looked up on `PATH` as `add` saves it, and\na relative path is saved as the absolute path it names in `--cwd`, which defaults to the folder you\nrun `add` in. The service is ready once its process runs. `--file` keeps the full definition shape\nbelow, for readiness over HTTP, environment references or a credential file.\n\nThe presets built into the CLI are `auth-service`, `broker`, `delivery`, `exec-endpoint`,\n`feedback-intake` and `manager`. The `linear` and `web` presets come with the `@cotal-ai/linear`\nand `@cotal-ai/web` extensions, which `cotal ext add <package>` installs. Bare `service` lists the\npresets this machine has, and a name it does not have is refused as `DEPENDENCY_UNAVAILABLE`.\n\n`cotal service add <name> --preset <preset> --help` lists a preset's options. An option the preset\nfills itself shows `(default: ...)`, such as the space's root for `--root` and `pty` for the\nmanager's `--runtime`. A required option with no default is marked `(required)`, and an option that\ntakes a path shows `<dir>` or `<file>`. A missing required option is refused with each one named,\nand the refusal prints the command line with them.\n\nA preset's options describe the service and never choose its space. `add` saves into the space\n`--space` names, or else the selected space or the folder's, as other commands resolve it.\n\n`add` checks the options against the preset's schema, and checks that each declared path exists\nwith its required protection. It does not check that a root or credential suits the preset, such\nas a root's auth mode or which credential a file holds. Those are checked when the service starts.\nA refusal names the option or field at fault. Option values are not printed, except the `broker`\npreset's `server`: `add` checks it against the address rules a start applies, and its refusal quotes\nthe address unless it holds an `@`, which may be a credential.\n\n`--file` imports a version-1 definition. This one runs a program and treats it as ready once its\nloopback health URL answers 200:\n\n```json\n{\n \"schemaVersion\": 1,\n \"space\": \"main\",\n \"launch\": { \"type\": \"process\", \"executable\": \"/usr/bin/node\", \"args\": [\"/srv/reviews/server.mjs\"], \"cwd\": \"/srv/reviews\" },\n \"readiness\": { \"type\": \"http\", \"url\": \"http://127.0.0.1:8080/health\", \"expectedStatus\": 200, \"timeoutMs\": 10000 },\n \"envRefs\": { \"REVIEWS_TOKEN\": \"REVIEWS_TOKEN\" },\n \"credentialRef\": { \"kind\": \"file\", \"path\": \"/srv/reviews/reviews.creds\" }\n}\n```\n\n| Field | Meaning |\n|---|---|\n| `schemaVersion` | `1` |\n| `space` | The selected space's name. Another name is refused |\n| `launch` | `{type: \"process\", executable, args, cwd}`: an executable by absolute path, by a path relative to `cwd`, or by a bare name that `add` finds on `PATH`, at most 256 arguments of at most 8192 characters each, and an absolute working directory. Or `{type: \"adapter\", adapter, options}` for an installed adapter, which a preset writes for you |\n| `readiness` | `{type: \"process\", timeoutMs}`: ready once the process runs. `{type: \"http\", url, expectedStatus, timeoutMs}`: ready when a loopback `http://127.0.0.1` or `http://[::1]` URL answers that status. An adapter definition uses `{type: \"adapter\", timeoutMs}`. `timeoutMs` is 1 to 120000 |\n| `envRefs` | Optional. Child variable name to the name of a variable in the environment `start` runs in. `CLAUDECODE` and names starting `COTAL_` or `CLAUDE_CODE_` are refused |\n| `credentialRef` | Optional. `{kind: \"file\", path}`: an absolute, protected credential file. The child receives its path as `COTAL_CREDS`, never its contents |\n\nA `space` that names another space is refused `SCHEMA_INVALID`, exit 1, before `add` checks whether\nthe selected space already has a service of that name. No other field is accepted. `add` saves the\ndefinition with the space's root, which `show` lists as `Added in`; a file that sets `root` itself\nis refused `SCHEMA_INVALID`, exit 1, and nothing is saved. The file is at\nmost 256 KiB. A `readiness` value that breaks these rules refuses `SCHEMA_INVALID`, exit 1, naming\nthe field and its rule, such as a `timeoutMs` outside 1 to 120000, a field the chosen type does not\ntake, or a `url` that is not on loopback. A launch argument over 8192 characters refuses\n`SCHEMA_INVALID`, exit 1, naming its position and that limit.\n\nA process launch runs its executable as you, so the executable follows the\n[`space create`](#selecting-a-space) rule for other local writers. The file it names, where a link\npoints, every folder above that, and the folder a link is in must be writable only by you or root.\n`add` checks this before it saves, and `start`, `restart` and an installed unit check it again\nbefore anything launches or stops. An unsafe path refuses `PERMISSION_DENIED` (exit 2), and the\nrefusal names each unsafe path and one remedy such as `chmod g-w <paths>`.\n\n`add` looks a bare executable name up on its own `PATH` once and saves the absolute path it finds,\nwhich its receipt shows as `Executable` and `--json` as `result.executable`. Every later `start`\nruns that file, whatever its own `PATH` is. A `PATH` with a relative or empty entry refuses that\nlookup `UNSUPPORTED` (exit 2), as a unit install does, because the entry would resolve against\nwhichever folder `add` runs in. A name not on `PATH` refuses `DEPENDENCY_UNAVAILABLE` (exit 2). A\ndefinition an earlier release saved with a bare name refuses to start `SCHEMA_INVALID` (exit 1);\nremove it and add it again.\n\n### Running a service\n\n`start` records the instance before it launches anything and prints its full ID. A start that fails\nbefore its process exists records the instance as failed. Its refusal names the cause, such as a\nspace that is prepared but not started. `start` can then try again on the same instance, and\n`stop`, `remove` and `cotal down` find nothing to stop.\n\nA definition starts only in the root it was added in. One a forgotten space of the same name left in\nanother folder refuses `start`, `restart` and `install` `CONFLICT` (exit 2) before anything launches.\nThe refusal names the root it was added in and `cotal service remove <name> --space <space> --yes`,\nwhich forgets it so you can add it again for this project. Bare `service` marks it `inherited from\n<root>` in its `State` column and names that `remove` as its Next, and `--json` gives each\ndefinition its `root` and a `rootMatch` of `this-root`, `inherited` or `not-recorded`. A definition an earlier release saved has no recorded\nroot: it starts as before and is listed as `root not recorded`, as are a controller stack's own\nmember definitions.\n\nA `start` or `restart` whose environment does not set a variable that `envRefs` reads refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2) before anything launches or stops. It names each missing variable\nand the child variable it feeds, never a value. `show` lists the references by name.\n\nA `start`, `restart` or `install`, or an installed unit, whose executable or working directory was\nmoved, removed or changed mode since `add` refuses `DEPENDENCY_UNAVAILABLE` (exit 2) before anything\nlaunches or stops. The refusal names the path and what is wrong with it, with its mode when the\nmode is the cause. The Next names the fix, such as `chmod u+x <path>`, quoting paths that need it\nfor executable and working-directory permission repairs. No permissions change automatically.\nFor a path that is gone, it\nalso names `cotal service remove <name> --space <space> --yes`, after which you can add the service\nagain with another path. `add` refuses a path that does not exist the same way and names the field\nto correct.\n\n`stop` and `restart` ask before they act in a terminal and need `--yes` in a script. Enter, Ctrl-C,\nCtrl-D or closed input at that question leaves the service unchanged and exits 0. Without an\ninstalled unit, a `restart` whose process is already gone stops nothing, so it does not ask. A\nstart or stop that finds nothing to do says so and reports `no-op`.\n\n`status` names the file a running instance's process executes, as the kernel reports it, so the\n`stack-broker` row shows which `nats-server` serves the stack. `--json` carries it as `executable`.\nA stopped instance shows no running process, and a host with no `/proc` shows it as not readable.\nFor a background instance with no recorded supervisor, when its recorded state is running but its\nprocess is gone, `status` keeps its historical readiness and records no exit code or signal.\nIts Next names `cotal service logs <name> --space <space> --instance <id>`, where the process's own\noutput is kept.\n\n`remove` forgets a definition once every instance is stopped and no unit is installed. It deletes\nthose instances' logs and lists each file it deleted.\nWith `--instance <id>` it forgets only that stopped instance record, and keeps the definition,\nits other instances and its logs. A command that names no instance while several records exist refuses\n`TARGET_AMBIGUOUS`; its Next lists each instance with its state and the `remove --instance` command\nfor each stopped one, so a finished record stops blocking the bare form. A `start` that selects a\nrecord of an earlier revision of its definition, as a definition moved aside and added again leaves,\nrefuses `CONFLICT` (exit 2) and launches nothing. Its Next names\n`cotal service remove <name> --instance <id> --space <space> --yes` and then `start` again, after a\n`cotal service stop` of that instance when it still runs. A controller stack's\nmember definitions and records are not removed this way, since its `up` and `down` read them:\n`remove` refuses `UNSUPPORTED`, exit 2, and names `cotal down --yes`, then `cotal space forget <space>`,\nwhich removes them with the space.\n\n`install` writes an owned systemd user unit for a saved direct-process definition with no\n`envRefs` or `credentialRef`, and enables it without starting it. It is Linux only. `uninstall`\nremoves that unit. A preset is not installed as a unit in this release, the manager included:\n`install` of one refuses `UNSUPPORTED`, allocates nothing, and for a preset that runs in the\nforeground names the `ExecStart=` line, `cotal service start <name> --foreground`, for a unit of\nyour own.\n\nThe sections [Local service definitions](#local-service-definitions),\n[Service presets](#service-presets) and\n[Owned process services](#owned-process-services)\ndescribe each preset and the process driver in detail.\n\n## reconcile-gate\n\n```bash\ncotal manager reconcile [--space <s>] [--server <url>] [--endpoint <e>] [--instance <id>] [--yes|--dry-run] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space the frozen gate lives in |\n| `--server <url>` | the local mesh | Broker URL |\n| `--endpoint <e>` | `manager` | Endpoint whose gate is frozen |\n| `--instance <id>` | this folder's persisted manager instance | Instance id |\n| `--yes` | ask and default to cancellation | Confirm this selected gate repair outside a terminal |\n| `--dry-run` | off | Resolve the local coordinate without minting or checking native guards |\n| `--json`, `--plain` | terminal report | Finite structured receipt or plain shared report |\n\n**When you need this.** A manager restart killed after deregistration begins but before the new\nincarnation finishes leaves the endpoint's issuance gate *frozen*, held by a\nprocess that no longer exists. The freeze is what stops two incarnations serving at once, which is\ncorrect. The successor manager now completes that dead registration itself on boot, including on\nthe remote user-auth path. A foreign remote manager blocked by this gate also asks the host to repair\nit before one registration retry. Both use the same guard this command uses: they act only when the freeze-holder is affirmatively gone under a complete\nCONNZ sweep (`gone` and `sweepComplete=true`). If that registration's spec write already committed,\nit finishes the same freeze at the committed registration revision. If the spec did not advance, it\nabort-reopens the gate at generation+1 with processEpoch unchanged and continues the normal takeover.\nLive, unknown, unestablishable, and\nwrong-op-kind still refuse; there is no TTL.\n\nUse this command when the automatic path cannot run: the delivery daemon is down, the repair targets a\nnon-manager endpoint, or you want to lift the freeze without starting a manager. It checks that the\nholder really is gone, then finishes the dead operation the same way as the\ninterrupted restart would have: revoke the old credentials, evict their holders with verification,\nand reopen the gate.\n\nThe command revokes the old credentials 16 at a time. It then verifies the holders' eviction in\nshared sweeps of up to 256 holders on the delivery daemon. Each sweep scans the broker a fixed number\nof times and kicks live connections 16 at a time, so holders that are already gone add almost\nnothing and live ones add one broker round trip per 16 connections. The daemon must serve the\n`evictPrincipals` verb; an older daemon refuses it and the gate stays frozen.\n\nEach sweep durably records the holders it verified before the next sweep starts. If a holder is not\nverified gone, the command leaves the gate frozen with those records kept. An interrupted sweep\nrecords nothing, and the sweeps before it stay recorded. A retry still repeats the freeze-holder\nliveness check, then skips only progress bound to the same registration operation, frozen-gate\nrevision, and holder set.\nThe output reports holders completed before this attempt, completed now, and still remaining. A new\nfreeze or changed holder set starts from zero. Cursor cleanup happens only after reopen; a retained\ncursor is harmless because its old gate revision cannot authorize a later freeze. The governance slot\nrelease after the reopen is cleanup in the same way: a slot it fails to release does not fail the\nrepair, because the reopened gate is already past the slot's generation.\n\n**It refuses far more often than it acts, on purpose**, and always says which check stopped it:\n\n| Refusal | What it means | What to do |\n|---|---|---|\n| `holder-alive` | The freeze-holder still has a live connection: a manager *is* running | Stop that process first. Reconciling would evict a live manager's credentials |\n| `holder-unknown` | The connection sweep could not prove the holder absent | Not safe to proceed: an unprovable holder is treated as a live one. Re-run once the broker answers completely |\n| `liveness-unestablishable` | The delivery daemon gave no verdict: it was unreachable, timed out, or refused | Act on the delivery lease line in the refusal (below). Silence is never read as death |\n| `not-frozen` / `no-gate` | The gate is open, or there is no gate at that coordinate | Nothing to repair: check `--endpoint` / `--instance` |\n| `wrong-op-kind` | Frozen under a takeover or retirement, not a registration | Out of scope for this command; it will not reinterpret another operation's intent |\n| `eviction-unverified` | The holder looked gone but eviction could not be verified | The gate is left frozen, unchanged. Investigate the broker before retrying |\n| `raced` | A newer manager moved the gate mid-repair, and nothing was reopened | Run the command again, which reads the gate afresh |\n\nWhen the daemon gives no verdict, the refusal also reads the delivery lease (`lease.0`) and names\nwhat is blocking the rail:\n\n| Lease reading | What to do |\n|---|---|\n| absent | No daemon is running. Start it (`cotal up` runs it) and re-run |\n| unreadable | The daemon cannot be named, so do not assume none is running. Fix the lease read, then re-run |\n| held, not ready | That holder claimed the shard and has not bound its rails. Wait for it, or stop it so its lease lapses |\n| held, ready, no answer | The query may have gone to another daemon still subscribed to the rail, such as a stopped one whose lease lapsed. Re-run before stopping anything. If no run gets an answer, stop any other delivery daemon for the space, then stop or restart the holder |\n| changed hands | The holder took the shard after the query was sent, so it was never asked. Re-run before stopping anything |\n\nThe command reads the lease before it sends the query and again after the query fails. It names a\nholder as the blocker only when the same run of the same daemon held the lease both times, and two\nrows from a daemon too old to record its run never count as the same run. Even then a ready holder\nmay not have been asked: the rail is queue-grouped, so any daemon still subscribed to it can take\nthe query. A row whose times are not valid dates reads as unreadable.\n\nA daemon that answered and refused keeps its own reason, followed by the same lease line. The lease\nline names the holder, whether it is ready, the space account that holds the lease bucket, when that\nholder acquired the shard, and when the row was last written. A ready holder rewrites the row on\nevery renewal and keeps its acquisition time, which only a successful acquisition sets. A row\nwritten by a daemon that predates the acquisition time reports it as unknown. The lease reads never\nchange the outcome: the gate stays frozen and the command exits 2. A manager's boot self-heal uses\nthe same check and reports the same line.\n\n`raced` means the repair's own reopen lost its compare-and-set and wrote nothing. A reopen whose\noutcome is unknown, such as one whose acknowledgement was lost, fails with an `unavailable` error\nthat says it may have committed. Re-run the command: it refuses `not-frozen` if that reopen landed and\nfinishes the repair if it did not.\n\nThere is no `--force`, and no path that discards gate state: the only way this reopens a gate is by\nproving the holder is gone and then completing the operation properly.\n\n**What reopening the gate does for the endpoint's governance slot.** A registration takes the\nendpoint-wide governance slot before it publishes its spec, and holds it until its gate reopens. An\ninstance that died between those two points leaves the slot held with no registration behind it.\nThis command does not write that slot and never has; the registration path is its only writer. What\nthe reopen does is advance the holder's gate past the generation the slot is stamped with, which is\nwhat marks the slot abandoned. The next registration for that endpoint then reclaims it as part of\nits ordinary start. So the repair here is still one command followed by starting the manager, and\nthe slot needs no separate step.\n\n## endpoint deregister\n\n```bash\ncotal endpoint deregister <class> <full-instance-id> [--space <space>] [--server <url>] [--yes|--dry-run] [--json|--plain]\n```\n\nThis removes a departed instance's registration, not a process or an agent. Both the native\nclass and full instance ID are required; there is no default instance, prefix match or saved\nreference. The selected static space must have validated local signing authority. Open and\nuser-auth targets refuse instead of borrowing a static credential path.\n\nThe command resolves one root, account and broker policy, including its TLS requirement.\nA terminal reviews that full target and defaults to Cancel. Scripts need `--yes`.\n`--dry-run` reports the offline target and intended guard without contacting the broker,\nissuing an operation credential or deleting anything. It does not prove apply will be allowed.\n\nApply delegates to the existing native guard. An answering instance refuses. An unanswered\ninstance also refuses unless the broker affirmatively reports that instance rail empty.\nAn unavailable probe or an in-flight registration refuses. After that evidence, the native\nhelper checks governance and deletes status before spec, pinning both revisions. The issuance\ngate is not changed and no process is stopped; the same instance may register on a later start.\n\nThe JSON/plain receipt retains the full coordinate and acknowledged removed revisions.\nA registration that moves during deletion is not success: its spec was not removed, but an\nolder status may already have been removed. That uncertain outcome retains the target and is\nnot retried. Raw probe diagnostics and credential material are not copied into the receipt.\n\nThe old `deregister-instance` spelling is a hidden migration refusal. It starts no operation\nand names the literal replacement, and says that the replacement needs a static space's signing\nauthority, since an open or user-auth space has no command that removes a registration. Its former\ndefault-to-this-folder instance is not retained.\n\n## runtimes\n\n```bash\ncotal runtimes [--json | --plain]\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`, `herdr`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\nRUNTIME STATE PACKAGE\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux not installed @cotal-ai/tmux\ncmux not installed @cotal-ai/cmux\nherdr not installed @cotal-ai/herdr\nadd one that is not installed with `cotal ext add <package>`\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `not installed`\nmeans it is a known runtime you can add with `cotal ext add` and the package shown. `--json` writes\none document whose `result.items` are `{name, state, package, reachable, error}`, with `state` one of\n`built-in`, `installed` or `not-installed` and `reachable` null when the provider was not asked. An installed provider\nwhose package fails to load is shown with its error and makes the read `partial` (exit 4). The command\nreads this machine only, so its `target` is null. Selecting an unknown or uninstalled runtime\nvia `up --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage. `spawn --runtime <name>` refuses the same way when the launch goes to this root's own\nmanager. A launch that may reach a manager on another host leaves the check to that manager, as\n[`spawn`](#spawn) describes. `up --runtime` chooses the runtime every seat uses unless that seat names its own. `spawn --runtime`\nchooses the runtime of that one managed seat, and the manager keeps every other seat where it is. There is no silent fallback to `pty`.\n\n## seats\n\n```bash\ncotal manager seats [--drain [--yes|--dry-run]] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--drain` | off | Retire every seat whose agent has exited. A seat whose agent still runs is kept |\n\n`cotal seats` is the older spelling, hidden from `cotal help`, and runs the same command. A drain\nasks first on a terminal and needs `--yes` in a script, as [Manager maintenance](#manager-maintenance)\ndescribes.\n\nThe pty runtime used to start a detached custodian process for every Linux seat. It now spawns\nin-process, but custodians that an earlier manager started keep running, and one whose agent has\nexited stays resident while a manager still holds its connection. This command lists the custody\nrecords under `COTAL_SEAT_ROOT` (default `~/.cotal/seats`), one line per seat:\n\n| State | Meaning |\n|---|---|\n| `live-child` | The agent process still runs. The seat is never signalled, and a manager can still adopt it |\n| `childless` | The agent has exited, or the record comes from an earlier boot. `--drain` retires the seat |\n| `drained` | `--drain` proved the custodian and the agent gone and removed the record |\n| `refused` | The record cannot be read, carries no start or boot identity, this host publishes no boot identity, or the reap could not prove the processes gone. The record stays on disk |\n\nA drain signals only a custodian whose recorded start identity still matches the live process, so\na reused pid is never touched. No process outlives a reboot, so a record from an earlier boot is\nreported childless and `--drain` removes it without signalling anything. A record with no start or\nboot identity is refused with or without `--drain`, and is never reported as running or exited.\nOn a host that publishes no boot identity (`/proc/sys/kernel/random/boot_id`) every record is\nrefused the same way, because no record can be tied to this boot.\nThat refusal and an unreadable record signal nothing. A refusal from the reap itself can come after the drain already\nsent `SIGKILL` to the custodian. Its detail names the pid or process group the reap could not prove\ngone, so check those processes before you retry. The command exits non-zero when any record is\nrefused. It is Linux-only, and on other platforms it refuses `UNSUPPORTED`.\n\n## send\n\n```bash\ncotal send -t <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send -c <channel> \"<text>\"\ncotal send -r <role> \"<text>\"\n```\n\nChoose one destination: `--to/-t` for a direct message, `--channel/-c` for a channel,\nor `--role/-r` for the role's native work queue. Choose one body source: a quoted positional\nargument, `--file/-f <path>`, or explicit `--stdin`; `--file -` reads stdin as `--stdin` does. There\nare no implicit stdin reads. Files and stdin must contain nonempty UTF-8 text; their content is\npreserved and never printed in the receipt. A file that cannot be used is refused with its path:\n`TARGET_NOT_FOUND` when it does not exist, `PERMISSION_DENIED` when this account may not read it,\nand `USAGE_INVALID` (exit 1, without the command's help) for a directory or text that is not UTF-8.\nThe refusal names the mesh the send was aimed at when this machine's records name one, and its\nremedy keeps the destination with a quoted body. No destination, several destinations, several body sources or an empty message is refused. A lone\n`-` is refused too, because stdin is read only through `--stdin` or `--file -`. An empty\n`--to`, `--channel` or `--role` value is refused for that flag, and the remedy keeps it.\nThe recipient grammar is checked before connector installation or target preparation. Channel\nand role names use the native subject grammar. A channel name holding `*` or `>` is refused with\n`name one concrete channel, without wildcards`, and a channel or role name outside the grammar is\nrefused with the rule it breaks.\nA direct target resolves by its full identity or an unambiguous name. Names match without regard\nto case, so `bob`, `Bob` and `BOB` are one name, and a participant whose name another shares in any\ncase is reached by its full identity. A name that no participant\ncan carry, such as one holding `\\` or longer than 128 characters, is refused with the name rule\nbefore any presence lookup. A name collision refuses\nwith `TARGET_AMBIGUOUS` and lists the full identities that carry the name, each with its display\nname, its kind (`agent`, `endpoint` or `chat session`) and the local time its process started, and a target that is not\npresent refuses with `TARGET_NOT_FOUND` and points at `cotal who --all`. No fuzzy match selects an\nagent for you.\n\nFor a channel post, repeat `--mention <agent>` to add explicit wake hints without changing the\ndestination. Each one resolves like a direct target and is sent as that agent's name, which is what\na receiving session compares. An instance id is refused when its name also resolves to another\nparticipant, and a shared name refuses with the full identities to send to with `--to` instead.\nDirect and role sends have no native mention field and refuse that option as `USAGE_INVALID`\n(exit 1), before connecting.\nA direct send may use `--reply-to <message-id>` to identify the message it answers. The sender\nstill has no inbox and does not promise thread continuity.\n\nThe receipt names the destination and message identifier without repeating the body. Direct and\nrole sends include the broker's stored sequence. Storage does not confirm delivery, acceptance\nor completion. A direct send to an agent the roster shows offline also warns on stderr:\n`<agent> is offline. The message is stored and delivered when <agent> reconnects; a new agent spawned as <agent> does not receive it.`\n`--json` lists it as the `RECIPIENT_OFFLINE` warning. Use `--json/-j` for the same receipt as one versioned envelope, `--plain` for a text report, or\n`--verbose/-v` to show the actual sending principal. A post to an unregistered channel warns\n`CHANNEL_UNREGISTERED`: it says whether the channel already has messages or this message starts it,\nand lists the space's registered channels, so a mistyped name stands out. It reads that count as\n`cotal history` lists channels. Its native space policy still controls publication and no registry\nmetadata is created. Metadata or a count it cannot read stays not checked. A role send is stored\nwhether or not anyone holds the role. When no present participant holds it, the send warns\n`ROLE_NOT_HELD`: the message waits on that role's queue until one takes it, and the warning names the\nclosest role a present participant holds, or lists the roles present when none is close. A roster\nthat is not current within two seconds adds no warning. A message over the broker's message limit, envelope included, refuses\n`UNSUPPORTED` (exit 2) with its size and the limit, and nothing is sent. A file or stdin body is read\nonly to one byte past that limit. A pipe or a device such as `/dev/zero` has no size to report, so\nits refusal names the limit alone. A channel post the broker\nrefuses was not stored, so it refuses `PERMISSION_DENIED` (exit 2): the channel does not exist in\nthe space or this identity may not post to it. The remedy lists the channels this identity may post\nto, read from the grant ledger as `cotal whoami` reads it, or with `--creds` from an agent file's own\nclaims. When the ledger is on another machine it says so and lists the space's registered channels\ninstead. A broker whose JetStream store holds more\nthan its cap refuses every write with JetStream error 10023 and stores nothing, so `send` refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2), naming the store and the `--max-file-store` cap this machine\nrecorded for it. Its Next is `cotal history clear --yes`, which purges the space's channel history;\na larger cap applies after that through `cotal down` and `cotal up --max-file-store <bytes>`. Any other unconfirmed publish outcome is\nunknown and never retried automatically. A broker that cannot write its store sends no\nacknowledgement at all, so when such a publish times out and the space's broker log on this machine\nrecords a store write failure, the `OPERATION_UNKNOWN` message names its time, cause and file and\nsays the message was probably not stored. Its Next names the fix for that cause, write access to\nthe store for a permission error or free space for a full disk, and restarts the broker before the\nmessage is sent again. When the broker disabled JetStream, it adds that `cotal down` names the\nmanager's process to end first if the manager's stop cannot be recorded.\nThe command sends once and exits. It does not wait for an answer, retain an inbox\nor transfer replies to a later chat session.\n\nThe sending principal comes from the selected space's credentials or user login. Its display name\nis the OS login and host, not `COTAL_NAME`. A one-shot sender never registers as a participant.\nThe broker and native channel policy still decide which publications are authorized.\n\nThe transient endpoint never joins the roster and binds no inbox. With the static credential this\nmachine records for the space, or on an open mesh, it reads the roster and the channel registry once\nwhen it connects and binds no watch consumer, so it leaves nothing on the broker when it exits. On a\nuser-auth mesh it runs on your user bearer, which has no batched read of those buckets, so it watches\nthem instead, and each watch's consumer stays on the broker for five minutes after the send exits.\nA `--creds` credential reads once when its own grant allows the batched read, and watches otherwise,\nas an agent credential from `cotal auth mint` does. A sustained send rate on a watch can reach the\nbroker's limit of 1000 consumers per stream, and the broker refuses further sends until the old\nconsumers expire. A channel send also reads that channel's policy, with one Direct Get where the\ncredential allows it and with one more scan consumer otherwise. A `--creds` credential that may not\nread the roster and the channel registry at all refuses with `PERMISSION_DENIED` (exit 2) before\nanything is sent, and the remedy says to run without `--creds`.\n\nA recipient can still answer a `send --to` or `send --role` with `cotal_dm` or from a `chat`\nsession. On a static or open mesh every send has its own principal under the same display name, so\nthe sender's name works only while it names one principal among the senders the recipient has heard\nfrom and the present peers. Once the recipient has heard two sends from the same login and host, a\nreply by that name refuses with `TARGET_AMBIGUOUS` and lists the principals. When none of them is\npresent on a current roster, the refusal says they are no longer present and names how to answer one\nmessage. Answer by the id on the message instead (`from.id` in `chat --stdio`, with `replyTo` set to\nthe message id), or with `/reply` in `chat`. The reply is stored\nunder the sender's id in the space's DM history, which an operator's DM view such as the\ndashboard's Direct messages lens shows. The `cotal send` that asked has already exited, so the reply\nnever reaches that shell.\n\n## chat\n\n```bash\ncotal chat [<channel>] [--name <n>] [--plain | --stdio] [--space <s>] [--server <url>]\n```\n\n`chat` opens one live session in the selected space, under an identity of its own. Each opening is\na new principal with its own inbox and presence, so two windows are two sessions, and leaving ends\nthe session. `--name` sets its display label, 1 to 128 characters, not blank and without control\ncharacters; a longer label is refused as `USAGE_INVALID`. The default is your OS login and host with\na short suffix. The label carries no authority. When another live participant already carries the label,\nthe session still opens and warns on stderr, naming that participant, because a direct message by the\nname refuses as ambiguous while both are present.\n\nThe mesh is resolved once, and its recorded auth mode picks the admission. No other mode is tried.\n\n| Mode | Admission | Lifetime |\n|---|---|---|\n| open | Routing identity only, with no credential | Until you leave |\n| static | The space's manager admits the session and mints its own credential ([Static chat sessions](control-surface.md#static-chat-sessions)) | 15 minutes, never renewed |\n| user | The auth service admits it under your own `cli` grant; sign in with `cotal login` first | Up to 8 hours, never extended |\n\nOn a static mesh, `chat` reads the space's service registry before it asks the manager for a\nsession. When the registry lists no live manager, as after `cotal service stop stack-manager`, it\nrefuses `TARGET_UNAVAILABLE` (exit 2) at once and names bare `cotal up`, as the run verbs do. A\nmanager that stays registered but does not answer, such as one that crashed, is refused\n`TARGET_UNAVAILABLE` too once its answer deadline passes, with `cotal status` as the next step,\nbecause no session was asked for.\n\nA static or user session shows its end time in the banner, and says when it ends a minute before.\nAfter a session ends, `--plain` answers every line with how to leave, and `/quit` or the end of\ninput leaves.\n\nA named channel is the session's initial channel. On a static or user-auth mesh it is also the\nsession's whole read and post list, so a session opened without one can send and receive direct\nmessages only. A send, join or channel mode outside that list refuses with `PERMISSION_DENIED`\nbefore anything reaches the broker. `/leave` does not change that list, so `/join` returns the\nsession to a channel it left. On a session opened without a channel, the refusal says to\nleave and run `cotal chat <channel>` with the flags this session was started with, such as\n`--stdio` and `--space`. Before the session is admitted, `chat` reads the channel registry on the resolved\nmesh the way `send` does, under the same one-shot identity, and says on stderr whether the channel\nis registered, unregistered, or could not be read. The space's native policy decides whether a send\nis allowed, and nothing creates registry metadata.\n\nWhen the channel's replay is on, the session holds its retained messages in the replay window in\nits inbox. The inbox keeps at most 500 unread messages, so a channel that holds more replays only\nits newest 500 and the oldest are dropped. One `inbox` gap reports how many were dropped.\n`cotal history <channel> --limit <n>` reads further back. `channel show` names the bound in its\n`Chat replay` row.\n\nThe terminal view needs a terminal for input and output; without one, `chat` refuses and names\n`--plain` and `--stdio`. `--plain` is the line flow, with a separate confirmation for each send,\nand it writes its punctuation in ASCII as every plain report does.\n`--stdio` runs the NDJSON participant protocol for programs: requests on stdin, records on stdout,\ndiagnostics on stderr. Closing stdin ends the session. `--stdio` does not combine with `--plain`,\nand `chat` has no `--json`. Under `--no-input` only `--stdio` runs.\n\nIn `--plain`, a line you type becomes the draft, and it is sent only when you answer `y` to the\nquestion that follows. Any other answer keeps the draft unsent. A slash command typed as the answer\nruns, and the question is asked again. A new draft that replaces a kept one says so, with the kept\ndraft's length. At the end of input an unsent draft or an unfinished `:multi` message is reported\nby its length and never sent. `:to` lists the recipients by number, and `:to <target>` sets one directly:\n`#<channel>`, `@<name>`, `@<owner>.<actor>` or `role:<role>`. A target in another form, such as a\nname without `@`, is refused with that list of forms, and the numbered list opens. An `@<name>`\nthat names no present peer and no sender the session heard from is refused there with the reason a\nsend to it would get, and the numbered list opens. A `#<channel>`\nholding `*` or `>` is refused with the sentence `cotal send -c` gives. In the list, an empty line\nkeeps the current recipient, and `/quit` leaves. `:multi` starts a message of several lines, which a line\nholding only `.` ends. A line starting with `/` runs a slash command, and `//` sends a line that\nstarts with `/`. `/help` lists the slash commands, in `--plain` and in the terminal view. A slash\ncommand that takes a channel reads it with or without its `#`.\n`/history` reads the current channel's retained messages. On an open mesh, which runs no delivery\ndaemon, it reads them the way `cotal history` does.\n\nA direct message to an agent the roster shows offline is stored, and the view adds a notice that\nthe agent receives it when it reconnects, as [`send`](#send) warns. `--stdio` carries that roster\nstatus as `recipientStatusAtSend` in the stored result.\n\nMessage times show in local time, with the date when it is not today, the same way `history`\nshows them.\n\n`/reply` answers the latest direct message or role task the session received. It addresses the\nsender's principal and quotes the message id, with no name lookup. A sender shows by display name,\nand with its principal when another principal in the session carries the same name, ignoring case.\n`--plain` cannot relabel a line it already printed, so it prints one line naming those principals\nwhen a name becomes shared. A typed `@<name>` must name one principal among the present peers and\nthe senders the session heard from. A name that more than one of them carries refuses and lists\nthem, so type `@<owner>.<actor>` instead. A target that parses as `<owner>.<actor>` goes to that\nprincipal whether or not it is present or was heard from, and is never matched against display\nnames. A peer whose display name parses as a principal therefore cannot be reached by that name;\n`/reply` and its full principal still reach it. Typing the session's own principal refuses with\n`USAGE_INVALID`, and so does its own name when no other participant carries it. A `--stdio` `dm` request resolves its `to` the same way.\n\nEvery admission presents the session as an endpoint, so `--kind endpoint` is accepted and changes\nnothing. `--kind agent` refuses with exit 1 because a chat session never presents as an agent, and\nso does any other kind.\n\n`--role`, `--link`, `--token`, `--creds` and `--lifecycle-uid` are declared but no admission\nsupports them yet, so each refuses with exit 2. A role would put the session on that role's work\nqueue, and the view has no way yet to answer the sender of a task. The static and user-auth\nadmissions take no role.\n\nA session that ends by leaving, by end of input or at its lifetime exits 0 when its connection\nclose was confirmed and it was retired, or was an open-mesh session with nothing to retire. It exits\n4 when its connection close was confirmed and its retirement was requested but not confirmed. It\nexits 3 when the retirement outcome is unknown, its connection close was not confirmed, or no close\nwas observed. A session that cannot reconnect because its inbox is gone from the broker, as when the\nspace restarted and retired it while the connection was down, ends by itself with that reason and\nexits 5 when its close is confirmed. In the terminal view Ctrl-C and Ctrl-D leave. With `--plain` or `--stdio`, Ctrl-C ends\ninput and the session closes, then exits 130. A terminate signal exits 143 and a hangup 129. A\nsignal that arrives before the view starts is held: no session is opened if admission has not\nbegun, and a session admitted meanwhile is closed at once. The exit status is still the signal's.\nWhen the reader of `--plain` or `--stdio` output closes it, as `| head` does, input ends the same\nway and the session closes, then exits with its close status: 0, 3 or 4.\n\nBefore a session is admitted, an error prints on stderr only, since `chat` has no `--json`. An\nerror that ends `chat` exits 1 for `USAGE_INVALID` or `SCHEMA_INVALID`, 2 for any other refusal, 3\nwhen the outcome is unknown and 5 when it failed.\n\n### The --stdio protocol\n\nstdin carries requests and stdout carries records, one JSON object per line, in UTF-8 with LF line\nends. A CR before the LF is dropped, a blank line is ignored, and a last line with no LF still\ncounts when input ends. A line longer than 1048576 UTF-16 code units is discarded up to its next LF\nand reported once with an `error` record. Input is decoded as UTF-8 with replacement, so bytes\nthat are not valid UTF-8 become U+FFFD. In ordinary string content the line can still parse: a\n`text` value is sent with U+FFFD in their place, and a field that accepts only some characters,\nsuch as `id`, refuses. In an escape sequence such as `\\` or `\\u` and its four digits, or outside a\nstring, they make that line's JSON parse refuse. stdout carries records and\nnothing else. The session's diagnostics go to stderr, prefixed `cotal chat: `, and never repeat a\nmessage body.\n\nEvery request and record carries `\"schemaVersion\":1`. A request names its `type` and an `id` you\nchoose, 1 to 128 printable ASCII characters, and its `result` record repeats that id. A request that\nreuses the id of one still in flight is refused with `USAGE_INVALID`, in a result under that same\nid that follows the first request's result. Requests run one at a time in arrival order, and a\nrefusal waits its turn too, so every `result` and `error` record comes back in the order its line\narrived. `message`, `presence` and `gap` records can arrive between results, so match each result\nby its id. A missing field, a field the type does not take or a malformed value refuses with\n`USAGE_INVALID` before the session sees the request. A channel must be a valid channel name with no `*` or `>`, and is refused with the sentence `cotal send -c` gives for that name. A role must be a\nvalid role name. A `text` that is empty or only whitespace is refused with `the message is empty`,\nas `cotal send` refuses an empty message.\n\n| `type` | Fields | Result |\n|---|---|---|\n| `send` | `channel`, `text`, optional `mentions` (names of 1 to 128 characters) | `stored`: `messageId`, `channel`, `registered` |\n| `dm` | `to` (a display name or `<owner>.<actor>`, 1 to 512 characters), `text`, optional `replyTo` (a message id, 1 to 512 characters) | `stored`: `messageId`, `seq`, `duplicate`, `recipient` (`id`, `name`) |\n| `anycast` | `role`, `text` | `stored`: `messageId`, `seq`, `duplicate` |\n| `status` | `status` (`idle`, `working` or `waiting`), optional `activity` | `ok` |\n| `attention` | `mode` (`open`, `dnd` or `focus`) | `ok` |\n| `channelMode` | `channel`, `mode` (`normal`, `quiet` or `muted`) | `ok` |\n| `join` | `channel` | `ok`: `joined`, `backfilled`, `durable`, optional `reason` |\n| `leave` | `channel` | `ok`: `left` |\n| `inbox` | optional `peek` (default `false`) | `ok`: `peek`, `messages` |\n| `roster` | none | `ok`: `rows`, `view` |\n| `channelInfo` | `channel` | `ok`: `channel`, `registered`, `replay`, `readable`, `postable`, optional `description` and `instructions` |\n| `connection` | none | `ok`: `state`, `identity`, `bufferedInbox`, `joinedChannels`, `attention`, `channelModes` |\n| `invoke` | `endpoint` (1 to 200 characters), `command` (1 to 128), optional `args` (an object) and `timeoutMs` (an integer from 1 to 120000, default 30000) | `ok`: the endpoint's data, or `null` when it returned none |\n\n`identity` in the `connection` result names the session, and no other record names its space. It\nhas `space`, `mode` (`open`, `static` or `user`), `principal` (`<owner>.<actor>`), `lifecycleUid`,\n`label`, the `allowSubscribe` and `allowPublish` ceilings, and `expiresAt` in epoch milliseconds\nwhen the session has a set end. A roster's `view` is `current`, `unpopulated` or `stale`, and its\n`rows` are complete only when it is `current`. A row has `id`, `name`, `kind` and `status`, and\n`role`, `activity` and `sessionKind` when they are set. A `status` request with an `activity` sets\nboth in one presence write, so a peer gets one `updated` record carrying both.\n\nA `result` record has `type`, `id` and `outcome`. With `ok` or `stored` it carries `result`, which\nis `null` when the request returns nothing. With `refused`, `unknown` or `failed` it carries `error`\nwith `code`, `message` and, when the native layer gave one, `cause` with its own `code` and\n`detail`. `refused` means nothing happened. `unknown` (`OPERATION_UNKNOWN`) means the effect may\nhave happened. The session never retries it, and it reports an unexpected failure of its own the\nsame way. `failed` (`OPERATION_FAILED`) is a step that did not complete and had no effect. `stored`\nmeans the broker's stream accepted the message. It does not say anyone read it.\n\nThe session also writes these records on its own:\n\n| `type` | Fields | When |\n|---|---|---|\n| `message` | `message`: `id`, `ts` (epoch milliseconds), `kind` (`channel`, `dm` or `anycast`), `from` (`id`, `name`, optional `role`), `channel` or `service`, `text`, optional `replyTo`, `mentionsMe`, `historical`, optional `deliveryCount` | A message arrived |\n| `presence` | `change` (`joined`, `updated` or `left`), `row` (a roster row) | A roster row changed, this session's own row included. As the session opens, its own row and each member already online come as `joined` |\n| `gap` | `scope` (`live` or `inbox`), `reason` | Messages may have been missed |\n| `error` | `id` (`null`), `error` | A line was refused before an id could be read from it, or was too long |\n| `end` | `reason`, `close` | The session ended. Nothing follows it |\n\nA `presence` record's `joined` is a member coming online and `left` one going offline. `updated`\nmeans a field the row shows changed, such as `status` or `activity`. A presence change the row does\nnot show, such as a member's attention or channel modes, writes no record. `cotal who --all --json`\nshows attention.\n\nA message's `text` is untrusted. JSON escapes its control characters, so a body can never start\nanother record. `channel` is set on a channel message and `service` on an anycast. `mentionsMe`\nmeans the message's mention list holds this session's label, ignoring case. The sender writes that\nlist, so treat it as a hint. `deliveryCount` appears only on a durable delivery and is 1 the first\ntime. Above 1, an earlier copy went unacknowledged and may already have been shown.\n\nA message is acknowledged once its record is handed to stdout. A DM or anycast is written when it\narrives. A channel message is held in the inbox instead when its channel is quiet, when attention is\n`focus`, or under `dnd` unless it mentions the session, and join backfill is held too. A muted\nchannel's messages are dropped. `inbox` with `peek` lists the held messages and leaves them held.\nWithout `peek` it returns them, and they are acknowledged once its result is handed to stdout. A\nmessage still unacknowledged when the session closes is left unacknowledged and does not come back\nto this session.\n\n`join`'s `backfilled` counts the messages the join read back from the channel's retained history:\nthose sent before the join and within the channel's replay window, not counting this session's own.\nIt is 0 when the session is already on the channel or replay is off for it. Each of them then follows\nthe rules above. One the session already handed over, holds or dropped is not held again, and one on\na muted channel is dropped, so the inbox can gain fewer messages than `backfilled` says. The session\nremembers at least the last 4096 messages it handled for this.\n\nA `live` gap means the connection dropped or is reconnecting, so live channel messages may be\nmissed until it returns. Its reason defaults to\n`connection <state>: live channel messages may be missed until it returns`. An `inbox` gap means\nthe held buffer was full: channel messages were dropped and direct messages were left for the broker\nto redeliver. Its reason counts each kind that happened. One gap covers the drops of up to a second,\nand a replay past the bound is one gap when the session starts. When a write waits for stdout to\ndrain, or more than 4 MiB of records are waiting, the session stops reading stdin until stdout drains.\n\n`end` is the last record. Its `reason` is `input closed` when stdin ended, `interrupted`,\n`terminated` or `hangup` when SIGINT, SIGTERM or SIGHUP ended input, and `session ended` when the\nsession ended by itself, for example at a static session's expiry. A session that could not\nreconnect gives that reason instead, and its `close` carries it as `lost`.\nWhen stdin ends, the requests already read finish first. `close` has `retirement` (`retired`,\n`requested`, `unknown` or `not-applicable`), `connectionClosed` (`confirmed` or `not-confirmed`) and\n`message`. A static session's `close` can carry `credentialValidUntil` in epoch milliseconds: the\nbroker accepts its credential until then unless its eviction was verified. Once the session has\nended by itself and before the `end` record, a request refuses with `LIFECYCLE_BLOCKED` and\n`the session is ending; no new requests are accepted`. When the reader closes stdout, stderr says\n`stdout closed by the reader; ending the session`, and no later record is written, `end` included.\nAny other failed write, such as to a full disk, says `stdout could not be written; ending the\nsession` and the one line about lost output, and the session ends the same way with exit 3.\n\n## channel\n\n```bash\ncotal channel\ncotal channel set <name> [--replay | --no-replay] [--window <duration>] [--delivery <live|durable>] [--desc <text>] [--instructions <text>]\ncotal channel defaults [--replay | --no-replay] [--window <duration>] [--delivery <live|durable>]\ncotal channel show <name>\ncotal channel members <name> [--follow] [--json | --plain]\ncotal channel remove <name> [--purge] [--yes]\n```\n\n`channel` has the shorthand `ch`; `remove` also accepts `rm`. The inventory shows configured\nmetadata, not every channel with traffic. `show` includes inherited replay and delivery settings.\n`members` reads the broker-derived membership feed. Its timestamp is the last successful feed\npoll, not proof of current activity or access. With `--follow --json`, it emits membership NDJSON\nrecords. A missing feed remains unknown rather than an empty confirmed census. Without `--follow`, a\ncensus with no member for a name that has no saved entry and no retained messages is refused with\n`TARGET_NOT_FOUND`, listing the registered channels, or naming the registered spelling when only the\ncase differs. A channel with a saved entry or retained messages and no member reads as empty. A\nfollow shows the census again only when it changes: a poll that observes the same members writes no\nrecord and prints nothing, and the `end` record's `lastAsOf` is the last poll the follow read. A follow's\nview offers no command to follow. A lost connection is\none `TARGET_UNAVAILABLE` error record, or one line, while the follow re-establishes it, and a `gap`\nrecord and the current census follow once the feed answers again, so a follow outlives a broker\nrestart. A credential that can no longer connect ends the follow with the refusal a one-shot\ncommand with that credential gets, as [`history --follow`](#history) does. The delivery service\nwrites that feed, and an open mesh runs none, so `members` refuses an open mesh with `UNSUPPORTED`.\n\nRemoval keeps retained history unless `--purge` is present. The confirmation names the channel,\nspace and history scope. Scripts pass `--yes`. A name with no saved entry and no retained messages\nis refused with `TARGET_NOT_FOUND` before the confirmation, listing the registered channels, or\nnaming the registered spelling when only the case differs. Removing a channel that has retained\nmessages but no saved entry is a `no-op`, and its Metadata row says `no saved entry`; `--purge`\nstill deletes that channel's retained messages. In `--json`, `result.removed` says whether a saved entry was removed. Neither\nremoval nor replay settings revoke grants or stop participants; future posts can create retained\nhistory again. A `set`, `defaults` or `remove` that the broker refuses for the credential's grant\napplied nothing, so it is refused with `PERMISSION_DENIED` (exit 2), naming the credential's profile,\nthe write it lacks and the subject the broker refused. With `--creds`, the Next is to run it without\n`--creds`, on the operator credential this machine records. A remove whose credential may not read\nthe registry skips the name check, and the broker's answer to the remove decides. A change that the\nroot's default `.cotal/channels.json` would undo at the next start warns that it will, as the\n[channel seed file](#channel-seed-file) section says.\n\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | none | `set`/`defaults`: replay history to new joiners, or not. They take no value: `--replay off` is refused with the flag to use |\n| `--window <duration>` | none | `set`/`defaults`: replay window, for example `24h` |\n| `--delivery <live\\|durable>` | none | `set`/`defaults`: choose the live-only or durable-backstop delivery class; history retention is separate. Shell completion offers both values |\n| `--desc <text>` | none | `set`: channel description |\n| `--instructions <text>` | none | `set`: instructions shown to joiners |\n| `--purge` | off | `remove`: also delete the channel's retained history |\n| `--yes` / `-y` | off | `remove`: confirm the stated channel and deletion scope without a question |\n| `--follow` | off | `members`: follow broker-observed membership changes |\n| `--json` / `-j` | off | One JSON document; `members --follow --json` emits NDJSON records |\n| `--plain` | off | The same report without color |\n\nInspects and edits the channel registry: replay and delivery policy, description, and joiner instructions. `live` has no per-member backstop; it does not disable ACL-bounded retained history. Changing a channel's delivery class does not settle already pending durable copies. `--delivery durable` requires a registered static-auth or user-auth space; an open or unregistered target has no established delivery-service support. A recorded auth mode does not prove that daemon is currently healthy. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `defaults` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\nOn a remote user-auth mesh that view is served by the public exchange; space-history `purger`\nand the read-only admin view are not.\n\n`set` and `defaults` change only the fields you name. Each write lands on the registry entry as it\nwas read, so writers changing different fields of one channel at once all keep their change: one\nthat loses the race reads the entry again and merges again. A write that keeps losing to other\nwriters refuses with `CONFLICT` (exit 2) and saves nothing. Two writers setting the same field leave\nthe value of whichever wrote last. When the entry already holds every value you name, nothing is\nwritten: the receipt is `Channel unchanged` or `Channel defaults unchanged`, and the outcome is\n`no-op` (exit 0).\n\nWith the static credential this machine records for the space, or on an open mesh, `list` reads the\nregistry with one Direct Get and leaves no consumer on the broker. On a user-auth mesh your login has\nno Direct Get row on the registry, and neither has an agent credential passed with `--creds`, so\n`list` scans it with a consumer that stays on the broker for five minutes after the command exits.\n\n\n## history\n\n```bash\ncotal history [<channel>] [--dms] [--since <time|seq>] [--limit <n>] [--from <name>] [--grep <text>] [--follow] [--json | --plain]\ncotal history clear [--dms] [--yes | --dry-run] [--json | --plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also read DM history, which needs a DM read grant. With `clear`, also clear DM history |\n| `--since <time\\|seq>` | none | Only messages after an ISO 8601 date and time, an age such as `30m`, `2h` or `1d`, or a chat stream sequence. A date, or a date and time without an offset, is local time |\n| `--limit <n>` | 50 | How many of the newest retained messages to read, 1 to 1000 |\n| `--from <name>` | none | Keep messages from this sender name or id |\n| `--grep <text>` | none | Keep messages whose text contains this, ignoring case |\n| `--follow` | off | Keep listening and print each new message |\n| `--json` / `-j` | off | One JSON document, or with `--follow` one JSON record per line |\n| `--plain` | off | The same report without color or marks |\n| `--yes` / `-y` | off | `clear`: confirm without prompting. Scripts and `--json` require it |\n| `--dry-run` | off | `clear`: show the selected target and streams without connecting, and purge nothing |\n\nReads the newest retained messages in the selected space, newest last: one channel, or every\nchannel the identity may read. The channel may be written as `#general`, and a name holding `*` or\n`>` is refused with `name one concrete channel, without wildcards` before connecting. A channel with\nno retained messages and no saved entry warns `CHANNEL_NOT_FOUND`, names the channels that hold\nmessages, and reports no replay policy for it. When the space's broker runs on this machine and logged\ndamage in a stored file since it started, `history` warns `STORE_DAMAGE_LOGGED` with the broker's\nwords, because it serves only what it can still read. The read stays complete, as it read the stream\nthe broker serves. A broker whose log is not on this machine is not checked. `--limit` bounds the read, and `--since`, `--from` and `--grep` then\nfilter that window on this machine. They do not search the whole stream, and with older retained\nmessages behind the window the report says that `--from` and `--grep` searched only the messages\nread. At `--limit 1000`, the most it reads, the report says older retained messages are not shown and\nnames no larger limit. `result.window` says how many messages were read, whether older retained\nmessages exist that the window did not reach, and whether the window goes back to `--since`. The report names the local time and the ISO instant a\ntime `--since` resolved to, or the chat stream sequence a bare number was read as. A four-digit\nnumber from 1900 to 2099 is still a sequence, and the report says to write a date in full. A bare\nnumber past the chat stream's newest sequence names no message, so it is a `USAGE_INVALID` refusal\nthat names that sequence and how to write a date. An age outside the supported date range, a date\nthat is not on the calendar, or a sequence that cannot be represented without rounding is a\n`USAGE_INVALID` refusal before connecting. A time is an ISO 8601 date such as `2026-10-23`, or a date and time such\nas `2026-10-23T09:00:00Z`, so `-5` and `10/23/2026` are refused. A date alone, or a date and time\nwith no offset, is local time, so `--since 2026-10-23` starts at local midnight.\nThe report shows each message's local time, with its date when it is not today; `result.items[].ts`\nis the ISO time. A DM row shows its recipient's name when the roster carries that principal or a\nmessage in the window came from it, and the principal otherwise. `--dms` reads the roster for those\nnames, waiting up to two seconds for it. `result.items[].to` is the principal.\nA line break or other control character in a message's text shows as its `\\uXXXX` escape in the\ntext and `--plain` reports, so a message cannot clear the screen or start a row of its own.\nA row shows the first 500 characters of a longer message and marks the cut with the message's size.\n`--json` carries the text as it was sent, in `result.items[].text`.\n\nRetained history is not every message sent. The chat stream keeps a fixed number of messages per\nsender on each channel, so a busy sender's oldest messages are discarded while other senders'\nmessages from the same time remain. `result.retention` names the limit and each sender at it on the\nchannels read, and the report prints a `gap` line for each. A gap is a fact about the stream, not a\npartial read. `result.replay` gives each channel's replay policy for new members, read from the\nchannel registry. DM gaps are not reported.\n\nA channel the identity may not read is refused with `PERMISSION_DENIED` (exit 2), never shown as\nempty. On a user-auth mesh each channel read uses the connection's broker-enforced channel grant.\nRevocation follows the effective credential and connection retirement, as it does for live channel\nreads. Concurrent commands sharing one identity wait for the broker to release its transient history\nreader; no command deletes another reader. Each channel has a ten-second read budget, with a\nthirty-second budget for the whole channel walk. A busy or cut-short read fails with `OPERATION_FAILED` (exit 5) rather than showing\nan empty conversation. Reading every channel leaves out channels the grant does not cover and names\nthem in a `CHANNELS_DENIED` warning. DM history and `--since` by sequence are not available to a\nuser-auth identity. An agent's `--creds` credential reads the same way, one channel at a time under\nthe lifecycle its own history grant names, with the same limits. A channel that grant leaves out is\nrefused with `PERMISSION_DENIED` before it is read, quoting the channels the grant does name, or\n\"no channel\" for a grant that names none, and its `CHANNELS_DENIED` warning names them too. An\nobserver or admin credential reads across channels.\nA part that could not be read is a warning and a `partial` run (exit 4): the\nretention, a replay policy, the channel list when a channel is named, or one channel of several.\n\n`--follow` prints the window and then each new message until interrupted, each as one line in the\nform `watch --plain` uses: `[<space>] <time> <sender> <destination> <text>`, with a 24-hour local\ntime and `#<channel>` or `DM to <principal>` as the destination. A purge by `history clear` is not\nannounced to a follow or to `watch`; messages sent after it keep arriving. A lost connection is\nreported in one line while the follow re-establishes it, so a follow outlives a broker restart. A\ncredential that can no longer connect ends the follow. It may have expired, or the broker may now\nreject it, as after `reset space`. The follow then ends with the refusal a one-shot command with that\ncredential gets, `AUTH_REQUIRED` or `PERMISSION_DENIED`, and its Next. Without `--creds` on a\nstatic-auth space the follow mints its own credential from the space's trust, so when the broker\nrejects it the Next says to run the follow again, which mints one from the current trust. With\n`--json` each line is one record: `{\"schemaVersion\":1,\"stream\":\"history\",\"type\":<type>,\"target\":\u2026,\"receivedAt\":\u2026,\"data\":\u2026}`.\nThe first record is a `snapshot` holding the window, then one `event` per message. An event has\nno `seq`, because the live feed carries no stream sequence; the snapshot's items keep theirs. To\nread on from a follow, give `--since` the last event's `ts`. An `error`\nrecord names a failure on the live feed, and a lost connection is one `TARGET_UNAVAILABLE` error. A\n`gap` record says the connection was re-established and messages sent while it was down are not\nshown. An `end` record always closes the stream. The exit code is the snapshot's, the refusal's\nwhen the credential ended the follow, or 130 or 143 when SIGINT or SIGTERM ended it.\n\n`history clear` purges retained history from every channel in the space it targets; `--dms` extends\nit to direct-message history. It refuses extra channel names, read filters and `--follow` before\npurging. These inputs cannot narrow a clear operation. It is the same operation as [`clean history`](#clean). On a user-auth mesh the purge rides a short-lived\npurger view over your login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\nA purge the broker refuses for the credential's grant purged nothing, so it is refused with\n`PERMISSION_DENIED` (exit 2), naming the credential's profile; with `--creds`, the Next is to run it\nwithout `--creds`, on the operator credential this machine records.\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. The\nline stream is also what a pipe gets. On `--plain`, and under a locale that is not UTF-8, it writes\nno status marks and its punctuation in ASCII. The line stream reports a lost connection in one line\nwhile it re-establishes it. A credential that can no longer connect ends it with the refusal a\none-shot command with that credential gets, as [`history --follow`](#history) does. Ctrl-C or\nSIGTERM ends either view at any time. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. Inside the TUI, operator control (`D` kill, `:spawn`, `:status`, `:purge`) rides the\nsame per-action instrument path as `cotal stop` and `cotal ps`, never the observer; a raw\n`--creds` file cannot drive it. `a` (or `:attach <agent>`) runs\n[`cotal attach`](#managed-seats) in place and returns to the console on detach. See\n[Watch a mesh](watch-a-mesh.md).\n\nMessage bodies, names and other text from peers are untrusted. Both views show a control character\nin them as its `\\uXXXX` escape, so a message cannot clear the screen, retitle the terminal or plant\na hyperlink. The TUI keeps a body's line breaks, and `--plain` prints each message on one line.\n\n<a id=\"web\"></a>\n\n## Web dashboard\n\nThe dashboard is an owned service preset from `@cotal-ai/web`, not a top-level `web` command.\nIt is not started by `up`. An absent extension requires the explicit `cotal ext add @cotal-ai/web`;\nstarting a service never installs it or creates a caller credential.\n\n```bash\ncotal service add web --preset web --space main --root /absolute/registered/root --creds /absolute/private/dashboard.creds --read-only\ncotal service start web --space main\ncotal service status web --space main\ncotal service logs web --space main\ncotal service stop web --space main --yes\n```\n\nThis route requires a registered static-auth target, an existing protected credential file\nand explicit read-only intent. `--host` chooses a concrete bind/browser address, default\n`127.0.0.1`; wildcard binds refuse. `--port` is 1 to 65535, default 7799. Broker coordinates and\nrequired TLS come from the registered target. Broker TLS does not encrypt the dashboard's HTTP\nlistener. The file supplies the only connection authority.\nNo cached human login, managed-seat identity, operator signer or channel-purger credential is\nsubstituted. User-auth/open targets refuse, because no supported caller handoff is connected for them.\nThe page marks this service read-only and hides channel delete; the server also refuses it before\nreading a delete body or opening a mutation connection. The broker still enforces the file's scope.\n\nThe real child exclusively claims native PID custody. After the HTTP listener and signal handlers\nexist, it writes a create-only owner-private session record for the full instance and launch, then\na private process-initialization phase. This is not a fresh authenticated endpoint-health reply.\nLogs print only the session file's reference, never its bootstrap URL or readiness credential. A\nbackground `service start` ends with the `service logs` command for the new instance, which is where\nthat reference appears. An endpoint error or warning in that log names only the error's class, its\nname and code, never its message. Open the private file locally and use its single-use launch link in the\nintended browser. There is no automatic browser action. The existing cookie and origin gates remain in use. Confirmed close\nremoves only this child's unchanged session/PID records; uncertainty retains custody.\n\nForeground service start uses the shared terminal lifetime. OS installation and full native caller\nparity are not supported. An old cached `command:web` descriptor is hidden and refuses with the matching\nextension-update remedy before package loading or repair. It is not resolved by removing the\ndashboard, starting a second process or silently reinstalling it.\n\n## linear\n\n```bash\ncotal ext add @cotal-ai/linear\ncotal linear account add <name> --mode <write|readonly> (--token-stdin | --token-file <path>)\ncotal linear account login <name> --mode <write|readonly>\ncotal linear account show <name>\ncotal linear inventory <account> [--json|--plain]\ncotal linear call <account> <tool> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]\ncotal linear resource <account> <uri> [--inventory <digest>] [--timeout <ms>]\ncotal linear prompt <account> <name> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]\ncotal linear serve <account> --endpoint <reverse-dns-name> [--space <s>] [--server <url>]\ncotal linear caller <name> --endpoint <reverse-dns-name> --out <path> [--channels <a,b>] [--expires-in <s>] [--json|--plain]\n```\n\nServes one Linear account as a registered Cotal endpoint that agents call with `cotal_describe` and\n`cotal_invoke`, and gives the operator a direct client for the same server. Until the extension is\nadded, `cotal linear` and `cotal help linear` name the `cotal ext add` that adds it. It only talks to `https://mcp.linear.app/mcp`\n(mode `write`) or `https://mcp.linear.app/mcp/readonly` (mode `readonly`); there is no URL or header\noption. Use a separate account per Linear workspace, and prefer a `readonly` account with a\nrestricted read key wherever writes are not needed.\n\nAn account's credential is sent as `Authorization: Bearer` and is never taken from argv. `account add`\ntakes a Linear API key: `--token-stdin` stores it under the cotal home as a 0600 file, and\n`--token-file` points at an existing file that must not be readable by other users. The file is read\nat each use, so rotating it needs no restart. `account login` runs Linear's OAuth flow instead: it\nregisters a client, prints the authorize URL, and waits on a loopback redirect. It asks for scope\n`read` in `readonly` mode and `read write` in `write` mode, keeps the tokens in a 0600 file, and\nrefreshes them before a request is sent. OAuth requests only go to `https://mcp.linear.app`.\nRedirects are refused so the credential is never forwarded.\n\n`inventory` reads the server's capabilities and every page of its tools, plus resources, resource\ntemplates and prompts when the server advertises them. Names and schemas are printed as the server\nsent them. The digest covers the whole inventory; pass it to `--inventory` so a call is refused\nbefore dispatch if the inventory changed. Capabilities this command does not represent, such as\nresource subscriptions or logging, are listed under `unsupported`.\n\nBare `linear` and `linear account` show their scoped operations without opening an account or\ncontacting the upstream. Account add/show, inventory and the native call/resource/prompt forms\nsupport shared rich/plain reports and version-1 JSON envelopes. Scoped help and completion use\nthe same operation/flag declarations as validation, and each operation's help shows its own usage\nline. Extra operands and wrong-mode flags refuse before account access; a flag the operation does\nnot take is named, with the flags it does take and its usage. Positional values are bounded to 8192 characters without terminal controls.\nPiped tokens are bounded to 1 MiB of strict UTF-8; their contents and argument parse errors are\nnot echoed. Account reports describe recorded configuration or a native save acknowledgement,\nnot current access to Linear. An account name with no record refuses with `TARGET_NOT_FOUND` and\nthe `account add` route. A record that exists but cannot be read refuses with\n`DEPENDENCY_UNAVAILABLE`.\n\n`inventory --json` retains the full native inventory inside the result envelope. Human reports\nshow at most 60 whole tool rows. Read-only hints remain upstream declarations, not enforcement or\nwork progress. `call`, `resource` and `prompt` retain the complete native reply under\n`result.reply` with `--json`; rich/plain output shows at most 60 whole result lines. The bearer\nis scrubbed by the native client; other upstream content is not claimed to be secret-redacted.\n\nA tool result with `isError: true` remains an MCP result (exit 0), with a visible warning. This does\nnot assert that the requested work succeeded. A native refusal with outcome `not-executed` returns\nexit 2; that includes a deadline that expires while queued, discovering or connecting. An unknown\nprotocol/transport outcome returns exit 3 without replay, including for read-only requests. The\nnative outcome and reason remain in the result. Errors carry a status or class, not HTTP response\nbodies. An interrupted account save or OAuth flow can return unknown because private state or an\nexternal authorization may already have changed.\n\nOAuth login keeps the native human authorization flow, so it offers plain output but not JSON\nand refuses when input is disabled. The existing foreground `serve` route keeps its native authority and human output; it does not\naccept finite-output flags, and `--json` or `--plain` there refuses as a usage error (exit 1) that\nsays so. Its refusals carry their identifiers in the text report instead, such as the proposal to\napprove on a user-auth mesh. The existing `caller` route\nkeeps its native static issuer but supports shared JSON/plain private-file receipts. Its output\nmust be outside managed state in an existing protected physical parent. An existing destination\nrefuses before provisioning. Publication is create-only under the cooperative output lock, and\nuncertain provisioning/publication is not retried. It prints neither credentials nor environment-\nexport launch recipes. The preferred supported standalone path is now `auth mint --endpoint`\nafter a live digest-verified surface is available. No caller or managed launch is inferred from an\naccount, inventory or reply.\n\n### Serving the endpoint\n\n`serve` registers the account as an endpoint named by `--endpoint` and serves it until Ctrl-C. The\nname must be a reverse-DNS name in a namespace you own, such as `com.example.linear`; it is\nauthorized for a static-auth mesh's local owner because you configured it. `serve` reads the inventory\nfirst, so an account that cannot be reached is never registered. On a static-auth mesh, it publishes\nthe contract to the contract store, opens the issuance gate,\nregisters a fresh instance, writes its ready status, and mints a scoped serve credential through\nthe gate. Every step runs on short-lived executor credentials scoped to this one endpoint instance.\nThe space signer stays in the `serve` process for renewal at 75% of the credential's life and is\nnever written out, printed, or handed to the serving connection. Ctrl-C stops serving, closes the\nLinear session and removes the instance's service record, each within 10 seconds. A start that\nfails after registration removes the record before it exits and says whether that removal\ncompleted; a registration that fails part-way rolls its own record back and reports when it could not.\n\nThe endpoint serves five fixed commands: `inventory`, `call-tool`, `read-resource`, `get-prompt` and\n`complete`. Each needs the one `linear.mcp` capability. That capability is permission to call the\nendpoint; which tools a call can reach is decided by the Linear account and its mode. `inventory`\nreplies in pages that fit a broker message: the first page carries the server, its capabilities,\nits instructions and per-section counts, and every page carries entries verbatim with an opaque\ncursor bound to the inventory digest. An entry is never cut: discovery refuses an inventory whose\nfirst-page head (server, instructions, capabilities) or any entry prints over 48 KiB the way an\nagent tool prints it (indented JSON, which grows with nesting), naming the entry, so the first page\nand every entry it accepts fit one reply and the tool an agent reads it with. A cursor or `inventoryDigest` from an older inventory is refused before anything is sent to\nLinear. `call-tool`, `read-resource`, `get-prompt` and `complete` require the digest the caller read.\n\n### Callers\n\n`caller` provisions an isolated caller on a static-auth mesh: an `agent` credential with its own\nmailboxes and request rows for only the five Linear commands on that endpoint, plus the channels\nin `--channels`. It carries no spawn, run, admin or provisioner capability. The credential is\nwritten 0600 to a create-only `--out` outside managed state and never printed. Its physical parent\nmust already exist and be protected. The shared receipt contains the native identity, lifecycle,\nexpiry and acknowledged footprint, not credential bytes. An existing output refuses before\nprovisioning. A failure after native issuance starts stays unknown with confirmed identity/file\nreceipts retained, and is not retried. The command does not print environment-export launch recipes.\n\nThis remains the native standalone caller exporter, not a connector setup or generic caller bundle.\nA supported bootstrap must carry its actual credential and minted lifecycle together; a file alone\nis not a native caller identity. No seat is started, and managed static `cotal spawn` agents still\nrefuse endpoint capabilities. `auth mint --endpoint` now covers supported untargeted synchronous\nsurfaces through live native resolution. Targeted/journaled/action issuance and caller/launch\nadapters are not supported.\n\nOn a user-auth mesh, `serve` uses the registered auth provider's service authority. The owner must\napprove the original service proposal and the operator must bind its endpoint name. Until then\n`serve` refuses with `PERMISSION_DENIED` (exit 2), and its Next line names the proposal and the\ncommand that approves it:\n\n```bash\ncotal service-endpoint approve <proposalId> --caller-ceiling linear.mcp --space <space>\n```\n\nThe `linear.mcp` ceiling covers the five Linear commands, so callers can be granted them later; an\napproval without it has an empty ceiling and no caller can be granted. Repeat the\nsame command after approval. Its instance, registration body and request identities are retained;\na changed account, target or embedded contract refuses instead of replacing them. The shared\nrenewal source activates and renews the access bearer. The service verifies the signed bearer and\nembedded cluster before serving, and reports ready only after the broker flush and host acknowledgement.\nNo local signer or ordinary enrolled agent is substituted.\n\nStopping a user-auth service closes its serving connection, renewal source and Linear session.\nIt retains the approved registration for a later start; stopping does not claim remote revocation.\nUse the space's service approval and exact caller-grant controls to revoke access. Callers need\nexplicit instance-scoped grants through that remote authority. An account or visible registration\ndoes not grant invocation rights. The standalone `caller` credential exporter remains static-auth\nonly. Open meshes refuse because they have no credential system to scope.\n\nNot represented yet, and listed under `unsupported` when the server advertises them: resource\nsubscriptions, logging, progress notifications and experimental capabilities. The server cannot\ncall back for sampling, elicitation or roots. Every call acts as the account's one Linear identity.\n\n## pending\n\n```bash\ncotal pending <agent> [--durable <exact-dm-durable>] [--limit <1..100>] [--space <s>] [--json|--plain]\n```\n\nThe finite read reports the selected DM durable's native pending count, ack\nfloor, delivered sequence, frontier, retention limits and up to 20 candidate\nIDs by default. JSON and plain output use the common one-document result. A\nname must match one observed roster entry; an exact principal ID wins over a\ndisplay-name match, and an ambiguous display name refuses. `--durable` reads a\nprevious lifecycle by its complete durable name without substituting a\nsame-named current agent. An absent or unreadable durable is not `pending 0`.\nThe caller needs the native admin read authority; user-auth has no equivalent\nview here and refuses without a static or bare fallback. A failed read does\nnot prune a locally saved mesh registration.\n\nCandidates are read without acknowledging DMs. The transient read consumer is\ndeleted after the bounded pass; an unconfirmed cleanup or candidate read is\nreported as partial. Candidate IDs are not evidence of delivery, a missing\nmessage or complete history. Recipient contents are not printed. The retained\n`deliver pending` spelling keeps its earlier output shape.\n\n## deliver\n\n```bash\ncotal deliver [--space <s>] [--server <url>] [--tls] [--creds <file>] [--root <dir>] [--shard <n>] [--shards <n>] [--dev-mint]\ncotal deliver pending <name> [--limit <n>] [--durable <name>] [--json]\n```\n\nWith no positional, `cotal deliver` runs the delivery daemon (see\n[the delivery daemon](delivery-daemon.md)). `deliver pending <name>` never starts the daemon: it\nis an operator-only read over one recipient's DM durable, for the moment after a send when the\nquestion is \"what does the broker actually hold for them.\" It resolves `<name>` against a short\npresence watch (an `offline` card still counts, since the recipient may be dead, that is what\nthe verb exists to inspect); when neither a card nor the durable can be found, it prints\n`\u2717 not-found: no agent \"<name>\" and no DM durable for it in space <s>` and exits non-zero, never\n`pending 0`. On a match it prints the durable name and one fact per line: `pending`,\n`ack-pending`, `delivered`, `ack-floor`, `created`, `frontier`, and the stream's `max_age` /\n`max_msgs_per_subject` / `discard` limits (`--json` prints the same facts as one object), followed\nby a bounded, unacked read of up to `--limit` (default 20) recent candidate message ids under the\nheading `recent candidate ids (from the ack floor; not proof of a hole)`, a list of what is\nthere, not proof that nothing was lost.\n\nThe verb needs the `admin` credential profile: it runs through the same static-mesh route as\n`cotal auth mint --profile admin`, and refuses a user-mode mesh, naming the retired static credential,\nbecause there is no user-mode inspection authority yet. Pass `--creds <file>` for an off-registry\nadmin credential. A same-name respawn never inherits a predecessor's held DMs (the durable is\nlifecycle-keyed); an old lifecycle's durable is reachable only by the name a live read printed\n(the `<durable>` line on the first line of this verb's output). Pass that name with `--durable\n<name>` to read it directly once the lifecycle's card is gone from the roster. This skips the\npresence watch on `<name>` entirely, so `<name>` is required but only echoed in error text.\n\n## auth mint\n\n```bash\ncotal auth mint <name> [--profile agent|observer|admin] [--out <private-file>] [--json|--plain]\ncotal auth mint <name> --provision [--role <role>] [--space <space>] [--server <url>]\ncotal auth mint <name> [--expires-in <seconds> | --expires-at <unix-seconds> | --no-expiry]\ncotal auth mint <name> --identity <private-creds> --out <private-file>\ncotal auth mint <name> --replace\ncotal auth mint <name> --endpoint <class> --out <private-file> [--channels <a,b>] [--expires-in <seconds>]\n```\n\nThe fixed-profile forms use the existing native static credential profiles. No generic host\nprofile is exposed.\nA space registered as open refuses `UNSUPPORTED`, since it authenticates no one and issues no\ncredentials. Per-user-auth spaces refuse too; a cached login does not replace signing authority. The\nresolved root supplies the persona ACL, signer and default storage together. A root that cannot be\nresolved refuses with its next step, such as `cotal up`. Conflicting current-folder and\nselected-root accounts refuse before issuance.\n\nA label takes letters, digits and `_`, since `-` separates the parts of a principal's name. `--role`\ntakes letters, digits, `_` and `-`. A label or role with any other character, or an empty one,\nrefuses `USAGE_INVALID` without the command's help. The refusal names the character, the value and\nthe characters allowed, and its Next names the value with each such character replaced by `_`.\n\n`agent` is the default profile. Its channel ACLs and role come from the selected root's\n`<name>.md` persona file, when present. It does not fall back to `default.md` for a different name.\nExplicit `--allow-subscribe`, `--allow-publish` and `--role` override the file's grants. A persona\nfile of that name that cannot be read as a persona refuses the mint `SCHEMA_INVALID`, exit 1, even\nwhen the flags name every grant. The mint refusal does not print file values or parser text.\nIt names the file and `cotal persona show <name>` to inspect it. A role from the file that is not one word\nof letters, digits, `_` and `-` refuses the same way, naming `cotal persona edit <name>`. With\n`--identity` naming an agent credential this space issued, the new credential keeps that\ncredential's read channels, post channels and role in place of the persona's, and each flag still\nreplaces its own grant. The receipt lists the grants as May read, May post and Role, marking those\ncarried from `--identity`; JSON has them under `result.grants`, with `fromIdentity` naming the\ncarried ones. An empty ACL flag means no explicit entries. The native read-ACL resolver still enforces boot membership.\nWithout a matching persona, `--identity` or grant flags, the agent credential has no channel grants\nor role. Its validity does not establish that it can perform a particular task.\nObserver and admin have fixed native scope and reject agent ACL, role and provisioning flags\nrather than implying those inputs narrowed anything.\n\nThe default credential is created through its canonical secret-store kind. An existing label is\nnot overwritten. Use another label or an explicit private output. `--replace` replaces the label's\nstanding credential when it has expired, has no expiry claim or has a key that\n`auth revoke-credential` revoked. It mints under a new identity unless `--identity` names one, which\na revoked key never is, and removes a no-expiry record beside it unless `--no-expiry` writes a new\none. The receipt names what it replaced and its public key under `result.replaced`. A replacement\ndoes not revoke the credential it replaced, so a copy of one with no expiry claim stays valid. The\nreceipt then warns `REPLACED_CREDENTIAL_VALID`, naming that key and the\n[`auth revoke-credential`](#auth-revoke-credential) route that stops it. A standing credential that is still\nvalid refuses `CONFLICT`, naming its expiry, and so does one that is not a native credential.\n`--profile` still defaults to `agent`, so a replacement of an observer or admin credential needs\n`--profile observer` or `--profile admin`; a replacement with another profile than the one it\nreplaced warns `REPLACED_PROFILE_CHANGED`. A replacement holds only the grants its persona file,\n`--identity` or grant flags name, not those of the agent credential it replaced. When both are\nagent-profile credentials, the old one names this space's account as issuer, and their grants\ndiffer, the receipt warns `REPLACED_GRANTS_CHANGED`, naming both and the\n`--allow-subscribe`, `--allow-publish` and\n`--role` flags that name the replaced ones. With no standing credential under the label, `--replace`\nmints a new one and warns `NOTHING_REPLACED`. Exported files stay unchanged; the warning names\n`doctor auth --space <space>` to inspect their state and the guidance for each file, and the\nspace's root to run it in, since `doctor auth` reads the root it runs in.\n`--replace` takes no `--out`. An exported file stays outside\nmanaged `.cotal` and workstation state. Its path, label, profile and public key, without the\ncredential, are recorded under `credentials/` in the Cotal home so `doctor auth` lists the file. A pre-existing explicit output must be an owned private\nnative credential matching the nkey deliberately supplied through `--identity`; unrelated files\nand identities refuse. New outputs use create-only publication. The output's directories follow\nthe [`space create`](#selecting-a-space) path rule, which also accepts a root-owned sticky directory\nsuch as `/tmp`, and the file is owner-only. Unsupported platforms refuse rather than weakening it.\nA credential is never written through a symbolic link: a link among the output's folders refuses\n`PERMISSION_DENIED`, naming the link, the folder it points to and the same file named without the\nlink. A path through a file refuses `USAGE_INVALID`, and a folder this account cannot create\nrefuses `PERMISSION_DENIED`, naming the folder. None of them writes anything.\n\n`--identity` reuses the native nkey. The out-of-band agent mint reports its newly allocated\nlifecycle UID, with a warning that it did not reuse the old inbox/delivery footprint. Use that\nreported UID for the resulting client; do not infer it from the label or assume the old UID was\npreserved. No private nkey or JWT appears in a receipt. A key revoked with `auth revoke-credential`\nrefuses `CONFLICT`, because a credential signed now would postdate the revocation and the broker\nwould accept the key again; mint a fresh key without `--identity`.\n\nWithout `--provision`, fixed-profile minting writes credentials only. `--provision` uses one native provisioner\nconnection to pre-create that lifecycle's bind-only DM/delivery and role footprint. It performs\nthe authenticated preflight against the same recorded broker and TLS requirement, without\npruning a registration. An acknowledgement proves neither a joined agent nor task completion.\nA partial or unknown result retains the allocated public identity and lifecycle for diagnosis\nand does not retry the mint.\n\nA fixed-profile credential expires 24 hours after it is minted unless `--expires-in` or\n`--expires-at` sets another time, in native seconds. `--no-expiry` issues one with no expiry claim and\nrecords that request beside the file, in `<file>.no-expiry.json`, so `doctor auth` reports it as a\nwarning rather than a finding. The receipt reports the resulting JWT expiry. The three flags are\nmutually exclusive, and an `--expires-at` that is not in the future refuses before anything is written. So does an expiry\nafter the last date Cotal can represent, in the year 275760. JSON is\none finite document; plain output uses the same public receipt. Signing material is exported by\n[auth signer](#account-signer-export), not a signer flag on mint. The old top-level `mint` spelling\nis refused with the literal namespace replacement.\n\nA minted credential stays valid until it expires, and one minted with `--no-expiry` until it is\nrevoked or the space identity rotates. [auth revoke-credential](#auth-revoke-credential) stops one\ncredential and keeps the store. Rotating the space identity stops every credential the space issued:\n`cotal down`, then `cotal reset space <space>` (or `cotal clean all --force` from the root when\n`down` left no registration), then `cotal up`. That reset also removes the space's store, with its\nworkflow runs and their journals, channel history and stored messages, so the space starts again\nwith no history. `cotal reset space <space> --dry-run` lists what the store holds first. It also stops every\nearlier full backup of the space from restoring into this root: only its registry restores, and\nchat and DM history start empty (see [Backups](#backups)).\n\n### Endpoint caller credentials\n\n`--endpoint` is a separate closed mode, not an ACL override on an observer or admin profile. It\nrequires a private `--out` in an existing protected physical parent outside managed state. Only\n`--channels`, `--expires-in`, space/server and presentation flags combine with it. It has no default\nlifetime: without `--expires-in` the caller carries no expiry claim. An empty or\nomitted channel list grants no explicit read/post channels. Supplied channels deliberately enter\nboth ACLs, as in the native standalone Linear caller form. No persona ACL or role is inferred.\n\nThe same static-root/account checks run first. The existing native control instrument then resolves\na live endpoint and digest-verifies its visible command surface. This issuer accepts only nonempty,\nbounded untargeted synchronous ephemeral surfaces. A targeted, journaled or action command refuses\nthe whole issuance before footprint work; unsupported rows are not silently dropped. The result\nrecords the observed responder and contract digests, not a promise that every future incarnation\nhas the same implementation. The caller is scoped to the class's observed command names, not that\nsingle answering instance.\n\nThe native agent provisioner creates the new lifecycle's bind-only footprint and additional endpoint\nrequest rows. No spawn, run or admin capability is added. The ordinary native agent baseline remains;\nthis is not a new hand-written permission profile or a managed-seat launch adapter. User-auth/open\nspaces still refuse. The selected broker's required TLS is retained for discovery and provisioning.\n\nAn immutable owner-private `<out>.issuance.json` intent precedes the first footprint operation.\nIt contains public identity/lifecycle, requested scope and contract coordinates, never credentials.\nIts presence is not a grant or successful submission receipt. An existing output or retained intent\nblocks another issuance, even if no output was published. Partial/unknown provisioning retains that\noriginal intent and does not replay the native call. Success reports native footprint acknowledgement\nand the exact privately published file; provisioning is not presence, task completion or cleanup.\nTemporary provisioner close is reported separately and can leave a partial result.\n\nRich/plain receipts show the actual caller/lifecycle, described endpoint owner and responder,\nexplicit channel scope and native expiry. Command tables show at most 40 rows with an omission\ncount; JSON retains every command and contract digest. The local issuance request is not a broker\nrequest acknowledgement.\n\nNo credential bytes or environment-export recipe are printed. Endpoint describe/invoke consume\nthis paired file under the same registered static root:\n\n```bash\ncotal ep describe ep:<class> --space <space> --creds /absolute/private/caller.creds\ncotal ep invoke ep:<class> <command> --space <space> --creds /absolute/private/caller.creds --args '<json>'\n```\n\nKeep the matching `<private-file>.issuance.json` beside the credential. The consumer checks both\nprotected snapshots and validates the selected root's full static trust chain. The native JWT\nsignature, matching user seed, current validity window, account and current delegated signing key\nare checked before connecting. A retained older signing key is not guessed from a different root.\nThe caller coordinates and recorded commands must reproduce the signed publish/subscribe/response\npermissions and principal tags through the native builder. The unsigned local intent selects that\ncomparison; it grants nothing and proves no submission, lifecycle currency or completion. Original\nresponder/contract metadata remains an issuance observation, not an instance or future-code pin.\n\nThe connection retains registered TLS and uses only that file. Its broker admission is separate\nfrom local checks. Invocation accepts only an included untargeted synchronous ephemeral method\nfrom the fresh digest-verified surface. A changed targeted, journaled or action shape refuses before\npublication. Instance pins, scatter, admin, agent targeting and goal-follow flags are unsupported\nfor this caller mode. Calls do not repair a binding, switch authority or resubmit an unknown effect.\n`--server`, when supplied, must repeat the registered broker. Target/reference selection is\nread-only, including when a paired file is selected. Receipts retain the public caller binding;\ndescribe shows methods outside the file scope without granting them. Issuance next steps keep\nexplicit file selection instead of switching back to the normal operator instrument.\n\nArbitrary raw files, issued-generation files and different native profiles are not interpreted as\nthis standalone caller. User-auth issuance and managed static launch are not supported.\n\n## auth revoke-credential\n\n```bash\ncotal auth revoke-credential <public-key | creds-file> [--space <space>] [--yes] [--json|--plain]\n```\n\nIt stops one credential that `auth mint` wrote and keeps the space's store and every other\ncredential. Name the credential by its user public key, which the mint receipt reports as its\nidentity, or by its creds file, which must be private to you and issued by this space's account. A\nword that is not a user public key and names no file refuses `TARGET_NOT_FOUND` and says both. A\nfile from another account refuses `CONFLICT`. A file the space's system account issued, such as the\nstack's `connection-evictor` and `membership-observer` credentials, refuses `UNSUPPORTED`. Its Next\nis `cotal down --yes`, then `cotal up --rotate-sys`, which issues a new system account and retires\nthe old one's credentials, and after which earlier full backups no longer restore (see [up](#up)). The command records the key in the space's account,\nre-signed by the space's operator, and the broker refuses that key from its next start.\n\nThe broker loads a revocation only when it starts, so the space must be stopped: run\n`cotal down --yes`, then this command, then `cotal up`. On a running space it refuses\n`LIFECYCLE_BLOCKED` and names that route, and it refuses the same way while the root records a\nmaintenance attempt. A connection held with the credential ends at the `down` and is refused after\nthe `up`. It asks before it revokes, or takes `--yes`; `--json` without `--yes` refuses. A key\nalready revoked reports the time it was first revoked and changes nothing. `--json` reports\n`publicKey`, `revokedAt` in Unix seconds, `alreadyRevoked` and `takesEffect`.\n\n`auth mint --identity` refuses a revoked key. A credential the stack renews itself, such as its\ndaemons' or a managed agent's, is signed again for the same key at renewal, and the broker accepts\nthat newer credential, so stop or despawn that agent instead. A per-user-auth space refuses\n`UNSUPPORTED` and names `auth revoke <actor>`, and an open space issues no credentials to revoke.\n\n## Login\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>] [--plain]\ncotal logout --idp <auth base URL> [--local]\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. `--plain` presents the approval prompt and signed-in result without color.\nDevice approval is still interactive and has no native noninteractive or JSON result in this\nclient; `--no-input` refuses before starting a provider request. The IdP URL must use `https://`.\nPlain `http://` is accepted only on a loopback IP literal such as `127.0.0.1` or `::1`, for an IdP on this machine. `localhost` is refused because it\nis a name: a hosts entry would choose the IdP. It prints your IdP subject, the id the operator\ngrants against. When the trusted\n`/token` response advertises a same-origin space catalog, login validates and records that account's\nspaces immediately, lists them under `Your spaces`, and names `cotal use <name>` to select one. A\nspace registered from elsewhere that has not granted your account refuses with those discovered\nspaces as its remedy, because only that space's operator can grant you. On the root that holds the\nspace's grant ledger, the refusal names the operator's `cotal actor grant` command instead. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. A provider that cannot be reached, or that stops answering, refuses with\n`DEPENDENCY_UNAVAILABLE` (exit 2) and caches no session. `logout` revokes the IdP session, clears\nits cache, and removes only that\naccount's discovered registry entries. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\n# an upsert of the WHOLE row: name all three ACL flags, or pass --full for the wide defaults below\ncotal actor grant <actor> --sub <IdP subject> --scope a,b --allow-subscribe a,b --allow-publish a,b [--role <r>] [--label <l>] [--json|--plain]\ncotal actor grant <actor> --sub <IdP subject> --full [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>] [--json|--plain]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>) [--json|--plain]\ncotal actor label <actor> (--sub <IdP subject> | --owner <u_\u2026>) <label> [--json|--plain]\ncotal actor list [--sub <IdP subject>] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the resolved mesh | Space whose ledger to manage |\n| `--sub <subject>` | none | The IdP subject (shown by `cotal login`) the actor belongs to. With `list`, only that subject's rows are listed |\n| `--owner <u_\u2026>` | none | The derived owner token (alternative to `--sub`) |\n| `--full` | off | Fill each ACL flag left off with its wide default; without it, `grant` refuses unless all three are named |\n| `--scope <a,b>` | `spawn,role:default` with `--full` | Capability scope (`''` = none; `spawn` = may run agents; `run` = may start workflow runs; `role:<r>` = may delegate role r; `admin` = cross-agent control; `supervise` = eligible for the closed remote manager-service view when the host enables it). Any other name is refused |\n| `--allow-subscribe <a,b>` | `>` (all channels) with `--full` | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) with `--full` | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | none | Role (scopes the task-queue consumer) |\n| `--label <l>` | none | Display label for `actor list` (never the IdP subject) |\n| `--parent <owner.actor>` | none | Audit link to the spawning principal. An operator grant is the authority, so the link does not narrow the row |\n| `--json`, `-j`, `--plain` | off | Print one JSON document or line-oriented text, as `auth grants`, `auth grant` and `auth revoke` do |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nThe ledger stores no IdP subject. `actor list --sub <subject>` and `auth grants --sub <subject>`\nderive the subject's owner as `grant --sub` does, with this root's owner secret and IdP pin, and list\nthat owner's rows, interactive and managed-agent alike. The report is titled\n`Rows for <subject> (owner <u_\u2026>)`, and JSON adds `subject` and `owner` to its result and names the\nowner in its target. A subject with no rows lists none. An empty `--sub` refuses `USAGE_INVALID`.\n`actor label <actor> <label>` sets only an interactive row's label. The label is outside the\nauthorization digest, so the row keeps its lifecycle, grant time, capabilities and channels, and a\nbearer issued under it keeps working. The receipt names the earlier label, and JSON carries it as\n`previousLabel`. An actor with no row refuses `TARGET_NOT_FOUND`, and a managed agent's row refuses\n`UNSUPPORTED`. Neither writes anything.\n`cotal help actor` lists `grant`, `revoke`, `label` and `list`, and shell completion offers them.\n`actor list`, `grant` and `revoke` resolve their space and root as `auth grants`, `auth grant` and\n`auth revoke` do, and refuse the same way. With no space to resolve they refuse with a next step,\nsuch as `cotal up`, rather than assume one, and a root that does not hold the space's user-auth\nledger refuses `list` rather than report no rows. On a static or open space the refusal says that\nactor grants apply only to a space started with `cotal up --user-auth`, and `grant` and `revoke`\nrefuse that way before asking for `--sub`, `--owner` or the ACL flags.\nOtherwise a usage error refuses with exit 1 before any authority is read, and one refusal names every\nmissing input: `--sub` or `--owner`, and for `grant` the ACL flags left off without `--full`. An\ninput that is not valid refuses the same way, naming each field and why: the actor, `--owner`,\n`--role`, `--parent`, a channel pattern, or a `--scope` name other than `admin`, `run`, `spawn`,\n`supervise` and `role:<r>`. Nothing is written. A channel needs no creation, so a grant may name one\nthat has no message or registry entry yet.\n`grant --full` is the **full** envelope (all channels; scope `spawn,role:default`, so it may spawn and may delegate the default role). A\n`grant` that leaves off `--scope`, `--allow-subscribe` or `--allow-publish` without `--full` is\nrefused and writes nothing. A re-grant **replaces the whole row**, not the one field you name, so to add a capability spell\nevery field out: the new scope plus the row's current read set, post set, role and label\n(`cotal actor list` and `cotal auth grants` show what a row holds: its capabilities, read and post\nsets, role, label and parent). Under `--full`, a field left off does not stay as it\nwas: it reverts to the wide default in the table above. A re-grant retires the current interactive lifecycle through the running auth\nservice before it rotates the row, so copied bearers cannot cross an authorization update. The\nreceipt names the earlier grant as none, the same fields (titled `Actor grant stored unchanged`) or\nreplaced, and says when its lifecycle was retired; JSON carries `previous` and\n`previousLifecycleRetired`. If that\nretirement cannot be confirmed, the row is left unchanged and the command fails with the recovery\naction. `revoke` uses the same retirement before deleting the row, which lets a later grant create a\nreal successor instead of colliding with a live predecessor. `supervise` is separate from `spawn` and `admin`: it only makes a signed-in\nperson eligible for the host-provided closed remote manager-service view; it does not grant\nmanagement of another owner or a general host profile. `revoke` denies the next exchange and\nthe next connect with no restart, and evicts the principal's live connections. It asks nothing and\ntakes no `--yes`, because a later grant gives the actor access again under a new lifecycle. It reports\n`verified gone` only when the delivery daemon's eviction result names the principal and shows a\ncomplete scan with no connection left; any other result prints `live-connection eviction INCOMPLETE`\nwith its counts, or with the reason the result was not accepted. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--space <space>] [--fix] [--json|--plain]\n```\n\nThis inspects the current root's local credential claims and recorded renewal state. It does not\nprove current broker authorization, daemon adoption, connected agents or task progress. The shared\nreport keeps full credential paths and public issue/expiry metadata; JSON retains all diagnostic\nrows without JWTs, seeds, fingerprints or raw parser/renewal error bodies. On a root that holds several\nspaces, `--space` names one; it defaults to the sole space. Without `--space`, a root with several\nspaces refuses with `TARGET_AMBIGUOUS` (exit 2) and names them. In a folder whose root holds no space,\nthe report is partial (exit 4) and its next step is to run it in a space's root, which `cotal space`\nlists. In the root of a space registered as open, the report says that the space issues no\ncredentials, so there are none to check, and the result is a no-op (exit 0). With `--space` naming\na space whose signing trust this root does not hold, it refuses `AUTH_REQUIRED` (exit 2) and reads\nno other root. Its next step names the folder that space is registered at, where the same command\nruns, or else a space's root, which `cotal space` lists.\n\nPlain diagnosis starts no repair or connection. A required legacy credential or user-auth directory\nmigration refuses instead of silently moving files or reading canonical absence as an empty scope.\nA credential file that other accounts can open is a finding that names its mode, with `chmod 600`\non the file as its repair. That includes the space's own store credentials, and the other files are\nstill checked. Each file is also parsed the way `--creds` checks one, so a file the NATS\nclient cannot parse is a finding even when its claims are legible, such as one with Windows (CRLF)\nline endings.\nOnce the root holds the space's trust, a missing `membership-observer` or `connection-evictor`\ncredential is a finding too, with `cotal down` then `cotal up --rotate-sys` as its repair, because\nnothing else mints that pair again. A missing delivery or membership-rw credential is only noted,\nsince `cotal up` mints it.\nA credential with no expiry claim is a finding whatever its kind, since a copy stays valid until it\nis revoked or its signer rotates. `--fix` re-signs only the static daemon credentials, so an unbounded agent credential\nstays a finding until its file is gone. The exception is one `auth mint --no-expiry` wrote, whose\nrecord beside it still names the file's bytes: someone asked for it, so it is a\n`CREDENTIAL_UNBOUNDED_BY_REQUEST` warning instead, and the run ends ok, exit 0. Both get guidance that\nnames the ways out. `cotal auth revoke-credential <file> --yes` between `cotal down` and `cotal up`\nstops that credential and keeps the store. Replacing\na label's file with `cotal auth mint <label> --replace` once nothing uses it bounds it from then on,\nand so does removing any other file and its record and minting a replacement without `--no-expiry`;\na copy made before stays valid until it is revoked. Rotating the space identity (`cotal down`,\n`cotal reset space <space>`, then `cotal up`) retires every credential the space issued and removes\nthe file, and the reset removes the space's store with its history. An expired label's file is a finding too, and\nits guidance names that same `--replace`, next to the managed renewal that re-signs an agent a\nmanager runs. An agent file whose key was revoked is a finding in state `revoked`, with\n`result.items[].revokedAt`, however far off its expiry is. A revoked key is not signed again, so\nfor a label's file the guidance names `--replace`, and for any other agent file it names stopping\nthe agent that uses it. Each `--replace` this guidance names, for an unbounded, expired or revoked\nfile, carries the file's profile as `--profile observer` or `--profile admin`, and an agent file's\nread and post channels and its role as `--allow-subscribe`, `--allow-publish` and `--role`, since\nthe replacement is a new key, which holds only the profile and grants its mint names. For an\nobserver or admin file it names no managed renewal, as no manager runs one.\n\nEach file `auth mint --out` wrote for the space from this root and that is still there is listed\nunder Exported credential files, and in `result.exported`, with its state and expiry. These rows\nare not findings. An expired one is an `EXPORTED_CREDENTIAL_EXPIRED` warning. One near expiry or\nexpired names `cotal auth mint <label> --identity <file> --out <file>`, which signs the same key\nagain with the same grants. One whose key was revoked, or that an earlier space identity issued,\nnames a mint with a new key instead, because the broker refuses that key and `auth mint --identity`\nrefuses a revoked one. For an agent file, that mint names the file's read and post channels and its\nrole with `--allow-subscribe`, `--allow-publish` and `--role`, since a new key holds only the grants\nits mint names.\nA run with no findings says so in its heading only when nothing else is printed. While an exported\nfile needs attention, or a credential is unbounded by request, the heading starts `No findings;` and\nnames each, such as `2 exported credential files need attention`. The exit code stays 0.\nMissing local signing trust is not inferred to mean an open or healthy mesh. An unreadable renewal\nrecord prevents a complete healthy verdict. Locally ordered older incarnation files are labeled as\nfile observations, never proof of a live successor or permission to delete or respawn an agent.\n\n`--fix` explicitly permits native local material migration and the supported static daemon re-sign\nphase. Its path-move and re-sign acknowledgements are retained separately. It holds the native\nrenewal lease when using the broker and refuses another holder. Offline repair is labeled as\nuncoordinated; an authenticated preflight failure is not called an offline target. User-auth roots\ncannot receive a static-signer repair fallback. No system account is rotated and no agent is\nrespawned or silently given a new identity.\n\nA delivery or membership-rw credential the NATS client cannot parse holds no key to re-sign, and\nthe manager cannot renew it either. When no broker answers, `--fix` mints it again under a new\nidentity, as `cotal up` mints a missing one, and its result row has `reminted: true`. While its\nbroker answers it stays a finding, and its guidance and row name `cotal down` first. A refusal\nbecause the manager holds the renewal lease names the same step. Every result row that is not a\nplain re-sign has a `reason`. When `cotal up` starts with such a membership-rw credential, the\ndelivery daemon runs without it, and `up` warns `DAEMON_CREDENTIAL_UNPARSABLE`, naming the same step.\n\nA local re-sign does not erase a recorded broker refusal or claim that a daemon adopted it.\nA recorded refusal names the space's manager, the renewal owner, as the next step. A pre-per-space\n`.cotal/renewal.json` names no space, so it is reported as a leftover and never read as the space's\nverdict.\nRemaining findings, incomplete audit reads and partial/unknown repair outcomes keep classified\nfinite receipts. Inspect the original renewal owner and lifecycle rather than repeating a mutation\nor creating replacement agents merely to obtain a different report.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | none | Your presence name |\n| `--role <r>` | none | Your role |\n| `--channel <c>` | none | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | none | Join link (`cotal://\u2026`) |\n| `--token <t>` | none | Join token |\n| `--lifecycle-uid <uid>` | none | 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 |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\nA `--token` or `--creds` goes to `--server`, or else to the broker the space is registered at,\nkeeping that registration's TLS requirement. With no `--space`, that is the selected space, else\nthis folder's, and inside a project whose root has one space other than the selection, that space.\nA space not registered here is refused before anything is dialed. The default address is never\nassumed for a credential.\n\nA `--creds` join binds the inbox provisioned for its lifecycle, so the space's owner mints the\ncredential with `cotal auth mint <name> --provision` and hands over the lifecycle UID that mint\nreports. A credential minted without `--provision` has no inbox there, and `join` refuses, naming\nthat lifecycle and the mint that makes one.\n\nOn a registered user-auth space, sign in with `cotal login` first. `join` uses your existing `cli`\nactor bearer and renews it through the same login and exchange while the session runs. Its channel\nand DM resources must already be provisioned by the space operator. It never mints static\ncredentials or widens your grants. A changed principal, lifecycle or connection trust refuses\nrenewal and requires leaving and joining again. A revoked login is reported by renewal, and the\ncurrent connection can last only until its already-issued bearer expires.\n\nFor `nats://` and `tls://` addresses, if TLS is required but the broker does not offer it, `join`\nrefuses with a request for the operator's TLS-enabled broker address. It never retries over\nplaintext. A `ws://` address uses plaintext WebSocket. Use `wss://` for TLS.\n\n`join` has no `--json`. A refusal before the session starts prints on stderr with its code and\nexits 1 for `USAGE_INVALID` and 2 for any other refusal: `TARGET_NOT_FOUND` for a space not\nregistered here, a creds file that does not exist, a lifecycle with no inbox or a space not\nprovisioned for a console session, `PERMISSION_DENIED` for credentials the broker rejects,\n`TARGET_UNAVAILABLE` for a broker that does not answer, and `UNSUPPORTED` for a broker without the\nTLS the join requires.\n\nA running join that cannot come back ends the same way, with its reason and its Next. When it\nre-establishes its connection and finds its inbox gone from the broker, as after `reset store` while\nit was disconnected, it ends `TARGET_NOT_FOUND` and says to run the same `cotal join` again, or, for\na `--creds` join, to mint the credential again with `--provision`. When the broker rejects its\ncredential for good, as after `reset space`, it ends with the refusal a one-shot command with that\ncredential gets, as `history --follow` does. A user-auth join keeps re-establishing in both cases,\nand an open or `--token` join when the broker rejects it.\n\nA broker that comes back on an empty store soon enough for the client to reconnect by itself does\nnot end the connection, so the join keeps running without its DM inbox, and direct messages to it\nare not delivered. After a second check it prints one warning that its DM inbox is missing, on a\nline that starts with `!` like its other warnings, and keeps checking. If the inbox does not come\nback, interrupt the session and join again. A `--creds` join first needs the space's owner to mint\nthe credential again with `--provision`.\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThese commands consume it:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal team deploy cotal.yaml # deploy the manifest additively onto a running mesh\ncotal team remove <deployment-id> # retire a returned deployment\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` brings up a new broker and applies the manifest. It returns once the broker and manager are\nup; the manager then launches the agents one at a time, and `up -f` does not wait for them to join.\nIts launch line says so and names `cotal ps --space <space>`, which lists the agents that run, and\nthe manager's log, where a failed launch is recorded with its reason. When the manifest's space is already\nrunning from the same folder, it exits 1 and names `cotal team deploy <file>`, which adds all-new\nchannels and agents, and `cotal down` before another `up -f`. On a root that holds a stack preserved by\n`down --preserve-state`, it refuses `LIFECYCLE_BLOCKED` (exit 2) and starts nothing; bare\n`cotal up` resumes that stack. On a root whose records hold a controller stack, running or stopped,\n`up -f` and its dry run refuse `CONFLICT` (exit 2) and start nothing. The refusal names\n`cotal team deploy <file>` for the running stack, and `cotal down --yes`, then\n`cotal team boot <file>`. `team deploy` requires a registered,\nrunning mesh and applies additively: all-new live channel-only manifests, or bounded all-new agent\nand mixed manifests on static-auth or open meshes through one native manager. It does not launch\nuser-auth agents. `team remove` retires a returned deployment by its full ID within the limits in\n[Recorded team views](#recorded-team-views). `spawn -f` no longer deploys a manifest; an additive\nledger written by that older path is torn down with `cotal down -f <file> --run <id>`. `up -f`,\n`team deploy` and `team remove` take `--dry-run` to print the plan without mutating anything.\n`topology` validates the manifest and renders its channel / role / ACL graph. Given a folder\ninstead of a manifest file, `topology view`, `up -f`, `down -f` and the `team` commands that take a\nmanifest refuse with exit 1, naming the path and that it is a directory. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext [list] [--json | --plain]\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are loaded on demand by commands such as `supervise`, and by a running manager the first\ntime a managed seat names one with `spawn --runtime <name>`, so a `pty` manager launches a seat on\nan installed `tmux` without a restart; local process providers participate in `status` and\nselective `down`. Removing an additional runtime extension, one the manager was not started with,\nrefuses the next seat that names it; the manager's own runtime and the seats already running stay\nas they are. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical service preset and local-process example. Installed packages and their location are described in\n[config](config.md). When a package needs an export its linked `@cotal-ai/*` peer does not have,\n`add` rolls back and names which install is behind, as a later load of an installed one does.\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree. These packages never show up in `npm list -g`,\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\nOn POSIX, cotal creates a missing Cotal config directory with mode `0700`, even with a\ngroup-writable umask, whichever command writes there first. The extension writer creates a missing\ninstall prefix with mode `0755` and its `package.json` with mode `0644`. Its npm children start with 022 added to your umask. The\nextension writer leaves existing directory permissions as they are, and [`update`](#update) removes\ngroup and other write from the config directory and everything you own under it. Completion still refuses a pre-existing writable ancestor\nrather than weakening its shell-file ownership checks.\n\nBare `ext --json` (or `ext list --json`) writes one JSON document: `result.root` is the install\nprefix and `result.items` holds one row per installed extension with `pkg`, `version`, `spec` (what\n`ext add` was given), `seeded` (`true` for an entry the built-in seed installed) and `provides` (the\n`kind:name` refs the table shows). An empty prefix is an empty list. `--plain` writes the same report\nwithout color. The other `ext` subcommands refuse `--json` and `--plain` with `USAGE_INVALID`.\n`ext root` stays a raw path for `$(cotal ext root)`.\n\nAn installed package whose directory is gone is reported as not installed. One whose `package.json`\nis not JSON or has no `version` is reported as corrupt, naming the package and its `package.json`.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone. While it uninstalls, `remove` reserves each process record the package\ndeclares under every mesh root, and a record folder it creates there is mode `0700`, even with a\ngroup-writable umask.\n\nLoading an installed extension takes the same locks as `add`, `remove` and `cotal update`. When another\n`cotal` process holds them, the load waits up to five seconds in total (one deadline for both locks)\nwithout blocking the waiting process, then refuses. A running `cotal update`, `cotal ext add` or\n`cotal ext remove` holds the update-pass lock as well as the install/remove lock, so the load refuses with\n\"another extension update or mutation is in progress (pid N) - retry once it finishes\". The install/remove\ntext, \"extension install/remove is in progress (pid N) - retry after the active `cotal ext` command\nfinishes\", appears only when the update-pass lock is free but a live process still holds the\ninstall/remove lock itself, for example an update child that outlived its `cotal update` parent. Re-run\nthe command once the other one has finished.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `jcode`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `service-preset:web`) is the seventh built-in seeded on the\nsame path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically before the first\ncommand of each boot that is not a read, so you rarely call it; `cotal up` runs it when it first\nstarts a process). A read, such as `status`, `ps`,\nbare `use`, `ext list` or `docs`, installs and refreshes nothing. An installed extension's `reads` declaration is\ncached when it is added. A bare read and a read subcommand skip that seed prelude; a subcommand\nwithout the declaration still seeds. After an upgrade, a read that would load a\nbuilt-in still at another version names the package and its version with `cotal ext seed` instead\nof loading it. Each seeded connector's `\u2713 added` line goes to stderr, so the command that triggered\nthe seed keeps stdout to itself:\n\n| Flag | Meaning |\n|---|---|\n| (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. |\n| `--repair` | Recover after a seed that failed or was interrupted, or a lost authority (rebuilds that seed's connector; restores the removed-vs-never-seeded record from its durable backup). Also reinstalls a seeded built-in whose entry file is gone or whose `package.json` is not JSON or has no `version`. |\n| `--reset` | Discard the record and re-seed all seven built-ins (the six connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current. Refuses to downgrade a newer shared store. |\n\nA seed that fails names the connector and the reason, such as npm missing from PATH. Until\n`cotal ext seed --repair` finishes it, each command that seeds refuses. The refusal says whether\nthe last seed failed, and why, or was interrupted.\n\nWhen a newer `cotal` advances the operator-global seed store to its generation, it prints one\nmigration line naming the old and new generations, the exact CLI entry that wrote the store, the\ncommit timestamp, and `seed/stamp.json`. That writer and timestamp are kept in the stamp, so a later\nolder CLI refusal can say which executable wrote the generation it will not overwrite and when.\nLegacy generation-only stamps remain readable; their refusal simply has no writer provenance to add.\n\nAn older `cotal` refuses a seed store written by a newer version. The refusal names the store's\n`seed` folder and the `extensions` folder beside it, and says to restore both from a backup taken\nbefore the store moved to the newer generation; the two versions are context. When it can verify a\nsufficient `cotal` executable on PATH or at the installer's `~/.local/bin/cotal` location, the\nrefusal also names that absolute path, which uses the store as it is, so a reduced service PATH\ndoes not select the older binary again. Every maintenance mode, including `--force` and `--reset`,\nrefuses to replace a newer shared generation. Same-generation force remains supported. Reset still repairs corrupt state and resurrects\nremoved connectors. Older released executables may still allow a backward rebuild; do not use\ntheir force advice to recover from selecting the wrong installation.\n\nA source-checkout CLI (`pnpm cotal`, `tsx bin/cotal.ts`, `node bin/cotal.ts`, or a suite child of\nthose) refuses to write or garbage-collect that store. The refusal names the path, the generation\nit declined, and `COTAL_SKIP_CONNECTOR_SEED=1` as the way to run other commands from a checkout,\nbecause pointing `$XDG_CONFIG_HOME` at a scratch dir alone does not lift it. With the skip set, a fresh\nconfig gets no built-in connectors (`cotal ext list` shows none), so add one from the checkout with\n`cotal ext add <checkout>/extensions/connector-claude-code` against that scratch `$XDG_CONFIG_HOME`. `COTAL_HOME` does not relocate this\nstore. An entry that cannot be proven as a released install is refused the same way. Isolated\nrelease tests that must seed from a checkout-shaped `bin/` set `COTAL_ALLOW_CHECKOUT_SEED=1` after\npointing `$XDG_CONFIG_HOME` at a scratch dir; that override is documented here, not on the refusal\nline. An opt-in write still records the checkout path in `seed/stamp.json` as `writtenBy`.\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is the persona's `agent:` pin if it\nhas one, else `claude`; set `COTAL_DEFAULT_AGENT` or `cotal config set harness` (e.g. `opencode`)\nto change the fallback. It is a default, so a persona that pins its harness still wins over it. An `--agent` naming a removed\nconnector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request. `cotal agent-bearer` never takes the seed at all: it is exec'd by\nspawned seats on every bearer refresh, so it neither reconciles nor is refused by the store's\ngeneration (see [Plumbing](#plumbing)).\n`cotal completion` skips the seed as well, so a shell profile that sources its stub never reconciles\nand is never refused by the store's generation or the source-checkout guard. Printing a stub writes\nnothing, and `completion install` writes only shell configuration.\n\n## docs\n\n```bash\ncotal docs [<page>] [--refresh] [--json | --plain]\ncotal docs --search <query> [--json | --plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--search <query>` | none | Show the sections that best match a query, each naming its page |\n| `--refresh` | off | With a page: also try the version-pinned copy on docs.cotal.ai |\n| `--json` / `-j` | off | One JSON document |\n| `--plain` | off | The same report without color, and a page as its Markdown |\n\nReads the docs bundled with this installed version, offline: the same pages agents read with\n`cotal_docs`. Bare `docs` lists the pages by category (`Start here`, `Guide`, `Concept`, `Reference`,\n`Project`, the order of the docs index), each with its title; `result.pages` holds each page's slug,\ntitle, kind (its classification line, such as `Concept (informative)`), category (the heading it\nlists under) and summary, in that order, and\n`result.sources` the normative `spec`, `lang` and `schema`. A page is named by its slug,\nignoring case and a trailing `.md`; `result.body` holds its Markdown. On a terminal a page is drawn\nfor reading: each heading is a title, paragraphs and lists wrap to the terminal, a table is text that\nbecomes one record per row when its columns do not fit, and a code block keeps its lines. To a pipe\nor a file, and with `--plain`, the page is its Markdown. An unknown page is refused with\n`USAGE_INVALID`, naming every page.\n\n`--refresh` fetches the page from the immutable path for this version on docs.cotal.ai.\n`result.source` says which copy was served, `bundled` or `docs.cotal.ai`, and `result.refresh` says\nwhat the fetch did: `served`, `not-published` (the path has no copy) or `unreachable`. The bundled\ncopy is served whenever the fetch does not succeed. `--refresh` applies only to a page.\n\n`--search` ranks sections by the words of the query. A search with no match is an empty result.\n\n## config\n\n```bash\ncotal config [--user | --space <name>] [-v | --verbose] [--json | --plain]\ncotal config get <key> [--user | --space <name>] [--json | --plain]\ncotal config set <key> <value> [--user | --space <name>] [--json | --plain]\ncotal config unset <key> [--user | --space <name>] [--json | --plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--user` | your defaults for a write | Your own values, with no space's values |\n| `--space <name>` | the selected space for a read | Your values for one space |\n| `--verbose` / `-v` | off | Bare `config` also shows the preferences file and its revision, which its JSON result always holds |\n| `--json` / `-j` | off | One JSON document |\n| `--plain` | off | The same report without color |\n\nYour [preferences](config.md#preferences): `harness`, `persona`, `detach-key`, `color` and `editor`.\nBare `config` lists every key with the value a command uses, where it came from (a variable, your\nvalue for the space, your own value, or the default) and the values it outranks. `get` shows one\nkey. A read looks through `--space`, else the space a launch here uses: the space selected with\n`cotal use`, or with nothing selected the space `spawn` resolves from this folder, which is this\nfolder's space or the only running one. With none of those it reads your defaults. `--user` reads\nyour defaults alone.\n\n`set` and `unset` change your defaults, or your values for one space with `--space`. Only `harness`\nand `persona` have per-space values. `set --space` takes a space registered or prepared on this\nmachine, and refuses any other name with `TARGET_NOT_FOUND` (exit 2) and the known spaces.\nA read and `unset --space` also take a space that still holds a saved value, so a value left for\na space you removed can be read and removed, and refuse any other name the same way. A write that\nanother value outranks in this shell is still saved, with a `PREFERENCE_SHADOWED` warning naming\nthat value: a variable, or your value for the space a command here uses when you write your\ndefaults, with or without `--user`. The receipt's effective value is the one a command here uses,\nread through `--space`, else that space. A `persona` that the space's catalog does not hold is saved with a `PERSONA_NOT_FOUND`\nwarning naming the catalog. A variable whose value `config set` would refuse still wins, and\n`config`, `config get` and `status` name it in a `PREFERENCE_INVALID` warning with the reason.\nSetting a value that is already saved, or\nunsetting one that is not, is a `no-op` and exits 0. An unknown key or an invalid value is refused\nwith `USAGE_INVALID` and the valid choices, and a write that lost a race with another writer is\nrefused with `CONFLICT`. A preferences folder you cannot write is refused with `PERMISSION_DENIED`\n(exit 2), naming the folder, before anything is changed. So is a folder another account could write,\nwith the `chmod` that fixes it, and a linked `preferences.json`, which `config set` never replaces\n(see [Preferences](config.md#preferences)).\n\n`result.settings` (or `result.setting` for `get`) holds each key's `value`, its `winner`\n(`{source, name, value}`, where `source` is `environment`, `space`, `user` or `default`) and the\n`shadowed` layers below it. A write's result has the `previous` and new `value`, the file's new\n`revision`, and `effective`, the key as a command there now resolves it.\n\n## completion\n\n```bash\ncotal completion [--json|--plain]\ncotal completion script <bash|zsh|fish|powershell>\ncotal completion install [shell] [--json|--plain]\ncotal completion uninstall [shell] [--json|--plain]\n```\n\nBare `completion` reads local integration files. It reports the script version, startup reference\nand recorded ownership separately. It does not inspect or load the running shell. An unreadable\nfile or ownership record produces a warning and a partial result, exit 4. The warning names the\nfile and why. A record wired into another startup file, as an install under another `ZDOTDIR` leaves\none, names that file and the `ZDOTDIR` to read it with. A folder above the files that is a symlink,\nowned by another user, or group- or other-writable is named once, with the `chmod go-w` that fixes\nit when one does. The files under it are not opened, and one that does not exist is reported missing,\nso a report with nothing installed stays complete, exit 0. Next offers that `chmod` first, offers\n`install` and `uninstall` only when nothing stops them, and offers `uninstall` only when something is\ninstalled. A relative `ZDOTDIR`\nis resolved from the current directory for this report, as zsh resolves it.\n\nIn zsh, initialize its completion system with `autoload -Uz compinit && compinit` before\nsourcing the native zsh stub.\n\nLoad completion in the current shell:\n\n```bash\nsource <(cotal completion script bash)\nsource <(cotal completion script zsh)\ncotal completion script fish | source\ncotal completion script powershell | Out-String | Invoke-Expression\n```\n\n`completion script` writes raw shell code and takes no `--json` or `--plain`. Generation is\noffline. The stable hidden `__complete` entry reads registered command specifications, the spaces\nregistered or prepared on this machine, the extension manifest, the bundled docs index and local\npersona files. It does not contact the mesh or refresh extensions. Every `--space` offers the spaces\n`cotal use` offers, and every `--creds` lets the shell complete a file name. `space show`, `forget`\nand `delete` offer the spaces registered or prepared here. `config` offers its keys, and `config set color` and `config set harness` the\nvalues a write accepts. `docs` offers its pages, `watch --view` its views and `space add --mode` its\nmodes. A channel name for `send -c`, `history` or `channel show`, `members`, `set` and `remove` comes\nfrom the channels the target space's personas declare, as `spawn` reads them, so a space prepared\nand not started offers its own. The broker keeps the others, so a persona that declares only `>`\noffers none. A\ncandidate that holds a control character, such as a persona file name with an escape sequence or a\nnewline in it, is not offered. `persona` still lists that file with the characters escaped. A\npersona catalog folder that cannot be read fails the completion of a persona name, so the shell\noffers none, and with `COTAL_COMPLETE_DEBUG=1` it prints the folder. A record it skips, such as a\nprepared space record that does not parse, is named on stderr only under that flag too. The stubs\ndiscard stderr, so run the completion by hand to see it, as\n`COTAL_COMPLETE_DEBUG=1 cotal __complete persona show ''` does. The bash\nand zsh stubs complete file names where a value takes a path. The fish stub offers only cotal's own\ncandidates.\n\nIn bash and zsh, a file argument such as `team check <file>`, `up -f <file>`, `--channels <path>`,\n`--creds <path>`, `run start --file <program>` or `auth mint --out <private-file>` completes from the\nshell's own file names, and so does a folder such as `space add --root <dir>`.\nA value with a fixed set of choices, a duration or free text does not offer files.\nA stub installed by an earlier release offers no files. `cotal completion` reports it as differing\nfrom this version, and `completion install` refreshes it.\n\n`install` and `uninstall` take a shell name or detect it from `SHELL`. Pass `powershell`\nexplicitly when PowerShell inherited another shell's `SHELL`. A `SHELL` that names a shell\ncompletion does not support, such as tcsh, is refused with `UNSUPPORTED` (exit 2), naming it. On supported POSIX hosts:\n\n- Bash uses `~/.config/cotal/completion.bash` and an owned block in `~/.bashrc`.\n- Zsh uses `~/.config/cotal/completion.zsh` and an owned block in `$ZDOTDIR/.zshrc`, or\n `~/.zshrc` when `ZDOTDIR` is unset.\n- Fish uses `~/.config/fish/completions/cotal.fish`, which fish loads automatically.\n- PowerShell on Linux or macOS asks the installed `pwsh -NoProfile` for its current-user\n all-hosts profile and wires an owned `completion.ps1` stub into that file. A missing shell,\n invalid profile reply or missing profile directory refuses without guessing a path.\n\nAn absolute `XDG_CONFIG_HOME` replaces `~/.config` for the owned stubs; a relative value is\nignored. Mutation refuses a relative `ZDOTDIR`, missing startup directory, symlink, untrusted\nowner or unsafe file access before it creates anything. A missing startup directory is named, with\n`ZDOTDIR` when that variable chose it, and with the `mkdir` that creates it. One refusal names every refused startup\nfile, stub, record and directory on the way to them with its reason, and a group- or other-writable\none with its mode and one `chmod go-w <paths>` that fixes them all. A startup file that is a\nsymlink, as a dotfile manager such as stow links it, is never written through: `install` refuses\nit, exit 2, names it and gives the line to add to the file it points to yourself. Bare `completion`\nwarns that it is a symlink and does not read it. A startup file its owner made read-only is never\nreplaced. `install`\nstill writes its stub, then fails, exit 5, and names the line to add to that file yourself.\n`uninstall` refuses it, exit 2, and changes nothing until you remove that line. On Windows, PowerShell script generation works but automatic\nprofile editing remains unsupported: the guarded POSIX file writer cannot safely replace\nWindows reparse-point paths. Installing files does not establish that the host runs its\nprofile or permits scripts under its execution policy.\n\nInstallation writes a private `.ownership.json` record beside its stub. Reinstall refreshes only\nan unchanged recorded stub. Uninstall removes only that stub and the unchanged recorded startup\nblock, keeping other startup bytes. A startup file that `install` created is removed when nothing\nelse is left in it; one that was there before stays, even empty. Modified or unrecorded material is preserved with a refusal.\nFor a recorded stub or startup block you edited, the refusal names `--force`: `install --force`\nrewrites the stub and each block from `# >>> cotal completion >>>` to `# <<< cotal completion <<<`,\nand `uninstall --force` removes them. Startup content outside those markers is kept. A block whose\nend marker is gone is still refused, with the file named, because only you can tell which lines\nare cotal's; remove them there yourself. For an integration you modified, bare `completion` offers\n`install` and `uninstall` only with `--force`.\nA stub from an earlier release without an ownership record needs manual review; matching\ngenerated text alone is not treated as ownership. An unowned startup reference to a managed stub must be reviewed\nbefore removing the stub. These commands cannot unload functions in a parent shell.\n\nCotal writers use a per-shell lock and recheck file identity and bytes before changing them.\nAn unconfirmed filesystem mutation is reported as unknown and is not retried. A confirmed change\nfollowed by another refusal is partial, with the acknowledged file paths retained. This is not\na filesystem compare-and-swap against an editor that ignores the lock.\n\n`update` refreshes existing unchanged recorded stubs through its current-generation CLI. It never\nadds startup wiring or reinstalls a removed stub. Modified and unrecorded integrations are kept;\nany guarded refresh it cannot complete is reported. `update --check` does not refresh completion.\n\nThe old `completion <shell>` form is a usage error. Custom startup commands need manual migration;\nsee [Upgrading](UPGRADING.md#shell-completion-script-in-0910). No command silently\nrepairs custom wiring. Install and uninstall support one finite JSON document or the plain report.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <type>] [--email <contact>] [--details <text>] [--json|--plain]\ncotal feedback \"<summary>\" --key-file <private-file> [--json|--plain]\n```\n\nThe type is bug, idea, friction, praise or other; severity is low, medium or high. Optional area\nand contact name are included only when supplied. The public intake requires a traceable email,\nfrom `--email`, the existing configured contact or local git email. No environment setup is\nrequired. Do not put credentials, private transcripts or unrelated customer data in feedback.\n\nA protected `--key-file` selects keyed intake without placing a credential on argv. The file holds\none key: an opaque bearer token with no whitespace, optionally followed by one newline. An existing\ninternal intake configuration remains supported. The old raw `--key` input refuses before the\nrequest and does not echo its value. Private key files must meet the shared file-ownership and\nprotection policy; unsupported environments refuse rather than weakening that policy.\n\n`--url` can select a self-hosted intake. A Cotal intake accepts reports only at `POST /v1/feedback`,\nso the URL names that path, such as `https://intake.example/v1/feedback`; the origin alone gets\nHTTP 404. Remote intake requires HTTPS; HTTP is limited to literal loopback development hosts. URL\ncredentials and fragments are refused, and redirects are not followed. One request has a 20-second\ndeadline and is never automatically retried or rerouted to a different credential/intake.\n\nA connection that never opens sends nothing: a refused connection, a failed DNS lookup or a failed\nTLS handshake refuses with `TARGET_UNAVAILABLE` and the native error code. A timeout or a broken\nconnection after the request was written, or an unconfirmed server outcome, can mean the report\narrived: inspect the original intake before sending it again. An intake refusal names its cause and\na next step. HTTP 401 means the key is missing or refused, 404 means the URL is not the intake's\nfeedback path, and 429 means the intake is rate limiting submissions. A 429 names the retry time\nwhen the intake's `Retry-After` header gives one.\n\nJSON/plain receipts report the intake origin, HTTP acceptance and a bounded record ID when the\nserver provides one. They do not repeat the submitted summary, details, contact values, key or\nresponse body. Acceptance does not prove that anyone read the report or acted on it. A success\nresponse without an established ID is reported with that limitation, not retried for a new ID.\nRun a self-hosted intake with [`feedback-intake`](#server-daemons).\n\n## telemetry\n\n```bash\ncotal telemetry [--json|--plain]\ncotal telemetry off|minimal|regular [--json|--plain]\n```\n\nThe CLI sends anonymous usage data: one small event per command as it exits, and at the default\n`regular` level one install summary per day. [Telemetry](telemetry.md) lists every field and what\nis never sent. The bare form prints the level in force, what set it (the settings file,\n`COTAL_TELEMETRY`, `DO_NOT_TRACK` or `COTAL_TELEMETRY_DISABLED`), the install id, the intake URL and\nthe settings file. A level argument writes the file; `on` is an alias of `regular`, and writing the\nlevel the file already holds is a `no-op`. A level the environment forces still wins, and the\nreport says so. Under `DO_NOT_TRACK`, `COTAL_TELEMETRY_DISABLED` or `COTAL_TELEMETRY=off` the file\nis neither read nor created, so a level argument saves nothing and is a `no-op` with an\n`ENVIRONMENT_OPT_OUT` warning. Any other word is refused `USAGE_INVALID`, naming the levels.\n`cotal telemetry` itself sends nothing.\n\nThe first interactive run at `regular` prints one notice to stderr. `COTAL_TELEMETRY` set to\nanything but a level refuses `USAGE_INVALID`. A settings file that does not validate refuses\n`SCHEMA_INVALID` on `cotal telemetry` and on every command that reports usage, until it is fixed or\nmoved away. Under the environment opt-outs above nothing reads it, so it stops no command.\n\n## run\n\nOperate durable workflow runs (cotal-lang programs) from the terminal.\n\n```bash\ncotal run start --file <program> [--timeout <dur>] [--local --admit-read <channels> --admit-publish <channels>] [--on <instance>]\ncotal run resume <runId> [--local --file <program>] [--on <instance>]\ncotal run [--endpoint <ep>] [--json] [--on <instance>]\ncotal run --page --on <instance> [--limit <n>] [--cursor <cursor>] [--endpoint <ep>]\ncotal run show <runId> [--endpoint <ep>] [--limit <n>] [--json] [--on <instance>]\ncotal run answer <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]\ncotal run amend <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]\ncotal run revoke <runId> --local --by <who> --reason <text> [--endpoint <ep>]\ncotal run check <runId> <program> [--endpoint <ep>]\n```\n\n`ps` (or `list`), `journal` and `migrate` still work as compatibility aliases of bare `run`, `show`\nand `check`.\n\nEvery verb also takes `--space <s>`, `--server <url>` and `--creds <path>` to pick the mesh and the\ncredential. A hosted verb refuses a raw `--creds` file `UNSUPPORTED`, exit 2, because the file\ncarries no endpoint-caller triple for the manager call and that route cannot mint one. Its next step\nis to run the verb again without `--creds`, in the mesh's project folder or with `--space`, where\nthe CLI mints the one-shot caller itself.\n\nHosted run verbs accept `--on <manager-instance>` to choose one full manager id\nas `cotal ps` prints it. A managed seat defaults to the manager that launched\nit; an unbound user-auth caller with several managers must choose one. Without\n`--on` its call is refused with exit 2 before any request, and the refusal names\neach live manager.\n`--on` does not apply to local, offline or seat-only run commands, and a failed\npin never falls back to another manager. Rich run reports label an explicit pin\nas the requested manager, and the JSON target carries `requestedManagerInstance`.\nFollow-up commands printed by the CLI retain the same explicit pin and fill\nin the run ID the command named or the manager returned, only when it matches the native\nrun-token shape. Keep the pin on later status, answer and resume calls.\n\nA run ID that is not 1 to 64 letters, digits, `_` or `-` is refused as `USAGE_INVALID`, exit 1,\non every verb that takes one, before anything is contacted.\nA hosted verb the manager refuses reports the outcome and code as other commands do, and with\n`--json` as one refusal envelope on stdout. A refusal the manager makes before acting is refused: a\ndenied credential or a step in the wrong state exits 2, and a program that does not validate is\n`SCHEMA_INVALID`, exit 1, like other invalid input. A run or step it holds no record of is `TARGET_NOT_FOUND`, with `cotal run` or\n`cotal run show <runId>` as the next step. A manager activates at most 8 runs at once, counting\neach `start` or `resume` until its run is recorded. One more is refused `LIFECYCLE_BLOCKED`, exit 2,\nbefore anything is written; the refusal names the runs still activating, and the next step is to\nwait until one has activated and run it again. Any other refusal of `start`, `resume`, `answer` or\n`amend` may follow a recorded change, so it is unknown, exit 3, and is not retried.\n\nAn unpinned hosted verb run by the operator reads the space's service registry first. When it\nlists no live manager, as after `cotal down stack-manager`, the verb refuses `TARGET_UNAVAILABLE`\n(exit 2) at once, as `ps` does, and names bare `cotal up` as the next step, instead of waiting out\nthe manager's answer deadline.\n\nA mesh a run verb cannot resolve or connect to is refused before any request under the refusal's\nown code, hosted or `--local`, as `ps` refuses it. With no mesh running that is `TARGET_NOT_FOUND`,\nexit 2, and with `--json` one refusal envelope on stdout.\n\n`start` hands the program to the mesh's manager, which validates it, mints the run id (the record\nnever takes a caller-supplied one), drives it in its own process, and answers with the id once the\nrun is recorded; a program that does not validate is refused with every problem listed, each\ncontrol character in it shown as its `\\uXXXX` escape. Every verb\nthat takes a program file refuses a missing one with `TARGET_NOT_FOUND` and one this account cannot\nread with `PERMISSION_DENIED`, exit 2, and a folder with `USAGE_INVALID`, exit 1.\nA `start` interrupted with Ctrl-C or `SIGTERM` after its request went out reports outcome `unknown`,\nbecause the manager may already have recorded the run, and exits 130 or 143. List the runs with\n`cotal run` before starting it again.\n`resume` asks the manager to take an existing run back and continue it from its step journal; the\nsource is the recorded program, so no `--file` is taken. A run recorded `completed` or `failed` is\nrefused `LIFECYCLE_BLOCKED` (exit 2) and gets no new activation, because nothing is left to continue;\nthe next step is `cotal run show <runId>`. Neither takes `--endpoint`: the manager records\nits runs under its own endpoint, and naming another is refused. Bare `run` lists the run records and\n`show` renders one run's durable records; both only inspect. `show` reads the\njournal from the manager in pages, each one reply, so a journal larger than the broker's payload\nlimit stays readable. A record or a journal row too large for one reply refuses `UNSUPPORTED`,\nexit 2, and names `cotal run show <run-id> --local`, which reads the run's stream from its\nproject folder with no reply. A manager from release 0.99.3 or earlier reads no journal page, so\nthrough it both refuse `UNSUPPORTED` and name the same local read. An open pause prints its question\nunder `Needs an answer`, and only then does Next offer `answer`. A checkpoint with a `to` prints\nbeside its question whom it escalates to, and once it has escalated, until when that attempt is open. A pause counts as open while its\nlast journal row is pending, so a settled or expired one is not listed there.\nA pause settled with an accepted answer prints its value as JSON plus the recorded answerer,\nartifact when present, time, and answer id, then one `amended` line per later amendment, in the\norder the store committed them, so the last is the current position. A value longer than 120\ncharacters is cut there, with a line that says so; `--json` carries it whole.\nExpired pauses and ordinary steps print no answer line.\nA step whose last journal row recorded an error prints its outcome and the recorded message under\n`Step errors`, a caught failure included, with each control character shown as its `\\uXXXX`\nescape. A run that failed records its cause on its status. Its recorded state reads\n`failed (<code>)` in the run list and the run view when the error carries a language code, and the run\nview prints the message under `Failure`. When a step's error is the run's failure, `Failure` names\nthat step and the message stays under `Step errors`. A run that failed before its status recorded a\ncause shows only `failed`. A failure raised by program code outside any step is recorded in no\njournal row. When the status recorded no cause either, the view says so under `Failure`. Either way\nit names `cotal service logs stack-manager --space <space>`, because a hosted run's cause is in its\nmanager's log.\nThe failure records no line or column in the program: a source position belongs to a problem\n`run validate` reports before the program runs.\n\nA run recorded `released` has no recorded reason. Its `Released` section points to the executing\nmanager's log for a hosted run or the driving process's output for a locally driven run. A journal\nread does not establish that execution host or log location, so the view does not guess one.\nA journal entry larger than the broker's configured `max_payload`, such as a large checkpoint\nquestion, can release a run with `L5010 Journal append rejected`. Repeating `resume` while the\ncause remains can release it again; this view does not change that state or prevent the retry.\n\nA program's `log(...)` lines are not journalled, so `show` does not print them. A hosted\nrun writes each line to its manager's log as `run <run-id>: log <text>`, which\n`cotal service logs stack-manager --space <space>` reads, and a resumed run writes the lines before\nits resume point again. `--local` prints them in the terminal that drives the run.\n`--json` on the run list or `show` writes one envelope on stdout, as other commands do, and nothing on\nstderr, whether the manager answers or `--local` reads. The list puts its rows under `result.items`.\n`show` puts the run under `result.view`: its `runId`, `endpoint`, recorded `spec` and `status`,\nits `programHash` as the list row carries it, and its rows under `result.view.journal`. A program\nrecord that cannot be read gives `programUnreadable` with its reason, and no `programHash`. A list row carries `runId`, `endpoint`, `state`, `holder`, `epoch`,\n`journalHigh`, `forkedFrom`, `startedAt` and `programHash` (the values the program's `run()`\nreports; `programHash` is absent for a run with no recorded program), `failure` on a failed run\nwith its `code` and `message`, and `revoked` or `revocationUnreadable` when the marker says so. A journal row is an `activation` or a `step`. A\nstep row carries its `step` key, the `effect` kind and its `name`, `state`, `outcome`, the recorded\n`status` and `errorCode` once settled, the recorded `errorMessage` of a failed or refused step,\nand `startedAt` and `endedAt` in epoch milliseconds. An open\npause adds its `asks`, its `deadlineAt`, and for a checkpoint the `onExpiry` it was armed with and\nthe `addressee` its `to` names. A checkpoint's escalation is armed with `proceed`, so it never\nescalates again. A settled pause adds its `answer` and its `amendments`, as the text view prints\nthem. A field the\njournal does not record is absent: a checkpoint opened before `onExpiry` was recorded carries none.\nAn unreadable revocation marker makes the result partial, exit 4, with a `REVOCATION_NOT_CHECKED`\nwarning naming the run. A `run --endpoint <endpoint>` that matches no run lists nothing and says so,\nnaming the endpoints runs are recorded under, with an `ENDPOINT_HAS_NO_RUNS` warning. The text view is presentation and is not a stable parsing target. With\n`--local`, only the list, `show`, `answer` and `amend` take `--json` or `--plain`. Any other local form\nrefuses either flag `USAGE_INVALID`, exit 1: `start --local` and `resume --local` name the\nmanager's form, which takes it, and `revoke` and `check`, which have no other form, name the same\ncommand without it.\n`run --page`, which is not in release 0.88.0 or earlier, asks one manager for a single bounded\ndiagnostic JSON page instead of the full list:\n`--limit` takes 1 to 16 rows (default 8), and `--cursor` continues a scan from a returned\n`nextCursor`. It requires `--on` and refuses `--local`, and a page proves neither absence nor\nownership ([Diagnostic inventory](workflows.md#diagnostic-inventory)).\n`revoke` writes the run's revocation marker from the project folder; `--by` and `--reason` are\nrequired ([workflows](workflows.md)).\n`answer` resolves an open\ncheckpoint through the manager, presenting as the holder that armed it; the manager records the\nanswerer from your credential, so no `--by` is taken there. A settled step refuses a second\n`answer` with `TARGET_NOT_FOUND`, also when it is sent before the run records the first, and a\nrevoked run refuses every answer with `LIFECYCLE_BLOCKED` and files nothing. A step the run holds no\nopen request at is also `TARGET_NOT_FOUND`; when an open checkpoint or `ask` has the name you typed,\nas `frage` names `/checkpoint:frage#0`, the next step gives its full key, and several matches are\nlisted for you to pick from. A run ID that no run is recorded under is `TARGET_NOT_FOUND` too, with\nor without `--local`, and the refusal says no run is recorded under that ID. An\nanswer without `--value` carries no value: the step settles as answered, and the\nprogram's checkpoint result has no `value`. `--value` is refused with `SCHEMA_INVALID` before\nanything is sent when the record would not hold what you typed: an object that names a key twice,\na number outside the range JSON carries (such as `1e999`), or a value nested more than 1000 levels\ndeep. An empty or blank `--artifact` is refused with `USAGE_INVALID`. The receipt of `answer` and\n`amend` names the step, the value it filed or that none was given, any artifact and the answer id. A value longer than\n120 characters is cut there too, with a line that says so. With `--json` they are in\n`result.answer`, whole, where `valueGiven` says whether a value was sent. `amend` records a changed\nposition on a settled checkpoint or `ask`: it files a new answer beside the accepted one, naming it, and the journal lists it under the step. The pause stays\nsettled and the run keeps the answer it acted on. A step that is still open or settled without an\nanswer refuses an amend. So does a run ID that no run is recorded under, and that refusal says no\nrun is recorded under that ID. With `--local` these refusals are `TARGET_NOT_FOUND`, exit 2, as the\nhosted ones are, since nothing was written. The run records an accepted answer a moment after `answer` returns, and an\namend sent in between is refused with a message saying the answer is not recorded yet; amend again\nonce `run show` lists it. A spawned seat may amend only an answer recorded under its own name.\n`check` replays a run's journal against an edited program, from this terminal under a read\ncredential (`--local` only; the manager serves no run-migrate command). It prints whether the\nmigration is admissible, every orphaned step with its verdict and code, and exits 0 on admissible\nand non-zero on not. An edited program that does not validate is refused before the replay, with\neach problem's code, position, cause and fix. It writes nothing: the commit that would file the\nmigration is not reachable yet, and the report says so. A run ID with no record on the endpoint is refused\n`TARGET_NOT_FOUND`, exit 2, as `run show` refuses it. `check` refuses `--adopt`, `--release` and `--discard-approvals`, since they decide what a\ncommit does and the check files nothing. `--timeout` sets the default\nlimit of a checkpoint, `ask` or turn that sets none of its own (default 1h). A checkpoint that\nexpires under `onExpiry: \"fail\"` says in its L4007 whether the deadline came from its own `timeout`\nor from this default. It takes a duration\nlonger than zero, such as `30m`, and refuses anything else with `USAGE_INVALID` before the run starts. `--on <instance>` sends a hosted verb to one manager\ninstance, with the same rules as [`ps`](#managed-seats). On a mesh whose managers this terminal can\nlist, an `--on` that names none of the running ones is refused `TARGET_NOT_FOUND` at once, naming\nthem, and nothing is sent. On a user-auth mesh where you run more than one\nlive manager, an unpinned hosted verb is refused and `--on` picks one. `--local` takes no `--on`. `--local` drives in this process instead, over one\nconnection per invocation under the run's own credential minted from the project folder's trust\nmaterial, and is the path on a bare broker with no manager or for a run with no recorded program\n(`cotal run resume <runId> --local --file <program>`); `answer --local` and `amend --local` take\n`--by <who>` and print the same receipt as the hosted path, and `start --local` requires\n`--admit-read` and `--admit-publish` (comma-separated\npatterns or `none`), the run's channel ceiling. An entry that is not a channel or a pattern, such as\n`a b`, is refused `USAGE_INVALID` naming it. When the run's status records that its program failed\nunder this drive, `start --local` and `resume --local` fail `OPERATION_FAILED`, exit 5, naming the\nrun and its language code with that code's title. A failure whose code has no title shows the first\nline of its recorded message instead, with control characters escaped. Their Next is\n`cotal run show <runId> --local`, which prints the whole message. Any other error the drive ends on\nstays `unknown`, exit 3. On a\nuser-auth mesh the host's own manager refuses the family by name, and `--local` has no credential\nthere. A participant's manager started with `cotal supervise` hosts a logged-in user's runs through\nits issuing host: the auth callout issues the user's manager connection, and every `run` verb rides\nthe versioned rail under that issuance.\n[User-auth run start](design/user-auth-run-start.md)\nrecords the path. The guide is [workflows](workflows.md).\n\n## Server daemons\n\nThree long-lived infrastructure roles ship with the CLI: the delivery daemon, the auth service and\nthe feedback intake. They are not part of everyday operation. The delivery daemon comes up\nautomatically with `cotal up --detach` in auth mode.\n\n`deliver`, `auth-service` and `feedback-intake` are deprecated entries kept for existing units and\nscripts; help and completion omit them. Each entry's own help\nmarks it deprecated, with its removal release and the preset that replaces it. Each accepts its existing flags and\nprints a warning on every run naming its service preset and the release that removes it. The\ndelivery daemon and auth service that `cotal up` starts run the same code through hidden entries\nand print no warning. New setups use `cotal service start <preset>`; see\n[deprecated daemon entries](UPGRADING.md#deprecated-daemon-entries). [`supervise`](#supervise), the\nmanager's entry, is deprecated the same way and names `cotal manager start`.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>] [--root <dir>]\ncotal auth-service --space <s> --server <url> [--port <n>] [--exchange-public-port <n>] [--exchange-public-url <https://\u2026>] [--exchange-trusted-proxy] [--advertised-server <url>] [--agent-provisioning-url <https://\u2026>] [--auth-timeout <seconds>]\ncotal auth-service --space <s> --server <url> --callout-only [--auth-timeout <seconds>]\ncotal auth-service --server <url> --callout-only --spaces <manifest> [--auth-timeout <seconds>]\ncotal service add intake --preset feedback-intake --space <s> --root <absolute-root> --keys <private-keys.json> --creds <private-creds>\ncotal service start intake --space <s>\n```\n\n`auth-service` runs a user-auth space's identity plane: the NATS auth callout, the\ncapability-gated local exchange and JWKS, and, when `--exchange-public-port` is set, the closed public\nexchange/discovery face forwarded by an HTTPS reverse proxy. `--exchange-public-url` is the proxy URL\nadvertised to clients; `--exchange-trusted-proxy` opts into last-hop `X-Forwarded-For` attribution.\n`--advertised-server` and `--agent-provisioning-url` set the broker address and agent-provisioning\nendpoint the public bundle advertises, as on [`up`](#up). `--auth-timeout` is the broker's\n`authorization { timeout }` in whole seconds, which the callout answers inside; give it the value the\nbroker config renders, which `cotal up` passes from `--auth-timeout`.\n`cotal up --user-auth` starts and supervises the service for you, so you run it directly only to\nrecover one by hand.\n`--callout-only` runs a callout replica beside the full service: it answers connects alongside it\nand serves no HTTP, so it refuses `--port`, the public-face flags and `--service-endpoints`. It\nrefuses to start in a space that admits service endpoints. It never writes `auth-service.json`, and\nit exits non-zero when it loses a store connection or the broker. Give it the same `--auth-timeout`\nas the full service, the value the broker config renders.\nSee [callout replicas](identity-and-auth.md#per-user-authentication).\n\n`--spaces` serves several spaces from one callout process, one lane each, and stands in for\n`--space`. The two are alternatives: naming both refuses, and `--spaces` without `--callout-only`\nrefuses. The manifest is a JSON file holding the spaces this process serves:\n\n```json\n{ \"version\": 1, \"spaces\": [\"<s>\", \"<s>\"] }\n```\n\nAn empty list, a repeated space, an unknown field, a version other than 1 and an entry that is not\na space name all refuse. Every lane dials the one `--server`, so a deployment whose spaces live on\ndifferent brokers runs one host process per broker rather than naming a server per space.\n\nEach lane applies the per-space refusals a single-space replica applies, so a space with missing\nmaterial, a mismatched issuer pin, an unreachable broker or open service-endpoint admission refuses.\nAt startup the host serves every space in the manifest or none: one that cannot be served is an\nerror naming that space, and no lane is left running.\n\nThe host rereads the manifest when it changes on disk, on `SIGHUP`, and on a slow backstop interval,\nand opens or closes the difference. Write a replacement and rename it into place: a manifest that\ndoes not read or parse whole is reported and the lanes already open are kept. A lane that loses its\nstore connection or the broker closes on its own and the other lanes keep answering; the host\nreopens it, retrying every 5 seconds while it fails.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred for standalone dev, signed by the local account of the space the\ndaemon serves. `--creds` can start a daemon that already looks healthy, but production renewal is\nnot that file alone: the manager and the daemon must address one credential store. On a stock split host with two project roots, a\ndirect `deliver` is not an independent repair; keep the daemon under `cotal up` on the broker\nhost, or inject the same store into both processes ([embedding](embedding.md#supervisor-signing-authority)).\nTyped by hand on the workstation, `deliver` dials the broker recorded for `--space` in the mesh\nregistry (a mismatching `--server` is refused before any dial, and a record for a different\nworkspace root is refused outright); with no record for the space it falls back to the local mesh.\nThe daemon serves the workspace root that `--root <dir>` names, which must hold `.cotal/`, or else\nthe nearest `.cotal/` above its working directory. With neither, it refuses at start and names the\ndirectory it searched from, before it reads a credential or dials a broker.\nSee the [delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\nat `POST /v1/feedback` (requires `--keys`, in the format the `feedback-intake` service preset\ndescribes, and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\nWithout `--server` it dials the broker its `--space` is registered at, and refuses an unregistered\nspace before dialing.\n`--port` takes a decimal port from 1 to 65535 and is checked before the intake dials the broker.\n`--max-bytes` and `--rate-limit` are checked at the same point. Each must be a positive integer no\ngreater than 9007199254740991, and a refusal names the value it rejected.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you run it directly only to debug a completion, with\n`COTAL_COMPLETE_DEBUG=1`. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either, so `cotal help` leaves it out. Its local arm uses `--dir` to discover the\ncapability-gated loopback service. A remotely enrolled, already-granted agent instead receives\n`--exchange-url <https://base>` in its launch argv: that arm sends `{owner, actor, actorToken}` to the\npinned public exchange with no local capability, follows no redirects, and refuses every non-HTTPS\nURL because the actor token is the credential in the request body. Because a seat execs it on every\nbearer refresh, it skips the connector-seed boot gate entirely: it reads one 0600 token file,\nexchanges it and prints the bearer without consulting or writing the operator-global seed store, so\na newer store generation cannot refuse a live seat's refresh. For the same reason the seat's command\nnames the CLI entry file the `cotal` bin link resolved to at spawn, not the link: an install that\nlater replaces the link, such as the get.cotal.ai launcher over an `npm i -g` link, leaves running\nseats able to refresh. The command runs before the rest of the CLI loads and reads none of its\npreferences, extensions or config folder, so a refresh does not pay the CLI's start-up time. A command line it\ncannot parse goes on to the full CLI, which refuses it with the command's help. `--manager-call` asks for the\ninstance-bound `manager-caller` view; `--manager-instance <id>` selects an explicit live candidate,\nand an owner with more than one live candidate is refused with their ids until it names one.\nThat mode still prints only the raw token and does not update `--health-file`. A spawn runs it once as the agent auth\npreflight. When it fails there without printing a sentence of its own, the refusal names the cause: the 30 second\ntimeout, the signal that killed it, or its exit code. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n\n`cotal app-identity` is plumbing for a Claude app conversation, and you never run it directly\neither. The Claude connector's local server runs `cotal app-identity provision --name <name>` when\na conversation connects. It picks the mesh as `cotal spawn` does (`--space` selects one) and prints\none JSON line. A new identity opens only after a person approves it at the mesh's authority, so\nprovision needs a signed-in mesh whose authority advertises connection approval v1, and the\nconversation's native `--session`. It keeps the request and its secret in a private record under\n`~/.cotal/app-identities`. Until the person decides, it answers\n`{\"ok\":false,\"pending\":true,\"requestId\":\"<id>\",\"approval\":{\"id\":\"<id>\",\"url\":\"<url>\",\"expiresAt\":\"<time>\"},\"proposed\":<selection>}`,\nwith no credential, secret or path. `proposed` is `null` for a request without a selection.\nProvisioning again sends the same request: it answers the same approval while pending, and the\nlaunch env and the reference that `retire` takes once approved. The grant is what the person\napproved, which can differ from the request. A denial or an expiry drops the record, and the next\nprovision makes a new request. `--selection <json>` proposes channels, a role and capabilities for\nthat conversation. A mesh with static credentials, an open mesh, and a signed-in mesh whose\nauthority this machine holds have no approval page, so provision opens no identity on them.\nThe connector passes `--require-approval`. Every provision needs approval with or without it, and\nan earlier CLI that opened identities without approval refuses the unknown flag before doing\nanything. A saved request that was never issued needs the person's decision even when an earlier\nversion saved it.\n`cotal app-identity retire --dir <dir>` still removes an identity an earlier version opened on a\nstatic mesh: its broker footprint, then its files and directory. When the broker is not answering it\nkeeps them. Each provision also retires those directories whose owning process has exited. For a\nsigned-in identity, `retire` keeps the record until the mesh confirms retirement, and names the\nmesh's phase while it is pending. A request still pending is dropped.\n`cotal app-identity reap` retires the selected mesh's saved conversations whose server process no\nlonger exists. The connector runs it after each signed-in connection, so a connection never waits\nfor that cleanup. Like `agent-bearer`, it skips the connector-seed boot gate.\n\n## watch\n\n`cotal watch` opens the read-only activity screen for the selected space. `w` is its shorthand.\nIt uses the existing observer view authority; a user-auth space requires the corresponding\nadmin view grant. It does not register a participant, bind an inbox or expose local process\ncontrols. Leaving with `q`, Esc or Ctrl-C stops only this observer.\n\nUse `--view events` or `--view messages` to select a feed, or press `f` to change it in the\nterminal. Arrow keys scroll; `g` returns to the latest observations. The feed retains at most\n300 entries. A reported turn ending is not task completion. An interrupted feed keeps its\nlast observations and reports the gap instead of showing them as current. When the connection\ncomes back, after a broker restart too, the feed resumes and reports the transition as a gap.\n\n`--plain` or a noninteractive invocation uses the attributed line stream. Interactive filters\nare not supported by that stream. A line carries at most 16,384 code units of a message's text, the\nobserver stream's row bound. A longer message is cut there, and its line ends with the count not\nshown and the read that shows it whole: `cotal history <channel>`, or `cotal history --dms` for a\ndirect message. Authenticated observer views keep direct messages out of\nthe channel tap. Opening this view never launches, stops or attaches an agent. If the reader\nof the stream closes it, as `| head` does, the observer stops at its next line, so on a quiet mesh it\nruns until a message arrives or Ctrl-C ends it with exit 130.\n\nA target this observer cannot open, such as a stopped project's space, is refused before any view\nexists, under the refusal's own code and exit 2, as `history`, `channel` and `chat` refuse it in that\nfolder. The terminal gets the refusal's report, with the mesh it resolved and its next step;\n`--json` gets that code, message and next step in the stream's `error` record, ahead of its `end`.\n\n## Named agent status\n\n`cotal status <agent>` reads the authorized manager census and separates Identity,\nExecution, Mesh and Last reported activity. JSON has `identity`, `execution`, `mesh`\nand `activity` fields; missing optional observations are null with source annotations.\n`--on <manager-instance>` restricts the read. It takes no `--all` or `--components`.\n`cotal status manager`, or an answering manager's instance id, names the manager itself, which runs no\nrow of its own: it refuses `TARGET_NOT_FOUND` like any unknown name, and its Next says the manager is\nanswering and points to `cotal status` and `cotal endpoint describe manager`.\nA requested model is not provider-confirmed. Connector-reported provider\ninformation is labeled exact or name-matched under the same proof. A new manager\nrow marks a roster observation exact only when its principal and lifecycle UID\nmatch the managed seat and its presence view is current. Older or mismatched rows leave exact presence\nand activity null and label any name-matched values separately. A verified\ncondition and its reported start appear with activity; an unverified condition\nstays in a separate name-match field. A waiting observation is not a measured\nblocked duration, and a work event is not task completion. Attach and stop\nhints retain the answering manager's instance when it is a valid native id.\n\nA same-name match on several managers refuses as ambiguous. An unanswered manager leaves\nthe search partial; no matching row in that partial read is not proof the agent is gone.\nThe command starts, stops and attaches nothing.\n\n## Manager maintenance\n\n`cotal manager`, also `cotal mgr`, shows its two scoped operations. The group and its operations\nsupport `--json` and `--plain` through the shared reports. Neither operation installs connectors\nor starts a manager.\n\n`manager seats` lists retained local PTY custody records. `--drain --dry-run` reads the census\nwithout submitting a drain. A real drain reviews the local store and defaults to cancellation;\nscripts require `--drain --yes`. Its scope is all safely childless records in that store when\nthe native pass runs, not a frozen list from the review. Each native reap rechecks process\ncreation identity. Live children stay. An unresolved preflight blocks draining; a partial read\nreturns exit 4. An unconfirmed effect during a submitted drain returns exit 3 and keeps confirmed\nremovals in the result. Raw native diagnostic bodies are omitted.\n\n`manager reconcile` uses the existing guarded issuance-gate repair from the owning local mesh\nroot. It resolves the registered broker without an alternate-target fallback and preserves its\nTLS requirement across repair, liveness and eviction connections. It requires that root's auth\nmaterial and verified holder liveness. Review defaults to cancellation; scripts require `--yes`.\n`--dry-run` checks only the local coordinate and issues no credential or native guard query.\n\nA pre-effect native guard refusal returns exit 2. Its error names the guard's condition and keeps\nthe guard's own sentence as its native cause, which names the freeze-holder and the next step.\nA failure after the repair may have submitted\nwork returns exit 3, without replaying mutations or hiding possible revocation/eviction progress.\nRe-observe that gate and its operation-bound durable cursor before another explicit repair.\nThe final receipt reports the native operation and before/after coordinates, not a running-manager\nverdict. A reopened gate with retained cursor cleanup returns exit 4.\n\n\n## Workflow command forms\n\n`cotal run` (or `cotal r`) lists the authorized workflow records. `run show <run-id>` reads\nrecorded state, open requests, each step's recorded error and the last 20 journal rows. `--limit <n>`\nprints the last `n` rows instead, from 1 to 1000, and is refused with `--json`. A settled\n`race` row names the branch it took, and a cancelled row in a branch that lost a race names the\nbranch that won and the race. An answer whose step was in a branch that lost a race, or\nwhose pause was cancelled after accepting it, is listed as `not acted on`. Its JSON result contains\nthe full returned status and journal. Revoked admission and unreadable revocation markers remain distinct\nfrom the record's state. Neither a record nor a receipt is a live agent-progress report.\n\n```bash\ncotal r\ncotal r start ./workflow.cotal.js\ncotal r show <run-id> --json\ncotal r resume <run-id>\ncotal r answer <run-id> '<step-key>' --value '<json>'\ncotal r amend <run-id> '<step-key>' --value '<json>'\ncotal r check <run-id> ./edited.cotal.js\ncotal r revoke <run-id> --local --by <actor> --reason '<text>'\n```\n\nStart takes a positional source file. A source file a `run` verb cannot read is refused naming\nits path and the cause: `TARGET_NOT_FOUND` for a missing file or a link whose target does not exist,\n`USAGE_INVALID` for a directory and `PERMISSION_DENIED` for a file it may not read. `-` is a file\nname there, since `run` does not read a program from standard input. A run verb that reads a source\nfile refuses an empty one or one that is not valid UTF-8 with `SCHEMA_INVALID`, before it checks or\nsends anything, because a run's source must not be empty and its text is never replaced. A hosted\nstart sends the program to the manager in one message, so a program over the broker's message limit\n(`max_payload`) is refused `UNSUPPORTED` with its size and the limit, and nothing is sent. Resume uses the host's recorded program. Hosted answers\nand amendments are attributed to the authenticated caller, never a supplied name. Local answers and\namendments require `--by <who>`; a missing or empty value refuses `USAGE_INVALID` before contacting\nthe broker, with a JSON refusal under `--json`. `check <run-id> <file>`\nreplays the run's recorded journal\nagainst the edited program and prints whether the migration is admissible, and it files no migration. Revocation keeps\nthe existing local trust and explicit actor/reason requirements and does not cancel a task.\nHosted start, resume, answer and amend have JSON and plain receipts. The local drive and local\ncheck retain their native output rather than claiming an unsupported structured format.\n\n```bash\ncotal r validate ./workflow.cotal.js\ncotal r plan ./workflow.cotal.js\ncotal r simulate ./workflow.cotal.js --handler ./results.json\n```\n\nThese three forms are offline and reject mesh or caller selection. Validation checks the\nlanguage and nothing a broker decides, so a valid program over the broker's message limit is still\nrefused by a hosted `start`. A program that does not validate is refused by all three with every problem listed: its\ncode, position, cause and fix, as a hosted `start` lists them. `--json` carries them under\n`result.problems`. A warning leaves the program valid. All three print it on stderr with its code,\nposition, cause and fix, and `validate --json` lists it under `result.warnings`. One is L3021, a `fanOut` over a list written in the call whose items carry no\n`id` and that passes no `key`, which the run refuses when it reaches it. A list held in a variable\ngets no warning, because it can change before the call; the run refuses it the same way. Plan lists validated static call sites in source order; it does not evaluate branches,\nloop counts or an execution schedule. Simulation uses the native simulator with a JSON results\nscript. It executes no JavaScript handler and invents no unscripted outcome. A scripted\ncheckpoint result needs a `status` of `resolved` or `expired` and may carry `value`, `by` and\n`artifact`; any other shape, a scripted `at` included, is refused `SCHEMA_INVALID`, because the\nsimulator stamps `at` from virtual time. A scripted turn result needs a `status` of `done`, `blocked` or\n`handoff` and may carry `to` and `note`; any other shape, a scripted `at` included, is refused the\nsame way. When the script has no result for an effect, the\nsimulation does not complete: it exits 5 with `OPERATION_FAILED`, the message names the step and the\nentry to add, and `target.step` carries the step. When the program itself fails under the script,\nas a checkpoint scripted `expired` under the default `onExpiry: \"fail\"` does with L4007, the\nsimulation completes and exits 0. Its program outcome is failed, with the step and that step's\noutcome, or the last step the program settled before its own code failed, and the language's error.\nJSON carries this as `result.outcome` and `result.failure`. A simulation does not establish live authority, installed applications or readiness. All three support JSON and\nplain output and contact no broker.\n\nThe `--handler` file is one JSON object. Each table maps a step's name to its result: one value\nfor every occurrence, or a list used in order. A step with no name is keyed by its effect, such as\n`ask`. `spawn`, `sleep`, `notify`, `monitor`, `observe` and conclaves need no entry, and a sleep\nmoves the virtual clock forward.\n\n```json\n{\n \"checkpoints\": { \"approve-plan\": { \"status\": \"resolved\", \"value\": true, \"by\": \"ana\" } },\n \"asks\": { \"plan\": { \"steps\": [\"design\", \"build\"] } },\n \"turns\": { \"build\": [{ \"status\": \"blocked\", \"note\": \"needs a key\" }, { \"status\": \"done\" }] },\n \"events\": { \"await-reply\": null }\n}\n```\n\n| Field | Meaning |\n|---|---|\n| `turns` | `turn` results: `{status, to, note}`, where `status` is `done`, `blocked` or `handoff` |\n| `asks` | `ask` replies, each checked against the step's `schema` |\n| `checkpoints` | `checkpoint` results: `{status, value, by, artifact}`, where `status` is `resolved` or `expired` |\n| `events` | `wait` results. `null` is a timeout |\n| `clock` | `{start, turn, ask, checkpoint, wait}`: the start time in milliseconds and how long each effect takes, such as `\"5m\"`. A turn takes 5 minutes and the others 1 minute unless set |\n| `faults` | `[{at, kind, code, message}]`: the step at `at`, a step key such as `turn:build#1`, fails with that error |\n\nThe simulation's `log(...)` lines print under `Log`, and `--json` carries them in `result.log`, each\nwith its `scope` and `values`. A step with no entry, or a list shorter than the step's occurrences,\nstops the simulation with L6001 and exit 5. The failure names the table, the step's name and its step key,\nand the remedy names the entry to add. For a list that is too short, it also says which result the step\nneeds, counted from 1, while the step key counts occurrences from 0. A failure in the program itself\nnames its code and cause.\n\nInside a manager-launched static or open seat, `run turns` reads that seat's assigned turns and\n`run yield <turn-id> --status <done|blocked|handoff>` reports an outcome. Handoff also requires\n`--to <agent>`; `--note <text>` carries context. The native launcher supplies the caller context.\nThese forms verify the seat's own credential and manager, never substitute an operator caller,\nand refuse user-auth self impersonation. They skip operator catalog and installation repair.\nYielding one turn does not complete the entire workflow.\n\n\n## Update availability\n\n`cotal update --check` queries the latest registry version and reads recorded extension versions.\nIt performs no install, reconcile or process restart and takes no mesh selection. It supports\n`--json/-j` and `--plain`. The registry read runs npm with a temporary cache that is removed\nafterwards, no log files and no npm update notifier, so it leaves nothing in npm's cache, log\ndirectory or notifier stamp. Missing registry data or an unreadable extension inventory remains\nunknown or partial, never an empty successful check.\n\nThe report compares first-party recorded versions with the running CLI. It does not inspect\npackage contents, signatures or loaded process generations. `cotal update` and `update --self`\nkeep the existing guarded update, continuity checks, download verification and current-generation\nreconciliation. Their installer progress is native output, not a structured check receipt.\n\n\n## Account screens\n\nBare `login` chooses a known provider from cached account metadata or registered spaces. When\nnone is known, an interactive terminal asks for the provider auth URL. `--idp` selects it directly.\nThe device-approval link and code are written only to stderr. Session tokens are never printed.\nThe sign-in receipt separates account identity from each space's grants. This client's device\nflow requires approval and refuses `--no-input` before requesting a device session.\n\nBare `logout` chooses among cached providers. A single cached provider is unambiguous; scripts\nwith multiple providers must select one with `--idp`. `logout --json` and `--plain` report the\nsame result. No cached session is a local no-op, not proof that all provider sessions are gone.\nRevocation must return before local cleanup. If revocation is unconfirmed the login is retained\nfor retry, and the refusal names `--local`; a later cleanup failure is partial and keeps the\nconfirmed server outcome. `logout --local` clears this machine's cached login and that account's\ndiscovered registry entries without contacting the provider, for a provider that is down or does\nnot confirm sign-out. The provider session stays valid until it expires or is revoked at the\nprovider, and the receipt says so.\n\n\n`endpoints --json` and `endpoints --plain` show the authorized live presence snapshot, including\nagents and infrastructure. This legacy live read is labeled as presence, not a saved-reference\ninventory or proof that a service registration is active. Native contract resolution remains a\nseparate read. Each `--json` item carries the presence values: `reportedStatus`, `condition`,\n`activity`, and the ISO 8601 times `statusSince`, `activitySince` and `activeAt`, each null when the\nrecord has none. The report works out the ages it prints from those times.\n\n\n## Managed launch view\n\n`spawn [<persona>] --on <instance>` launches through that manager and attaches after its native\nreadiness result, when that manager's runtime streams a terminal. The attachment is pinned to the attributed accepting manager and the allocated identity/incarnation,\nnot a second display-name guess. `--detach/-d` returns a finite JSON/plain-capable receipt after\nthe same readiness result. While it waits, it prints the launch it asked for, with the space as a\nfield, and one line that says the manager answers when it refuses the launch, or when the agent\njoins or exits, within about 30 seconds. The receipt or refusal that follows names the mesh in its\n`Mesh:` heading. A launch the manager reports failed, such as an agent whose process exits at once,\nfails `OPERATION_FAILED`, exit 5, with the manager's reason. The terminal receipt shows the full lifecycle, original request, accepted goal where reported,\nrole and model request separately from any provider acceptance claim. When the host granted the\nagent other read or post channels than it asked for, as a host that narrows a request to your grant\ndoes, the receipt adds a `Granted` line with the channels it got and the ones it asked for, and\n`--json` carries both as `acl.requested` and `acl.effective`. Its Next section names the actual agent, space and accepting manager\nin attach/status commands when their tokens are safe shell words; it otherwise retains\nplaceholders beside the full identity fields.\n\nA manager on a runtime that streams no terminal, which is `tmux`, `cmux`, `orca` and `herdr`,\nanswers with that fact and the launch stops there: no attach is asked for, nothing is partial, and\nthe command exits 0 with the agent running. Its `Runtime` row reads `<runtime> \xB7 no terminal\nstream`, its `View` row reads `not attached; the seat runs in the manager's <runtime> session`, and\nits Next gives `cotal watch` and `cotal send -t <name>` with one line saying `attach` is not\navailable there. `--json` carries the same answer as `result.streamsTerminal`, beside the\n`result.runtime` that names the backend. A manager from before 0.104.2 reports neither, so\n`streamsTerminal` is null and the launch asks for an attach. When that manager refuses it because\nthe runtime streams no terminal, the launch ends there too, with one line that names the runtime,\nand exits 0. Such a manager does not mark that refusal, so the CLI accepts it only for a runtime\nwhose provider, registered or installed with this CLI, declares no terminal. Any other failed\nattach is partial, exit 4, with the manager's reason as `Native detail` and a Next that gives\n`cotal ps` and `cotal attach` with the space and manager instance. Without usable input/output terminals, an attaching\n`--on` launch refuses before allocation and names `-d` or a spawn in this terminal. A shell tool's\ninherited parent PTY is not enough.\n\nA launch whose agent application waits for you before it joins is ready once its process runs.\nClaude with `--opt resume=` and no session id opens its session picker, so the CLI says the agent\nis running on its manager and host and waiting for you at Claude's session picker. In a terminal it\nattaches to the agent at once, `-d` included; otherwise it prints the attach command. When the\nreadiness window passes and the manager reports the agent's process still running, the CLI says\nthe agent has not joined the mesh yet, attaches when the spawn was not detached, and otherwise\nprints the attach command. Both exit 0, and `--json` reports them as `managed-launch-running` with\n`waitingFor` naming the screen or null. The outcome stays unknown when the manager cannot say\nwhether the process runs.\n\nA managed launch prints its `Managed launch` plan on stdout before its receipt, so\n`cotal spawn <persona> -d > out.txt` keeps both, and a refusal prints on stderr. With `--json`,\nstdout holds only the envelope and the plan stays on stderr. The manager that refuses a launch\nwrites one line to its own log naming the original request, the persona and the reason, so\n`cotal service logs <definition>` on its host shows why a spawn failed after the terminal is gone.\nAn unpinned launch that no manager received has no such line, because no manager answered it.\n\n`--harness/-H`, `--model/-m`, `--name/-n`, `--prompt/-p` and `--cwd/-C` retain their declared\nmeanings. The legacy `--agent` spelling remains accepted; using both harness spellings refuses.\nA `--file <persona-file>` launch uses this route too unless `--foreground` is given. A manifest is\ndeployed with `cotal team deploy <file>`, not `spawn`.\n\n`--foreground` runs the native application directly. It does not support a structured receipt,\nmanager pin or manager control credential. Native exit or terminal loss may end that process;\nno detachable-view or reboot-survival claim applies. Protected internal handoff/enrollment\nchildren keep their existing direct execution and native authorization checks.\n\nIf the agent joins but attachment fails, the result is partial with its original identity and\nmanager. No second launch occurs. A view exit sends no stop request. This route does not offer\nno-manager startup choices or imperative planning.\n\n\n## Observer stream\n\n`watch --json` emits versioned watch NDJSON records from the same MeshView as the terminal.\nActivity, events and messages filters use the shared classifier. Metadata snapshots identify\nscope, reported roster state, native channel counts and membership-read failures. Feed rows keep\nnative message IDs and timestamps; coalesced updates are identified as observed rows, not new\nmessage publications or proof of task completion. No global sequence is invented.\n\nThe feed has a 300-entry window and bounded per-channel prefill, not a complete history export.\nWindow loss, connection transitions and text beyond the 16,384-code-unit row bound produce gap\nrecords, and a cut row carries `textTruncated` and `omittedCodeUnits`. Identity fields are not clipped. The stream does not claim that arbitrary publisher\ncontent is secret-redacted. Leaving the observer stops its own connection and sends no agent\ncontrol request. Connection or authorization failure is an error, not an empty conversation.\n\n\n## Recorded team views\n\n`team` (`t`) reads fresh launch summaries, additive ownership ledgers and their\nroot-local attempt records at the selected local root. A team read or `--dry-run` preview that\ncompletes exits 0. Its JSON marks `completeness` as `partial`, and its report has a line\nsaying what it does not establish. A report names each record's kind, recorded state, next action\nand auth mode in words, such as `boot returned` and `static auth`. The JSON result keeps the\nrecorded values, such as `broker-boot-returned` and `auth`. A deployment ID that no record in the\nroot names is refused with `TARGET_NOT_FOUND`, which names the ID, by `team show` and by\n`team remove`; `team show` adds that an ID is 16 hex characters when the value has another shape.\nWhen native `up -f` returns from its broker start, it writes a creation-only fresh\nsummary with its full run ID, initial broker PID, declared agent/channel names\nand the initial control-plane, auth-service and delivery-responder results.\nOlder summaries without a broker PID, auth-mode pin or responder observation\nremain readable; `team show` labels their absent facts not recorded and\nsays the record proves no current ownership. A PID\nor a responder bound when boot returned is not current process or listener\nownership proof. A private create-only fresh boot attempt is written and fsynced\nbefore the launch spec and broker start. A returned summary is also published create-only and\nfsynced; an intent without a matching auth-mode-pinned summary showing a\nreturned control plane, auth service and (for auth mode) bound delivery\nresponder stays unsettled and\nblocks a later fresh boot. `team show <id>` and the team inventory expose the original intent without\nclaiming whether its broker started. `team show` displays up to 100 declared names from a\nsummary and retains the complete list in JSON; none are claimed to have joined. A missing\nor refused summary after boot is not a reason to boot again. A summary records the absolute folder\n`up -f` ran in. After that folder is moved or copied, the summary still reads but names the old\npath. It settles nothing in the new folder and blocks no `up` or `down` there, and `team show <id>`\nrefuses it with CONFLICT, naming the folder it was recorded at.\nThe inventory's `completeness` stays `partial` because older fresh boots may have no summary. The\nterminal lists up to 100 records; JSON retains all readable records. Its Next column\nsays inspect the stack for a fresh record or review the native scoped teardown plan for an\nadditive ledger; neither row grants permission to stop anything. A fresh summary is\nnot an additive ownership ledger or evidence that an agent joined, and it does not establish\ncurrent process health. For a fresh summary, `team show <id>` names the recorded root and offers\n`cotal down --space <space> --dry-run`, the preview of the guarded whole-stack stop, with the\nrecorded space filled in. `team remove <id>` refuses a fresh boot ID with `CONFLICT` and offers the\nsame stop. The summary is not authority to run it.\n\nFor an additive record, `team show` prints up to 100 full recorded agent identities\nand lifecycle UIDs where the ledger has them, with the full validated ledger in JSON,\nwithout claiming those agents are still live. Its follow-up uses\n`down -f <file> --run <id> --dry-run` from the recorded root. The file is a required argument,\nbut the full run ID selects the ledger; an edited manifest is not new teardown authority. Review\nthe native plan before a separate apply. Listing ownership does not confer permission to stop\nanything. One guarded `team deploy <file>` route accepts a\nchannel-only manifest whose declared cards are all absent and explicitly set\n`deliveryClass: live`. It refuses agents, runtime requests, defaults,\nbroker bind changes, access edges, existing cards,\na changed re-apply, registered broker lists and unresolved prior attempts before an effect.\nA card that does not set `deliveryClass: live` is refused with `UNSUPPORTED`. The refusal names\nthe cards, and its Next says to set `deliveryClass: live` on each card or to create a durable\nchannel with `cotal channel set <name> --delivery durable`. `team deploy --dry-run` warns\n`LIVE_DELIVERY_REQUIRED` for the same cards, so the preview says the apply would refuse.\nIt selects the exact registered root, single broker address, auth mode and TLS\nrequirement, then holds the local maintenance lock. The original user bearer and its deployer and writer\nviews must name the same owner and actor. A refused view stops before the\nintent; no static or broader caller is substituted. A scoped writer uses one\nnon-reconnecting broker connection and KV create-only on the existing bucket,\nnever the legacy merge-and-put path. The private intent is published and synced\nbefore any create. Every channel has a synced submitted step before the native\nrequest and a synced accepted revision after its returned acknowledgement.\nThe returned receipt matches every declared channel and both steps. A missing\nacknowledgement or receipt leaves the attempt unsettled; no create is retried.\nIf closing the writer connection fails after the receipt is confirmed, the result\nkeeps the confirmed revisions and warns about the close. A returned receipt\nproves only historical created revisions, not the current\ncards, readers or removal authority. `team show <id>` presents one row per\nreturned channel; incomplete attempts show their submitted/accepted steps.\nThe JSON result retains both forms of evidence. Team inventory and status\nshow returned versus unsettled state. Additive broker addresses in reports\nomit URL user info, queries and fragments; the protected intent keeps the\nexact connection target. Invalid or orphan steps stay visible and\nblocking. Guarded channel-only card removal uses the returned create revisions as described below.\nCompletion reads those receipts locally; it does not query the broker or guess ownership from an\nedited manifest. Channel apply emits one finite rich, JSON or plain result.\n\nThe agent route accepts one to 100 all-new agents on a registered static-auth\nor open mesh, optionally with up to 100 new channel cards that explicitly use\nlive delivery. Defaults, bind changes and stale waivers refuse. User-auth agent\napply refuses before inline persistence. The route selects a manager through an attributed status read,\nthen pins credential issuance and every subsequent request to that full instance.\nIt requires native execution and a matching runtime; it never starts a replacement\nmanager. `--runtime` overrides the manifest's requested runtime but does not\ninstall or switch a provider.\n\nA mixed deployment also requires every requested application to appear available\nin that manager's connector inventory. This is a startup observation, not proof\nthat a later launch will succeed. A separate deployer reader checks the requested\nchannel keys, and a separately scoped writer uses one non-reconnecting connection\nfor native KV create-only. Manager credentials are not used as channel authority.\nExisting cards refuse; no patch, adoption or legacy merge-and-put occurs.\n\nOne private intent records all channels, ordered resolved agent hashes and the\nmanager before effects. Channels are created first, with submitted and accepted\nrevision steps synced before continuing. Each agent's original request is synced\nbefore submission, its native allocation before goal following, and its matched\njoined result before the next agent. A joined result must match the accepted\nname, principal, lifecycle, harness and runtime. Unreadable or unknown phases\nremain unsettled and are not replayed. The full receipt is saved only when both\nchannel and agent sequences match the original intent.\n\nA later failure retains earlier channel creates, allocations and joins without\nrollback. When it created anything, its Next adds `cotal team remove <id> --dry-run`.\nWriter-close uncertainty is reported separately from returned native\nreceipts. The finite JSON/plain/rich report distinguishes those receipts from\ncurrent health and task completion. `team show` exposes both native journals,\nincluding partial channel creates when a later launch did not complete. Inventory,\nstatus, down and reset distinguish returned mixed deployments from unsettled\nattempts. Retained verification and guarded removal use the paths below.\n\n`team boot <file> --dry-run` shows the resolved fresh topology, its target root,\nrequested applications, roles, models and channels with declared delivery,\nreplay and window settings. It shows the manifest's `defaults:` too, and a channel field that\ninherits one is marked `(default)`, because boot seeds them as the space defaults. A setting\nneither declares is not inferred from a saved registry that this preview does not read. An open manifest with an\nexplicit durable default or channel declaration is warned as unsupported:\nits native fresh apply refuses before effects because open mode starts no\ndelivery daemon. Direct `up -f --dry-run` refuses the same contradiction.\nA space with no prepared or registered root previews the current folder. Its root source says\nwhether that folder belongs to other spaces, and a hint names the `cotal space create` the apply\nneeds: `--root .` for an unprepared folder, or a folder of its own when this one belongs to\nanother space.\nIt reads the local control\ndirectory, recorded spaces, maintenance journal, the selected manager PID record,\npossibly live process records, the root's additive deployment ledgers and\nattempts, and fresh boot attempts/summaries, plus selected-space service\ndefinitions, instances, supervisor receipts and retained native attempts. An\nunreadable additive or fresh record is listed rather than treated as absent.\nThe team inventory separately lists unsettled fresh and additive attempts\nand returned agent/channel receipts. `team show <id>` distinguishes those\nhistorical records without inferring current card or broker state. Bare\n`down --dry-run`, `team deploy --dry-run` and both reset previews expose the\nrelevant retained additive IDs and unreadable paths. The legacy bare `down`,\n`down --preserve-state`, fresh `team boot` and `reset store` refuse unsettled\nintents and unremoved agent/channel receipts before effects. Bare `down` and a later\n`team deploy` name `cotal team remove <id> --dry-run`, then `--yes`, for each deployment that\nblocks them. A complete matched\nstatic agent-retirement receipt releases only its original deployment's blocker.\nA bare `up` of a stopped controller stack starts it again over a returned channel-only deployment\nmade on that stack, and keeps the deployment's records and cards as they are. Any other retained\ndeployment, such as an unsettled attempt or an agent deployment, still refuses the start with\n`LIFECYCLE_BLOCKED`, and its Next names `cotal team show <id>` for each one.\nThis is a known-blocker guard,\nnot complete broker ownership. The service records belong to the workstation's selected-space store,\nnot necessarily this boot root; their recorded\nstate does not establish live ownership. A dead manager record remains visible,\nnot proof that a later boot may replace it. For a concrete candidate broker\naddress, the preview also lists other local registrations with that exact\naddress. A match blocks fresh apply; no match does not prove exclusivity against\naliases or remote tenants. A host-derived candidate leaves this comparison not\nchecked.\nWhere the manifest supplies a broker URL or uses the default without a host override,\nit makes a credless reachability probe. A failed probe is not proof of absence on a\nTLS-first listener. Its `completeness` is `partial`: port binding, exclusive local ownership,\ninstalled providers, credentials, grants and startup readiness have not been checked.\nIt writes no record and starts nothing.\n\n`team boot <file>` requires a prepared local root and starts its environment through the service\ncontroller. A root without a stack journal takes the fresh static-auth or open route. An eligible\nstopped stack can resume in static-auth, open or user-auth mode. The manifest's channels seed the broker. The stack's own manager\nstarts its agents from a launch spec pinned by digest. One rich, JSON or plain receipt includes\nthe controller members and requested topology. Agent joins are not checked, so the receipt is\npartial. A manifest that requests no agents leaves no join unchecked, so its receipt is `ok`. The fresh attempt and summary are launch records, not stop authority. No additive\nownership ledger is created; `cotal down` uses the stack journal and each member's native lifecycle.\n\nBefore writes it checks the selected root, auth mode, broker address, runtime, connectors and\nretained ownership records. A journal, legacy PID record, possibly live component, service instance,\nsupervisor receipt, native attempt, additive ledger or unsettled fresh attempt blocks a fresh boot.\nUnreadable records are never treated as absent. The lock protects local preparation, and ownership\nand bind checks run again before startup. A bind probe releases its socket and does not reserve\nthe address. A later failure keeps its attempt and any started members, reports the known partial\nor unknown outcome and never repeats effects. A failed summary write also keeps the running stack.\n\n`team boot --foreground` holds the stack's lifetime in the terminal. Ctrl-C or SIGTERM invokes the\nsame guarded whole-stack stop as bare `cotal down`, without force killing members. Terminal loss\nor an external kill is not a confirmed stop. When a `cotal down` from another terminal completes,\nit prints `Foreground stack released` and exits 0, as a foreground `up` does, although its start\nreceipt was partial. Foreground apply takes neither JSON nor plain output;\na dry-run remains a finite preview. It does not establish native apply readiness or platform support.\n`up -f --foreground` still refuses on its retained launcher path.\n\nA `team boot` in a root whose stack is up, with no `cotal down` since, refuses with\n`LIFECYCLE_BLOCKED`, says the space is already up and starts nothing. Its Next offers\n`cotal team deploy <file>`, which adds the manifest's channels and agents to the running stack, or\n`cotal down` before the boot.\nA recorded stack resumes only after its last whole-stack stop completed and its manager was never\nattempted. Its broker inputs must be recorded, every applicable member must have its original\nsettled stopped record, and no supervisor, retained custody or earlier team boot may remain. A\nstack that an earlier team boot started refuses with `LIFECYCLE_BLOCKED`, and its Next names\n`cotal up` in the root, which starts that stack again, and `cotal team show <id>` with each earlier\nboot's ID. The broker requires its kernel-exit receipt. Auth and delivery use their settled process\nstop and released custody; that is not proof of credential, lease or authority retirement. A generic\nprocess stop now records a `process-exit` receipt on the stopped instance when that stop signalled\nthe pinned process and saw it exit. `service stop --json` and `service status --json` expose it as\n`processStop`, including the stop request, original process identity, signal and observation time.\nHuman stop and status reports show the recorded signal, request and exit observation time, or\n`Process stop evidence: not recorded`. This is retained evidence, not a new signal from a repeated\nstop. An instance that was already gone gets `processStop: null`. Resume does not yet require this receipt. The resume receipt\nidentifies the prior stop and each member's evidence. Changed records refuse under the lock.\nA journal without recorded broker inputs cannot take this route. Restart it once with its original\nexisting `--store-dir` and its original `--max-file-store` and `--host`, if set, then run `cotal down`\nbefore the team boot. Omitting those values is safe only when the original start set neither.\nPreparation rechecks that explicit old store's directory identity instead of recreating it if it\ndisappears. These checks do not exclude an unrelated local process changing the path. Open broker\nconfiguration stays inside the selected root's `.cotal` directory, even when its store is elsewhere.\n\nFor a first user-auth team, prepare with `cotal up --user-auth --idp <url>`, then `cotal login`,\nexplicitly grant the invoking user with `cotal auth grant cli --sub <subject>`, and run `cotal down`\nbefore `cotal team boot <file>`. Do not start the manager between preparation and that stop. A\nstatic-auth or open preparation uses `cotal up --no-manager` before `cotal down`. No implicit owner\ngrant or borrowed caller is substituted. A previously attempted manager is never adopted.\n\n`--space` must agree with the manifest. Hand-registered and discovered spaces are not local boot\ntargets. Public broker addresses and fresh-attempt views omit URL user info, queries and fragments;\nthe private intent keeps the target. Native warnings and failures remain on stderr. Installed\nextensions may produce their own output outside the finite result.\n\n`team deploy <file> --dry-run` resolves the manifest and personas and requires a\nregistration for the declared space in this root. It shows the declared\nchannel delivery/replay/window values separately from its non-atomic live\nregistry classification. If the selected running mesh is open, an explicit\ndurable channel declaration is warned as unsupported by its missing delivery\ndaemon; the read-only preview remains available. Additive deploy does not\nseed manifest-wide defaults. For an existing\nunmanaged card, it names only explicitly declared fields that differ from the live value, never the live\ninstructions or description body. No declaration authorizes overwriting a\nsaved card.\nIt makes scoped, TLS-bound\nnative reads of the presence and channel-registry buckets. A complete presence\nscan can prove an empty roster; a failed or malformed scan refuses rather than\nclassifying absence. It classifies channel cards against the registry and agents\nagainst a unique prior ledger for the same manifest content. The derived membership\nfeed, when readable, shows a lower bound of other readers, not authorization.\nThese observations are non-atomic and do not reserve names or prove that a manager,\nruntime, connector, channel writer or launch grant will be ready. An unregistered or\nonly prepared space refuses. `--runtime` changes the requested runtime; `--allow-stale`\nis apply-only and refuses in preview. Apply accepts all-new live channel-only\nmanifests, or bounded all-new agent and mixed manifests on static/open meshes\nthrough one native manager. An identical settled channel-only deployment returns\na no-op only after its recorded target and every original create revision match\nleader-served native reads. It reuses that full deployment ID without issuing a\nchannel writer, creating an intent or changing cards. Missing or changed cards,\nambiguous or unsettled attempts, and removed ownership cannot become this no-op.\nNothing is repaired or adopted. The result names its observation time; it does\nnot establish current readers or health.\nFor an identical settled agent or mixed deployment, reapply verifies the original manager and every\noriginal incarnation. Static-auth uses active live slots; open meshes use the pinned manager's exact\nidentity and lifecycle rows. Mixed cards must retain their original create revisions and content.\nMissing, retired or replaced agents refuse; nothing is relaunched or adopted. Persona drift refuses\nunless `--allow-stale <names>` names the stale agents. Those agents keep their old policy and receive\na warning; the waiver never restarts or updates them. The no-op records an observation time and\nlabels its ordered reads non-atomic. Unsettled, ambiguous or removed ownership cannot become that\nno-op. User-auth agent launch and retained verification remain unsupported.\nRuntime requests must match the original incarnations; no runtime or manager replacement is inferred.\n\n`team remove <full-deployment-id> [--dry-run] [--yes]` can retire a returned\nstatic-auth agent-only deployment with at most 100 original incarnations. It\nrequires the original registered root, broker, TLS mode and exact manager.\nThe revision-32 native `slots` query checks each recorded name, principal and\nlifecycle UID. A successor, omitted slot or unconfirmed transition refuses\nbefore a stop. A dry-run only reads this impact; apply requires a default-cancel\nterminal confirmation or `--yes` in scripts. Every `team remove --dry-run` exits 0 with\n`completeness` `partial`: it reads the manager's slots and the card revisions in order, not atomically, and the membership feed\nis display-only, so the preview does not establish what the apply will find. The apply reads the\nslots and revisions again before it acts.\n\nUnder the root maintenance lock, apply records one private synced removal intent\nbound to the original creation receipt. Each necessary stop has a synced original\nrequest marker and an expected principal/UID guard. An accepted stop is not\ncompletion. The CLI observes that same incarnation under a 30-second loop budget, in addition\nto each native read's request bound, checking\nits native retired slot, retired lifecycle head and cleanup-complete fact. It\nretains both revisions and the answering manager epoch before continuing. An\nalready retired incarnation needs no repeated stop. A missing row, successor,\ninterruption or observation failure stops the sequence without retrying effects.\n\nOnly a complete matching retirement sequence yields the create-only full removal\nreceipt. Earlier confirmed retirements and stop requests remain in rich/plain/JSON\noutput on a partial or unknown result. An unsettled removal intent stays blocking\nand is never resumed by repeating its effects. `team show` keeps its original creation and removal\njournals, and inventory/status/down/reset distinguish historical retired receipts. It lists a card\nremoval's steps in the order they were written: the cards in manifest order, and each card's delete\nsubmission before its outcome.\nThe full receipt releases only this deployment's known additive blocker, not any\nother service, broker, shared-root or process guard. It never authorizes stopping\ncurrent alias holders. No channel, broker, service or remote account is removed.\n\n`team remove <full-deployment-id>` also removes the registry cards of a returned channel-only\ndeployment, with `--dry-run` for a read-only preview and `--yes` for scripted apply. It pins the\noriginal receipt, root, broker, auth mode and TLS requirement, and refuses changed card revisions,\nconflicting retained ownership or unreadable records. A changed card refuses `CONFLICT` naming\neach changed card. With no registration for the space, as after the stack's `cotal down`, the\nrefusal names `cotal up` in the deployment's root, which registers the stack again, and then the\nsame `team remove`. The preview discloses delivery and replay\nfallback to space defaults and removal of description/instructions. A derived membership feed is\ndisplay-only and never authorizes or blocks this metadata change.\n\nApply records a private intent before any delete. Each card delete is conditioned on its original\ncreate revision, then read back as a later removal marker. Already absent cards name the remover\nunknown. Missing acknowledgements remain unknown with a blocking intent and no retry. A complete\nreceipt releases only that deployment's retained blocker. History, grants and readers are unchanged;\nthe channel stays addressable and no reader exclusivity or complete cleanup is claimed. Repeating a\ncompleted removal returns its historical receipt without deleting a later same-name card. A new\ncard intent records the registry stream's name and creation time.\n\n`team remove <full-deployment-id>` also removes what an unsettled deployment attempt created,\nsuch as one whose agent exited on launch, when the attempt's own journal records every effect. In\nmanifest order, each channel and agent reached its accepted step or was never submitted, and no\nmember has a step after one that was not submitted. An accepted agent counts by its native\nallocation, whether or not it joined. Removal then takes the route of a returned deployment of the\nsame shape, with the same preview, confirmation and receipts. Each allocated incarnation is retired\nthrough the original manager by its identity and lifecycle UID, and one the manager already retired\nand cleaned needs no stop. Each card is then deleted at its accepted create revision. Agent\nretirement still needs static auth. The removal is bound to a digest of that journal, which is\nderived on each read and never written, so a journal that changes later no longer matches it. A\nchannel or agent submitted with no recorded acceptance, an unreadable record of the attempt or a\njournal with no step leaves the effects unknown. `team remove` then refuses with\n`LIFECYCLE_BLOCKED` and names that step.\n\n`team remove <full-deployment-id>` on an unsettled removal intent reconciles it from native\nobservation instead of refusing. `--dry-run` shows each original member's classification, and\n`--yes` records only what the observation proves. It never sends a stop or delete, never repeats\nan unknown action and never continues a member that was not submitted; that member is reported\nunattempted and keeps the deployment blocking.\n\n- A card whose delete was submitted without a confirmed outcome is read with the deployer view\n from the registry stream the intent names, between two stream identity reads. A later removal\n marker, or no message at all, records `observed-removed` with the remover unknown, and it counts\n toward the receipt. A later value records `observed-superseded`; that card is not owned and the\n deployment stays blocking. A card still at its create revision stays unknown, because the\n original conditioned delete could still be applied. An older revision, a revision past the\n stream's last sequence or a recreated stream refuses. A read failure is never treated as absence.\n An intent written before stream binding keeps its submitted cards unknown.\n- An incarnation whose stop was submitted without a confirmed outcome is read with the privileged\n control instrument, which holds no stop or attach rows, from the original manager's exact-name slots. Only the original lifecycle\n UID's retired slot and head with cleanup complete record a retirement, under the original stop\n request. A terminal in progress stays open. A live original, an active slot with no live process,\n a retired slot without cleanup proof and an alias that now names a successor stay unknown. No\n process or presence absence settles anything. Open and user-auth deployments are unsupported.\n- A mixed deployment reconciles retirement first. Its card intent is observed only after a\n complete retirement receipt, and a retirement settled by reconciliation does not start a card\n phase.\n\nA receipt is written only when every original member settles as removed or retired. The\nreceipt then releases that deployment's blocker as an ordinary removal receipt does.\n\n`team remove <full-deployment-id> --dry-run` can also select a validated legacy ledger. It refuses a\nfresh boot ID or a changed root, broker, auth mode or TLS requirement. The old ledger removal path\ncannot adopt a new receipt. For a legacy ledger, a scoped deployer read asks the serving manager for a bounded native PS\nsnapshot and compares recorded IDs, names and lifecycle UIDs. It reads current\nKV revisions for ledger-named channels without exposing their descriptions or\ninstructions; an unavailable registry read refuses rather than treating cards\nas absent. The old ledger has no creation revisions, so a current revision and a\nmatching name do not authorize deletion. It also reads the derived membership\nfeed when available and shows other observed members on owned channels; that feed\nis display-only and cannot authorize channel removal. A missing or unusable manager\nPS refuses rather than calling owned agents absent. On a user-auth mesh a deployer view the\nidentity provider refuses is `PERMISSION_DENIED` (exit 2), with the provider's sentence as the\nremedy. An unavailable membership feed\nstays explicitly unknown. Other retained or unreadable additive attempts under\nthe root are shown separately; their effects are not attributed to this ledger.\nThese native reads are not atomic. The preview changes nothing and cannot\nauthorize a stop. Legacy removal still refuses effects, including with `--yes`. Agent retirement\nremains static-auth only. Channel-only metadata removal uses the existing scoped writer on static\nor user-auth meshes, or the open mesh's bare connection; it grants no new access.\n\nA returned static mixed deployment uses one preview, confirmation and maintenance lock. It first\nretires the original agent incarnations through their native manager. Only a complete retirement\nreceipt permits fresh card checks and deletes conditioned on the original creation revisions.\nBoth receipts are required to release the deployment's blocker. A partial or unknown phase stops\nthere without replaying its effects. An interrupted transition can continue only after complete\nretirement and before any card-removal intent exists. Open and user-auth mixed removal refuse.\n\n`team check <file>` resolves the native manifest and referenced personas without a broker.\nIt lists the manifest's `defaults:` and each channel's delivery class, replay and replay window.\nA field the channel leaves out shows the manifest default it inherits at boot, marked `(default)`.\nA field neither declares does not claim a live registry value; the native seed may merge over\nsaved metadata. The JSON result carries `defaults` beside the channels.\n`--graph` shows effective channel access edges from merged agent policies, marking persona-inherited\nchannels that have no manifest card. It also shows each agent's role, capabilities and whether\ncapabilities came from the manifest, a persona or neither. Terminal tables show up to 100\nrows of each kind. The JSON result includes all resolved sources, merged policies,\ninherited scopes and, when requested, the channel graph, without persona bodies,\nkickoff prompts or opaque launch-option values.\nA manifest it cannot resolve, such as one whose `space:` is not a name `space create` takes, is\nrefused with `SCHEMA_INVALID` (exit 1). The refusal names each\nproblem by file, line and column where the parser has them, the key path, and the reason, and\n`--json` lists every problem under `target.issues`. A missing required key, an empty file, a\nvalue of the wrong shape and one outside a key's fixed values each get a plain reason. An unknown key\nthat misspells a required key left out beside it, such as `chanels`, is one problem that names the\nkey meant. A problem in a referenced persona\nnames that file and the parser's reason without quoting its contents. A persona file that does not\nexist is named at its entry's line along with the manifest's other problems, so fixing those does\nnot uncover it later. A manifest file that does not exist is refused with `TARGET_NOT_FOUND`\n(exit 2), naming its path. A folder given as the manifest, such as `team check .`, is refused with\n`USAGE_INVALID` (exit 1), naming the path and that it is a directory, as `topology view` refuses it,\nand `--json` gives the path as `target.file`. `team boot` and\n`team deploy` refuse a manifest they cannot resolve the same way, before they contact a broker.\nThis file check does not prove live authorization, installed applications or launch readiness.\nThese read views do not apply a boot, an additive deployment or a removal.\n\n\n## Terminal input receipts\n\n`input <agent> <text>` writes through the selected manager's native input command.\nUse `--text` when the value begins with a dash; do not combine duplicate positional and flag\nsources. `--no-enter` omits the trailing Enter request. JSON and plain receipts identify the\nanswering manager and its reported byte count without repeating the input body. An acknowledgment\nis not proof of application consumption or task completion. Unknown results are not retried.\nManaged-launch failures retain native request and accepted-goal identifiers when a reply supplied\nthem, including when following that accepted goal failed. No identifier is invented for a silent\nsubmission, and a missing effect outcome is not a proved pre-execution refusal.\n\nGoal observation keeps the caller's authorized route. A class caller reads its original goal on\nthat class route; an explicit `--on` or credential-bound instance stays pinned. Learning the\naccepting instance ID does not confer an instance route or cause another launch. The native\nfollower verifies the returned fact against the original caller and goal. A launch the manager\nrefuses before its first effect, such as a missing persona file or connector, carries\n`not-executed`. The CLI reports it `refused` with exit 2 and prints the manager's reason. A missing\npersona names the manager, its host and the file it looked for, which is created on that host. A\nmissing connector is `DEPENDENCY_UNAVAILABLE`, as it is for a spawn in this terminal, and the\nrefusal names the manager's instance and the host its status reports. An\nunpinned launch that no manager received is refused with `TARGET_UNAVAILABLE` and exit 2. With no\nmanager running, the CLI says so before it sends and names bare `cotal up`. When registered managers do not answer the launch describe, the launch\nwas never sent and is refused the same way. The reason follows `ps` when this folder records\nits manager stopped, starting or running but silent. When every live manager\nreports `classSpawn: false`, the refusal is `DEPENDENCY_UNAVAILABLE`: it names each manager and its\nhost, says none has an agent application installed, and names the `cotal ext add` that adds one.\nA registered manager whose rail the broker reports empty is skipped without waiting and named\nin a `MANAGER_SILENT` warning; the reason comes from the managers that answered.\nBoth refusals say to add the application where the manager runs, not on your machine. A manager\nreads its applications when it starts, so restart it after adding one. A failure after the manager reserved\nthe name or wrote a launch file keeps its native outcome, and the CLI never reclassifies it from\nits message.\n\n\n## Stopping an agent\n\n`stop <agent>` requests graceful native despawn. `--force` requests the existing\n`graceful:false` contract; it does not grant authority or bypass incarnation checks.\n`stop --self` uses only a verified managed seat caller and its attributed manager. It accepts no\nother target or credential. Missing, mismatched and unsupported user-auth seat context refuses\nwithout switching to the operator. JSON and plain modes show the acknowledged native result,\nnot a separate measurement of process exit. Stop and input do not seed connector installations.\n\nFailed name resolution refuses before a stop, terminal input or first attach is submitted. The\nreceipt retains the inspect error and responding manager. It records local pre-submission proof\nwithout inventing a native effect outcome.\n\nWhen the manager refuses a stop, terminal input or first attach before anything ran, for example\nbecause the agent is not running or the caller may not control it, its reply carries\n`not-executed`. The CLI reports `refused` with exit 2, names the agent and prints the manager's\nreason on one line. A failure after an effect may have started, such as a terminal write the\nruntime rejected, stays `unknown` with exit 3, still shows the manager's reason, and is not retried.\n\nManaged imperative spawn accepts `--restarts <n> --restart-window <ms>` as a pair, using the\nexisting native `supervise` object. Both values must be positive safe integers. Direct foreground\nlaunch and the legacy manifest form reject these flags instead of ignoring them.\n\n\n## Native endpoint reports\n\n`describe <class>` shows the authorized command table, capabilities, target modes and answering\ninstance/epoch. JSON includes the native compiled schemas and verified closure digests. This is\nnative discovery, not a saved-reference inventory or a grant of authority.\n\n`invoke <class> <command> --args '<json>'` validates public inputs before target preparation,\nthen uses the native fetched contract before publication. `--on` pins one full instance ID; a\ncredential-bound pin must agree. Request IDs and actual replying instance/epoch remain in the\nresult. Refusals with a proved `not-executed` outcome stay distinct from unknown mutations.\nThere is no blind retry of an unknown operation.\n\nPlain and rich reports show at most 60 whole result lines; `--json` preserves the full returned\nvalue. Endpoint-provided result content is not claimed to be secret-redacted. A successful action\nacceptance is not whole-task completion. This route does not use saved references or cover the\ncomplete endpoint namespace.\n\n\n## Watch views\n\n`watch --view` accepts `feed`, `events`, `messages`, `roster`, `channels`, `waiting`, `dms` and\n`topology`; `activity` remains the feed spelling. The terminal cycles available views with `f`.\nRoster and waiting rows show reported state without inventing blocked duration. Channel counts\nkeep retained messages separate from observed arrivals. Topology displays broker-sourced live\npatterns and durable channels, not task dependencies or permission grants. Missing or unreadable\nmembership is explicit, never an empty complete graph. Long identities wrap without clipping.\n\nThe authenticated observer remains narrowed to channel traffic. Its DM view refuses instead of\nbroadening credentials or inventing a private conversation identity, and with `--json` its `error`\nrecord names the space and the reason. Open-space DM observations\nretain native sender and target IDs. Use `--json` for versioned view records or `--plain` for line-oriented reports outside a\nterminal. Both use the same bounded observer lifecycle and shared view model. A line-oriented\nroster, channels, waiting or topology view prints its report again each time that report changes.\n\n\n## Authority\n\nBare `auth` reports only safe cached provider metadata. It does not verify a session online,\nturn a sign-in into a grant, or print tokens. Its next steps name the `--space` you gave or the\nspace a bare command resolves; with no space they point to `cotal space`. `auth grants` reads the\nselected root's authoritative user-auth actor ledger; a root without its owner secret and pinned IdP\nrefuses rather than reporting an empty authoritative list. A static or open space has no such ledger\non any machine, so there `auth grants`, `auth grant` and `auth revoke` refuse with `UNSUPPORTED` and\nsay that actor grants apply only to a space started with `cotal up --user-auth`. `auth grant` and\n`auth revoke` refuse there before they ask for a subject or the ACL flags. A space that cannot be\nresolved refuses with its next step, such as `cotal up` or `cotal space`.\n\n`auth grant <actor>` and `auth revoke <actor>` use the existing local operator authority path.\nChoose `--sub` or `--owner`, never both. Grant requires explicit scope/read/post lists unless\n`--full` deliberately selects the native wide defaults. These usage errors refuse with exit 1 before\nthe space's authority is read, and one refusal names every missing input. Re-grant/revoke\nretain predecessor lifecycle retirement and concurrent-row checks before replacing or deleting\nauthority. A retirement that did not run, because the auth service is not running, refused the\nconnection or answered that the retirement did not run, is refused (exit 2) with the actor row\nunchanged and the recovery named, usually `cotal up`. Any other failed retirement request may have\nstarted the retirement, so it is unknown (exit 3) and is not retried. Grant\nrows in reports contain scope and ACL metadata, not token hashes or secret material.\n\nRevocation receipts separate committed deny-new from the delivery daemon's live-eviction notice.\nThey do not claim all related processes stopped. An interrupted mutation is unknown and is not\nretried. Neither grant management nor actor reads install connector packages. Grant management\ndoes not issue credentials, export signers, retire lifecycles or act on capability handles.\n\n\n## Saved endpoint references\n\n`endpoint` (`ep`) reads local references only and labels live state Not checked. `show <name>`\nshows the saved space, native class, full instance pin and storage revision. These commands do\nnot connect to a broker or infer registrations from participant presence.\n\n`endpoint add <name> --file <reference.json>` imports a strict version-1 reference object.\nGuided add is available in an interactive terminal. Names use case-safe hexadecimal\nstorage keys under the workstation endpoint directory. Imports are capped at 256 KiB. References\nreject unknown fields, raw inline credentials, target-space mismatches and malformed native pins.\nA credential reference is an owned private regular file; its contents are not read when saving.\nExisting names are not silently overwritten.\n\n`endpoint remove <name>` defaults to cancel, or takes `--yes` for scripted confirmation. It\ncompares the held revision and content under the private writer lock before forgetting metadata.\nIt never deregisters a native endpoint, stops a process, removes credential files or revokes grants.\n\nProtected no-follow/nonblocking file access supports POSIX hosts. Windows uses its access-list\nmodel for local records instead: a credential reference there must be a regular file, and its\nowner, mode and links are not checked. Saved references do not provide native live\ninventory, invocation resolution or the remaining endpoint verbs.\n\n\nSaved-reference reads validate record structure without opening or revalidating its credential\nfile. A moved credential does not prevent inspecting or forgetting the local mapping. Protection\nis checked when importing/saving a credential reference and must be checked again before any\nfuture invocation uses it. Read receipts label credential validation Not checked.\n\n## Local service definitions\n\n`service` (`svc`, `services`) reads the selected space's local definitions and existing component\nPID observations. Saved definitions, process presence, ownership and serving readiness are separate\nfacts. The definition table counts its already-read instance records and labels any sole instance's\nrecorded supervisor and historical readiness. The separate instance table gives full IDs,\nprocess observations and each recorded supervisor; a record count is not a count of live services.\nRetained native install, uninstall and unit-action attempts appear with their full instance ID,\nstep, position and whether an instance record still exists. They mark the inventory partial:\na submitted or unknown step may have changed the original unit even if its instance record\nis absent. A legacy PID record is not adopted into the new instance controller. Unreadable\ncomponent records make the inventory partial. Reads do not start, repair or install a service.\n\n`service add [name] --file <definition.json>` imports the version-1 service object described under\n[Adding a service](#adding-a-service). A declared preset uses\n`service add <name> --preset <preset> <declared-options>`. Guided input offers installed presets\nand file import. Input is capped at 256 KiB, checked against the selected native space and its\nschema before a private record is saved. Whether a root or credential suits the preset is checked\nat start. A process definition needs an existing executable and\nabsolute working directory. Credential file protection is checked without reading its contents.\nAdapter definitions use an installed local-process provider's versioned, closed option schema.\nAdding or previewing a definition validates cached metadata without importing provider code. The\ncache records SHA-256 fingerprints of the declaration and installed package metadata. Missing,\nchanged or version-skewed metadata refuses with an update or re-add remedy. These fingerprints\nare not publisher signatures or a proof of every executable file. No remote schema is fetched.\n\n`service show <name>` reads declared policy, not live state. Public reports omit argument bodies,\nadapter option values and credential contents. Missing launch paths do not prevent reading the\nsaved declaration. Records live under the workstation store's case-safe per-space service path.\n\n`service remove <name> --yes` forgets only the unchanged definition. Interactive removal defaults\nto keeping it. Unknown or foreign instance custody makes removal refuse. The process driver verifies that every\nowned direct incarnation is gone and that no OS supervisor is installed before removing its\nunchanged instance records and definition. Logs are retained. No process or OS unit is removed\nby this operation. The presets are described below. A preset is not installed under an OS\nsupervisor.\nOn Windows, local records use the access-list model described under `up`. There, `cotal ext add`\nand the setup seed record a service receipt for an extension that declares service adapters or\npresets, and its process services run under a custodian as\n[Owned process services](#owned-process-services) describes.\n\n## Service presets\n\nBare `service` lists declarative presets separately from definitions and process observations.\nUse `service add <name> --preset <preset> --help` for that preset's option flags. Parsing, scoped\nhelp and completion derive those flags from the adapter's closed schema. Boolean options have\npositive and negative forms; a pair refuses. A boolean whose schema admits only true, such as an\nexplicit intent, has only the positive form. Numeric and complex options use JSON syntax.\nA preset no registered or installed provider declares refuses by name; for `web` the refusal\nnames `cotal ext add @cotal-ai/web`.\nOptions that collide with service/global flags require the file form. No preset is installed or\nprovider code imported by help, completion, a guided question or definition creation.\n\nA provider registers a `service-preset` alongside its `local-process` adapter. Preset metadata\ncontains its name, adapter, summary, readiness deadline and option defaults. It does not duplicate\noption schemas, contain executable factories or supply credentials. A default is a literal value or\na fact of the selected space: its registered root, its registered broker, or a private file of the\nspace under the root's `.cotal`, such as the delivery credential `cotal up` keeps there. A file\ndefault that does not exist refuses, naming the flag that replaces it; no other file is chosen. Cached preset and adapter declarations share\none package-metadata fingerprint receipt. A changed selected declaration refuses during dispatch.\nPath annotations require absolute physical files/directories, or owned private files, without\nreading their contents during definition validation.\n\nThe CLI's own presets are `auth-service`, `broker`, `delivery`, `exec-endpoint`, `feedback-intake`\nand `manager`. The `linear` and `web` presets come with their extensions. The intake preset is\n`feedback-intake`. Supply its `--root`, protected `--keys` and scoped\nbroker `--creds` paths in the selected registered space. The keys file is JSON with one entry per\nkey, `{\"keys\":[{\"key\":\"<token>\",\"tester\":\"<tester-id>\",\"name\":\"<display-name>\"}]}`, where `name`\nis optional. A tester's `--key-file` holds their `key`. The intake serves `POST /v1/feedback` on its\n`--host` and `--port`. Optional options include `--server`,\n`--host`, `--port`, `--channel`, `--store`, `--max-bytes` and `--rate-limit`. A supplied store must\nalready be private. Without one, the intake appends to a private instance-keyed store beside its\nservice records. Feedback records and logs remain private local data and are retained on removal.\n\nThe `broker` preset uses `nats-broker`. It requires a prepared local `root`, a protected existing\n`config` inside that root's `.cotal`, an absolute `binary` and one explicit `server` address. It\nprepares no trust, generates no configuration and starts no other service. Legacy broker custody,\nknown shared registrations, maintenance and mismatched transport refuse. Its creation-pinned\ndirect child writes a native ports report in a fresh private launch directory, and the readiness\ncheck sets that report to `0600` before it reads it. Its listener must\nmatch the declared IP, port and transport. `add` refuses a `server` that is not one `nats://` or\n`tls://` URL naming an IP literal, and DNS, wildcard and mapped-IPv6 targets, so none is saved. A\ndefinition saved before that check refuses on start, before launch, naming its `server` option and\nhow to add it again. This reports the child's client listener, not JetStream readiness, endpoint serving\nor a fresh trust check.\n\nBroker stop has a separate kernel-exit policy, with no invented endpoint principal or epoch.\nKnown live or unresolved dependants refuse. After confirmation the controller rechecks the scope\nand process, sends one SIGTERM and waits for that incarnation to exit. Its receipt distinguishes\nprocess exit and removal of the ports file from a native shutdown acknowledgement or cleanup.\nAn unconfirmed stop keeps its original request; no force kill, replay or restart follows. A typed\nadapter refusal before child launch keeps its boundary code, safe cause and remedy. Its local\ninstance receipt remains diagnostic history, recorded as failed with no process, so a later start,\nstop, removal or `down` is not blocked by it. Named multi-service stops do not use this policy. Local registry absence does not prove that\nno remote client or tenant uses this broker, and whole-stack sharing is not checked.\n\nCreating the intake definition launches nothing. Starting it does not start a broker, manager, delivery\ndaemon or installer, mint a credential, or substitute an operator caller. The native child\nreuses the existing HTTP/mesh intake implementation and preserves the target's TLS requirement.\nIt publishes a create-only, full-instance/full-request startup receipt only after the HTTP listener\nand native signal handlers exist. Adapter readiness matches that receipt to the creation-pinned\nchild. It reports an owned running intake, not native endpoint serving, developer action or a\npromise that a future request will succeed. An old receipt is never a replacement launch.\nThe top-level `feedback-intake` entry is deprecated: it keeps its flags for existing callers and\nwarns on every run.\n\n`delivery` uses the selected registered broker root and an existing protected delivery credential\nfrom that root's native store. `--root` defaults to the space's root and `--creds` to the delivery\ncredential `cotal up` keeps for the space; `--server` may repeat the recorded broker but cannot\nreplace it. Open targets refuse. The native daemon still checks its store,\naccount scan and singleton shard lease. No manager, broker or installer is started implicitly.\nA live, foreign or unresolved delivery record refuses rather than being adopted. When a member of\nthe space's controller stack runs it, as `stack-delivery` does while the stack runs, the refusal\nnames that member with its pid and instance, and its Next is that member's stop with `down`. The\nadapter publishes its request-specific startup receipt after the native runner reports Ready\nand its shutdown handlers exist. That is not a fresh liveness response or proof of endpoint\nserving. The existing `deliver` machine-facing entry stays for the stack callers that still use it.\n\nNative service children retain only the explicit base/path/terminal environment allow-list and\nselected references. `COTAL_HOME` and XDG paths retain the invoking workstation registry. Arbitrary\nambient credentials and agent identity markers are not inherited by generic process services.\nA component needing the invoking caller must supply its supported native handoff, not infer a new\noperator identity. In particular, the web service does not receive the invoking caller's\nauthority.\n\n## Native Linear service\n\nThe `linear` service preset uses an existing native `account`, a reverse-DNS `endpoint` in a\nnamespace you own, and the selected registered static-auth or user-auth `root`. An optional `server` only\nrepeats its broker. Account login/token setup remains a separate explicit operation. A start whose\naccount has no record refuses with `TARGET_NOT_FOUND` and the `linear account add` route before a\nchild is launched. That check reads the Cotal home of the process that runs `service start`. While\n`COTAL_HOME` is unset, a definition whose `envRefs` set `HOME` (on Windows, `LOCALAPPDATA` or\n`USERPROFILE`) can give the child another home, so that start refuses with `UNSUPPORTED` before a\nchild is launched. Set `COTAL_HOME` for the start, or remove that reference.\n`service add` checks only the declared option types and lengths. The account\nand the reverse-DNS endpoint name are checked when the service starts, and an invalid endpoint\nname refuses that start before a child is launched, leaving a failed instance record. The native\nstatic issuer, inventory, registration, credential renewal, request handling and deregistration\nstay in the Linear implementation. No generic caller authority, new account or broker dependency\nis supplied by the preset. User-auth spaces use their remote service authority and retain the\noriginal approved registration on restart. Open spaces refuse instead of using another issuer.\n\nOn static-auth roots, an explicit start can allocate another instance through the native registration path. The preset\ndeclares foreground control through the shared terminal-bound lifetime; OS-supervised service\ncontrol is not supported. The child keeps the selected\nroot and registered TLS requirement and does not print upstream bodies or credentials into its\nstartup report. Its private phase records initialized native serve handlers and installed shutdown\nhandlers. It does not claim a broker-flushed subscription, a fresh authenticated serving response\nor upstream business-work completion. Reports label it process/adapter initialization, not\nendpoint-serving proof.\n\nStop closes the native serving connection and Linear session. Static-auth roots also attempt bounded\nderegistration; user-auth roots retain the original approved registration. Original process exit\nstill does not establish that deregistration, revocation or other cleanup completed. The legacy\n`linear serve` foreground entry remains available for installed callers; the independent\nservice route does not call that parser or its printed launch output.\n\n## Native auth service\n\nThe `auth-service` preset requires a registered hosting `root` with its existing prepared\nuser-auth projection. It loads the native callout account, issuer, owner secret and service key\nprojection. Saving or starting this preset does not generate those keys, create an account or\nstart its broker, delivery daemon or manager.\n\nThe preset options derive from the native daemon flags. Ports are bounded integers; public\nexchange options keep the native validation and reverse-proxy requirement. Advertised URLs\ncannot put credentials, query parameters or fragments on the child argv. No environment-export\nonboarding or generic caller-credential substitution is used.\n\nThe preset supports a prepared local user-auth root and its registered plain loopback NATS\nbroker only. The native authority path does not yet carry a required TLS policy through every\nconnection. TLS-required and remote broker targets refuse before authority startup rather than\nweakening that requirement. The preset declares foreground control through the shared\nterminal-bound lifetime; OS-service installation remains unsupported.\n\nThe real child exclusively claims the existing native per-space PID slot and publishes its\ncreation pin privately before opening authority planes. Any existing slot, including a dead or\nmalformed legacy record, refuses without adoption or reclaim. The startup phase is written only\nafter native discovery, flushed callout/listener initialization and shutdown handlers. Discovery's\nloopback capability stays in its native private store; no capability or bootstrap URL appears in\nthe startup phase. This is process/startup evidence, not a fresh authenticated endpoint response.\nA confirmed native close releases only this child's matching PID custody. An unconfirmed close\nkeeps that evidence. Service reports still do not claim lease, registration or family cleanup.\n\n## Manager stop impact\n\nThe native manager status contract includes its selected `lifecycleUid` and a caller-scoped `stopImpact` observation. It lists\nvisible managed principals and lifecycle IDs, their runtime modes and the seat actions a default\nstop or `--with-agents` would request. In-process PTYs without a release hook stop by default;\nreleasable seats lose manager custody. Preservation or pending resume uses the native retained-state\npolicy instead. None of these labels confirms seat exit, authority retirement or cleanup. From\nmanager cluster revision 33, a manager that is not `active` and holds a preservation inventory also\nreports `preservation`: the attempt and the inventory digest a preserved-exit stop names. A\npreserved-exit stop request carries that binding with `withAgents: false`; the manager accepts it\nonly when it is fully preserved, whole-manager and quiescent, and its result is\n`preserved-exit-returned`. The open-space local receipt keeps its revision-32 shape.\n\nThe manager resolves visibility through the same fresh owner/admin policy as `ps`. An\n`owner-filtered` observation is not the full manager impact, even if its rows happen to match a\ncount. A refused stop reports the selected manager, visibility, maintenance state and pending-work\ncounts that blocked its preflight, without exposing other owners' agent rows. Owner-filtered\nvisibility requires the stack operator or an authorized admin-scoped caller; the command never\nsubstitutes one. On a user-auth space, the refusal's next step is the whole-row\n`cotal actor grant` that adds `admin` to your current grant, as the auth plane prints it from the\nledger row. A stop in the folder that hosts the space reads the manager's own local receipt\ninstead, so its owner stops the whole manager without that scope or a sign-in. No stop policy is\naccepted while any blocker remains. This is\nan observation at the reported time, not a reservation, permission to stop or a guarantee that the\nset cannot change. Older managers can omit it; the native service adapter refuses when the\nrequired process/lifecycle correlation is absent. Generic process launch/stop still refuses managers.\n\nThe `manager` service preset uses the `manager-service` adapter. It requires the selected registered\n`root` and an explicit installed `runtime`; an optional `server` can repeat the registered broker.\nThe optional `console-port`, `console-host`, `ws-port` and `max-sessions` pass explicit native\nsettings, and an optional `name` names the manager as `supervise --name` does. Omitting the host keeps the native loopback binding; port zero requests an allocated\nconsole port. A declared `launch` also requires `launch-sha256`. It names an existing private\nresolved launch spec, pins its full SHA-256 and must name this space. The child parses those\nverified bytes before authority preparation and keeps that snapshot through startup. An optional\n`roster` names an existing [roster file](define-a-team.md#roster-files) by absolute path. Only its\nowner or root may write it or its directory. The child parses it before startup, and each launch\nboots the agents it lists the way `supervise --roster` does. A resumed manager takes no roster.\nSaving these options does not prepare a manifest, provision an agent or prove that requested agents\njoined. Saving it starts nothing and grants no authority. Its native child starts no broker or\ndelivery daemon, does not adopt an existing legacy manager, and never prints its private console URL.\nIt declares one instance per definition and runs in the background or with `start --foreground`.\nIt is not OS-supervised installation or platform parity.\nA root that `space add --root` registered has no `.cotal` until a command writes there. A start\nmakes it, as a directory private to you, after its other checks pass and once the root itself is\nprotected against other local writers. It does not replace a `.cotal` that is already there, such\nas a link, which the child refuses.\n\nThe real child exclusively claims its creation-pinned native PID custody before hosted authority\npreparation or local endpoint initialization. That starting record is not readiness; another existing slot is not adopted or reclaimed.\nAfter a crash, the record the killed child left names a dead pid. The manager adapter removes that\nrecord before the next launch, so the next `up` relaunches the manager; a live, foreign or\nunreadable record still refuses.\nIt binds its actual native identity before endpoint construction. A startup phase appears only\nafter native start, settled custody and signal handlers. On authenticated meshes the adapter also\nrequires a current status response pinned to that native instance and epoch, with the same process\nlifecycle UID. It preserves the invoking managed-seat caller, registered root/broker and required\nTLS. Missing caller support refuses without an operator substitution. The completed status read's\nown admission is excluded from stop impact; other admitted work remains counted.\n\nAn open mesh uses the pinned child's private request-bound local observation, and so does a stop\nin the folder that hosts a user-auth space. Its service, launch,\nrequest and both process creation identities must match, and its registration must match the\nlaunch identity. Output labels this a child-local receipt, not a broker-attributed endpoint\nresponse. A retained stop handoff blocks readiness adoption on either route instead of treating\na live PID as a new serving launch.\n\nA real `service stop` or `restart` reads complete caller-visible impact before confirmation.\n`--with-agents` requests native agent stops, not generic descendant signalling. An interactive\nconfirmation defaults to cancel and shows every affected identity, lifecycle and requested seat\naction. Scripts require `--yes`. The offline `--dry-run` still imports no provider and performs no\nnative impact query; it is not stop authorization.\n\nThe native stop reserves its original PID custody. It reclaims an existing reservation only when\nthe pid it names has exited and no request was published for this launch, and otherwise refuses and\nnames that pid.\nIt rechecks the confirmed scope, binds the full service/launch/stop request and both process creation\nidentities, and uses the existing native shutdown intent for agent reap. The child rechecks the\nsame complete scope before accepting. Changed or incomplete scope refuses without a broader policy.\nThe controller requires immutable accepted/returned evidence and the original process exit before\nrecording a native stopped state. An unconfirmed request retains its reservation and never escalates\nto force kill, restarts, or treats PID disappearance as completion. A later bare `up` or `down`\nsettles it only from the same evidence, and only then releases the reservation. Reported cleanup remains not\nchecked. Retained native-policy markers prevent a metadata change from selecting generic signals,\nand an unconfirmed native stop cannot be forgotten just because its process disappeared. Legacy shutdown refuses the native child rather than bypassing that boundary.\n\n## Resolving endpoint targets\n\n`endpoint describe <target>` and `endpoint invoke <target> <command> [--args '<json>']` use the\nexisting native caller and digest-verified contracts. `ref:<name>` requires a saved reference.\n`ep:<class>` bypasses local references. An unprefixed name checks the selected space's saved\nreferences first, then uses the native class only when no reference exists. An unreadable or\ninvalid reference refuses instead of selecting another target.\n\nA selected reference is disclosed on stderr and in `result.resolution`, with its revision and\nresolved native class, instance and epoch. The CLI holds its cooperative writer lock and checks\nthe same bytes before invocation. `--on` must agree with a saved instance pin. A missing pinned\ninstance does not fall back to the class queue. Unknown native effects are not blindly retried.\n\nBoth read the endpoint's contract first, and so does `auth mint --endpoint`. When no instance\nanswers that read within 10 seconds, the command refuses with `TARGET_UNAVAILABLE`: nothing is\nserving the endpoint, or nothing could reach this caller. Its Next says to start the service that\nserves the endpoint. Nothing was invoked or issued.\n\nA reference's file credential uses the [paired native caller adapter](#endpoint-caller-credentials).\nIt must match the selected registered static root, space, endpoint and signed native scope. A\nconflicting explicit `--creds` refuses. Arbitrary raw files do not supply a caller triple, and a\nreference's instance pin cannot be dropped to fit a class-only file. Neither case substitutes an\noperator identity. References without a file use the normal registered-space caller and its\nexisting capability checks. No local reference grants remote authority.\n\n## Declared capabilities that remain gated\n\nThe CLI grammar also names `endpoint watch <target> <record-key-or-topic> [--since <native-sequence>]`,\n`endpoint session <target> <command>`, `endpoint work lease <target> <pool>` and\n`endpoint work commit <target> <pool> --lease <private-file> --result <JSON>`. They return a typed\n`UNSUPPORTED` result before any broker connection, subscription, cursor, session, lease or commit.\nThe current resolver supplies command contracts, not a verified mapping of these application\ncapabilities. This is a CLI integration limit, not a claim that the selected endpoint lacks them.\nNo arbitrary subject, pool implementation or remote execution is guessed.\n\n`endpoint invoke --handle <private-file>` is also an explicit unsupported boundary, because no\nverified native auth-slot adapter is connected. It opens no file, redeems no handle and publishes no call.\nAn opaque low-level auth string alone does not establish receiver-side attenuation. Offline\n`auth handle inspect` remains explicitly unverified metadata without `--space`; its online\nstatic-registry signature inspection also does not establish presenter binding or use authority,\nand is not a substitute for that gate.\n\n`endpoint describe --refresh` refuses as unsupported and sends nothing. This CLI keeps no resolved\ncontract between calls: each describe makes a fresh describe request and fetches the contracts\nagain, so there is no cache for the flag to bypass.\n\nA saved-reference or paired-file invocation uses the described instance and epoch without bind-repair\nto a different handle. A pre-effect bind refusal remains a refusal, not permission to choose another\ninstance. Invocation receipts attribute resolution to the native responder that answered.\n\n`endpoint --live` adds authorized native service-registration metadata to the local reference\ninventory. It uses the existing scoped service-record read rows, without adding grants or reading\nmessage bodies. The SDK paginates the filtered metadata list; per-key spec/status reads come from\nthe leader. The list can lag concurrent changes and is not an atomic whole-space snapshot.\n\nThe snapshot reads at most 1000 registration keys. Omitted keys, unreadable records or unavailable\nregistry access make the requested live portion partial and return exit 4, while preserving the\nlocal references. Status projection, reported state, process ownership and a serving response are\nseparate. No active probe or process control is implied by a registration row.\n\n## Clearing retained history\n\n`history clear` purges all retained channel history in the space it targets: `--space`, else this\nfolder's space, else the selected one. It takes no channel argument. `--dms` also requests DM\nhistory under the existing purger authority. In a terminal, confirmation names the space, broker\nand space-wide scope and defaults to cancellation. Scripts\nand `--json` require `--yes`. The old `--force` spelling refuses with the replacement.\n\n`history clear --dry-run` resolves the target and names the affected streams offline. It issues no\ncaller credential, connects nowhere and does not check authorization or retained message counts.\nA space that is prepared but not started is refused with `TARGET_UNAVAILABLE` (exit 2), and the\nrefusal says how to start it.\nThe purge uses the existing purger instrument or one-action user purger view, not a broader caller.\nOn a user-auth mesh whose grant lacks scope `admin`, the purger view is not issued and the refusal\nis `PERMISSION_DENIED` (exit 2). Its remedy is the identity provider's own sentence, which names\nthe missing scope and the whole-row re-grant that adds it.\n\nJSON and plain receipts retain each native stream acknowledgement and message count. A later\nrefusal after an acknowledged purge is partial. An unconfirmed submitted purge is unknown and is\nnot retried. A confirmed clear interrupted by SIGINT, SIGTERM or SIGHUP exits 130, 143 or 129 and\nsays which purges were acknowledged or submitted, or that none was submitted and no history was\ncleared. Clearing history does not stop agents, remove stream definitions or reset identity;\nnew messages may arrive after the acknowledged purge.\n\n## Following an endpoint action\n\n`endpoint invoke <target> <command> --wait` requires the command's digest-verified action marker\nand the native goal-result read surface. The CLI submits once, retains the accepted goal ID and\nfingerprint and follows through the native helper. Reconciliation reads the accepting instance,\nnot an unrelated class winner. A reference keeps its described binding for submission.\n\n`--wait --json` emits goal NDJSON. It exports the original request, acceptance and observed terminal\nresult. Intermediate native progress is not exported; a gap record states that limitation. No\nglobal sequence or task progress estimate is invented. An unknown observation keeps its accepted\nidentifiers and is not retried. Ctrl-C or termination ends observation, not the underlying action.\n\nGoal-stream terminal values are capped at 16384 UTF-8 bytes. A larger value is omitted with an\nexplicit gap and retained goal identity, not split into invalid JSON. Read that original goal's\ncanonical result if needed; do not resubmit an action to recover its output.\n\n## Unconfirmed requests\n\nClassified failures use the same report layout as successful commands. The report names the\noutcome and stable code, then shows available request, goal and native responder attribution.\nUnknown and partial results use waiting treatment, not a healthy completion marker. Plain mode\nkeeps the same state words without ANSI. The JSON envelope stays on stdout; diagnostics stay on\nstderr. Raw argument objects and arbitrary native detail payloads are not copied into the human\nreport. Confirmed history purge acknowledgements remain visible when a later stream fails.\n\n## Persona previews\n\n`spawn --file/-f <persona-file>` selects a persona, not a team manifest. It cannot be combined\nwith a positional persona or the persisted internal `--config` spelling. A bare filename is\nstill a file, including when it has no extension. Managed paths remain relative to the manager's\nworkspace and are read on that execution host. No file upload is implied. Direct foreground\nexecution reads its local file. A supported Mesh-manifest shape returns a schema refusal with\nthe literal `cotal team deploy <file>` remedy, before agent allocation.\n\n`spawn --dry-run` never refreshes the catalog, repairs stale registrations, imports connector code,\nsigns in to the broker, redeems enrollment, provisions or allocates an agent. Where the launch reads\nwhether the recorded brokers answer to choose its space, as in a folder whose registered space may\nbe stopped, the preview reads the same without signing in, so it names the space the launch uses. The managed preview\nshows the request and exits 0 with `completeness` `not-checked`, because the manager resolves the\npersona, harness and working directory on its own host at launch. It does not substitute the\ncaller's files. A named `--runtime` is checked on this machine only, through the lookup the launch\nuses: `result.requested.runtime` names it, and `result.runtimeCheck` gives the `host` it was checked\non, whether it is `available` there, and the `error` when it is not. A runtime this machine cannot\nrun is a `RUNTIME_UNAVAILABLE` warning, because a manager on another host checks its own.\n`--runtime auto` is refused as the launch refuses it. With `--foreground`, the preview resolves the\nlocal persona, harness precedence and physical working directory. Binary, login, model-policy and live authority checks are labeled not\nperformed. Both previews say where the persona came from: `--file`, the command line, a preference\nor the default. A harness that names no connector this workstation knows is a `HARNESS_UNKNOWN`\nwarning, or the `PREFERENCE_INVALID` warning `config` gives when only `COTAL_DEFAULT_AGENT` chose\nit. In a folder whose `.cotal/` names no space, as after `space forget`, the preview keeps `Mesh:\nmain`, says under it that the held default was assumed, and its JSON `target` names the folder as\n`root` and the space as `assumed`.\n\n`spawn --foreground --enrollment-file <private-file> --space <name>` uses the existing one-seat\nforeground enrollment path. The file is opened with protected ownership/mode and bounded input\nchecks; its URL is not printed, copied into the child environment or persisted by this input\nadapter. It does not supply a managed enrollment authority path. Dry-run does not read or redeem\nthe enrollment file. On Windows the file must be a regular file; its owner, mode and links are not\nchecked.\n\n## Owned process services\n\nPreset inventory checks the selected installed declaration without importing provider code. It\nshows declared foreground/multi-instance capabilities, process versus endpoint readiness, and\nnative-request-bound versus direct-process stop policy. Missing, skewed or unreadable metadata\nleaves the inventory partial, with unknown capability fields. Passing metadata checks is not\nproof that an application, dependency or service is ready. Linux direct-process\ninstances can use the owned systemd user-unit route described below. An adapter\ndoes not acquire that mechanism from its name or preset.\n\nThe direct-process driver pins the machine, the boot, the OS user and each process's start. On\nLinux these are the machine ID, the kernel boot ID and the process start ticks. On macOS they are\nthe hardware UUID, the boot session UUID and the process start in microseconds, read through the\nbundled binding described under the identity pins above. On Windows they are the system ID that\nWindows gives this publisher's apps on the machine (`GetSystemIdForPublisher`), the account's SID and\neach process's creation time, read through the same binding. The record also keeps where Windows\nderived that ID: a TPM, UEFI or the registry. An ID from the registry changes when Windows is\ninstalled again. When Windows has none of these, the driver refuses. Windows records no boot pin.\nThe custodian described below holds each instance's job instead, and a restart ends it.\n\nInstance reports separate recorded supervisor policy from supervisor discovery, which remains not\nchecked by this direct-process driver. A recorded `none` means no supervisor assigned by this\ncontroller. It is not proof that an external unit cannot supervise or restart the process.\nA retained supervisor receipt binds its definition revision, full instance, machine and OS user.\nIts file view hashes only the recorded physical unit, without printing its body or environment.\nMatching bytes are not loaded/active state or enabled-autostart proof. Missing, modified or\nunreadable unit files do not turn a receipt into an uninstalled service. Direct start/stop/remove\nrefuse while such a receipt is retained; they do not launch beside a unit or implicitly uninstall\nit. `service status` can read an unchanged recorded systemd user unit on the same host through\none bounded exact-unit query. It checks the returned physical fragment and reports native state,\nmain-PID attribution, invocation, reload need and presence of unverified drop-ins. Missing or\nambiguous observations stay partial. It neither prints unit/environment contents nor discovers\nownership from names or cwd. This observation proves neither endpoint readiness nor effective\nlogin/reboot autostart. Inventory and direct-process dry-runs perform no OS query;\nLinux native-unit removal dry-runs make one bounded exact-unit status read.\nOn Linux, `service install <name> --instance <full-id> --dry-run` asks the\nsystemd user manager for its config directory, then renders a\nbounded direct-process unit and private environment candidates and reports\ntheir paths and digests without printing their bytes or the inherited `PATH`.\nWith no existing instance, the inspection checks the declared process,\ndurable CLI entry and user manager, then reports that apply would allocate a\nstopped full ID; it\ninvents neither an ID nor a unit candidate. On macOS the same preview renders\na bounded LaunchAgent plist candidate for a settled stopped direct-process\ninstance, with its label, path and digest but not its body or inherited `PATH`.\nIt checks the `Library/LaunchAgents` segments without following symlinks;\nit does not query a launchd domain, load a job or enable autostart. An existing\nplist remains unattributed. With no instance it reports a possible stopped-ID\nallocation, not a plist. macOS install apply refuses, because it has no native\ncontroller or custody records.\nLinux bare install allocates that stopped full ID only after its source validation;\nit does not start a process. If later installation fails, the allocation is\nreported as a changed resource rather than hidden or retried.\nThe unit environment pins an absolute-entry `PATH` from this invocation;\nrelative or empty entries refuse rather than acquiring another working\ndirectory at boot. It requires a settled stopped instance, its current\ndefinition revision and no recorded supervisor, host environment reference or\ncredential reference. An existing unit at that path remains unattributed, not\nowned. The directory's parent ownership, private seed, receipt intent, loaded\nunit and autostart are not established; the preview is partial and authorizes\nno install. Linux apply retains a per-step intent before exclusive private file\nwrites, isolated connector seeding, a bounded private-tree fingerprint, user\nmanager reload, enable without `--now`, and the exact supervisor receipt. It\nreturns the full instance and observed unit-file state, not a process-readiness\nor effective reboot-autostart claim.\nA retained installation or removal intent blocks a new install and is reported\nas partial by `service status` with its exact unit path, private-store path,\ncurrent step and last acknowledged step. A bounded native read for each retained install, removal or start/stop/reseed\naction distinguishes an unchanged physical unit fragment from a name-only\nstatus when the file is missing or modified. It also classifies the private\ndirectory without inspecting its children. None of these reads permits an\neffect to resume. It cannot\nauthorize a blind retry. A changed\npre-effect target leaves a planned step and a partial receipt; an unconfirmed\nsubmitted effect is unknown and is never automatically repeated.\n`service remove` refuses while any native attempt names the definition. There is no\nexplicit reconciliation for an uncertain attempt.\n`service uninstall <name>\n--instance <full-id> --dry-run` reads only a matching recorded systemd unit,\nits private-store directory and one native status snapshot. A retained native\naction, or missing, modified or foreign receipt files, refuses. An owned private\ndirectory does not prove its descendants unchanged. A separate unexposed\nremoval candidate requires a bounded no-follow fingerprint of the complete\nprivate tree to match the install receipt. Changed content is preserved and\nremoval refuses. The preview does not reserve a stopped unit, enable state or\nremoval authority; its report remains partial. Linux apply requires the exact\nunchanged loaded inactive unit and private tree, with `--yes` in scripts or a\nscoped terminal confirmation. It disables autostart, removes only the pinned\nunit and private tree, reloads the user manager, then removes the supervisor\nreceipt. The definition and stopped instance record remain.\n\nA retained native start, stop or reseed action is reported as partial by\n`service status`; it does not authorize another request. Native start and stop\nrequire the exact owned receipt and current native unit observation. An older\nprivate store is reseeded while the unit is stopped, with a separate retained\naction and refreshed generation/tree digest before start. An unconfirmed seed\nis never retried. Restart uses the same exact stop and subsequent start; if\nstart does not confirm, the stopped original remains in its result.\nUninstall retains a step before disable, exact unit unlink, reload, private\ntree removal and receipt deletion. A non-confirming step remains visible in\n`service status`; a missing unit alone never clears its attempt.\n\nThe private unit entry resolves only its built-in command and validates the explicit controller\nhome, space, definition and full instance before any extension overlay, repair or package load.\nIt refuses an inherited managed-spawn handoff without reading or deleting that file.\nBefore native replacement, it requires the recorded private seed generation,\nexecutable provenance and absence of unfinished seed markers; it never reseeds\nthat store at boot. For a\nrecorded direct-process unit it also requires no retained installation or\nremoval intent, the current native unit invocation and matching physical\nfragment before replacing itself with the saved executable. It does not fork a detached\nwrapper or retry execution. A starting record does not establish successful replacement or\nreadiness; an interrupted replacement remains unknown. A later unit invocation refuses an\nunsettled starting, stopping or unknown attempt instead of replacing its request.\nInstall dry-run makes only the bounded native directory read above; uninstall\ndry-run reads the exact recorded unit and private-store directory. Neither\nallocates an instance, enables autostart nor removes a receipt or unit.\n\nProcess definitions support `service start/stop/restart/status/logs <name>`. The driver requires a\nstable machine identity and readable process-creation tokens. Linux and macOS add the boot identity.\nWindows adds the account's SID and a custodian that holds the service's job, described below. It\nrefuses another platform instead of using PID-only ownership. It launches executable and argv\nseparately without a shell. No broker, manager or other dependency is started implicitly.\n\nOn Windows each background instance runs under its own custodian. The custodian is a detached\nCotal process that creates the program inside a new job object. The job ends every member when its\nlast handle closes and allows no breakaway, and only the custodian holds that handle. So a\ncustodian that exits for any reason, a restart of the computer included, ends the service and\neverything it started. Commands reach the custodian over a named pipe and prove that they hold the\ninstance's 32-byte capability, and the custodian proves the same back before anything is done. The\ncapability lives in a protected file beside the instance records, which name only that file. A stop\nterminates the whole job and waits until it is empty, and its receipt records the signal\n`job-terminate`. When the program has exited but processes it started still run, `service status`\nreports the process as `members-running` rather than gone, and a stop ends them. `cotal down`\nrefuses a stack with such a member and names that stop. Windows has no foreground start. A\nmanager's agents run in its job, so a manager stop without `--with-agents` is refused while the job\nstill holds them.\n\nInstance IDs are assigned once and printed in full. With several records, scripts need\n`--instance <full-id>`; a terminal can choose from those IDs. `start --new-instance` explicitly\nrequests another process-driver instance. Otherwise one serving instance is a no-op and an\nunresolved prior launch blocks another allocation. A launch that failed before its child existed\nis recorded as failed, not unresolved. A stopped instance retains its logical ID\nand receives a new launch request. Unknown startup retains its receipt and is not resubmitted.\n\nOnly safe base environment names and the definition's selected `envRefs` are passed. A declared\nprotected credential file is forwarded to that program as the `COTAL_CREDS` path, not credential\nbytes. Control credentials are not inherited wholesale. The program still runs as the current\nOS user; this is not a process sandbox. Native managers require their service adapter and\nagent-impact guards and are refused by generic process launch/stop.\n\nAdapter definitions use the same owned direct-child controller. The local-process provider\nadds a service ABI declaration and launch/readiness hooks. Its closed option schema supports\nbounded objects, arrays and scalar fields, with no schema references, regular expressions,\ncoercion, defaults or unknown-option removal. Every object closes its declared fields. An\nunsupported schema refuses rather than being widened. A declared path option and its ancestors\nfollow the [`space create`](#selecting-a-space) rule for other local writers; a private file stays\nowner-only. One refusal names the option's entry and every unsafe directory above it, with one\n`chmod` that fixes them all. `npm i -g` under umask 0002, an ordinary Ubuntu user's default, leaves the bundled\n`nats-server` and its directories group-writable, so where the private-group proof is unavailable,\n`up` refuses the broker's `binary` path and names that `chmod g-w`. When every unsafe folder is\ninside Cotal's own install root, as it is for the runtime Node an earlier installer left\ngroup-writable, the refusal says the folders belong to Cotal's own install and names `cotal update`\nas the fix.\n\nThe provider declares foreground, multi-instance and log capabilities. An explicit start records\nits definition revision, full instance, launch request and adapter package/version/declaration\nfingerprint before loading provider code. The launch hook receives the definition's `envRefs`\nnames, never their values, and returns executable, argv and working directory separately. It must not launch children, mint credentials or start dependencies while\nplanning. A changed adapter is not adopted by a retained instance. Native stop declarations add\nclosed impact and terminal hooks; a missing hook refuses instead of using generic signals. The\nmanager's adapter supplies its own native policy and receipts. A preset is not installed under an\nOS supervisor.\n\nReadiness hooks receive the pinned process and a bounded cancellation signal. Ready observations\nmust match that process and carry a current observation time. An endpoint-serving declaration\nalso returns its checked native class, instance, principal, lifecycle and epoch. The adapter must\ncorrelate those native coordinates with its owned launch using its supported authority; a matching\nname or successful HTTP request is insufficient. Reports preserve those coordinates and their\nobservation time. Local status does not refresh them or promote them to current live authorization.\nA failed or timed-out hook retains an unknown receipt and is not retried. A cancellation signal\ndoes not prove third-party code stopped working.\n\nExtension removal checks retained service records in every local service space, including spaces\nabsent from the mesh-selection registry. Live, foreign, unreadable or unresolved instances block\nremoval. A starting intent and the extension writer lock prevent an uninstall from crossing the\ncontroller's final provider check. Providers still run as the current OS user, not in a sandbox.\n\nProcess readiness waits up to one second, or its deadline when that is shorter, for the process to\nexit. One that exits by then fails the start with `OPERATION_FAILED`, exit 5, and its exit code is\nrecorded; a restart whose replacement exits so is partial, exit 4, and names that exit code. A\nbackground start also quotes the last line the process logged during that launch, and says its log\nrecords the cause when it logged nothing. A live\ncreation-pinned direct process is reported Running. HTTP readiness uses\na bounded loopback request with credentials omitted and redirects disabled. Its success is\nReachable, not proof that the child owns that listener or serves a native endpoint. Status labels\nthe stored readiness observation and its time separately from the current process observation.\nBefore it launches a process with HTTP readiness, `service start` sends one such request. When\nsomething already answers there, such as an instance of the same definition on its port, any answer\ncould come from it rather than the new process, so the start refuses `CONFLICT`, exit 2, names the\naddress and that instance, and launches nothing. When no instance answers there, the refusal names\neach process of this account that listens there by PID and program name, as `SERVICE_ADDRESS_HELD`\ndoes, and its Next says to stop that process. The start does not signal it. When the HTTP deadline passes first, `service start`\nnames the loopback address without the URL's path, the expected status and the deadline. When the\nservice kept answering another status, such as 503, it exits 5 with `OPERATION_FAILED` and names that\nstatus. When the last check got no answer, such as a refused connection, it exits 3, names what it\nsaw, and its Next also names `cotal service show <name>`, which shows the readiness deadline. Either\nway the process is left running, and the Next names the `service logs` and `service stop` for that\nexact instance.\n\nA Ctrl-C or `SIGTERM` while a background `service start` waits does not reach the service, which\nkeeps running. The start exits 130 or 143 and names the instance it left. Its Next is\n`cotal service start <name>` to wait for that instance's readiness again, or the `service stop` for\nit. Until a later command settles that start, `service status` and `cotal status` say the start was\ninterrupted.\n\nStop and restart default to cancellation in a terminal and need `--yes` in scripts. The selected\nrecord revision and creation identity are checked before signals, including escalation. A reused\nPID is never signalled as the old process. Restart holds the definition operation across the\nconfirmed old-process exit and one replacement launch. An unconfirmed stop cannot cause a\nreplacement. `--dry-run` creates no instance and sends no process signal.\n\n`start --foreground` is an owned foreground process, not an attachable view. It rejects JSON and\ncannot adopt an already running background instance into the terminal. Before allocation it prints\nthe terminal lifetime and output policy. Native auth and Linear presets use their installed signal\nhandlers through this same flow; neither endpoint readiness nor native cleanup is inferred from\nprocess exit. A foreground manager stops through the same request-bound native stop as\n`service stop`, keeping its agents: the SIGINT or SIGTERM the CLI receives is the confirmation.\nCtrl-C also reaches the native child, which ignores a `SIGINT` that carries no stop request and\nprints one warning line for it. A plain `SIGTERM` stops it with the default agent policy.\nNative service child boot never repairs or seeds connector packages. Background stdout/stderr\ngo to owner-private logs. A start or restart moves a log of 16 MiB or more to `<log>.1`, replacing\nan earlier one, and the run writes a fresh log, so the log grows until the next start.\n`logs --tail <1..1000>` reads back from the end of the log until it has that many lines, reading at\nmost 16 MiB. The result is partial only when those 16 MiB hold fewer lines than the tail asked for.\n`service remove` deletes `<log>` and `<log>.1` for each instance it removes. Foreground output is\nnot captured there. Log text is not promised to be secret-redacted. Live following uses the bounded\nread-only stream described below.\n\nFor a recorded systemd user unit, logs use its saved original invocation, boot and OS user,\nnot the native manager's current invocation. Finite tails read at most 1 MiB and 1000 records.\nIndividual native JSON records are capped at 256 KiB. Native journal cursors and microsecond\nwall/monotonic timestamps are kept; binary or repeated message values stay native JSON. Missing\ninvocation evidence refuses without substituting an old direct-child file. Native diagnostics are\nwithheld, not suppressed by the query; their presence makes a finite result partial.\n\nJournal following freezes those same selectors. It reports oversized, incomplete or unattributed\nrecords as gaps and emits native cursors, rather than inventing a sequence. Ctrl-C closes only the\nowned journal reader. It never signals the service or adopts a later invocation. This scope includes\nreadable process-origin messages, not every user-manager lifecycle diagnostic. Journal access and\nretention may omit messages, and an empty result does not prove there was no output or work.\nJournald must retain an accessible per-user journal; no alternate file, privilege escalation or\nremote log service is used. These are authorized local logs and can contain secrets.\n\nA background start runs its process as the leader of its own process group. `service stop`, and\nthe stop half of a restart, signal that group and wait until the process and every member of the\ngroup are gone, so a descendant left in the group, such as a server a shell backgrounded, ends with\nit. A foreground start shares the command's group, so only its own process is signalled.\n`service stop --dry-run` names which it would signal, the process group with its member count or\nthe process alone, and the grace period before `SIGKILL`; `--json` carries it as `stopSignal`. A\ndescendant that leaves the group, such as one started with `setsid`, is not followed, and a\nstopped/direct-process-gone observation is not proof that all external work ended. When the HTTP\nreadiness address of a saved service still answers while none of its instances runs, `service\nstop`, `service status`, `cotal service`, `cotal status` and `cotal down` print a\n`SERVICE_ADDRESS_HELD` warning. It names the address and any process of this account that listens\nthere, by PID and program name. The process driver itself provides no service presets, agent-aware\nmanager control, owned OS units, non-Linux lifecycle parity or full preset and OS-supervisor\nintegration.\n\n`service logs <name> --instance <id> --follow` follows that private file without controlling the\nservice. JSON emits log snapshot/event/connection/gap/error/end records. Events carry native file\nidentity and byte offsets, not a manufactured global sequence. Initial tails and reader backlog\nare bounded at 1 MiB and chunks at 16384 bytes, with explicit gaps for omissions, replacement or\ntruncation. Instance-generation changes are attributed. UTF-8 decoding can replace invalid bytes\nand is labeled accordingly. Output backpressure pauses the reader instead of growing a buffer\nwithout bounds. Ctrl-C, termination and closed output end the view, not the service.\n\n`auth retire <full-lifecycle-uid> [--space <space>] [--yes]` validates the native UID and resolves\nthe selected auth target. A lifecycle UID is 26 to 32 lowercase letters and digits, and\n`cotal ps --wide` shows each seat's. On a user-auth mesh that runs elsewhere and advertises an\nagent-provisioning endpoint, it releases that one lifecycle there with your login\n([Held agent names](identity-and-auth.md#held-agent-names)) and reports the mesh's answer: revoked,\nalready revoked (`no-op`), `TARGET_NOT_FOUND` (exit 2) for a lifecycle the mesh does not list, or\n`OPERATION_UNKNOWN` (exit 3) for a release the mesh could not confirm, which is not repeated. A\nrevoked lifecycle may still be retiring. On any other mesh the CLI refuses `UNSUPPORTED` (exit 2): the registered auth\nendpoint accepts retirement only from the requester's own current serve identity and gate, which this\nCLI does not have. It neither borrows a manager identity nor substitutes the interactive or managed\nloopback host doors. `--yes` cannot create authority, and no retirement request is sent. The refusal\nnames the levers that work: [auth revoke-credential](#auth-revoke-credential) for one minted\ncredential, which keeps the store, and rotating the space identity, which stops every credential the\nspace issued and removes the space's store with its history (see [auth mint](#auth-mint)). The rotation also leaves\nearlier full backups of the space restoring only their registry.\n\n`auth redeem <private-handle-file> [--space <space>]` selects the existing local auth target and\nrefuses with `UNSUPPORTED` because its conferring-handle redemption path is not exposed. It does\nnot read the file, consume a handle or issue credentials. Session grants, agent enrollment and\nparsing an unverified handle are not substituted for that authority.\n\nNested help follows declared command words, for example `cotal help auth handle inspect`.\nAliases resolve within their declared parent. Help reads cached metadata and imports no provider,\nstarts no service and opens no credential file. A command's `-h` selects the deepest declared\nmode before its data arguments; it does not execute that mode. Scoped options refer to the\ncommand's shared flag definitions. Completion uses the same declared subgroups and option names,\nwithout treating an option's value as another command word.\n\n\n## Endpoint class scatters\n\n`endpoint invoke <target> <command> --all` submits one native ephemeral class scatter.\nIt freezes the expected live instance set, gathers attributed replies and reconciles registration\ncurrency through the native SDK. It does not probe additional instance rails or retry a mutation.\nInstance pins, targeted commands, `--cast` and `--wait` cannot combine with a class scatter.\nThe installed helper does not submit journaled commands through this route.\n\nFinite JSON retains the original request, each valid member result and the native missing, churn,\nunexpected, duplicate, late and invalid buckets. Invalid-reply diagnostic bodies are omitted.\nCoverage and command success are separate: complete coverage can still contain refusals or failures.\nAll confirmed pre-execution refusals exit 2 (invalid input exits 1), all executed failures exit 5,\nunconfirmed execution exits 3 and mixed or partial outcomes exit 4. Reports keep full IDs, show\nbounded member/observation rows and state their omissions. A missing reply is not proof of death.\nResult values are endpoint-provided and are not guaranteed to be secret-redacted.\n\n\n## Personas on an exact manager\n\n`persona --on <full-manager-instance>` lists that manager's authorized catalog.\n`persona show <name> --on <id>` reads a card only when its native ownership policy allows it.\nA name-only row does not grant access to its instructions or permission to redefine it.\nThese operations never substitute the local catalog or another manager.\n\n`persona create <name> --on <id>` accepts one of `--file`, `--prompt` or `--from`, with\n`--from -` as explicit piped input. Leading frontmatter follows the native persona merge:\nthe supplied name wins, explicit model/role/application and channel fields override the file,\nand the manager preserves existing ownership and redefinition policy. Inputs are bounded to\n1 MiB and must be valid UTF-8. A malformed leading block refuses before the remote request.\n\nThe remote wire accepts name, body, model, role, application, initial read channels and read/post\nACLs. `--harness`, `--subscribe`, `--allow-subscribe` and `--allow-publish` name those fields.\nOther frontmatter, including kind, tags, description, attention settings, variant, launch options,\ncapabilities and owner, refuses as unsupported instead of being silently discarded. Remote edit\nand removal are not native wire operations and refuse; no local file is changed instead.\n\nCreation is silent unless `--announce <concrete-channel>` is supplied. Announcement preparation\nmust resolve to the same space, root and account context before the write. A confirmed save and\na later denied or unconfirmed announcement produce a partial receipt retaining the save. Do not\nrepeat the definition to recover an announcement; read the channel first. A stored announcement\nis not delivery, a spawned agent or task completion. Finite JSON/plain receipts keep the requested\nand answering manager, epoch and original request, without printing the creation input body. Rich results offer\nfollow-up show or spawn commands with the original exact manager pin; a suggested launch still\nrequires the caller's native spawn authority.\nWhen the manager writes the file into this root's catalog, the receipt's `Local files` line names\nit and JSON `localFilesChanged` is true. On a static space the manager records an owner only for\none of its own seats. The CLI mints a new caller for each call, so its card is operator-owned, as a\nlocal `persona create` writes it, and the manager does not show it over `--on`. The receipt then\noffers `cotal p show <name>` for a file in this root, as the `--on` listing's Next does, and a\n`show --on` that the manager answers not-found names that command when this root's catalog holds\nthe file. A peer joined with its own\ncredentials is not one of the manager's seats either, so on a static space it cannot redefine or\nread over `--on` what it defines.\n"
27569
+ "body": "# `cotal` CLI reference\n\n> **Reference**: describes the TypeScript reference implementation (the `cotal` CLI), not the wire contract. \xB7 **For:** operators \xB7 **Wire contract:** [SPEC](../SPEC.md)\n\n`cotal` is the operator command line for the reference implementation: bring a mesh up, mint\nidentities, launch agents, watch what they do, and tear it all down. It is a thin client over the\nwire contract, whose normative subjects and schemas live in the [SPEC](../SPEC.md). This page is\nlookup material for the commands. If you are new, start with\n[Getting started](getting-started.md).\n\nFor the other integration interfaces, see the [API reference](api-reference.md). For a\nwalkthrough, use [Choose a workflow](using-cotal.md).\n\n## Find a command\n\n| Task | Sections |\n|---|---|\n| Install, prepare or upgrade | [setup](#setup), [update](#update), [completion](#completion) |\n| Select and operate a space | [Selecting a space](#selecting-a-space), [Mesh registry](#mesh-registry), [up](#up), [down](#down) |\n| Launch and manage agents | [spawn](#spawn), [Managed seats](#managed-seats), [persona](#persona), [models](#models) |\n| Message and inspect peers | [who](#who), [whoami](#whoami), [chat](#chat), [send](#send), [history](#history) |\n| Deploy a team | [Manifest deploys](#manifest-deploys) |\n| Operate services and integrations | [service](#service), [Endpoint control](#endpoint-control), [linear](#linear) |\n| Coordinate longer work | [run](#run) |\n| Observe and recover | [watch](#watch), [console](#console), [Backups](#backups), [doctor](#doctor) |\n| Script commands or manage usage reporting | [Structured output](#structured-output), [telemetry](#telemetry) |\n\n## Running it\n\n```bash\nnpm install -g cotal-ai # puts `cotal` on your PATH (needs Node 22+)\ncotal # the selected space, common commands, and where to start\ncotal help # the same, then every command group (also `cotal --help`)\ncotal help <command> [<subcommand>] # one command's or subcommand's usage and flags\ncotal version # cotal-ai version + each installed extension's (also `cotal --version`, `cotal -V`)\ncotal version --json # the same as one JSON document (also `-j`)\n```\n\nThe shell installer prints its absolute launcher path. Use it when an older installation still\nwins plain `cotal`. Having `~/.local/bin` somewhere on PATH does not establish precedence, and a\nchild installer cannot change the parent shell's command cache, aliases or functions. Inspect\n`type -a cotal` in that shell. After putting the new directory first, refresh its command cache\nwith `hash -r` (or `rehash` in zsh). An alias or function must be corrected separately. To restore\nconnectors after an old executable rebuilt them, run the verified newer absolute executable with\n`ext seed`, without `--force`.\n\n`npx cotal-ai <command>` runs it without a global install. Bare `cotal` starts nothing: it names\nthe space this folder has registered, else the selected space, else, with nothing selected, the space\nthis folder prepared, and lists common commands. It reads no\nbroker. When this folder's own records show the stack of its registered space stopped, as after\n`cotal down` there, and another space is selected, it names the selected space, which the commands\nrun there target, as for a prepared folder. Otherwise, when that folder's space is not the selection,\nit adds that the folder's space applies while it runs and the selected space applies outside the\nfolder and while it is stopped. When the folder holds a space that is prepared, or whose stack is\nstopped, it names what it holds and the `cotal up` that starts it, as the note under a command's\n`Mesh:` heading there does. In a copy or a moved folder, which holds the records of a stack that\nruns from another folder, it says what the folder holds: a copy of a controller stack, a legacy\nroot's records and the folder that space is registered for, or records moved from their recorded\nfolder. It ends that line with the step the commands there name. It points a prepared space to the `cotal up` that starts it, with\n`--root <dir>` when the space is prepared in another folder and `<dir>` quoted when a shell would\nsplit it, and a machine with no space to `cotal setup`. While the space it names is prepared or\nstopped, that `cotal up` is its first step and it offers no `cotal spawn`, in any folder. Otherwise,\nin a folder that holds another space, its first step is that folder's own `cotal up`, which it says\nstarts this folder's space, and `cotal spawn` follows. Each `cotal up` it names reads\n`cotal up --max-file-store 67108865 or more` when that stopped stack recorded a file store cap below\n67108865, as `status` says, because a bare `up` fails on that cap. With spaces on the machine but none selected\nor in this folder, it names `cotal use <name>` and `cotal space` instead. Every command generates its own help, usage, and shell completion from its declared\nflags, and `cotal <command> --help` prints the same help as `cotal help <command>`. A word after the\ncommand that names none of its subcommands refuses with `--help` too, as `cotal help` and the command\nitself do (exit 1), rather than print the command's help. A subcommand's help gives its whole\nusage, from `cotal` through its arguments, and lists only the options that subcommand takes.\n`-v` is not a version flag.\n\nA command's help shows its summary, then Usage, Shorthands when it has any, Examples, its\nsubcommands under Commands, Options, and the structured output it supports. Every command and\nsubcommand `cotal help` lists has one to three examples, the most common use first. Each is a\ncommand that works as written, with a note on what it does. A value in angle brackets, such as\n`<persona>`, is one you fill in. `cotal help spawn` shows:\n\n```text\nExamples\n cotal spawn run an agent in this terminal\n cotal spawn -d launch through a manager and return\n cotal spawn <persona> -p \"review PR 12\" start a persona with a task\n```\n\nSome commands have short aliases, listed in `cotal help` and under Shorthands in each\ncommand's help: `sp` (spawn), `a` (attach), `s` (status), `r` (run), `x` (ext), `h` (history),\n`t` (team), `p` (persona), `ch` (channel), `w` (watch), `ep` (endpoint), `mgr` (manager), `svc` and\n`services` (service), and `cfg` (config). Subcommands can have their own, such as\n`cotal ext rm` for `cotal ext remove`. An alias behaves the same as its command. A command or\nsubcommand is never selected by a prefix, so `cotal spaw` is an unknown command. The refusal for an\nunknown command, leading flag, subcommand or option names the nearest one when it is one edit away,\nor two for a word longer than four characters, so `cotal spaw` suggests `spawn`. `cotal despawn`\nsuggests `stop`, the command Cotal's own text calls a despawn. A command, alias,\nor extension command that would claim a word another one already claims is refused when it\nregisters or installs.\n\nIn a dev clone, `pnpm cotal <command>` runs `bin/cotal.ts` through `tsx`, but `bin/` loads the other\npackages from their built `dist/`. Run `pnpm build` after `pnpm install`, and again after you edit a\npackage, or the command fails with `ERR_MODULE_NOT_FOUND` for `@cotal-ai/cli/dist/index.js` or runs\nthe old code. A Windows source build needs the matching MSVC Developer Command Prompt to compile\nits first-party process identity binding. Published packages already contain the x64 and arm64\nbinaries and need no compiler or native download.\n\nCommands that change the installation run the automatic connector seed and stop when the\noperator-global seed store is not current for the checkout's version, which includes a fresh config\nand a store another version wrote. The error includes\n`this would migrate the machine-wide store and can take an installed cotal down`. A\ncheckout never writes the connector store an installed `cotal` uses. For day-to-day\nwork, set `COTAL_SKIP_CONNECTOR_SEED=1` to skip that seed step:\n\n```bash\npnpm install\npnpm build\nCOTAL_SKIP_CONNECTOR_SEED=1 pnpm cotal status\n```\n\nPointing `XDG_CONFIG_HOME` at a scratch dir is not enough by itself. Isolated release tests\nthat must seed from a checkout also set `COTAL_ALLOW_CHECKOUT_SEED=1`, as described under\n[Built-in connectors are seeded extensions](#built-in-connectors-are-seeded-extensions).\n\nBefore publication, the release workflow installs the packaged candidate into fresh homes and\ncaches on Linux, macOS and Windows, on x64 and arm64 with Node 22, 24 and 26. Each setup seeds\nconnectors, starts the broker, delivery daemon and Manager, checks native process identity and\nManager control, then stops and restarts them. Every platform must pass before normal or snapshot\npublication can proceed.\n\nAn undeclared flag is a usage error, and so is a flag given more than once unless it is\nrepeatable, as `--opt` and `down --session-store` are. A word that starts with a dash and a digit,\nas `-1` does, after a flag that takes a value is that flag's value, so `--limit -1` gets the\ncommand's own check of `--limit`. So is an unknown command, and a first word\nthat is not one of the command's subcommands when it declares them. So is input a command checks\nbefore it acts, such as a missing argument, an invalid flag value or two flags that conflict, and\n`cotal help` with a subcommand that does not exist. So is a word that starts with an en dash, an em\ndash or a minus sign followed by one of the command's flag names, as an autocorrecting editor writes\n`--`: the refusal names the dash and the flag to type. After `--` such a word is an argument as\ntyped. The command prints the error, then any remedy,\nthen its help on stderr, writes nothing on stdout, exits 1, and does not run. A refusal for a missing\n`--yes` outside an interactive terminal prints its error and a remedy that names `--yes`, without the\nhelp. A control character in\na word the error quotes, from the command line or from a file, shows as its `\\uXXXX` escape, so a\ntyped word cannot clear the screen or retitle the terminal. A refusal that repeats an argument\nlonger than 128 characters, such as an unknown command, a space name or a run id, shows its first\n128 characters followed by `\u2026 (<n> characters)`, in text and in the `--json` message. The command\nstill checks and uses the whole value. A usage error is found before any effect, so it never seeds\nconnectors or writes configuration. Help, version and completion never do either.\nA word that starts with `-` reads as an option. A message or name that starts with one goes after\n`--`, which the refusal names, and an option value that starts with one is written `--limit=-1`,\nwhich the refusal gives. An option given with no value, last or just before another option, is\nrefused naming the option and its placeholder. A short option takes its value after a space, so `-c=general` is refused\nrather than read as the value `=general`. An option typed with a typographic dash, as pasted from\ntypeset text, is refused naming the option it meant, rather than read as a positional.\n\nCommand output, including error lines on stderr, the guided `setup` and `meshes add` prompts and\nthe `console` screen, is colored only when stdout is a terminal, so piped or redirected output is\nplain text. A terminal whose `TERM` is `dumb` gets no color either. A non-empty `NO_COLOR` turns color off on a terminal too. `FORCE_COLOR` turns color on even when output is\npiped or `TERM` is `dumb`, unless it is `0` or `false` in any letter case, and it takes precedence over `NO_COLOR`\nwith no Node warning about the pair. `--no-color` turns\ncolor off for that run, whatever the environment says. It sets no `NO_COLOR` for the programs the\nrun starts, but they are no longer forced into color by `FORCE_COLOR`.\n\nAt any question the CLI asks, Ctrl-D on an empty line or an input that closes cancels the question\nas Ctrl-C does, so the command reports its own cancelled outcome.\n`--no-input` means the run never prompts, opens an editor or opens a browser. A command that would\nprompt behaves as it does without a terminal, `web` prints its link without opening it, and\n`personas edit` is refused. A refusal for a missing `--yes` names what kept the command from asking:\n`--json`, `--no-input`, `COTAL_NO_PROMPT=1` or no interactive terminal. `--json` implies `--no-input`. `--no-color` and `--no-input` work before\nthe command word on every command, and after it on every command except one that takes its words\nraw. After `--` they are ordinary words. Each is given once, as any flag is, before or after the\ncommand word. No command may declare a flag with either name. The output\nflags `--json` (`-j`) and `--plain` may also come before the command word, as in\n`cotal --json status`; they are read as the command's own flags, so a command without that output\nrefuses them as it does after the command word. A command that takes its words raw, such as the\nhidden completion entry, has no flags to read them as and refuses them before its word with a usage\nerror. A `--` before the command word ends these leading flags. An installed extension\nwhose command declares one is an internal error, and `cotal` exits 1 before running any command.\nShell completion treats the current `--` word as a flag prefix until it is committed. After `-- `,\nit completes positional values without offering options. A string option's value is completed\nfrom its own candidates or file hint, without inserting other option names.\n\nCommands come from the surfaces the binary composes: the base mesh CLI, the manager and the\ndelivery daemon, plus any operator-installed extensions. Hidden entries such as the deprecated\n`deliver` and `auth-service` are not completed.\n`cotal ext add <npm-package>` installs any registry providers a package contributes: commands,\nruntimes, service presets and local process lifecycle descriptors. The `web` dashboard and\noptional manager runtimes ship this way. A `@cotal-ai/*` package named without a version installs\nthis cotal's version, as `setup` and `update` do; a version or tag you name is installed as given.\n\n## Structured output\n\nA command whose help has a `structured output` line naming `json` takes `--json` (`-j`) and writes\none JSON document on stdout. Most commands have one, among them `cotal status`, `cotal space`,\n`cotal service`, `cotal up`, `cotal down`, `cotal ps`, `cotal history` and `cotal config`.\n`cotal help <command>` shows whether a command and each of its subcommands has one.\n`cotal version --json` writes:\n\n```json\n{\"schemaVersion\":1,\"command\":\"version\",\"target\":null,\"outcome\":\"ok\",\"completeness\":\"complete\",\"result\":{\"version\":\"<version>\",\"extensions\":[{\"package\":\"@cotal-ai/web\",\"version\":\"<version>\"}]},\"warnings\":[]}\n```\n\n| Field | Meaning |\n|---|---|\n| `schemaVersion` | `1` |\n| `command` | The command that ran |\n| `target` | What it acted on, or `null` for a command that reads only local metadata |\n| `outcome` | `ok`, `no-op`, `refused`, `failed`, `partial` or `unknown` |\n| `completeness` | How much of the read scope the result covers: `complete`, `partial` or `not-checked`. It says nothing about whether the work succeeded |\n| `result` | The command's result. A `partial` read keeps what it read; a refused, failed or unknown run has `null` |\n| `warnings` | One `{code, target, message}` for each thing it could not read or check |\n| `error` | Present when the outcome is not `ok` or `no-op`: `{code, message, remedy, cause}`. `remedy` is the step to take, present when the refusal names one, and the text report prints it under Next. `cause` keeps an endpoint's own code, outcome and safe detail |\n| `changed` | What a command that did not succeed changed anyway |\n\nDiagnostics stay on stderr. A command that refuses or fails under `--json` reports it with the\nenvelope alone and writes no text report on stderr. The text report goes to stderr only when the\nenvelope cannot be written to stdout, and the exit code is the same either way. A usage error under\n`--json` is a `refused` envelope with code `USAGE_INVALID`, and the help it names stays off stdout. A word that names none of a command's\nsubcommands and is near none of them is a usage error that lists the subcommands the command takes.\nA command without a JSON output refuses `--json` as an unknown option.\n\nA subcommand's help has its own `structured output` line, and only the forms that show one write a\ndocument. Bare `cotal ext` and `cotal ext list` do. `ext add`, `ext remove`, `ext root` and\n`ext seed` report as text, refuse `--json` and `--plain` with a usage error, and exit 1 on any\nfailure.\n\nA command whose `structured output` line also names `plain` takes `--plain`, which writes the same\nreport as plain lines: no color, ASCII labels, and no marks other than the `!` that begins a warning\nor failure line on stderr. Plain writes these punctuation marks in ASCII wherever they appear: `\xB7`\nand long dashes as `-`, `\u2026` as `...`, `\u2192` and `\u21B3` as `->`, and `\xA7` as `section`. Other characters,\nsuch as the letters of a name or a message, are written as they are.\nUnder a locale whose character set is not UTF-8, as `LC_ALL`, then `LC_CTYPE`, then `LANG` decide,\nreports and error lines draw their marks and punctuation as plain does, and keep their color. With\nnone of the three set, the marks are drawn in UTF-8. Windows consoles are always treated as UTF-8.\nA usage refusal under `--plain` or such a locale prints the classified report that begins\n`Request refused`, then the command's help. Every refusal line, the `\u2717` line included, shows a\ncontrol character in the text it quotes, such as a file's bytes or an argument, as an escape like\n\\u001b, so that text cannot move the cursor or change the screen. A refusal of a value that names\nits own fix prints that line and its next step without the help: an `--on` value that is empty, a\nprincipal id or not an instance id, a `spawn --on` that needs a terminal or `-d`, `attach` with no\nname and no terminal, and a `run` command given no run id.\nPass one of `--json` and `--plain`; both is a `USAGE_INVALID` refusal. A table that does not fit the\nterminal is written as one labeled record per row, so no value is cut off. A warning on stderr wraps\nto the terminal at spaces, each continuation line under its text.\n\nShared human reports for a resolved space begin with `Mesh: <space>`, including\nempty `ps` results, manager inventories, the impact a command shows before it\nstops something, and classified failures. A failure raised after the command\nresolved its space carries that space in `target.space`, even when the failing\nstep named no space. The label comes from the operation's target, not a new read\nof the current selection. Global reports do not invent a selected mesh. JSON\nretains `target.space` without a text header. A refusal for a name no space has, such as one\n`space create` refuses or one `space show` and `space forget` do not find, carries the name as\n`target.requested` and has no `Mesh:` heading. Plain watch feeds and followed\nhistory prefix each new row with the mesh name so the context remains visible as\noutput scrolls.\n\nOn a terminal, a long value, line, warning or failure wraps at spaces, and each continuation line\nstarts under the text it continues. A labeled value with a word too wide to fit beside its label,\nsuch as an identity, starts on the line under the label. A word wider than the line, such as a path,\nkeeps a line of its own and is never split. A command wraps as one word, so a copied line never holds\npart of one: a command under Next, and a command a sentence names in backticks after other words,\nfrom its `cotal` to its closing backtick, whatever words its arguments are. A command that holds a\nbacktick itself, as a quoted path can, is named between double backticks, as Markdown writes such code,\nand wraps whole to the matching pair. A line that starts with a command, such as a usage line, wraps at\nits spaces. Help wraps the same way, including the title of `cotal help`.\n\nA question that defaults to No, such as the one before `history clear`, `down`, `channel remove` or\n`persona remove`, is answered by typing: `y` or `yes`, in any case, then Enter confirms it. Enter\nalone, any other text, Esc or Ctrl-C keeps No, so a word typed at the prompt by mistake never\nconfirms a step you cannot undo. `space delete` asks you to type the space's name instead. A\nquestion that defaults to Yes is a Yes and No choice: the arrow keys move the selection, `y` and `n`\nanswer at once, and Enter takes the selection.\n\nA usage line breaks only between options, never between an option and its argument. A ` | ` stays\non the line before it whenever it fits there. A compact `|`, such as the one in\n`<persona.md>|--prompt`, is a break point too: the line ends right after it, the next line starts\nwith the following option, and no space is added or lost. A usage too wide for the line breaks after\nits own ` | ` or compact `|` alternatives first. A group too wide for the line, such as\n`(--to/-t <agent> | --channel/-c <channel> | --role/-r <role>)`, takes lines of its own and breaks\nthe same way inside, and so does a group nested in it. A group that fits, such as\n`[--json|--plain]`, stays whole. Only a single option with its argument that is wider than the line\nbreaks at its spaces. An example's command is not wrapped, so each stays one line to copy.\n\nWhen the flag names leave their descriptions fewer than 30 columns, each description goes on the\nlines under its flag. A report's list of commands, and the notes beside a help's examples, follow\nthe same 30-column rule. Below 48 columns,\na record puts each label on a line of its own with its value under it. Output to a pipe or a file is\nnever wrapped.\n\nA read that could not read part of what it was asked for is `partial`: `result` holds what it read,\n`completeness` is `partial`, each part it could not read is a warning, and it exits 4. A list is\nnever presented as complete when part of it is missing.\n\nA target the command cannot resolve or reach is refused before any request, with the code for why:\n`TARGET_NOT_FOUND` (no such mesh), `TARGET_AMBIGUOUS` (several match), `TARGET_UNAVAILABLE` (its\nbroker or manager did not answer), `AUTH_REQUIRED` (it needs a sign-in or a fresh credential),\n`PERMISSION_DENIED` (it refused the credential), `DEPENDENCY_UNAVAILABLE` (trust material, the\nuser-auth service or a space catalog it needs could not be used), or `UNSUPPORTED`.\n\nOn Linux, a broker on a loopback address whose process is stopped by a signal, such as SIGSTOP,\nstill accepts connections and never answers. Once the first dial to it times out, the refusal names\nthat process's pid and gives `kill -CONT <pid>` as the remedy, without waiting out the longer\nconfirm, so a command such as `send` or `run` returns in a few seconds. `cotal status` shows the\nconnection as `broker process <pid> is stopped` and offers the same command. A broker that runs but\ndoes not answer, or one on another host, still waits out the confirm.\n\nA command with a JSON output exits by its outcome, with or without `--json`:\n\n| Outcome | Exit | Codes |\n|---|---|---|\n| `ok`, `no-op` | 0 | none |\n| `refused` before any effect, for invalid input | 1 | `USAGE_INVALID`, `SCHEMA_INVALID` |\n| `refused` at a known boundary before any effect | 2 | `PERMISSION_DENIED`, `AUTH_REQUIRED`, `TARGET_UNAVAILABLE`, `TARGET_NOT_FOUND`, `TARGET_AMBIGUOUS`, `DEPENDENCY_UNAVAILABLE`, `UNSUPPORTED`, `CONFLICT`, `LIFECYCLE_BLOCKED` |\n| `unknown`: it may have had an effect that cannot be attributed. Do not retry blindly | 3 | `OPERATION_UNKNOWN` |\n| `partial`: some of the work completed, or cleanup remains | 4 | `OPERATION_PARTIAL` |\n| `failed`: a confirmed failure with no cleanup left | 5 | `OPERATION_FAILED` |\n\nAn error such a command does not classify is `unknown`, exit 3. Other commands still exit 1 on any\nfailure. A command run from a folder that no longer exists refuses with `USAGE_INVALID`, exit 1,\nbefore it does anything, and says to `cd` to an existing folder. It prints that line and the `cd`\nremedy without the command's help, because the command itself was not mistyped. A command whose folder has a\n`.cotal` that is a file rather than a directory, such as a notes file, refuses with `CONFLICT`, exit\n2, before it does anything, names that file and says to rename or move it. `help`, `version` and\n`completion` still work in both. `docs` and `telemetry` also work from a folder that no longer\nexists, and there `help` says the folder is gone instead of showing a space.\n\nA long-running command that SIGINT stops ends cleanly first. It stops observing, closes the\nsession it holds and writes its last record, then exits 130. SIGTERM ends it the same way and exits\n143. This holds for `watch`, `chat`, `console`, `history --follow`, `channel members --follow` and\n`join`, and for `service logs --follow`, the `--wait` follows and a foreground `up`. The JSON\nstreams of `watch`, `chat --stdio`, `history --follow` and `channel members --follow` end with an\n`end` record whose reason is `interrupted` for SIGINT or `terminated` for SIGTERM. Inside a terminal\nview a key that leaves, such as `q` or Ctrl-C, is not a signal, and the view exits as it does when\nyou leave it.\n\nOutput that cannot be written, for example to a full disk, is reported in one line on stderr, and\nthe command stops with exit 3, or with the non-zero status it had already set. It may have stopped\npartway, so check what it did before you retry. A reader that closes the output early, as `| head`\ndoes, ends the command quietly with the status it had already set, or 0. A command that follows a\nstream, such as `watch`, `history --follow`, `console` or `channel members --follow`, notices the\nclosed reader at its next line, so on a quiet mesh it runs until a message arrives or it is\ninterrupted. Ctrl-C ends it at once with exit 130, as it ends the commands above.\n\n## Commands\n\n| Section | Command | Purpose |\n|---|---|---|\n| Setup | [`setup`](#setup) | Prepare a space, application and runtimes; opt into startup separately |\n| Setup | [`help`](#running-it) | List the commands, or show one command's help |\n| Setup | [`version`](#running-it) | Print this binary's version and each installed extension's |\n| Setup | [`ext`](#ext) | List, install and remove operator CLI extensions |\n| Setup | [`config`](#config) | Show and set your preferences: the defaults your commands use |\n| Setup | [`telemetry`](#telemetry) | Show or set the anonymous usage telemetry level (`off`, `minimal`, `regular`) |\n| Setup | [`update`](#update) | Reconcile first-party extensions and check or opt into a coherent CLI upgrade |\n| Setup | [`completion`](#completion) | Report, print or install shell completion |\n| Setup | [`docs`](#docs) | Read this version's protocol and CLI docs, offline |\n| Mesh | [`up`](#up) | Start a local mesh (nats-server + JetStream), or boot a whole manifest with `-f` |\n| Mesh | [`down`](#down) | Stop the whole stack, selected registered components, or a manifest deploy |\n| Mesh | [`backup`](#backups) | Create an offline full-space or registry-only artifact from a preserved cut |\n| Mesh | [`clean`](#clean) | Legacy, hidden from `cotal help`: purge history or wipe a stopped store; `reset`, `history clear` and `backup cleanup-fallback` replace it |\n| Mesh | [`reset`](#reset) | Preview or reset one stopped local store or space identity |\n| Mesh | [`meshes`](#mesh-registry) | Legacy, hidden from `cotal help`: list, register and drop meshes; `space`, `space add` and `space forget` replace it |\n| Mesh | [`space`](#selecting-a-space) | List the spaces this machine can reach, or show one in full |\n| Mesh | [`sync`](#mesh-registry) | Refresh the signed-in account's advertised spaces |\n| Mesh | [`status`](#mesh-registry) | Read-only diagnostics for setup, processes, and the selected mesh |\n| Mesh | [`doctor`](#doctor) | Credential-health diagnosis and repair (`doctor auth`) |\n| Mesh | [`use`](#selecting-a-space) | Select the space commands target when none is named, or show the selection |\n| Mesh | [`join`](#join) | Legacy, hidden from `cotal help`: join a space as your own presence; `chat` replaces it |\n| Mesh | [`topology`](#manifest-deploys) | Legacy, hidden from `cotal help`: view a manifest's access graph; `team check --graph` replaces it |\n| Messaging | [`send`](#send) | Send one message, then exit: DM a peer, post a channel, or ask a role |\n| Messaging | [`chat`](#chat) | Open a live session under its own identity: terminal view, `--plain` lines, or `--stdio` for programs |\n| Messaging | [`channel`](#channel) | Read channel metadata and observed members, or change a channel's settings |\n| Messaging | [`history`](#history) | Read retained messages, or clear them |\n| Messaging | [`feedback`](#feedback) | Send feedback to the Cotal developers |\n| Messaging | [`pending`](#pending) | Inspect one DM durable's native counts and bounded candidate IDs |\n| Agents | [`spawn`](#spawn) | Launch an agent from a persona (foreground, or `--detach` via the manager) |\n| Agents | [`models`](#models) | List connector model catalogs and variants from the manager |\n| Agents | [`app-identity`](#plumbing) | Plumbing for a Claude app conversation's identity on a local mesh; you never run it directly |\n| Agents | [`start`](#spawn) | Removed: `cotal start` was merged into `cotal spawn --detach` |\n| Agents | [`stop`](#managed-seats) | Ask the manager to stop a managed agent |\n| Agents | [`ps`](#managed-seats) | List managed agents and their mesh status |\n| Agents | [`attach`](#managed-seats) | Stream and drive a managed agent's terminal (pty runtime) |\n| Agents | [`input`](#input) | Type one line into a managed agent's terminal without attaching |\n| Agents | [`persona`](#persona) | List, show, edit, create, or remove local personas |\n| Agents | [`personas`](#persona) | Legacy, hidden from `cotal help`: the earlier persona parser; `persona` replaces it |\n| Observe | [`endpoints`](#endpoints) | Legacy, hidden from `cotal help`: list every endpoint in the live roster; `who --all` replaces it |\n| Observe | [`who`](#who) | The live roster of the selected space, and whether a manager runs each seat |\n| Observe | [`whoami`](#whoami) | The identity this CLI acts under, and what it may read, post and do |\n| Observe | [`describe`](#endpoint-control) | Legacy, hidden from `cotal help`: read a native endpoint's contracts; `endpoint describe` replaces it |\n| Observe | [`invoke`](#endpoint-control) | Legacy, hidden from `cotal help`: invoke one native command; `endpoint invoke` replaces it |\n| Observe | [`watch`](#watch) | Follow authorized activity without starting, stopping or attaching agents |\n| Observe | [`console`](#console) | Legacy, hidden from `cotal help`: the protocol TUI; `watch` replaces it |\n| Manager | [`runtimes`](#runtimes) | List the agent runtimes the manager can spawn through and whether each is reachable |\n| Manager | [`reconcile-gate`](#reconcile-gate) | Hidden from `cotal help`, which lists `manager reconcile`: unfreeze an issuance gate left frozen by a crashed restart when the successor cannot boot-heal it (holder gone, complete CONNZ sweep) |\n| Manager | [`deregister-instance`](#endpoint-deregister) | Hidden from `cotal help`: the old spelling of `endpoint deregister`, which refuses and names it |\n| Manager | [`seats`](#seats) | Hidden from `cotal help`, which lists `manager seats`: list the pty seat custodians an earlier Linux manager left, and drain the ones whose agent has exited |\n| Manager | [`supervise`](#supervise) | Deprecated and hidden from `cotal help`: run a manager in this terminal; use `cotal manager start` |\n| Manager | [`feedback-intake`](#server-daemons) | Deprecated and hidden from `cotal help`: run a feedback intake server; use the `feedback-intake` service preset |\n| Manager | [`deliver`](#deliver) | Deprecated and hidden from `cotal help`: run the delivery daemon; use the `delivery` service preset |\n| Manager | [`run`](#run) | Operate durable workflow runs: start, resume, list, inspect, answer a checkpoint, check an edited program with migrate |\n| Manager | [`agent-bearer`](#plumbing) | Hidden from `cotal help`: plumbing that prints a fresh bearer for a spawned user-auth agent; you never run it directly |\n| Manager | [`auth-service`](#server-daemons) | Deprecated and hidden from `cotal help`: run a user-auth space's auth service by hand; use the `auth-service` service preset |\n| Manager | [`daemon`](#daemon) | Linux: serve the local machine daemon's control endpoint, read its status, sign it in, check and request its own registration with a space |\n| Services | [`service`](#service) | Run Cotal's built-in services and your own programs: list, add, start, stop, status, logs |\n| Services | [`manager`](#manager) | Start, stop and check the manager that runs your agents, and its maintenance |\n| Identity | [`mint`](#auth-mint) | Moved: credential minting is `cotal auth mint` |\n| Identity | [`actor`](#actor) | Legacy, hidden from `cotal help`: the actor ledger; `auth grants`, `auth grant` and `auth revoke` replace it |\n| Identity | [`service-endpoint`](#service-endpoint) | Decide and inspect service endpoint registrations on the space's auth service |\n| Identity | [`login`](#login) | Sign in to a per-user-auth mesh's IdP (once per machine) |\n| Identity | [`logout`](#login) | Revoke the IdP session and clear the cached login |\n| Identity | [`auth`](#auth-mint) | Mint credentials, inspect account metadata, manage actor grants and export a protected account signer |\n| Teams | [`team`](#manifest-deploys) | Inspect teams, boot a fresh mesh from a manifest, or add its channels and agents to a running one |\n| Endpoints | [`endpoint`](#endpoint-authoring) | Save endpoint references, author contracts offline, inspect or invoke native endpoints, and deregister a departed instance |\n\nThe table has one row per command, including the hidden ones `cotal help` leaves out, under the\nsection help lists it in.\nInstalled extensions add their own: `@cotal-ai/linear` adds the [`linear`](#linear) command, and\n`@cotal-ai/web` adds the [web dashboard](#web) as a service preset.\n\nThe manifest modes of `up`, `spawn`, and `down` (`-f <cotal.yaml>`) plus `topology` are covered\ntogether under [Manifest deploys](#manifest-deploys).\n\n## setup\n\n```bash\ncotal setup [--destination <local|self-hosted>] [-H|--harness <connector>] [--runtime <name>]... [--project <dir>]\n [--space <name>] [--connection-file <bundle.json>] [--configure-only | --start [-d|--detach]]\n [--on <manager-instance>]\n [--demo|--full] [-y|--yes] [--json|--plain]\ncotal setup --skills\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--destination` | asked | `local`: a space this machine starts. `self-hosted`: a space registered here that runs on a server, including a Cloud space `cotal login` discovered. `cloud` is refused before anything is written or contacted, and the refusal names that route: `cotal login`, then `--destination self-hosted --space <name>`. |\n| `--harness`, `-H` | asked | The connector your agents run in. Setup runs its own setup actions and records it as the space's `harness` preference, so `cotal spawn` in the space launches it. When you have no `harness` preference of your own, it becomes yours too. |\n| `--runtime` | asked; none in a script | A runtime to set up besides the built-in `pty`: `tmux`, `cmux`, `orca`, `herdr` or an installed third-party one. Repeat it for each. |\n| `--project` | asked | The project folder: the space's root, whose `.cotal/agents` holds its personas. A script's folder must exist; a terminal offers to create it. |\n| `--space` | asked | The space to prepare (local) or the registered space to use (self-hosted). |\n| `--connection-file` | none | Self-hosted: register the space from a connection bundle exported where it runs, with the checks `space add --file` makes. |\n| `--configure-only` | on | Save preparation and start nothing. Refused with `--start`. |\n| `--start` | off | After successful preparation, start the local stack when needed and launch the default persona. Self-hosted setup uses its registered manager and starts no replacement local stack. |\n| `--on` | none | Select one self-hosted manager instance for startup. Local setup pins the manager it starts and refuses this flag. In a script, use it with `--start`. |\n| `--detach`, `-d` | off | With `--start`, leave the managed agent running without opening a terminal view. Required in scripts, JSON and plain mode. |\n| `--demo` | off | Also seed the guided expert team (`david`, `sven`, `me`) |\n| `--full` | off | The guided flow with the guided team (implies `--demo`) |\n| `--yes`, `-y` | off | Ask nothing: every input comes from the flags |\n| `--skills` | off | Reconcile Cotal skills only through installed connector providers, plus `~/.agents/skills`. Refused with the four inputs, `--runtime`, `--connection-file`, `--configure-only`, `--start`, `--on`, `--detach`, `--full`, `--demo`, `--json` or `--plain`. |\n\nSetup saves preparation before a separate startup decision. Configure-only is the default;\n`--yes` never turns it into startup. On a terminal it asks for each input no flag gave: destination,\napplication, runtimes, project folder and space.\nIt shows the answers before it writes: Save writes them, Change asks every question again with the\nanswers kept, and Cancel saves nothing. Cloud is listed but cannot be chosen. Its hint says to sign\nin with `cotal login` and choose Self-hosted, because a Cloud space is registered here when you sign in.\n\nWithout a terminal, or with `--yes`, `--no-input` or `--json`, every input is a flag. A missing one\nis `USAGE_INVALID` naming each missing flag, before anything is written. The refusal prints the\nscripted form for the destination and not the full command help. The local form, filled in:\n\n```bash\ncotal setup --destination local --harness claude --project ~/research --space research --no-input\n```\n\nThe `self-hosted` form also takes `--connection-file <bundle.json>`. A space name `cotal up` cannot\nstart is refused with the name respelled in letters, digits, `-` and `_` as the remedy, and an\nunknown `--harness` with the known ones and `cotal ext add <npm-package>`.\n\nA local space is prepared as `cotal space create` prepares it. A self-hosted space is one already\nregistered with `cotal space add` or a signed-in catalog, whose root must be `--project`, or one\n`--connection-file` registers. Setup then seeds the `default` persona into\n`<project>/.cotal/agents`, the catalog `cotal spawn` reads for that space, records the application as\nthe space's `harness` preference, runs the application's setup actions, and reconciles Cotal's\nskills. When you have no `harness` preference of your own, setup also records the application as\nyours, so a space you prepare later with `space create` uses it. A value you already set stays.\nA preferences folder another account could write is refused with `PERMISSION_DENIED`, exit 2, before\nanything is written (see [Preferences](config.md#preferences)).\nThe result names the next command: `cotal up` in the project for a local space, or\n`cotal spawn --space <name>`. `--json` writes one envelope whose `result.written` lists every file\nsetup wrote.\n\nSetup needs an installed connector. With none, a `--harness` that names no official connector is\nrefused with `USAGE_INVALID`, exit 1, and otherwise setup refuses with `DEPENDENCY_UNAVAILABLE`,\nexit 2. Its remedy names `cotal ext seed` on a released install. A source checkout does not seed\nconnectors unless `COTAL_ALLOW_CHECKOUT_SEED=1` marks a sandboxed release test, and the remedy says\nso.\n\nAfter successful preparation, the terminal asks separately whether to start, and only a typed `y`\nor `yes` starts it. An explicit\n`--configure-only` suppresses that question. `--start` skips it; scripts must also pass `--detach`.\nMissing application/runtime prerequisites keep preparation saved and do not launch anything.\n\n```bash\ncotal setup --destination local --harness claude --project <dir> --space <name> --start --detach --json\n```\n\nFor a local space, setup invokes the current native `up` in the prepared project, then pins the\nagent request to that root's recorded manager. Native up returning successfully is not reported as\nwhole-stack health. The managed launch must return its actual joined identity. Self-hosted startup\nuses the registered manager's persona catalog; `--on` pins its manager instance\nthrough credential issuance and launch. It uploads no local persona file. Without detach,\nthe terminal opens that allocated incarnation's native view. If it disappears before attachment,\nsetup reports the confirmed join and an incomplete view, not a healthy attached terminal. Leaving\nthe view sends no stop request.\n\n`result.startup` separates the requested self-hosted manager pin, native stack-command\nreturn, joined-agent receipt and any classified failure. A declined Start keeps the\npin in the suggested spawn command rather than silently selecting another manager. A joined result includes its full manager identity and a usable exact `attach`\ncommand when its returned tokens are safe to put in a shell command. Preparation remains saved\nafter a denied or uncertain start. Unknown outcomes are not retried; the setup heading is not\nshown as healthy and the next command inspects the original startup before attachment. JSON stdout has\none envelope. A confirmed local startup refusal keeps its code and diagnostic in\n`result.startup.failure` and the setup report. Interrupted startup or an incomplete child result\nremains unknown. Native startup output is not parsed or echoed as readiness evidence. Fresh prepared\nstatic-auth, open and plain-loopback user-auth roots use the controller stack described under `up`.\nManifest startup retains its native launch path.\n\nRuntimes are where agents run. `pty` is built in and always set up. The terminal lists the official\nruntimes and every installed third-party one, each with whether it is set up and whether the host\ntool it drives is on PATH, or named by the driver's own variable (`COTAL_ORCA_BIN`,\n`CMUX_BUNDLED_CLI_PATH`); installed ones and ones whose tool was found start selected. Setup\ninstalls each selected runtime as `cotal ext add` does, at this cotal's version, then asks its\nprovider whether it answers, as `cotal runtimes` reports. A selected runtime whose host tool is\nmissing is not installed, and one that does not install or answer leaves the rest saved and the\nresult `partial`. A script sets up only the runtimes `--runtime` names; an unknown name is\n`USAGE_INVALID` and a missing host tool is `DEPENDENCY_UNAVAILABLE`, both before anything is\nwritten. Setup never removes a runtime you leave out; `cotal ext remove` does. `result.runtimes`\nlists each one with its package, its host tool and whether setup found it set up, added it or left it\nout.\n\nA rerun with the same answers changes nothing and is a `no-op`; a space already prepared, started or\nregistered at that root is reported, not refused. A space name or root another space holds is\n`CONFLICT`. An application whose host tool is not on PATH leaves the rest saved and the result\n`partial` (exit 4), naming the tool; an application action that does not finish does the same.\n`cotal status` reports the machine's state, and it points\nstale Claude skills and out-of-date `.agents` skills at `cotal setup --skills`. See\n[Getting started](getting-started.md) and, for maintainers, [setup internals](setup-internals.md).\n\n## update\n\n```bash\ncotal update [--self] [--space <s>] [--server <url>] [--creds <path>]\ncotal update --check [-j|--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--self` | off | If a newer release exists, install that exact validated `cotal-ai` version globally and reconcile through the newly installed binary |\n| `--space`, `--server`, `--creds` | resolved mesh | Select the running manager whose continuity state is reported |\n| `--check` | off | Read version availability and recorded extensions without installation or restart; takes no mesh selector or `--self` |\n| `--json`, `-j`, `--plain` | off | Choose the finite check report; requires `--check` |\n\nUpdate reads the recorded mesh inventory without refreshing account space catalogues. An expired\nlogin to a remote mesh does not block the software update. Local manager continuity checks still\napply before changing the installation.\n\nAfter an upgrade, `up`, `down` and the `service` verbs still control the stack members and\nfirst-party service instances an earlier release recorded, as long as their adapter's declaration\ndid not change. A third-party adapter's instances stay pinned to the package version that started\nthem.\n\nWithout `--self`, `update` keeps the installed first-party surfaces coherent with the running\nbinary: it verifies each of the six built-in connectors and the web dashboard against the payload\nthe binary ships, byte for byte, and reinstalls one that differs in any way (a missing, changed or\nextra file, a link where the installed copy should be, an entry at another version, or an\nextensions prefix whose lock no longer matches what is on disk), brings what a\nconnector installed in its harness to this release (the `cotal` plugin copy Claude Code sessions\nload, and its Claude Desktop entry), then reinstalls other `@cotal-ai/*`\noperator extensions at the binary's exact version. A built-in that passes is reported as verified;\n`cotal ext seed --force` remains the unconditional reinstall. Each extension runs in an isolated child, so one\nfailure cannot poison later replays. It then checks npm; a newer binary is an informational notice\nwith `cotal update --self` as the next command, not an automatic install.\n\nBefore disk reconciliation, `update` reads the selected running manager. A machine with no recorded\nmesh has no running manager to observe, so that read is skipped and the command completes. The same\nholds when every recorded mesh is down and none is selected. A remote user mesh, registered with\n`cotal meshes add --mode user`, is named and skipped: its manager runs under another install, so\nthere is no custody on this machine to preserve, and a `legacy` verdict still comes only from a\nmanager this machine read. A stopped mesh that this machine runs, chosen with `--space` or by running\ninside its project, has no running manager either when no `cotal supervise` process on this machine\nmay be serving its space and nothing listens at its recorded broker address, so the read is skipped\nthere too. A controller stack that completed ordinary `cotal down` is eligible after its retained\nservice records prove every process gone. Missing instance records, live or unresolved processes,\nand an unfinished down still refuse. That is the cold path past a `legacy` verdict: `cotal down`,\nthen `cotal update`. A space prepared on this machine that never started, whether this folder's,\nselected or named, has no broker and no manager, so it is named and skipped the same way. Any other\nselected mesh whose manager cannot be read is still a refusal: a named space that is not\nrecorded, a mesh registered with `cotal meshes add`, a `--server` override, a `cotal supervise`\nprocess still running from any folder whose command line names that space or no space, or a broker\naddress where anything accepts the connection or the dial does not finish in time. With several\nmeshes running and no `--space`, `--server` or `--creds`, the install is machine-wide, so every\nrunning manager is reported in turn, each under its space name, before anything is written; a\n`legacy` verdict on any of them makes the whole run not a hot update. A folder that holds several\nregistrations, such as two remote spaces registered there with `space add --from`, is read the same\nway, one registration at a time. A selector flag still reports\none manager. A manager without a\ncustody generation is reported as `legacy`: it cannot preserve its manager-owned PTYs, so the\ncommand says that this is not a hot update and prints `exact`, `fork`, `fresh`, or `drain-only`\nfor every seat. This report sends no stop, preservation-commit, or replacement command.\nIt does not preserve a running PTY on a legacy manager. The built-in pty runtime spawns\nin-process on every platform and reports `legacy`. On Linux it still adopts seats that an earlier\nmanager left under a detached custodian, but it starts no new custodian. An incompatible native\n`@lydell/node-pty` or ConPTY ABI break remains an explicit per-seat maintenance cut.\n\nWith `--self`, the selected running manager is reported before any global install. When a newer\nrelease exists, Cotal then installs the exact version it validated, resolves and verifies that\npackage in npm's global root, then launches that binary with the same `--space` / `--server` /\n`--creds` selection to reconcile connectors and first-party extensions to the new generation. An npx\nor dev-clone invocation therefore installs and continues through a separate global copy; it never\nclaims the already-running process changed. From a source checkout, the `update --self` hint in\n`update` and `update --check` says so. If the binary is current, `--self` performs the normal\nlocal reconcile without reinstalling it. Each npm install it runs starts with 022 added to your\numask, so a umask of 0002 does not leave the installed tree group-writable.\n\nOnce global replacement starts, Ctrl-C waits for installation, verification and reconciliation to\nfinish before returning exit code 130. SIGTERM waits the same way and returns 143. The command\nexplains the wait, keeps its update lock and does not send terminal interrupts into the child\nprocesses that are replacing packages. An install or reconcile failure still reports its own\nnonzero result. Force-killing the process or closing its terminal is not a supported cancellation.\n\nRun through the installer's `cotal` launcher, `update` first removes group and other write from the\nfolders and files you own in Cotal's install root, without following links. An installer from before\nit set its own umask, run under a umask of 0002, left that tree group-writable. Running the installer\nagain repairs it the same way.\n\n`update` also removes group and other write from the folders and files you own in Cotal's config\ndirectory, `$XDG_CONFIG_HOME/cotal` or `~/.config/cotal`, which holds the extension prefix and the\nseed store. An earlier seed run under a umask of 0002 left those folders group-writable, and the web\nseed's check for other local writers refused them. Each repair prints how many entries it changed\nand where. A folder above the config directory, such as `~/.config`, is not Cotal's, so `update`\nleaves it as it is. The seed still refuses it and names `chmod g-w <path>` as the next step, and\n`update` exits nonzero.\n\nThird-party extensions are listed with their installed version and recorded spec but are not\nauto-updated in v1. Floating third-party updates require `@cotal-ai/*` peer-range validation and are\na future follow-up. A failed connector/extension install, npm metadata check, or requested global\ninstall is reported and makes the command exit nonzero. Independent extension attempts continue so\nthe output includes every failure; an unavailable npm registry does not undo a completed local\nreconcile, but the command still exits nonzero because it could not establish that the install is\ncurrent.\n\n## up\n\n```bash\ncotal up [--foreground|--detach] [--open] [--space <s>] [--root <prepared-dir>] [--server <url>] [--channels <path>] [--runtime <name>] [--manager-name <name>]\ncotal up --user-auth --idp <url> [--exchange-public-port <n> --exchange-public-url <https://\u2026> [--exchange-trusted-proxy]]\n [--advertised-server <url>] [--agent-provisioning-url <https://\u2026>] [--auth-timeout <seconds>]\ncotal up --tls-cert <cert.pem> --tls-key <key.pem> # serve broker TLS (both, or neither)\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\ncotal up -f <cotal.yaml> [--dry-run] [--runtime <name>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--server <url>` | auto (free local port) | Listen URL: one `nats://` or `tls://` URL with an IP address and a port, without credentials, path, query or fragment. `up` records and compares it with the scheme in lower case and without a bare trailing `/`, so `NATS://127.0.0.1:<port>/` names the same address as `nats://127.0.0.1:<port>`. A URL with no port is not given the default 4222. Any other value, a host name such as `localhost`, and an IPv6 address, which the stack's own clients cannot reach, refuse `USAGE_INVALID` before anything is written. That refusal gives an example on `127.0.0.1` with the port you passed, and prints no help. With no `--host` the broker binds `127.0.0.1`, so a `--server` naming another IPv4 address refuses `USAGE_INVALID` the same way and names the `--host` to add. A `tls://` address records TLS as required, so without `--tls-cert` and `--tls-key`, or a recorded TLS policy, `up` refuses it `USAGE_INVALID` before anything starts. On a prepared root it refuses `UNSUPPORTED`, exit 2, instead, as the `--tls-cert` row says. A controller stack's first `up` records it, and a later `--server` naming another address refuses `CONFLICT`, naming the recorded address and the bare `cotal up` that starts the stack there. A running space started again from its own project, that folder's subfolders included, at another address refuses `CONFLICT` too: it names the address the space runs at, the bare `cotal up` that reaches it, and the `cotal down` in its root. It does not offer a separate space in the folder, which is that space's own project. No command moves a stack that ran to another address. When another program holds the recorded address, bare `up` refuses too, so this refusal names that address and any process of this account that listens there, and says to stop it before bare `cotal up`. A different `--store-dir`, `--max-file-store` or `--host` refuses the same way, naming each recorded value. A controller stack's broker also needs an address that is not a wildcard, which `up` checks before it writes the stack journal. With `-f`, it overrides the manifest's `broker.servers` and follows that field's rules instead: a valid URL with no embedded credentials |\n| `--host <host>` | none | Bind host override for a **fresh** broker boot: an IP or hostname only, never a URL (that is `--server`) and never `host:port` (the port comes from `--server` or its default); a URL or port-bearing value is refused pointing at the right flag. 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. The broker's plaintext websocket and the manager's plain HTTP console bind the same host, so a wildcard bind opens all three listeners on every interface. Each of these refusals, and a concrete IPv6 address, refuses `USAGE_INVALID` (exit 1) before anything is written. Recorded on the mesh and reused by every later manager launch, so a repair or resume keeps remote [`attach`](#managed-seats) working. A live refresh (`\u2713 mesh already running`) does not rewrite `.cotal/auth/server.conf` or rebind nats; stop the broker, then re-run `up --host` |\n| `--space <s>` | the folder's space | Space name, at most 64 characters; a longer one refuses `USAGE_INVALID` before anything is written |\n| `--root <prepared-dir>` | current folder's workspace | Start the project in `<dir>` instead of the folder you run `up` from. `<dir>` must already have its own `.cotal`, which `space create <space> --root <dir>` or an earlier `up` there made. An absent, inherited or symlinked `.cotal`, or the machine's home store, refuses before startup. Relative file, restore, channels, TLS and store paths remain relative to the invoking folder. The native `up` selection rule still applies after startup. |\n| `--store-dir <dir>` | none | JetStream store directory (recorded; a repair up reuses it). Also accepted with `-f`. A path that is a link refuses `USAGE_INVALID`, naming the directory it points to. A store folder belongs to one space: a folder another space uses refuses `CONFLICT`, naming that space |\n| `--max-file-store <bytes>` | nats-server's dynamic cap | JetStream file storage cap in bytes (a positive integer, no unit suffix). At least 67108865: the space's membership bucket reserves 67108864 (64 MiB), and nats-server creates each of the other streams only while one more byte is free. A smaller value refuses `USAGE_INVALID`, naming 67108865, before anything starts. Without it nats-server sizes the store at start as three quarters of the free space on its filesystem. The cap is fixed at broker start: a running broker cannot change it (`cotal down` first), and a stopped stack's next `up --max-file-store` replaces the recorded cap. A controller stack keeps it across `cotal down`, because it refuses `down --preserve-state` in this release. On a legacy root, `down --preserve-state` keeps it for the resume, and a resume with a different value refuses `CONFLICT`, exit 2. When a broker `up` started cannot create the space's streams for want of storage, the failure names the cap and `cotal up --max-file-store <bytes>`. Once the space holds more than the cap, the broker refuses every write: `send` refuses naming the cap, and `status` shows the store full. Not accepted with `-f` |\n| `--auth-timeout <seconds>` | nats-server's 2 seconds | User-auth only: how long the broker waits for the auth callout's answer before it refuses a connect, a whole number from 1 to 60. `up` renders it as `authorization { timeout: <seconds> }` in `.cotal/auth/server.conf` and starts the space's auth service with the same value as its callout deadline, so the two never differ ([callout deadlines](identity-and-auth.md#callout-deadlines)). It is recorded in `.cotal/broker-policy.json`, so a later `up` on the root renders it again without the flag, after `cotal down` or a preserved resume included. It is fixed at broker start: a running broker refuses a different value (`cotal down` first), and a stopped stack's next `up --auth-timeout` replaces the recorded one. Refused `USAGE_INVALID` with `--open`, without user auth, with `-f` or with `--restore`. A resume renders the recorded value and refuses another one |\n| `--channels <path>` | `.cotal/channels.json` if present | Channel-registry seed file (JSON), in the [channel seed file](#channel-seed-file) format. An explicit path that is missing, unreadable or not JSON refuses `USAGE_INVALID`, naming the file, before anything is written. Content the registry would not take refuses `SCHEMA_INVALID` before anything starts, naming the file and the entry. `up` reads the file once, so a pipe or a FIFO such as `--channels <(\u2026)` works. A default `.cotal/channels.json` that is not a regular file, such as a FIFO, refuses `USAGE_INVALID` the same way |\n| `--restore <dir>` | none | Restore a completed offline backup before exposing the normal listener |\n| `--restore-only registry` | artifact selection | Restore only the registry component |\n| `--accept-missing-source` | off | Explicit disaster consent when the inode-bound preserved source is absent |\n| `--accept-stale-checkpoint` | off | Explicit consent to resume a seat whose checkpoint was captured outside its recorded recency horizon |\n| `--open` | off (auth) | Unauthenticated dev mesh: no JWT, no ACLs. A root whose first `up` recorded another mode refuses it |\n| `--user-auth` | off | Per-user auth: people `cotal login`; connects are authorized against the actor ledger. A root whose first `up` recorded another mode refuses it. A stopped root whose space has user-auth state refuses a start without it, `--open` included, as `CONFLICT`, exit 2, before anything is written; add `--user-auth` to start it |\n| `--idp <url>` | none | With `--user-auth`: the IdP auth base URL to pin on first enable. A later run may omit it or pass any spelling that normalizes to the pinned URL, such as one with a trailing slash; a different IdP is refused. With `-f`, overrides the manifest's `broker.idp` under the same rules: https (or http on a loopback IP literal), with no credentials, query or fragment |\n| `--exchange-public-port <n>` | none | With `--user-auth`: add the public exchange face on this loopback port, for an HTTPS reverse proxy to forward to |\n| `--exchange-public-url <https://\u2026>` | none | With `--exchange-public-port`: advertise the reverse proxy's HTTPS URL in discovery |\n| `--exchange-trusted-proxy` | off | With `--exchange-public-port`: attribute public failure buckets to the last `X-Forwarded-For` hop. Enable only when the listener is reachable solely through a trusted proxy; otherwise the socket address is used |\n| `--advertised-server <url>` | `--server` | With `--exchange-public-port`: the broker address the public discovery bundle tells participants to dial, such as a `wss://` address behind your proxy. Must be a `nats://`, `tls://`, `ws://` or `wss://` URL |\n| `--agent-provisioning-url <https://\u2026>` | none | With `--exchange-public-port`: the deployment's agent-provisioning endpoint the public bundle advertises. A participant's `cotal spawn` on a remote user-auth mesh sends its login bearer there to provision the agent; without one, that spawn needs a one-time enrollment ([Run a mesh](run-a-mesh.md#remote-supervised-seats-by-enrollment)). HTTPS only |\n| `--service-endpoints` | off | With `--user-auth`: the auth service also hosts service endpoints: the public registration door and the authenticated host route that `cotal service-endpoint` drives |\n| `--detach`, `-d` | on | Run in the background, which is also the no-flag default |\n| `--foreground` | off | Keep native stack lifetime in this terminal. Required when a systemd/launchd unit supervises `up` itself. Cannot combine with `--detach` |\n| `--tls-cert <path>` | none | PEM certificate to serve TLS with. Must be given together with `--tls-key`. Before starting the broker, Cotal checks readability, private-key mode, key/certificate match, the validity window, and host coverage. `nats-server` accepts an expired certificate and leaves the failure to clients, so Cotal performs these checks first. The decision is recorded; a later bare `cotal up` keeps serving TLS. Cotal's own clients, `up` included, check the certificate against Node's trusted CAs and the address they dial. For a self-signed certificate or a local CA, set `NODE_EXTRA_CA_CERTS` to that certificate or CA file (PEM) for `cotal up` and every other `cotal` command of the space; the stack's delivery daemon and manager inherit it from `up`. The stack's address is `tls://`, so every dial checks the certificate against the address it names: an address `up` chooses, the default or the one a stopped folder last ran at, becomes `tls://`, and the `TLS: serving` line of the receipt names it. A root prepared with `cotal space create`, or one whose stack the service controller owns, has no route that serves TLS: there `--tls-cert`, `--tls-key` or a `tls://` `--server` refuses `UNSUPPORTED`, exit 2, before anything starts. On a prepared root that refusal names `cotal up -f <cotal.yaml> --tls-cert <cert.pem> --tls-key <key.pem>` with a `tls://` address in the manifest's `broker.servers`, which serves TLS there. Elsewhere a `--server nats://` with `--tls-cert` refuses `USAGE_INVALID` before anything starts and names the `tls://` form, and so does `up -f` whose manifest `broker.servers` names a `nats://` address. A `--server nats://` on a root whose recorded policy serves TLS refuses the same way, naming `.cotal/broker-policy.json` and the `tls://` address to pass, because the broker it would start serves TLS while the address it would record is dialed in cleartext. When this client refuses the certificate in the handshake, `up`, with or without `-f`, stops the broker, records nothing and fails `OPERATION_FAILED`, naming the native error and the step to take |\n| `--tls-key <path>` | none | PEM private key for `--tls-cert`. Refused if group- or other-readable (tighten to `600`) |\n| `--file <cotal.yaml>`, `-f` | none | Launch a whole mesh from a manifest |\n| `--dry-run` | off | With `-f`: print the plan, mutate nothing. Without `-f` it refuses `USAGE_INVALID` before anything starts or is written. A preview from an unprepared folder under the machine home reads that folder's empty policy rather than inheriting the home store; applying there requires `space create` first. |\n| `--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. Without `-f`, that refusal is `DEPENDENCY_UNAVAILABLE` (exit 2) before anything is written, and for a known runtime the remedy names the `cotal ext add <pkg>` to run. With `-f`, overrides the manifest's runtime |\n| `--max-sessions <n>` | 64 | Live-session ceiling for the mesh manager. Each console pane and each `cotal attach` is one session, so size for agents \xD7 panes, not agent count. Recorded on the mesh, and for a controller stack in its saved manager definition, and reused by every later manager launch, so a bare `up` after `down`, a repair or a resume does not silently drop back to 64. A running manager cannot change it: `cotal down` first, then `cotal up --max-sessions <n>` |\n| `--seat-cap <n\\|unlimited>` | `unlimited` | Live seats this root's manager admits for one owner. Recorded in this root and read by the manager at each start, so a bare `up` after `down`, a repair or a resume keeps it. A running manager cannot change it: `cotal down` first, then `cotal up --seat-cap <n>`. Cannot be combined with `--restore`, `--file` or `--no-manager` |\n| `--manager-name <name>` | none | Name this root's manager. `who --all` and the manager choice show the name, and on a user-auth mesh `--on` takes it. It is recorded in the root, so a restart or repair keeps it. A running manager keeps the name it started with, so a different name is refused until `cotal down`. The name is one line of at most 128 characters with no `/` or `\\`, and it may not look like an instance id (26 to 32 lowercase letters and digits). Cannot be combined with `--no-manager`, `--file` or `--restore` |\n| `--no-manager` | off | Broker-only boot: start the broker and, in auth mode, the delivery daemon, and no local manager. A refresh under the flag of a mesh whose manager is live refuses rather than keeping or stopping it. For a controller stack, use `cotal service stop stack-manager --space <s> --yes` first. A legacy stack has no command that stops only its manager: `cotal down` stops the whole stack, then `cotal up --no-manager --space <s>` starts it broker-only. Cannot be combined with `--runtime`, `--max-sessions`, or an agent-declaring manifest |\n| `--rotate-sys` | off | Rotate the space's system account and re-mint its two `$SYS` creds. Needs a stopped mesh; refused with `--open`. A controller stack takes it once every member is stopped, and a first start on a prepared root refuses it |\n\n`cotal up` boots a local nats-server with JetStream and, in auth mode (the default), JWT auth and\nper-agent ACLs. It runs in the background by default and records the mesh so `cotal spawn` from\nany directory can find it; `--detach` explicitly names the same mode. `--foreground` keeps the\nnative stack in this terminal. A contradictory mode pair refuses before setup or seed work. `up`\nseeds the [built-in connectors](#built-in-connectors-are-seeded-extensions) only when it first starts\na process, or when `up -f` checks the connectors its manifest's agents use, so a refusal before then\nleaves the operator-global seed store as it was. When a start fails after that seed moved the store to this version, the failure\nsays so and names the store. In a\nfolder whose only `.cotal` above it is the machine's home one, such as a new folder in your home\nfolder, a bare `up` makes the folder's own `.cotal` and starts the stack there, as it does in a folder\nwith no `.cotal` above it, and a run refused before it writes there removes that `.cotal` again.\nWhen the home holds a stack an earlier release started there, the folder starts its own stack beside\nit. The home's stack keeps running and keeps its records, and commands in the folder reach the\nfolder's own stack from then on. In such a folder,\n`up -f` and `up --restore` refuse `CONFLICT` before anything is written: prepare the folder with\n`space create <space> --root .`, then run `up` again from it. In the home folder\nitself, whose `.cotal` holds this machine's own records, `up` refuses `CONFLICT` and names a project\nfolder, as `space create` does there. In a folder that is, or is in, a control directory, `up`\nrefuses `CONFLICT` before anything is started or written and names the project folder to run it from. A home that already holds a stack an earlier release started\nthere keeps starting it in place, and `up --root <dir>` from the home starts that prepared project.\nWhen `--root` names another prepared project, run bare\n`cotal down` from that project folder: bare down\nstops the folder's stack, and `down --space` is not a remote replacement for that scope. With no\n`--space`, `up` runs the folder's space: the one its auth account names, else the one its stack or\nrunning mesh records, else the one prepared there. A folder whose records name no space is a new\nproject, and `up` names its space after the folder, the name `cotal up --space <folder name>` would\ngive it. A folder name that is not a valid space name, such as one with a space in it, refuses\n`USAGE_INVALID`, exit 1. A name another folder on this machine registered or prepared, running or\nstopped, refuses `CONFLICT`, exit 2, and is not taken over. Both refusals write nothing and name\n`cotal up --space <name>`. A space named after its folder keeps its registration after `cotal down`,\nso the name stays the folder's while it is stopped. `cotal up` there starts it again on the same\nlauncher, and `cotal space forget <name>` frees the name. A second `up` in the same folder\nruns the same space again. A folder whose stack and registrations name more than one space refuses\nwith `CONFLICT` and asks for `--space`. With no\n`--server`, a fresh local stack selects a free port if the default address is occupied or\nreserved by another registered space, including one whose broker is stopped. It saves the chosen\naddress. A later `cotal up` of\nthe same space from the same folder starts from the URL that mesh recorded; an explicit `--server`\nstays fail-loud on collision. An address that a space registered for another folder keeps while it\nis stopped refuses `CONFLICT`, exit 2, naming that space and folder, and nothing starts, unless this\n`up` takes that space's name over. `up -f` reads its address from the manifest's `broker.servers`\nand is refused there by the same rule, with the field to point at another address in place of the\n`--server` to pass. Each refusal also names `cotal space forget <space>`, which frees that address\nwith the name. A second stack started on a held address answers for both registrations, which\nleaves the other space's `down` and its reads reaching this one. When the named space runs from another folder on this machine,\n`up --space` refuses `CONFLICT`, exit 2, naming that folder, with or without `--server`, `--json` or\n`--plain`, and writes nothing in the folder it ran from. A bare `up` whose folder names a space that\nruns from another folder refuses `CONFLICT` as well. Its Next names\n`cotal up --space <name>` to start a separate space in this folder, and the `cotal down` that stops\nthe other one in its own folder. A background boot also brings up the control plane (delivery daemon in auth\nmode, then the manager). `--no-manager` is the broker-only mode: it boots\nthe broker (and the delivery daemon in auth mode) and starts no manager, so there is no manager\npidfile to leave stale. A refresh under the flag of a mesh whose manager is live refuses rather\nthan keeping or stopping it. Stop a controller stack's manager first with\n`cotal service stop stack-manager --space <s> --yes`. A legacy stack has no command that stops only\nits manager: `cotal down` stops the whole stack, then `cotal up --no-manager --space <s>` starts it\nbroker-only. For a split topology with a manager, wait for `.cotal/manager.<spaceKey>.log` to contain `\u2713 manager up`, then stop that manager the same way on that\nhost and run [`manager start`](#manager) against the remote broker; see\n[Run a mesh](run-a-mesh.md). `cotal up --detach` prints `\u2713 running in the background:` with\n`manager` listed (pidfile liveness, not a teardown boundary); with `--no-manager` the line lists\nonly what actually started. Its receipt begins with `Mesh: <space>` and ends with a Next that names\n`cotal spawn` when a manager runs, and the stop. A start refused for its space, such as one at\nanother address of a space that runs from this project or one whose name another folder holds,\nprints no receipt: its refusal names the space under its own `Mesh:` heading on stderr. With no `--space`, in a new project, it also says\nthat the space is named after the folder. For a stack an earlier release started in\nthe machine home, the line names `cotal down --preserve-state`,\nbecause bare `down` refuses the home stack. With `--no-manager` it names `cotal up` first, because\n`--preserve-state` needs a manager. On POSIX, a foreground broker has its own session and process group.\nTerminal Ctrl-C reaches the CLI owner, which stops the manager through the same stop as bare\n`cotal down` (see [`down`](#down)), then the rest of the stack, and reports managed agents under\nthe same rule. When the manager stop is refused, Ctrl-C prints the refusal with the reap route and\nleaves the stack running, including the manager, broker, delivery and any auth service. A second\ninterrupt during teardown does not start another stop. Windows keeps its existing console signal\nbehavior. The `-f` form is a\n[manifest deploy](#manifest-deploys).\n\nAn `up` that starts or refreshes a space on this machine writes the `default` persona into\n`<root>/.cotal/agents` when the root has none, so a bare `cotal spawn` has a persona to launch. A\nspace prepared or started before it had one gets it on its next `up`, and a persona you edited is\nleft as it is. A controller stack lists the write under `result.written` and in its report, and the\nretained launcher prints the path it wrote. If the write fails, the stack stays up and the failure\nis a warning. A controller recovery or restore leaves the persona to the next `up`.\n\nA space name belongs to one registered mesh. `up` refuses a name that another live mesh holds. It\ntakes over a name whose broker is not running, and when that registration belonged to another root\nit prints that root and its broker address. It refuses instead when that root holds a restore or a\npreserved cut in its maintenance journal: finish that attempt from its root, or start this stack\nunder another `--space`. It also keeps the name, and names that root, when the root or its journal\ncannot be read, for example when access to a parent folder is denied, or when the recorded root is\nnot an absolute path. A recorded root matches this one only by the folder it resolves to on disk, so\na path such as `<this root>/link/..` whose link leads through an unreadable folder keeps its name\nlike any root that cannot be read. A root counts as gone only when it no longer exists or is not a\ndirectory. A readable root with no maintenance journal also gives up its name. Each of these\nrefusals ends `refused` with exit 2, on a controller stack and on the retained launcher, and shows\nits reason and the next step, such as `cotal meshes rm <space>` for a root that is not an absolute\npath. A bare `up` in a copy or a moved folder, which holds that space's records, leaves the name\nwith the folder it is registered for: it refuses `CONFLICT` and names that folder, as the stack\njournal section below describes.\n\nA foreground `up` holds the root's maintenance lock only while it starts the stack. A bare\n`cotal down` from another terminal can then stop the stack; the foreground `up` sees the completed\nstop or preserve, prints `Foreground stack released` and exits 0. On a controller stack, Ctrl-C or\n`SIGTERM` in that terminal runs the same guarded stop as bare `cotal down` and exits 130 or 143. A\nsignal while the stack is still starting lets the start settle, then runs that stop. When the start\nfails, the stop still runs before the start failure is printed. A hangup, such as a closed terminal,\nruns no stop: the command exits 129 and the stack keeps running until `cotal down` stops it. While\na foreground `up` holds the stack, it records its process in `.cotal/stack-foreground.hold`, and a\nsecond foreground `up` on that stack refuses `CONFLICT`, exit 2, names that process, and starts\nnothing. A foreground `up` on a stack that is already running without such a holder, because a\nbackground `up` started it or the holding terminal closed, takes over its lifetime. Before it\nblocks it says that the stack is already running and that Ctrl-C or `SIGTERM` there stops the\nwhole stack. A\nsystemd unit that runs `up --foreground` should set `KillMode=mixed`, so the stop signal reaches\nonly `cotal up`, which runs the guarded stack stop, and a `TimeoutStopSec` long enough for that stop.\nThe default `control-group` mode signals every member at the same time, and a manager signalled\ndirectly stops on its own, without the request-bound stop's receipts.\n\nA detached `up` interrupted by SIGINT, SIGTERM or SIGHUP stops where it is and exits 130, 143 or\n129. A member it was launching, with the launch recorded and its child not yet recorded, is recorded\non the way out: `failed` when no child was spawned yet, or with the child's process when one was, so\nthe next `up` or `down` settles it from evidence. Members it already started keep running, and it\nnames them and the next step: `cotal up`\nfinishes the start, and `cotal down --yes` stops them. When only its broker started and the space\nwas not registered yet, it names that broker's pid, `cotal down --yes` and then `cotal up`. An\ninterrupted `up --no-manager` names `cotal up --no-manager` in each of these, because bare `up` adds\nthe manager, and the `cotal down --yes` that settles such an `up` names it as its Next. An `up`\ninterrupted while it writes a new space's trust material leaves the space new, so the next `up`\nwrites all of it again. A bare `down` interrupted the same way exits with the same codes and names\nthe members still running; `cotal down --yes` finishes the stop. When it had signalled a member that\nhad not yet exited, it also names that stop's request: `cotal down --yes` stops the member again\nwhile it runs, and once it has exited, `cotal down --abandon-stop <request-id> --yes` finishes the\nstop. A bare `down` on the retained launcher, whose stop lines name each process and its pid, stops\none member at a time. A signal that lands while a member's stop is in flight lets that stop finish\nand print its line first. The `down` then exits with the same codes and names the members it\nstopped and those still running, and `cotal down --yes` finishes the stop. A second signal ends it\nat once and names the member whose stop is not confirmed, which `cotal down --yes` stops again\nwhile it runs. Any command interrupted while the CLI is still loading, before the command begins, exits with\nthe same codes and says it did nothing. `up` keeps that notice until its own takes over, so no moment\nof a detached start exits without one.\n\n`up` runs the `nats-server` on PATH when one answers `--version`, and the bundled one otherwise. A\nbroker start refuses a `nats-server` file that another local account could replace, or that sits in\na folder another account could write, as the folder of a Homebrew install on macOS is. So when the\none on PATH is like that and the bundled one is not, `up` runs the bundled one and never runs the\none on PATH. The `Broker executable` line of `status` names the one it passed over, and `--json`\ncarries it as `machine.natsServer.skipped` with the reason. A file that fails this rule is never\nrun, not even for its version. When the bundled one is missing or fails the rule too, the one on PATH\nstays the choice: `status` shows why on that line, and `up` and `setup` refuse `PERMISSION_DENIED`\n(exit 2) before anything runs, naming each folder and the `chmod` that would fix it. A bundled one\nthat fails the rule, with none on PATH, is refused the same way. One\non PATH must report version 2.12 or newer. An older one, or one that reports no version, refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2) before the broker starts, naming its path and version; upgrade it\nor take it off PATH. When `up` starts the broker, its receipt names the file, where it was found and\nits version on the `Broker executable` line, and `--json` carries them as `result.brokerExecutable`.\n`status` shows the same under This machine, and `setup` refuses the old one the same way. When the\n`nats-server` a detached start on the retained launcher ran exits before it answers, `up` fails\n`OPERATION_FAILED`, exit 5, naming that file, its exit code or signal, and its log `.cotal/nats.log`.\nThe next step is to read that log, then fix the file or take it off PATH, and run `cotal up` again. A\nbroker still running when the wait ends leaves the outcome unconfirmed, `OPERATION_UNKNOWN`, exit 3.\nA foreground `up` whose broker exits on its own stops its members and exits 5, or 0 when the broker\nexited 0, as it does after an external `down`. Its line names the exit code or signal, and says the\nmesh stays recorded only when the registry holds that broker's record. A broker that exits before\n`up` recorded the mesh leaves it unrecorded, and the line says so.\n\nThe generated broker configuration is owner-private (`0600`) in open and authenticated modes,\neven with a group-writable umask. `up` creates `.cotal`, the broker store and the persona catalog\n`.cotal/agents` owner-private (`0700`) too, as do `space create` and `setup`. An existing directory\nkeeps its mode. A `.cotal`, or a root without one, that this account cannot write refuses with\n`PERMISSION_DENIED` (exit 2), naming it and `chmod u+w`, before anything is started or written. A\nbroker store this account cannot write refuses the same way, naming the store's `jetstream` folder,\nor the store itself before a broker has made one or when this account cannot open it, and\n`chmod -R u+w`. If that unwritable folder also lacks read or search access, the step names\n`chmod -R u+rwX` instead, since `u+w` alone leaves it closed. A writable store root without read\naccess refuses before startup too, naming that root and `chmod -R u+rwX`; a writable `jetstream`\nfolder without read access does not trigger this additional root check. The step quotes the path when a shell\nwould split it or read a character in it as syntax, such as a space or a backtick. Without that check the broker\nexited and named the cause only in its log. `status` names the same step for a stopped space whose\nstore `up` would refuse. A service record folder of the space, under `services/` in the Cotal home,\nthat this account cannot write refuses `up` the same way, naming the folder and `chmod u+w`, before\nany member starts. The broker log `.cotal/nats.log` and the auth service,\ndelivery and manager logs are created owner-private as well, and the process records `up` writes, such as\n`.cotal/nats.pid` and its identity pin, are created `0600` under a fresh name that is never opened\nthrough a symlink, then renamed into place. The pid record and identity pin of a native service\ninstance are published the same way. A file Cotal writes in place under `~/.cotal` or a\nproject's `.cotal`, such as a generated configuration, a credential, a process marker, a persona,\na seat record or one of those logs, is never written through a symlink.\n\nEvery command that takes `--store-dir` refuses an empty value `USAGE_INVALID` (exit 1) before it\nruns, rather than reading it as the default store. A value that starts with `~` refuses the same\nway: no shell expanded it, so it would name a folder called `~`, and the refusal names the absolute\npath to pass instead.\n\nAn explicit `--store-dir` on `up`, `up --restore` or `backup restore` must name a directory or a\npath to create in one. A regular file there refuses `USAGE_INVALID` (exit 1) and names it. The\nexisting part of the path follows the [`space create`](#selecting-a-space) rule for other local\nwriters, so a group- or world-writable directory in it refuses `PERMISSION_DENIED` before anything\nis written. A path still to create needs its nearest existing directory to be writable by this\naccount, or it refuses `PERMISSION_DENIED` (exit 2) before anything is written. On `up`, a\nnon-empty directory is accepted, since it may hold an existing store. When it holds anything besides a\nstore's `jetstream` directory, its `.cotal-store-id` marker and the `ocsp` folder nats-server makes\nthere when it reloads its configuration, `up` warns and names those entries,\nbecause `clean store`, `reset store`, `backup cleanup-fallback` and a restore into that directory\nrefuse it while they are there. A restore creates its store\ndirectory itself, so `up --restore` and `backup restore` refuse a path that already exists, empty or\nnot, other than the preserved store, with `USAGE_INVALID` (exit 1) and restore nothing. A control\ndirectory refuses `USAGE_INVALID` too, before anything is written: a root's `.cotal` or a path in it,\nother than that root's own `.cotal/nats`, and the machine home's `.cotal`. A store there would mix\nwith credentials and records, or with another root's store, and `reset store` could not remove it.\n\nA store folder belongs to one space. nats-server takes no lock on its store folder, so a broker of\nanother space starts on it, and a reset or clean of the folder would remove that space's data too.\nA `--store-dir` on `up`, with or without `-f`, refuses `CONFLICT` (exit 2) before anything starts\nwhen another space uses the folder: its registration, its prepared stack journal or the broker\nconfiguration its root last rendered names the folder, or the folder's `jetstream` tree holds its\n`CHAT_`, `DM_` or `TASK_` streams, whether or not its broker runs. The refusal names each such\nspace, the root a record names for it and its streams. A store an earlier release let two spaces\nshare still starts from its stack's recorded store, from a legacy root's last served store, or as\nthe default store, and `up` warns `STORE_SHARED`, naming the other space; `--json` carries the\nwarning. `clean store`, `clean all`, `reset store` and `reset space` refuse such a folder, as\n[clean](#clean) and [reset](#reset) describe. A controller stack whose recorded store folder is\nmissing, as after it was removed by hand, starts its broker on a new empty store there and warns\n`STORE_MISSING`: the space's history starts empty. A legacy root whose last broker served a missing\nfolder warns the same. When a controller stack's broker logged damage in a stored file since it\nstarted, as `status` shows in its `Store` row, `up` warns `STORE_DAMAGE_LOGGED` with the broker's\nwords. It still starts the stack and repairs nothing.\n\nA file you own is set to `0600` through the open file. A symlink, a file another user owns or a name\nthat is not a regular file is refused and left unchanged. A private directory is set to `0700`\nthrough its path, which follows a symlink; this step does not refuse a symlinked `~/.cotal` or\n`.cotal`.\nOn Windows the owner and symlink checks do not\napply, because POSIX modes are not that platform's protection model; the file is opened with mode\n`0600` and private secrets are locked down by their access list. Local records under `~/.cotal`\nand a project's `.cotal`, such as the stack journal, service records and deployment attempts, use\nthe same model there. A store directory Cotal creates gets the private access list, and a record is\nwritten like a private secret. Reading a record or checking its path does not check an owner, a mode\nor an access list, and does not refuse a link: a symlink or junction is followed. Size limits, the\nrecord schema, the regular-file check and the refusal of a link inside a record store's own\ndirectories still apply. When a service path check refuses a path, the refusal names the option\nand each path that failed once, with its reason and one `chmod` that fixes every writable one. A\nwhole-stack failure shows that sentence as its native detail. On POSIX, a credential file a command\nis given, such as a `<private-file>` argument, and a saved private record must be a regular file this\naccount owns with no group or other access. One that is not, or that this account cannot open,\nrefuses `PERMISSION_DENIED` (exit 2) and names the file. When only its owner or mode is wrong, the\nNext is the copy or `chmod 600` that fixes it. A credential file that does not exist refuses\n`TARGET_NOT_FOUND`, and one whose path cannot be checked refuses `DEPENDENCY_UNAVAILABLE`; both\nname the file.\n\nA fresh prepared static-auth, open or plain-loopback user-auth root starts through the service\ncontroller. It records its mode, definitions and full instance IDs in the private\n`.cotal/stack-controller.json` journal. The root keeps the mode its first `up` recorded: a later `--open` or\n`--user-auth` that names another mode refuses `CONFLICT`, exit 2, whether the stack runs or not.\n`reset store`, `reset space`, `clean all` and `space forget` all keep the journal, so none of them\nchanges the mode. To run another mode, run `cotal space create <space>` in another folder, then\n`cotal up --open` or `cotal up --user-auth` there.\nStatic-auth startup prepares trust and configuration, starts\nthe broker, initializes native streams, then starts delivery and the optional manager. Open startup\nprepares an unauthenticated broker and streams, then the optional manager; it creates no delivery\nservice or credential. Each member reports its own readiness; the result does not prove agent or\ntask readiness. A prepared root never starts the legacy launcher: an option the controller stack has\nno route for, such as `--tls-cert` or a `tls://` `--server`, refuses `UNSUPPORTED`, exit 2, before anything starts, as it\ndoes on a root whose stack the controller already owns. `--rotate-sys` on a first start refuses the\nsame way, because a first start mints the space's `$SYS` creds. The refusal names each option, and\nnames `cotal up -f <cotal.yaml>` when the manifest route takes them all. A root with no prepared\nrecord and no stack journal starts on the legacy launcher whatever `up` names, one whose space its\n`up` registered included, so `up` never writes a stack journal there. A prepared user-auth root that\nthe legacy launcher started and that has no stack journal keeps that launcher for such an option.\n`--json` returns one\nstack receipt and `--plain` returns a line-oriented report. Partial startup keeps confirmed members\nand reports them; unknown operations are retained and never retried automatically. A failure while\ninitializing streams or recording the broker registration is attributed to the broker, not the next\nunstarted member. The failure target names that stage. Typed JetStream API errors retain their\nnumeric code and status without copying the server's description or changing the unknown outcome.\nThe journal records the absolute root it was written in, and the stack runs only from that folder.\nIn a copy of a controller project, or after the folder is moved, commands that read the journal\nrefuse `TARGET_UNAVAILABLE`, exit 2, and name the recorded folder, with another space selected too,\nwhile `up` and `down` there\nrefuse `CONFLICT`: a copy is told to use the stack from the original, and a moved folder to move\nback there first. A folder is moved when its recorded folder no longer holds the journal. A second Cotal home that reaches the original over a shared filesystem is\ntold the same, and it can adopt the copy only after the original's stack is stopped there and that\nfolder no longer holds its journal. When this machine records neither the space nor an instance of\nits stack, as on another machine, moving back would not start it, so a moved folder is told only to\nadopt the journal with `space create --adopt`. `status`, `send` and `history` there name the same\nnext step. `status` there reports under the journal's space, as the reads there refuse under it, and\nsays the folder holds a copy of that space or its records moved from the recorded folder. Bare\n`cotal` says the same, with that step. `clean store` and `clean all` there refuse `TARGET_UNAVAILABLE`, exit 2, naming the\nrecorded folder, and remove nothing, so `status` there does not offer `clean store`.\nCommands that reach a static-auth space from a moved folder, such as `send`, `history`, `ps` and\n`service stop`, refuse the same way. `status` there says the folder was moved and names the way back\nas its next step. The broker names a store inside the project folder relative to that folder and\nruns from it, so a broker still running when its folder is renamed keeps writing into the renamed\nfolder and does not create the old path again. A broker an earlier release\nstarted does, so when something is found at the recorded path, the refusal says to move it aside\nbefore moving the folder back. When the space is prepared or registered here and\n`space create --adopt` could take the journal over instead, the refusal names that command too. A\nfolder whose `.cotal` is a link to another project's `.cotal` is told that it is a link and to use\nthe stack from that project. `up` and `down` there refuse `CONFLICT`, exit 2, before anything reads\nthrough the link, naming the link and where it leads. When it leads to another project's `.cotal`,\nthey name that project; otherwise the next step is to remove the link and prepare the folder with\n`space create`. When the `.cotal` it leads to holds this folder's own records, because its stack\njournal was recorded here, as after moving `.cotal` away and linking it back, they name the way back\ninstead: `rm` the link, which removes only the link, then `mv` that `.cotal` back in its place, which\nkeeps the history. Nothing moves it for you. In the folder the records were moved to, commands say\nthey were moved there from this folder and name the same step. A `.cotal` link that leads nowhere makes them refuse `TARGET_NOT_FOUND`, exit 2,\nnaming where it leads. `status` there does not read the folder's boot and additive attempt\ninventories through the link. It warns `COTAL_DIR_LINKED`, or `COTAL_DIR_DANGLING` for a link that\nleads nowhere, with the same next step, and exits 4. For a linked folder, its selected space names\nthe same refusal, and Next gives that step in place of starting the space with `cotal up`. `space create` in a copy whose records name\nanother root says so and names that root, and in a linked folder it names the link.\n\nA project the legacy launcher started writes no journal, so a copy of one, the folder a later `up`\ntook its name over from, and one moved away from its recorded path are recognized from the\nregistration instead. While this machine registers that space for another folder, reads there refuse\n`TARGET_UNAVAILABLE`, exit 2, and name that folder, with another space selected too: the next step is to use the stack from it, or,\nwhen it no longer exists, to move this folder back there first. A bare `up` or `down` there refuses\n`CONFLICT`, exit 2, and names the same step, because the folder holds the space's records and no\nstack of its own, so a bare `up` does not take the name over from that folder. When that folder no\nlonger exists and the space's broker does not answer, the `up` refusal also names\n`cotal up --space <space>`, which takes the name over to run the stack from this folder.\n`status` there reports under that space and says the folder holds its records, naming the folder it\nis registered for, or that the folder was moved from there. A copy and the folder a later `up` took\nthe name over from hold the same records, so both read that way. Bare `cotal` says the same, with\nthe step the reads name. `clean store` and `clean all` there refuse `TARGET_UNAVAILABLE`, exit 2, as the reads do, and\nremove nothing, so `status` there does not offer `clean store`. A legacy root whose space this machine registers for no folder keeps its own\n`cotal up`, which starts it again at the address its last start configured.\n\nA journal that is not valid JSON, as an interrupted write or a full disk can leave it, makes `up`,\n`down`, `clean`, `reset` and `space create` refuse `SCHEMA_INVALID`, exit 1, naming its path, and\n`status` warns about it and gives the same step under Next, in place of `cotal up` and\n`cotal clean store`. When no member of the stack runs, the Next says to remove the file, and the\nnext `cotal up` starts the stack again from its saved definitions. When no space is recorded at the\nroot, as after `space forget`, there are no saved definitions, and the Next says to remove the file\nand run `cotal up`. When members still run, it names the `cotal service stop` for each one first,\nmanager first and broker last.\n\nA service instance record under `services/` in the Cotal home that is not valid JSON, cut short the\nsame way, is left as it is. `up`, `down`, `service` and `reset store` refuse it `SCHEMA_INVALID`,\nexit 1. `service` names its path and the `mv` that moves it aside, and so do the others for a stack\nthat has run. Once it is moved, Cotal no longer tracks the process that record names, so stop that\nprocess yourself if it still runs. For a stopped space, `status` warns\n`SERVICE_INSTANCES_UNAVAILABLE` and gives the same step under Next, in place of `cotal up`. The\ndefinition of a service you added that is not valid JSON is left as it is too. `service` and its\nverbs, such as `service show`, `service add` and `service stop`, refuse it `SCHEMA_INVALID`, exit 1,\nnaming its path, the `mv` that moves it aside, and `cotal service add <name> --space <space> --file <declaration>`,\nwhich adds the service again. An instance of it that still runs can then be stopped by its full\ninstance ID. Any other saved record that is not valid JSON, such as a native install, uninstall or\nunit-action attempt, a supervisor receipt, a stack member's definition or an endpoint reference, is\nrefused `SCHEMA_INVALID` with no step: an attempt or receipt can be what holds a native step back,\nso nothing says to move it. Other commands that read the definition of a service you added keep\nthat plain refusal, and `status` shows the saved definitions as unavailable.\n\nA service record folder that this account cannot read, such as its `instances` folder under\n`services/` in the Cotal home, is left unchanged. When `service` or `service logs` cannot list it,\nthey refuse `PERMISSION_DENIED`, exit 2, naming the folder and a quoted `chmod u+rwx <folder>`\nstep. A command that reads one record in it, such as `service show`, names the folder and\n`chmod u+rx <folder>`, which makes it readable only. A record file it cannot read is named with\n`chmod u+r <file>`. Each path is quoted when a shell would split it. A command that then writes a record there or\nholds one while it acts, such as `service add` or `service start`, refuses `PERMISSION_DENIED`,\nexit 2, naming the folder and `chmod u+w <folder>`, and nothing is written. No permissions change\nautomatically. `status` keeps its partial result and warns that the saved definitions or instance\nobservations could not be read, with that code.\n\nA controller stack opens three listeners. The broker serves clients at its `--server` port, and a\nwebsocket for the browser console's mesh session on a port `up` picks, which is always plaintext.\nThe manager serves its console over plain HTTP on a port it picks when it starts. All three bind the\n`--host` address, and loopback without it, so `--host 0.0.0.0` opens each of them on every interface.\nThe receipt's `Listeners` section names each one with its address, how far it reaches and whether it\nis encrypted, and says when a plaintext listener is reachable from other machines.\n`cotal service status stack-broker` and `cotal service status stack-manager` name their own while\nthey run, and `--json` carries the same list as `listeners`. The console's port is recorded nowhere,\nso it is named by its host.\n\nA journal root always keeps that controller route and its recorded mode and broker address, even\nwhen a completed stop removed its mesh registration. Bare `up` reuses that address; an explicit\nconflicting `--server` or `--open` refuses before effects. With no journal and no registration, as\nafter a damaged journal was removed, `up` starts at the address the saved `stack-broker` definition\nnames and warns `STACK_ADDRESS_RECOVERED`. With no definition either, a root that holds the space's\ntrust or a store from an earlier start refuses `USAGE_INVALID`, exit 1, and asks for `--server`.\nOnly a fresh prepared root starts at the default address. A journal whose stack never launched a\nmember pins neither, as when its first `up` was refused because another broker answers at the\naddress or another program holds it. The next `up` plans it as a first start, with that address or\nanother `--server`, and `down` reports that nothing runs there. That refusal names the address and\nany space this machine registered there. Its next step for a first start is another `--server`, or\nstopping that space or program. For a stack that ran, it is stopping that space or program and\nrunning bare `cotal up`, because the stack keeps its address. A stack whose broker does not listen on its recorded address,\nbecause a bind host recorded beside it names another address, or because no bind host was recorded\nand the address is not `127.0.0.1`, cannot be reached and pins no bind host. `up` there refuses\n`CONFLICT` before anything starts and names `cotal up --host <host>` with the address's own host,\nwhich replaces the recorded bind host once the broker is stopped. Legacy-only options refuse rather than switching launchers.\n`up -f`, recovery and existing legacy roots retain their native paths; those paths refuse the\nnew JSON/plain flags. `up -f` checks its manifest before it refuses them. A manifest that does not\nvalidate is refused `SCHEMA_INVALID`, exit 1, naming each problem, and a file it cannot read is\nrefused `USAGE_INVALID`, exit 1. A valid manifest is refused `UNSUPPORTED`, exit 2, and the remedy\nnames only the flag to omit. A legacy root is one with no stack journal that is neither prepared nor\nregistered for the controller, such as a folder an earlier release started and a normal `down` then\nunregistered, or a new project a bare `up` started. Plain `cotal up` there starts it again on its\nretained launcher, with its identity, store and history. Its `down` drops the registration that kept\nthe broker address, unless the space is named after its folder: that registration stays, and it does\nnot move the folder to the controller stack. Without a registration, or with only that kept one, and\nwith no `--server` or `--host`, it starts at the address in the broker configuration its last start\nrendered there, and says so in one line. The mode is a separate decision, because `--server` and\n`--host` name where the broker listens and not whether it authenticates: a root whose last start was\nopen starts open again whatever address this one names, and the receipt says the mode it started\nin. A mode flag still decides for itself, so `--open` or `--user-auth` starts the mode it names.\nAn open root holds no account to name its space, so the broker configuration its last start rendered\nnames it, and plain `cotal up` starts that space again. A configuration an earlier release rendered\nnames none, and a root it deployed with `up -f` starts again as the space its newest open fresh team\nsummary names, while that configuration still serves its address.\nAfter `clean all` that configuration is gone. A root deployed with `up -f` then keeps only its fresh\nteam summary, and plain `cotal up` refuses `CONFLICT` and names `cotal up -f <manifest>` when the\nsummary names another address, mode or space. `space create` in such a folder refuses `CONFLICT`\nand names that `cotal up`.\nA controller `up` checks that the space is prepared or locally started at its root before it writes\ntrust, configuration, a definition, an instance or the journal; a refusal there leaves the folder as\nit was. When a completed stop and `space forget` removed that record,\n`cotal space create <space> --root <root>` prepares the same space at its root again, and `up`\nstarts it from its journal. Schema apply is not supported. Fresh `team boot` uses the controller\nroute described below. Recovery re-entry keeps its recorded mode.\n\nA whole-stack `up` or `down` takes the root's maintenance lock before it reads the journal. While\nanother whole-stack or maintenance command holds it, the command refuses `LIFECYCLE_BLOCKED` and\nnames that command's pid. A lock whose holder is no longer running, as a command killed by\n`SIGKILL` leaves it, is taken over by the next command that needs it, which warns\n`MAINTENANCE_LOCK_TAKEN_OVER` with the pid and start time of the process that left it. Taking the lock makes `.cotal/maintenance` when the folder has none. A\ncommand that then refuses or fails, such as `up -f` with a manifest that does not validate or `up\n--json` on the retained launcher, releases the lock and removes those folders again while they are\nempty, so a fresh folder is left without a `.cotal` that later commands would read as a project. A crashed member, or an `up` or `down` killed before it recorded its\noutcome, is settled by the next bare `up` or `down` from process evidence. A member whose recorded\nprocess (pid and start time) is gone is recorded as failed, the earlier operation is recorded as\nfailed, and the command continues with an `OPERATION_SETTLED` warning. A member that is still\nrunning is kept: `up` reuses it and `down` stops it. An `up` stopped after its broker started and\nbefore it registered the space leaves a broker whose stream setup and registration only that `up`\ncould finish, and trust is never prepared again under a running broker. The next `up` refuses\n`LIFECYCLE_BLOCKED`, names the broker's pid and `cotal down --yes`, and changes nothing; after that\n`down`, `cotal up` starts the stack. A member whose process cannot be established,\nor one with a stop that was requested and never confirmed, refuses `LIFECYCLE_BLOCKED` and names\n`cotal service status <definition> --instance <id> --space <space>`. A member whose launch record\nnames no process, because the `up` that launched it was killed after it recorded the launch and\nbefore it recorded the child, also names `cotal down --abandon-launch <instance-id> --yes`. That\ncommand refuses `TARGET_NOT_FOUND` when no member has that instance, and `LIFECYCLE_BLOCKED` when\nits record names a process or a settled state; either way nothing changes. Otherwise it records the\nmember `failed` and stops the rest of the stack with a `LAUNCH_ABANDONED` warning. A process that\nlaunch started without recording it is not excluded, and that `down` does not stop it; a broker\nthat still answers at the stack's address makes the next `up` refuse `CONFLICT`. It needs `--yes`\nand refuses `--dry-run`. A manager stop whose request\nwas published and not confirmed before `down` stopped waiting keeps the manager's record `stopping`\nunder that request, also after its process exits. The next bare `up` or `down` reads that request's\nown receipts first. When the manager recorded the stop as completed and its exact process is gone,\nthe record becomes `stopped` with that receipt, the request's reservation is released once the\n`down` that took it has exited, and the command continues. When the manager's only receipt is a\nrefusal, which it writes when it started no stop, for example because that `down` had already\nexited, the request is removed, its reservation is released, and the record returns to what its\nprocess shows, so `down` stops a running manager and `up` reuses it. Otherwise it refuses\n`LIFECYCLE_BLOCKED` and names the instance and the original request. `down --dry-run` writes\nnothing, so it does not settle such a stop and refuses the same way. `service start`, `restart` and\n`stop` refuse it `LIFECYCLE_BLOCKED`, exit 2, before any readiness probe, stop request or record write.\nThe earlier stop's outcome stays unknown. When the launch's\nrequest file does not read as its own, the refusal reports that it is unknown whether a stop was\npublished. A manager record that an earlier release left `unknown` while its launch still holds a\nstop request is recorded `stopping` under that request when its process is gone. Its exit is never\nrecorded as a crash, and no `up`, `down` or `service` command relaunches it, removes its record or\nremoves the stack's registration. A stop that has neither a completed nor a refused receipt, such\nas one whose manager was killed before it read the request, is cleared only on request:\n`cotal down --abandon-stop <request-id> --yes`, with the request ID the refusal names. It refuses\n`LIFECYCLE_BLOCKED` while the manager or the `down` that sent the request still runs, and\n`TARGET_NOT_FOUND` when no member retains that request; either way nothing changes. Otherwise it\nremoves the request, the stop reservation and a `--with-agents` shutdown intent that the same stop\narmed, records the manager `failed`, and stops the rest of the stack with a `STOP_ABANDONED` warning.\nWhether that manager stopped or released its agents' seats is not confirmed, and a seat that runs\noutside the manager process may still run. It needs `--yes` and refuses `--dry-run` and\n`--with-agents`. A `down`, or a foreground `up` running its stop, that exits while it stops a member,\nbecause it was killed or its terminal closed, leaves that member `stopping`. When that member is the manager and its request was not yet\npublished, the manager was handed nothing: the next bare `up` or `down` returns the record to what\nits process shows, and the next stop releases the reservation that `down` took once its pid has\nexited. When it is the broker or the delivery daemon, whose stop is a signal to its recorded\nprocess, the refusal names the same `--abandon-stop` command once that process is gone. That\ncommand records the member `failed` and stops the rest of the stack with a `STOP_ABANDONED`\nwarning. Whether the stop caused the exit is not confirmed. Such a stop records the member `stopping`\nbefore it signals, so a process that still runs may never have been signalled: the next bare `up` or\n`down` returns its record to `running`, `down` signals it again and `up` reuses it. When `up`\nredefines a member because its launch options changed, the journal records the\nreplacement's instance and launch request before the old instance is retired, and the new\ndefinition's revision before the replacement launches. After an `up` cut off in between, the next\nbare `up` or `down` adopts the replacement only when its record carries that launch request and\nrevision for the same operation; `down` stops it and `up` reuses it. Any other instance record\nrefuses `CONFLICT`, and nothing is adopted. A stack definition saved for another folder under the\nsame space name refuses `CONFLICT`, naming that folder and every stack definition saved for it;\n`space forget` removes them once that folder's stack is stopped. A preserve or resume that\nnever recorded its outcome stays with its own maintenance attempt. A member that exits before\nreadiness is recorded as failed with its exit code, and the refusal quotes the last line it logged\nduring that launch and names its log with\n`cotal service logs <definition> --instance <id> --space <space>`. When the broker this `up` started cannot\ncreate the space's streams, `up` stops that broker again, so the next `up` starts a fresh one. It\nfails `OPERATION_FAILED`, exit 5, and names the cause when the broker's log shows it ran out of\nopen files, with raising `ulimit -n` as its next step. Otherwise its next step names that log. When\nanother member, such as the manager, exits before readiness and the broker's log shows it ran out of\nopen files, `up` names that log and that cause. A running broker keeps the open-file limit it started\nwith, so its next step is `cotal down --yes`, then a higher `ulimit -n`, then `cotal up`. When such a\nmember exits before readiness while the space's store holds more than its recorded\n`--max-file-store` cap, `up` names the store and the cap, because the broker refuses every write\nuntil there is room, the delivery daemon's lease included. Its next step makes room with\n`cotal history clear --yes`, which purges the space's channel history, then runs `cotal up` again.\nTo keep that history, it names `cotal down --yes` and `cotal up --max-file-store <bytes>` with a\nlarger cap, after `kill -TERM <pid>` for a manager that still runs, as a full store refuses the\nmanager's stop.\n\nWhen the stack's broker process is stopped by a signal, such as SIGSTOP, it accepts connections and\nnever answers, so `up` refuses `TARGET_UNAVAILABLE` before it writes anything or records the broker's\nreadiness again. It names that pid and gives `kill -CONT <pid>`, as `down` does.\n\nBefore it launches a broker, the controller binds the broker address once, and so does the legacy\nlauncher that an unprepared folder or `up -f` starts. When another process holds it, `up` refuses\n`CONFLICT`, names the address and the bind error, and launches nothing. A\nfirst start names another `--server` as its next step, and a stack that ran keeps its recorded\naddress. On Linux, when nothing listens there and a connection's local end holds the port, the\nrefusal says so in place of another process. For a closed connection in TIME-WAIT, its next step is\nto wait up to a minute and run `up` again. When no member started, the refusal says the stack\nstarted no member. A text `up` prints one `Starting <definition>` line\non stderr before each member it starts, with the readiness timeout it waits for. A started stack's\nNext offers `cotal spawn --space <space>`, or `cotal up` to add the manager after `--no-manager`,\nand `cotal down`. The spawn carries `--space` because `up` keeps an existing selection, so a bare\n`cotal spawn` can target another space. Run from another folder, as `up --root <root>` is, the Next\nsays to run `cotal down` from the root, because `down` stops only the stack of the folder it runs in,\nand it offers `cotal up --root <root>` to add the manager. When another space stays selected, the\nreceipt says so and names `cotal use <space>`, as a legacy `up` does. When no registered or prepared space is\nselected, `up` selects the space it started and says so.\nThe receipt also names each service saved for the space that `up` does not start, such as one\n`cotal service add` saved, and `--json` carries their names as `result.savedServices`. Saved\nservices belong to the space name, so a space forgotten under that name leaves its services to the\nnext one. The receipt names those it left from another folder, which do not start here, with\n`cotal service remove <name> --space <space> --yes`, and `--json` carries them as\n`result.inheritedServices`. `cotal service show <name>` shows what one runs.\n\nOn a controller root, `down --preserve-state` refuses `UNSUPPORTED` (exit 2) in this release, in\nevery stack mode. It refuses while validating its arguments, before the confirmation and before any\nstop, write or manager request. Plain `down` stops the stack and keeps its store, channels and\nhistory for the next `up`. On a full store `down` refuses first, and [down](#down) says how to stop\nthe stack there with or without purging that history. Preserving a controller-owned stack returns\nin a later release.\n`backup create`, `backup restore` and `up --restore` need a preserved cut, so they refuse the same\nway on a controller root (see [Backups](#backups)).\n\nUser-auth controller startup requires a plain loopback IP and an auth provider with read-only\nserver preflight. A first enable checks the required IdP and its JWKS before creating maintenance\nstate, a stack journal, trust or credentials. Missing or invalid inputs are usage refusals; an\nunavailable JWKS is a dependency refusal. The check follows no redirect: a `3xx` answer at\n`<idp>/jwks` is refused as an unavailable JWKS, and the place it points to gets no request.\nMalformed IdP inputs are not echoed. An explicit `--idp`\nis also compared with the recorded pin before maintenance state changes, even while the broker is\nrunning. A different IdP refuses without refreshing members or rewriting their journal. The controller\nchecks again under its lock, and the provider repeats its authoritative checks during preparation,\nbefore any member launches. Later races or failures retain their records and are not rolled back.\nIt starts the broker, initializes streams, records provisional public trust metadata, then starts\nthe `stack-auth` service. Only that exact process's provider readiness supplies the final endpoints.\nDelivery uses the same hosting-root authority as native startup. No user caller is replaced by a\nstatic or borrowed caller.\n\nThe manager requires an existing signed-in, granted CLI login. First enable can therefore start\nthe other members and return partial `OPERATION_PARTIAL` with native cause `AUTH_REQUIRED`, while\nleaving the manager unattempted. Sign in with `cotal login`, obtain the required grant, then run\nbare `cotal up` to continue that known partial operation. `cotal manager start` refuses the same\nway, with `AUTH_REQUIRED` and exit 2, before it saves or launches anything. Stopping the manager\nneeds no sign-in in the folder that hosts the space: `cotal down` and `cotal manager stop` read its\nstop impact from the manager's own local receipt. `--no-manager` omits that member and its\nlogin requirement. Unknown outcomes are never continued or replayed. Public exchange faces and\nTLS-required or remote user-auth brokers still require the retained native route.\n\nThe journal reads earlier three-member auth/open records with an unattempted auth-service row.\nIts next write records all four roles. It neither adopts legacy processes nor converts a mode.\n\nA repair `up` on a mesh whose broker died reopens the store its record names, and refuses a\ndifferent `--store-dir` rather than silently opening a second store. `down` drops that record, so a\nlater start without `--store-dir` reads the store its last broker served from the broker config `up`\nwrote. A space named after its folder keeps the record, so its next start reopens the store the record\nnames. When that store is not the default `.cotal/nats` and still holds JetStream data while the\ndefault store holds none, such as after `backup restore --store-dir`, the start refuses `CONFLICT`\n(exit 2) and names it. Pass `--store-dir <dir>` to reopen it, or remove it with\n`cotal clean store --force`. While that store holds data, a\n`--store-dir` that does not exist is taken for a mistyped path, and the start refuses `CONFLICT`\n(exit 2) and names both. Create the directory first to start a new empty store there.\n\nWhen the broker or manager of a static-auth stack that `up` started in a folder exits, its pidfile\nstays. `cotal status` in that folder shows the row as `stale pidfile` and puts `cotal up` first under\nNext, which starts it again on the recorded address and store. `up` does not record `--runtime`, so\npass it again when the manager ran on one.\nWhile such a stopped folder's broker store remains, Next also names the step for a stale store:\n`cotal reset store <space>` when that reset reaches this folder's store, and `cotal clean store --force`\nwhen no registration lets a reset reach it, as after `down` in a legacy root started under another name.\n\nThe generated `.cotal/auth/server.conf` is written on a real broker boot and is not an\noperator-owned config. `--host` changes that file only when nats is actually started. A unit\nrestart that leaves an answering listener in place is a refresh, not a rebind. When a symlink or a\nfolder stands in its place, `up` leaves it as it is and refuses `PERMISSION_DENIED` (exit 2) before\nit starts anything, naming the path and the `mv` that moves it aside; the next `up` writes the file\nagain. A trust record under `.cotal/auth` that this account cannot read, such as one at mode 000, is\nrefused the same way, and its next step makes it readable again.\n\nOn an existing mesh, `cotal up` reconciles the presence and lease bucket TTLs. It writes a reserved\ncanary and waits for the bucket to expire it before reporting success. If the broker accepts the\nstream update but the backing store does not persist or enforce it, `up` exits nonzero with a TTL\npersistence error instead of trusting the value returned by stream info. A refresh that restores a\nmissing manager waits for that process to come up, then says so with its pid (`\u2713 restored in the\nbackground: manager (pid N)`). A manager that `up` starts and that exits at boot instead is reported\nas `! control plane degraded:` with the path of its log, and `up` then exits nonzero, whether it was a\nrefresh, a first `--detach` boot or `up -f`. The degraded message preserves the failure's own\nrecovery instructions. Spawn and retirement require a functioning control plane; follow the\nreported next step before retrying. The same happens when another\n`cotal supervise` takes over the manager pidfile before the started process comes up. A legacy\nrefresh that finds everything already running reports it like any other start: the `Mesh: <space>`\nheading, the `\u2713 mesh \"<space>\" already running` line, and a Next that names the launch and the stop,\nwith `cotal spawn` when a manager runs. A controller refresh prints its members under `Controller\nstack already running`, and its JSON result is a no-op unless it wrote the default persona. Neither\nlists a role the stack's mode does not use, such as auth on a static-auth stack. A first boot starts\nits manager without the restore line.\n\nA manager or delivery daemon that is killed, or a daemon that exits because its broker went away,\ncannot release its lease. The row stays until the bucket TTL removes it: 10s for the manager and\n30s for delivery. A repair `up` inside that window waits out the row when it can show the holder is\ngone, then starts the replacement, so one run restores the control plane. For the manager, the row\nmust name this workspace root and a pid that is not running on this host; the new manager logs\n`which is not running on this host; waiting` and the seconds left. For delivery, no daemon may be\nrecorded running in this folder and the row must be held by the credential this folder gave its\nprevious daemon; `up` prints `shard 0 is still leased to this folder's previous delivery daemon`.\nThis holds on a controller stack too, both for an `up` right after the crash and for one after a\n`down` that recorded the members as already stopped, because that `down` keeps the credential.\nThe wait never writes the lease. A row that is written again while `up` waits has a live holder, so\nthe start is refused and `up` exits nonzero. A row held by any other process is refused at once,\nas before.\n\nA delivery daemon that `up` launched and that exits before it is ready fails the start. The error\nquotes the failure the daemon logged during this launch and names its log,\n`.cotal/delivery.<spaceKey>.log`. It describes a lease held elsewhere only when the daemon logged\nno failure. A daemon start that fails on a root whose maintenance journal holds a restore or a\npreserved cut never suggests `clean store`, because that store holds the restored or preserved\ndata. Check the root with `cotal status` first.\n\nA full restore recreates the delivery daemon's durables from their checkpoints, at their recorded\nstart sequence. The daemon binds such a restored durable when it otherwise matches its own\nconfiguration, so a restored store starts. A durable that differs in any other setting still\nrefuses.\n\n\n`--user-auth --idp <url>` starts the space's auth service alongside the broker: the NATS\nauth callout plus its capability-gated local exchange, and optionally the closed public exchange\nface configured by the three `--exchange-*` flags above. The service is torn down with `cotal down`,\nand a re-run of `cotal up` heals a dead service on a running broker. `up` waits for the service to\nfinish binding: while the daemon it launched (or found running) stays alive, the wait extends past\nthe base 15s up to 60s; a daemon that exits is refused at once with \"exited before becoming ready\",\nand one alive past 60s is refused as \"alive and still starting\" (wedged), naming the pid record and\nthe service log. `--user-auth` and `--open`\ncontradict each other and are refused loudly; a running broker cannot change auth mode\nwithout a `cotal down` first. See [identity & auth](identity-and-auth.md).\n\n`--rotate-sys` renews the two `$SYS` credentials (`membership-observer`, `connection-evictor`).\nThey carry a 30-day expiry and nothing re-signs them in place, because the trust record never\ncarries the system-account seed, so they are renewed by issuing a **new system account** under the\nsame broker operator and minting fresh creds against it. The root keeps that seed in its own secret\nstore, written at space creation and replaced here, so an auth service started with\n`--service-endpoints` can mint the short-lived observer credentials service caller issuance needs. A plain re-`up` does **not** do this: it reuses the\nexisting trust record, and its `$SYS` creds along with it.\n\nThe rotation is safe to run on a real space, with one operational cost. The data account, the account\nsigning key, every agent credential minted from it, and the JetStream store are all untouched; what\ndies is the retired system account, and with it any out-of-band copy of the old `$SYS` creds, on every\nbroker that loads the rotated config. The cost is that **earlier full backups stop being restorable**\n(see below), so this is not a no-consequence operation. It needs the broker to restart on the rewritten\nconfig, so it runs as part of a boot:\n\n```bash\ncotal down\ncotal up --rotate-sys --detach # agents reconnect; nothing is re-provisioned\ncotal doctor auth # both $SYS creds healthy again, 30 days out\n```\n\nA controller stack rotates the same way. Outside a terminal its `down` needs `--yes`. The `up`\nreceipt adds a `System account rotated` section with the new generation and the creds' expiry, and\nits JSON carries them as `result.rotatedSystemAccount`, which is `null` on an `up` that did not\nrotate.\n\nA rotation is a stopped, fresh boot, and anything that is not one refuses it, all for the same reason\n(the on-disk material and the broker it runs on must never end up on different generations):\n\n- a live mesh, because the running broker would keep serving the retired account. On a controller\n stack any running member refuses it `LIFECYCLE_BLOCKED`, and the refusal names\n `cotal down --yes`;\n- an open mesh, whether that comes from `--open` or from `broker.auth: false` in a manifest, which\n has no system account at all;\n- `--restore`, because reinstating a trust root and superseding it in one command leaves no way to\n say which authority the mesh came up on;\n- an unfinished restore or resume attempt on this root, including one `cotal up` would recover on\n its own, because those paths can adopt a live listener and return without booting a broker;\n- a root that hosts more than one space, because the system account lives in the shared broker\n record and a rotation would retire every tenant's, while the root holds one `$SYS` cred pair\n pinned to one data account.\n\nTwo things to know before you run it:\n\n- **The retirement is config-load-bound.** Old `$SYS` creds are refused by any broker that loads the\n rotated config. A stale `nats-server` still running the *previous* config in memory would keep\n honouring them, so stop every broker for this root first. `--rotate-sys` refuses if this root's\n mesh is recorded as running, if anything unidentified is answering at the address it was given, or\n if the root's pid file names a live (or unreadable) process. Those are Cotal's own ownership\n records, not a scan of the process table: a `nats-server` you started by hand against this root's\n `server.conf` on some other port writes none of them and will not be seen. Do not run one.\n- **It invalidates earlier full backups.** A full artifact binds to the trust chain it was taken\n against, and that commitment covers the operator JWT and the system account. Every full backup\n taken before a rotation refuses to restore afterwards, so take a fresh `cotal backup` once the\n rotated mesh is up. `cotal up --restore` names this case when the data account still matches.\n\nThe commit is not atomic (a trust-record write plus two credential writes), so an interrupted\nrotation leaves the record ahead of the creds. That split is detected rather than silent: every\n`cotal up` on an auth mesh, and every `cotal doctor auth`, compares each `$SYS` cred's issuer against\nthe persisted record and names the retired account. `up` warns rather than refusing, because these\ncreds power the membership graph and live eviction, both of which degrade fail-soft; the mesh is not\nworth taking down over them. Re-running the rotation heals it, at the cost of one generation.\n\nWhile those creds are expired the mesh keeps delivering messages, but the\n[membership feed](delivery-daemon.md) and live connection eviction stay down; `cotal doctor auth`\nand the manager's log both name the credential and this repair.\n\n### Channel seed file\n\nA start seeds the channel registry from the `--channels` file, or from the root's\n`.cotal/channels.json` when you pass none. The file is one JSON object with two optional keys, in\nUTF-8. A byte-order mark at its start, which some Windows editors write, is ignored, and a file\nsaved as UTF-16 is refused with `USAGE_INVALID`, saying to save it as UTF-8:\n\n```json\n{\n \"defaults\": { \"replay\": false, \"replayWindow\": \"6h\", \"deliveryClass\": \"durable\" },\n \"channels\": {\n \"news\": { \"description\": \"Daily news\" },\n \"ops\": { \"deliveryClass\": \"live\", \"instructions\": \"Keep posts short.\" }\n }\n}\n```\n\n`defaults` takes `replay` (`true` or `false`), `replayWindow` (a number followed by `s`, `m`, `h`\nor `d`) and `deliveryClass` (`live` or `durable`). Each key under `channels` is one concrete channel\nname, as `channel set` takes it, and its object takes those three fields plus `description` (up to\n200 characters) and `instructions` (up to 2000). Each field in the file overwrites that field in the\nregistry, and fields the file leaves out keep their values. Any other key, a wildcard or invalid\nchannel name, or an invalid value refuses `SCHEMA_INVALID` before anything starts, naming the file\nand the entry. A default file that is invalid refuses the same way, with a remedy to fix or remove\nit. A manifest start (`-f`) seeds from the manifest's own channels instead. Because the next start\nwrites the default file's values back, a `channel set` or `channel defaults` of a field that file\nsets, and a `channel remove` of a channel it names, warn `CHANNEL_SEED_REAPPLIES` and name the file.\n\n## down\n\nEvery `down` runs to its end when the reader of its output goes away, as `| head` does. It\nkeeps stopping, writes nothing more, and exits with the stop's own status.\n\n`down <service-name>` routes one saved owned definition through the\nsame selection, confirmation, reservation, native manager-impact and stop controller as\n`service stop`. Use `--instance <full-id>` when selection needs a full instance pin. `--yes`,\n`--dry-run`, `--space`, `--json` and `--plain` keep that controller's meanings; structured receipts\nname `down` while retaining the service-stop result and actual identity. Unknown definitions do not\nfall back to legacy component pidfiles. A controller stack names its members `stack-broker`,\n`stack-auth`, `stack-delivery` and `stack-manager`. A preset name reaches the member built on that\npreset, as in [`service`](#service), so `cotal down manager` stops `stack-manager` and\n`cotal down delivery` stops `stack-delivery` through the service stop, and the receipt's target\nnames the member and the name you gave as `requested`. Several names in one `down` resolve each the\nsame way, so `cotal down manager delivery` stops `stack-manager` and `stack-delivery`, and each\nmember of the target carries its own `requested`. A name that is no preset, such as `nats`,\nrefuses `TARGET_NOT_FOUND` there and names the `stack-` member to use. Among several names it\nrefuses the whole set, so no service stops. A stop of `stack-broker` refuses\n`LIFECYCLE_BLOCKED` while a legacy daemon record under the root may front a process, or another\nservice instance of the space is live or unresolved. For those instances it names bare `cotal down`,\nor `cotal service stop <name> --instance <id> --space <space> --yes` for each one. When one of them\nis not a member of the stack, such as one added with `cotal service add`, bare `cotal down` refuses\ntoo, so the refusal names that service's stop first, then bare `cotal down`, or the stops of the\nstack's own members and the broker stop again. Its dry run warns with that refusal. Completion offers saved definitions without imports or a network request. No hidden connector repair or shared catalog preparation precedes this route.\n\nMultiple names resolve as one selected-space set. All owned targets and process roles are checked\nbefore effects. After confirmation, the controller reserves every definition in stable order and\nrechecks all records and native impact before the first stop. Stops follow the displayed input\norder, with each native guard rechecking at effect. A later failure stops the sequence; one receipt\nretains completed, failed/unknown and unattempted members. Reservation/preflight refusals also\nretain the full selected set. Release is attempted for every acquired definition hold; a reported\nrelease error makes an otherwise known outcome partial without erasing known stop results. An\nalready unknown stop stays unknown. Lock cleanup is not independently proved. This is not atomic and no rollback or cleanup is claimed. Dry-run reads metadata without reservations, provider imports or native impact\nqueries. Duplicate names and `--instance` with multiple names are usage errors. Named `--with-agents` refuses and points to\n`cotal service stop <manager-name> --instance <full-id> --with-agents`; it is never dropped.\n\nBare `down --dry-run` reads local process records for one registered space at the current\nproject root. A root with no `.cotal/` directory, or with no stack registered at it, refuses\n`TARGET_NOT_FOUND`, exit 2, because there is no stack there to preview. Its next step is the folder\nwhere `cotal up` started a stack, or, when this machine has no stack started or registered at all,\n`cotal up` in a project, as `cotal status` says. A bare `down` in such a folder refuses the same\nway. A root whose stack is\nalready stopped shows the no-op described below instead. In the machine home, a stack an earlier\nrelease started there is read the same way, and the Next names `cotal down --preserve-state`, its\nstop, or says that none of it is running. Add `--space <name>` to\nconfirm that exact space belongs to this root;\n`--json` and `--plain` produce a partial, finite inspection receipt. It lists pidfile\nliveness, readable additive deployment IDs, retained additive attempts and\nunreadable paths under this root, and saved service definitions, instance IDs, recorded states and\nhosts, supervisor receipts, and retained install, uninstall and unit-action\nattempts in the selected space's workstation store. Saved records are not attributed to this project root, and their recorded\nstates do not prove live process or supervisor status. A submitted native attempt may have\nchanged state; the preview neither reconciles nor retries it. For each saved service instance it\nalso shows whether it blocks bare `down`: one that still runs or cannot be verified blocks it, with\nthe reason, and one already stopped is skipped. When one blocks, the preview warns with the refusal\nthe real stop would give. That check observes processes on this host and changes nothing. Other local registrations with\nthe exact recorded broker address appear as possible sharing. No match does not exclude\naddress aliases, remote tenants or live attachments. The preview does not establish process\nownership, the complete service set, exclusive broker use or agent impact.\n`--with-agents` only marks the requested policy. It does not reserve or signal\na process, and the receipt is not an authorized stop plan. A fresh `team show`\nsummary points to this read-only command from the recorded root.\n\nOn a root with a controller journal, bare `down` reads only its recorded full instance IDs. On an\ninteractive terminal it shows the member set and the live manager's native agent impact and asks to\nconfirm, then stops manager, delivery and broker in that order. `--yes` acknowledges that scope in\nscripts and prints no plan. The receipt names the mesh once, lists each member with its result in\nstop order, and names `cotal up` as the next step. `--with-agents`\nrequests the manager's native retirement policy. Another live service instance in the space\nrefuses `LIFECYCLE_BLOCKED` before the first stop; the Next names the\n`cotal svc stop <name> --instance <id> --space <space> --yes` command for each one. So does a\nprocess record under the root that may front a process, such as a daemon an earlier release\nstarted; the message and the Next name each record's path and pid. A record whose process is gone,\nor whose pid now runs another start than its identity pin records because the pid was reused, fronts\nnothing. It refuses neither bare `down` nor a stop of `stack-broker`, `down --dry-run` names it, and\na complete bare `down` removes it with its pin and names it in a `STALE_PROCESS_RECORD` warning. The\nprocess that holds a reused pid is never signalled. The broker scope\nis checked again after its dependants stop. A failure stops the sequence and preserves completed and\nunattempted members. A member stopped by signal that has not exited 15 seconds after SIGTERM gets\nSIGKILL. Its row then reads `stopped with SIGKILL`, and a `STOP_ESCALATED` warning names its pid.\nOnly a complete confirmed stop removes the delivery credential and local registration. After the\ndelivery daemon stops, `down` reads its lease. When the daemon was already gone, or the lease still\nnames the daemon that credential gave, as after a kill or a release a full store refused, the\ncredential stays, and the next `up` uses it to recognise the row and wait it out before it mints a\nnew one. When the lease cannot be read, the credential stays too, and a `DELIVERY_LEASE_UNREAD`\nwarning says so. The JSON `cleanup` field then reads `registration-removed-delivery-credential-kept`. Saved\ndefinitions and the journal remain for the next `up`, and the space stays prepared at its root. A\nselection that names it stays, so the next command run without `--space` refuses with\n`TARGET_UNAVAILABLE`, exit 2, and names the root to start it in, rather than reach another space.\nNo descendant, lease or authority cleanup is\nimplied. A dry run reads records and, with a live manager, how much the space's store holds. It takes\nno lock, does not establish native impact and exits 0;\nits JSON marks `completeness` as `partial`. It lists members in stop order, says when the stop would\nfirst settle an earlier operation, and names a scope check the real stop would refuse as a warning.\nA member whose recorded process is already gone is recorded as stopped without loading its adapter\nor credential, so a second `down --yes` stops nothing and is a no-op. Before it stops a live\nmanager, `down` reads the workflow runs it hosts and records the unfinished ones, with the steps\nthey wait on, in the stack journal, where `reset store` lists them. A read that fails is recorded\nwith its reason and does not stop the `down`; the JSON receipt carries the record as\n`operation.runs`. When the manager is\nlive but the broker is not, `down` cannot read the manager's agent impact and refuses before any\nstop, from a managed seat's shell too. Run `cotal up --space <space>` to restart the broker from its recorded inputs while the\nmanager keeps running, then run `cotal down`. On Linux, a broker process stopped by a signal, such\nas SIGSTOP, is read from its process state, and `down` refuses `TARGET_UNAVAILABLE`, exit 2, at\nonce, naming its pid, and stops nothing. When the broker's process runs but does not answer, the\nmanager read waits out its bound and `down` refuses the same way. The broker stops last and\nis never force killed, so `down` does not signal it. Resume a suspended broker with\n`kill -CONT <pid>` and run `cotal down` again; end one that stays unresponsive, then take the route\nabove. The manager's stop rides a control credential whose issuance record the broker stores first,\nand a store over its cap refuses that write. With a live manager, `down` then refuses\n`DEPENDENCY_UNAVAILABLE`, exit 2, before any stop, naming the store and the `--max-file-store` cap\nthis machine recorded. Make room with `cotal history clear --yes`, then run `cotal down`. That\npurges all of the space's channel history. To keep it, end the manager on the stack's host with\n`kill -TERM <pid>`, using the pid the refusal names. A plain SIGTERM stops every agent that manager\nruns by its default policy, as [supervise](#supervise) describes, and it needs no credential. Then\n`cotal down` stops the other members, and `cotal up --max-file-store <bytes>` with a larger cap\nstarts the stack again with its history. The signal reaches only a process on that host.\n`cotal service stop stack-manager` and `restart` refuse the same way before submitting a stop, and\nname `cotal history clear --space <space> --yes` and then the same command again. A stop also\nnames `kill -TERM <pid>` as the way to keep that history. A dry run\nnames that refusal as a warning when the space holds more than the recorded cap.\n\nnats-server disables JetStream when its store refuses a write for permission or for space, and\nonly a restart brings it back. While the broker's log on this machine shows that, the manager's\nstop cannot record its credential either, so `down`, `service stop stack-manager` and `restart`\nrefuse `DEPENDENCY_UNAVAILABLE`, exit 2, before any stop, and name the logged failure. `up` refuses\nthe same way, because it cannot check the space's streams on that broker. A dry run names the\nrefusal as a warning. Each names the way out: end the manager on the stack's host with\n`kill -TERM <pid>`, which stops every agent it runs by its default policy, then run `cotal down`,\nfix the logged cause, and run `cotal up`. Fixing the cause alone does not bring JetStream back.\n\nController roots refuse\npreservation and manifest flags rather than switching to the legacy launcher. Outside a terminal,\na bare `down` that would stop a running member refuses without `--yes`, exit 1, and prints that\nline and its remedy without the command help. A stop records each step in the stack journal in the root's\n`.cotal`. When this account cannot write that folder, `down` refuses `PERMISSION_DENIED`, exit 2,\nbefore the plan, names the folder and the `chmod u+w` that fixes it, and stops nothing. Each\nmember's stop is also recorded in the space's service instance records, under `services/` in the\nCotal home, and a folder there this account cannot write refuses the same way. A dry run names\neither as a refusal the real stop would hit.\n\nA controller stack's members are stopped through the registration `cotal up` wrote for it. When an\nolder `meshes rm --force` dropped it, or an older `meshes add --force` replaced it with one added by\nhand at the stack's own root and broker, `down` rebuilds it from the stack journal for the stop and\nsays so with a `REGISTRATION_REBUILT` warning, as `--dry-run` does. It does that only while the\nstack's broker is the launch the journal records at that root and address, and otherwise refuses\n`CONFLICT`, exit 2, before any member stops. A registration added by hand at another address or\nfolder is not replaced: `down` refuses `CONFLICT` and names `cotal space forget <space>`, after\nwhich `down` stops the stack through its journal. While another registered space names this\nstack's exact broker address, bare `down` refuses `LIFECYCLE_BLOCKED`, exit 2, and stops nothing,\nbecause one listener answers for both registrations. It names each of those spaces with the\n`cotal space show <space>` that reads one, the `cotal space forget <space>` that takes it off this\naddress once its own stack is stopped, and the `cotal down` to run again after that. A user-auth stack's registration is not rebuilt,\nso it refuses `UNSUPPORTED`. A stop that refuses or is cancelled before any member stops puts the\nregistration back as it was.\n\nA bare `down` stops only the stack of the folder it runs in. In a folder whose root holds no\nstack, when the space named by `--space`, or else the selected space, is a stack this machine\nstarted in another folder, `down` refuses with `TARGET_NOT_FOUND`, exit 2, and stops nothing. A folder whose\nown `.cotal` holds no stack, such as one a failed start left, answers for itself unless `--space`\nnames another folder's stack. In a folder with its own controller stack, a `--space` that names\nanother space refuses with `CONFLICT`, exit 2, and names this folder's stack. Each refusal names\nthe space and, when this machine started it elsewhere, its root, and its next step runs the same\n`down` from that root. `--json` and `--plain` carry that refusal as structured output, because the\nfolder has no stack for a bare stop to print its own lines about.\n\nWhen the folder of a running controller stack is deleted, its stack journal and broker\nconfiguration go with it, so no member's own stop can run. A running broker writes `.cotal/nats`\nback into the folder, which does not bring them back. `down --space <space>` from any folder stops\nthat stack from the instance records this machine keeps, and so does a bare `down` in the folder\nthe broker wrote back, or in a folder with no `.cotal` of its own while the space is selected. Each\nrunning member gets SIGTERM at its recorded process, manager first and broker last, after `down`\nchecks that the process is still the one its record pinned, and its record is marked stopped. The\nmanager stops its agents by its default policy, so `--with-agents` and the other stop flags are\nrefused. A member that does not exit within its window, 90 seconds for the manager and 30 for the\nothers, ends the stop with `OPERATION_UNKNOWN` and later members are not stopped. Once every member\nis stopped, `down` removes the stack's saved definitions, their instance records and the\nregistration that names the folder, so the space can be created and started in another folder.\nThe same stop runs for a folder moved or renamed while its stack ran, which took its journal, store\nand trust with it. Its Next names `cotal space create <space> --adopt --root <folder>` first, which\nadopts those records where the folder is now and keeps its history, then\n`cotal space create <space>` for another folder. Outside a terminal it needs `--yes`, and\n`--dry-run` lists each member with its pid and process.\n`cotal status` marks such a project folder as gone, with `selected.rootGone` set to `true` in its\nJSON, and its Next names `cotal down --space <space> --yes`. A command that connects to a static-auth\nspace, such as `send` or `history`, is refused with `DEPENDENCY_UNAVAILABLE`, because the seed went\nwith the folder, and its Next names the same `down`.\n\nA bare `down` in a folder whose stack is already stopped stops nothing. It reports that the stack\nin that folder is not running and names `cotal up`, outcome `no-op`, exit 0, and writes nothing,\nso it needs no `--yes`. `--dry-run`, `--json` and `--plain` show the same. A controller stack\ncounts as stopped when its last whole-stack operation was a completed `down` and no member and no\nother service instance of its space runs. That check reads no process record under the root; those\nare read only as described below. A stack that stopped any other way takes the full stop, which\nalso finishes its cleanup. A legacy root counts as\nstopped when no registration names it, or only the one a space named after its folder keeps, and no\n`*.pid` record remains at the top of its `.cotal`, and the root still holds the broker configuration a start rendered there or a space prepared there.\nWithout that evidence a legacy root still refuses, with one next step: `TARGET_NOT_FOUND` when\nnothing shows a stack there, and `CONFLICT` when several registrations name the root or a process\nrecord remains that no registration explains. Before either kind of root reports the no-op, `down`\nreads every record a local-process descriptor names under the root, an installed extension's\nincluded, at any path and name under `.cotal`. A descriptor whose record name holds a space is read\nfor every space its files under the root name, as well as the stack's own. A record whose process\nruns, whose liveness the kernel does not answer, or that cannot be read or attributed refuses\n`LIFECYCLE_BLOCKED`, and so does a file whose space part decodes to no space. Nothing outside the\nroot's `.cotal` is read: a link anywhere on a record's path refuses at the link, unread, as does a\nrecord that is not a regular file, is larger than a process record, cannot be opened, or changes\nwhile it is read. Each is named once, at the path where reading stopped. A `.cotal` that is itself\na link never gives the no-op: on a stopped controller stack `down` refuses at `.cotal`, and on a\nlegacy root it refuses `CONFLICT` before any record is read. A template whose `{space}`\nnormalizes away names one path for every space and is read as that path. The refusal lists each\nrecord by name, path under the root and pid, and its next step names the pids and paths to check.\nIt stops, adopts and removes nothing. `--with-agents` on a stopped stack refuses\n`LIFECYCLE_BLOCKED`, because no running manager can stop the agents; start the stack first. Its\npreview, `cotal down --dry-run --with-agents`, shows the no-op and warns with that refusal. When the\nstack's last completed `down` took `--with-agents`, or the root never started a stack, no agent of\nit can remain, and `--with-agents` reports the same no-op.\n\nThe legacy bare-stack and manifest apply paths described below are retained. They do not\naccept the new named-service JSON/plain/yes apply receipt flags, except that `--json` and\n`--plain` show the no-op of a stack that is already stopped. A bare stop of such a root asks for no\nconfirmation, and `--yes` there changes nothing, so `cotal down --yes` stops a stack on any root.\nBefore a retained bare apply\nreaches its pidfile sweep, it now requires one locally started registration under\na real isolated project root and refuses pending maintenance transitions, any valid or\nunreadable additive ledger or attempt under that root, or any other local registration at\nthe exact recorded broker address. The additive ledger refusal names each ledger with the command\nthat finishes it: `cotal down --run <id>` for a readable one, and for an unreadable one the reason it\ncould not be read and the same command once it is repaired. It reads each record its sweep stops as the stopped-stack check\nabove does. A record that check leaves unread, such as one behind a link, refuses `LIFECYCLE_BLOCKED`\nbefore the first stop and is named once, where reading stopped. No process behind it is read or\nsignalled, and no record is removed. These are known-blocker\nchecks, not a complete live broker-sharing or stop plan; aliases, remote tenants,\nagent impact and process identity remain unproved. These paths do not check full\nwhole-stack target ownership, set exclusion, confirmed agent impact or schema\nreconciliation. The retained `down --preserve-state`\nroute separately refuses a retained or unreadable additive record under its\nmaintenance lock, without adopting its effects into the preserved set.\nA validated completed restore in `active` for the registered space does not block ordinary\nbare `down`. The other root, ownership, sharing and native process guards still apply, and\nstopping leaves the restore journal and fallback store unchanged.\nThe machine home is not an isolated project root, so bare `down` refuses the home stack. Stop it\nwith `cotal down --preserve-state`, which keeps its state for the next `cotal up`. The refusal names\nthat command. `--preserve-state` takes its agent inventory from the manager that serves the space.\nWhen no manager holds the space's lease, as on a stack started with `--no-manager`, it refuses\n`TARGET_UNAVAILABLE`, exit 2, before it stops anything or writes a maintenance record. Start the\nmanager with `cotal up`, then run `cotal down --preserve-state` again.\nIt also refuses `LIFECYCLE_BLOCKED`, exit 2, before it stops anything, while a saved service in the\nspace still runs or cannot be verified, because the service depends on the broker it would stop.\nThe refusal names each one and the `cotal service stop <name> --instance <id> --space <space> --yes`\nthat stops it. Bare `down` refuses the same way.\nThe preserved state records its store by absolute path. If the project folder is moved after\n`down --preserve-state`, `up`, `backup create` and `backup restore` refuse `CONFLICT`, exit 2, name\nthe folder it was moved from, and say to move it back there first. A store missing from its recorded\npath is refused `TARGET_NOT_FOUND` with that path; only `backup restore --accept-missing-source`\nrestores over a lost store.\n\nAfter those checks, a retained bare apply refuses `LIFECYCLE_BLOCKED`, exit 2, before its pidfile\nsweep while a service instance in the selected space still runs or cannot be verified, because the\nservice depends on the broker the sweep stops. It stops nothing. As on a controller stack, bare\n`down` does not stop a service itself: the refusal names each instance with the\n`cotal service stop <name> --instance <id> --space <space> --yes` that stops it, then `cotal down`\nagain. An instance whose process is already gone is skipped, and saved definitions stay. An\nunreadable service record keeps the code of its read refusal. A retained native attempt, an\nunsettled request, a process that cannot be observed, or a record from another host, which bare\n`down` does not touch, refuses too, and the remedy names the command that inspects or stops it.\n\n\n```bash\ncotal down\ncotal down --with-agents\ncotal down --abandon-stop <request-id> --yes\ncotal down --abandon-launch <instance-id> --yes\ncotal down --preserve-state [--store-dir <dir>] [--session-store <dir> \u2026]\ncotal down --dry-run [--space <name>] [--json|--plain]\ncotal down <service-name> [<service-name> \u2026] [--instance <id>] [--space <s>] [--yes|--dry-run] [--json|--plain]\ncotal down -f <cotal.yaml> | --run <id> [--dry-run]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--file <cotal.yaml>`, `-f` | none | Tear down this manifest's deploy |\n| `--run <id>` | none | Tear down one additive ledger, written by the retired `spawn -f` deploy, by its run id |\n| `--space <name>` | current space | With service names: the space whose saved definitions to stop. With bare `--dry-run`: confirm that space belongs to this root. A bare stop refuses it |\n| `--dry-run` | off | Print the manifest teardown, the named service stop or the bare stack preview, mutate nothing |\n| `--with-agents` | off | Bare whole stack only: also stop and deprovision every managed agent |\n| `--abandon-stop <request-id>` | none | Controller stack, with `--yes`: abandon a member's stop that no receipt settles, then stop the stack. See the settlement rules under [up](#up) |\n| `--abandon-launch <instance-id>` | none | Controller stack, with `--yes`: record failed a member launch whose record names no process, then stop the stack. See the settlement rules under [up](#up) |\n| `--preserve-state` | off | Bare whole stack only: fence the manager, retain principals and durable state, stop and prove the stack down, then publish `ready` |\n| `--store-dir <dir>` | the recorded store, else `.cotal/nats` | With `--preserve-state`: the store to cut. The mesh record names the store the broker runs on, such as a `backup restore --store-dir` target, and a different path refuses `CONFLICT` before anything stops |\n| `--session-store <dir>` | none | With `--preserve-state`: a harness transcript store directory to capture with every continuation-capable retained seat. Repeatable. No default and never inferred from a connector name; a path that does not exist or is not a directory is refused before anything stops |\n\nBare `cotal down` stops the whole local stack in dependency order and leaves managed agents running\nwhen their runtime lets them outlive the manager. Before signalling the manager it verifies the spare\ncapability of the exact recorded manager, which records what that manager's stop does with its\nseats, and it reports the agents left behind plus `cotal down --with-agents` as the explicit reap.\nWhen the manager had no managed agents, it prints no report. When they cannot be listed, for\nexample on a user-auth mesh with no login on this machine, it prints\n`could not list managed agents (<reason>)` and the stop continues.\nThe built-in pty runtime keeps each PTY inside the manager process, so those seats cannot outlive\nit: every manager stop stops and deprovisions them, and `down` reports them as stopped. Every manager\nstop the CLI makes runs this one path: `down`, Ctrl-C on a foreground `cotal up`, the teardown after\nthat `up`'s broker exits, the leftover-manager stop before `cotal up -f`, and the delivery cutover.\nEach holds the manager's stop reservation, so a second stop while one is in flight is refused,\nnames the process holding it, and leaves that stop's `--with-agents` policy in place. On Windows,\nmanager and delivery stops send an authenticated local cleanup request because Windows process\nsignals terminate without running cleanup handlers. The request requires the process's private\ncapability, its creation identity and the live stopper's identity and reservation. Other platforms\nuse `SIGTERM`. A `cotal supervise` that receives `SIGTERM` or `SIGINT` while it is still starting\nlets the start settle, then runs the same stop and exits without printing `manager up`, so its\nliveness lease is released and an immediate restart is not refused. When that start fails, the stop\nstill releases the lease before the process prints the start error and exits 1. A process still running after 20s on the Windows cooperative path, or 15s after\n`SIGTERM`, receives `SIGKILL`. Ctrl-C stops the manager first; when that\nstop is refused or the manager's exit cannot be confirmed, Ctrl-C signals nothing else, prints the\nrefusal with the reap route, and leaves the stack running; end it with `cotal down --with-agents`.\n`--with-agents` is a one-shot destructive policy bound to the exact verified manager process\nand the exact live `down` stop reservation; a stale, malformed, crashed, or different stop attempt\ncannot turn a later bare shutdown destructive. If a managed agent cannot be proven stopped within\nthe manager's stop timeout, the manager logs which one, still closes its broker connections and\nconsole listener, and exits with code 1. It does not release its pidfile, liveness lease or\nservice registration in that case, so no successor is handed authority while that agent may still\nrun; the lease lapses on its TTL. A named `down` is the service stop above and never stops a\nlegacy component by its pidfile, so `cotal down manager` or `cotal down web` on a legacy stack is\nrefused unless a saved definition has that name. Only bare `cotal down` stops a legacy root's\ncomponents, all together, under the folder you run it in. It takes no `--space`. Its stop lines\nfollow a `Mesh: <space>` heading, and its Next names `cotal up` to start the stack again. A bare `down` or\n`down --dry-run` whose `--space` names a space whose stack is recorded in another folder refuses\n`TARGET_NOT_FOUND`, exit 2, naming that folder and the stop or preview to run there. The\n`-f` / `--run` forms tear down a [manifest deploy](#manifest-deploys) without stopping the whole mesh\nand cannot be combined with service names or `--space`.\n\nA pinned manager with no spare-capability record is not signalled by bare `cotal down`. A current\nmanager always publishes the record, so a missing one means an older\nmanager: one that predates capability reporting, or one whose pty runtime reported that it cannot\ndetach its agents. Stop each managed agent explicitly, then run `cotal down --with-agents` from the\nmesh root to stop the whole stack. An older manager does not understand\nthe reap request, which is why the agents must already be stopped.\n\nAfter the known-blocker admission checks above, the retained bare `cotal down`\ninventories by pidfile. When this folder's registered broker answers and no\n`nats.pid` records it, the command does not say nothing is running. It names the space and the\nbroker address, says no pidfile records that process, says it will not stop a process it did not\nstart, and exits 1. Stop that broker with whatever started it (an init unit, a container, or the\nhand-run process). `cotal meshes rm <space>` only drops the registration. The probe runs whether or\nnot other owned components were running: they stop and clear their artifacts first, then the broker\nis named. A `--dry-run` does not run this probe.\n\n`down` reads each process record once. A component that exits and removes its own record while\n`down` runs counts as having no record, so the stop goes on. Any other failed read is an error.\nWhole-stack shutdown keeps delivery credentials until every required component has stopped.\nA refused stop preserves both the space-scoped credential and its legacy copy. Credential removal\nerrors remain failures. `down` removes the manager's delivery-aware marker, shutdown intent and\nspare capability only after every component has stopped, so a refused stop keeps them too.\n\n**Teardown verifies pinned process identity before signalling.** PIDs are recycled by every OS,\nso a recorded pid alone is not a durable target identity. `up` and the dashboard's native child record each\nprocess's creation identity in a sibling `<pidfile>.identity` pin, which holds the pid and the\nprocess start reported by the OS. Every stop path, including `down` for the broker, web and\nextension components, and the manager, delivery and auth-service stops, applies the same rule. A pin\nthat names a different start means the pid was reused, so teardown refuses and preserves it. A\ncontroller stack's bare `down` signals no such record, so it removes it, as described above. A torn\nor unreadable pin also refuses. On macOS, the reader calls `sysctl` through a bundled Node-API\nbinding for x64 or arm64 and records the process start in microseconds. A macOS pin that an earlier\nrelease wrote holds the `ps -o lstart` start date instead and is verified with that same reader. That\ndate keeps its spaces, including the padded day, in both the identity pin and the one-shot shutdown\nintent. A single record cannot span lines.\nOn Windows, the reader calls `GetProcessTimes` through a bundled Node-API binding for x64 or\narm64. It requests limited process-query access and preserves the creation FILETIME as a decimal\nstring. It launches no subprocess. Access denial leaves identity unverified; an unreadable pin\nstill refuses teardown. On either platform, a missing or damaged binding is an installation error\nand has no fallback.\n\nThe pidfile and its pin are published by renames, and the pidfile rename is the commit point. Just\nbefore it, the pin holds two lines: the old process's and the new one's. A launcher that dies\nmid-publish therefore leaves the old record or the new one, each checked against its own pin line,\nnever a pidfile without its pin. An old record with no pin is legacy, so its line holds `-` in place\nof the token and it stays legacy until the commit. A CLI older than this change reads a two-line pin\nas torn and refuses.\n\nPublishes of one pidfile are serialized by a lock file beside it, `<pidfile>.publish.lock`, because\nthe launcher and the daemon it starts both publish the same record. The next publisher reclaims a\nlock left by a crashed one. When no start token can be read for the new process, its pin line holds\n`-` in place of the token, which reads as a legacy record, and the publish ends in the legacy shape:\na pidfile with no pin. Teardown, and a daemon removing its own record on exit, take the same lock and\nremove the record only while the pidfile still names the pid they stopped, so a stop that races a\npublish leaves the new record whole.\n\nThe web dashboard service claims `web.pid` with an exclusive create, so a second dashboard for the\nsame mesh is refused, and writes its pin right after the claim. A stop that runs between the two\nreads a legacy record. Any existing `web.pid` refuses the start, whether or not its process still\nruns, and the start neither adopts nor reclaims it.\n\nThe pidfile pid and the pin pid are two coordinates. Automatic cleanup follows **proven death of\nthe pidfile target** (ESRCH on that pid): a torn sibling pin does not wedge a dead pidfile pid.\nA torn pairing where the pin names another pid, while the pidfile pid is still live or not proven\ndead, still refuses. Inspect both pids with `ps`. Do not delete `<pidfile>.identity` to force a\nstop; that weakens target-identity protection. Once the pidfile process is dead, rerunning\nteardown clears the stale record automatically.\n\nA Windows manager or delivery daemon without the cooperative capability must be stopped through\nits original supervisor before restarting with this version. Teardown refuses it without signalling\nor deleting its live record. It does not wait for a lease to expire or delete a lease by local pid.\n\nFor components using signals, the first teardown after upgrading a running pre-pin stack has a\nnarrower guarantee. A live record with no identity pin is signalled after a loud warning that it predates identity pinning. Restarting\nthe component writes the pin, so later teardowns receive full match and mismatch protection. The\nsame warning applies on platforms where no stable start token is available. For a legacy manager,\nbare `cotal down` also warns that agent sparing cannot be verified before it signals. Because the\nCLI cannot establish which SIGTERM handler that already-running binary carries, it never presents\nthe pre-signal seat inventory as confirmed spared; a genuinely older destructive handler may still\nreap those agents. `--with-agents` publishes a one-shot reduced-guarantee handoff bound to the\nrecorded manager pid and the live `.stopping` reservation's inode, then signals unconditionally.\nThat handoff cannot be replayed by a later stop attempt. A pin that exists and does not match the\nlive process still refuses before signal.\n\n`--with-agents` performs the old destructive logical teardown: managed processes stop and their\ncredentials, ACL rows, and delivery footprints are deprovisioned. `--preserve-state` is a different\nmaintenance transition: it stops retained processes while suppressing leave/deprovision cleanup, persists the manager's\nsame-principal resume inventory, stops the entire stack without removing run/auth artifacts, and\npublishes a stable inode-bound cut only after every recorded process is proven stopped and the exact\nrecorded NATS endpoint is unreachable. A missing or stale broker pidfile never counts as stopped. The\nattempt is bound durably before the manager is fenced, the resume document and attempt-bound\n`cut-intent` are fsynced before manager commit, and the manager's commitment itself is journaled\n(`cut-committed`) before any process stops. A retry after a crash at any of those boundaries reuses\nthe exact recorded attempt and finishes the remaining stop and endpoint proofs idempotently, without\nneeding the (by then intentionally dead) manager. A partial cut never publishes `ready`. It cannot\nbe combined with component names, manifest teardown, or `--dry-run`.\n\nOn a mesh that runs the delivery daemon, the cut stops it first so no join or leave can change\nmembership under the manager's inventory. Before any manager-owned endpoint stops, the cut then\nreads the presence roster and refuses while an endpoint the manager does not own is live. A refusal\nleaves the delivery daemon stopped. A deleted presence key is the only row it reads as absent. Any\nother row that is not a card with an `id` equal to the presence key it was read under and a string\n`name` refuses the cut with `presence record <subject> is malformed`. A participant can write any\nvalue under its own key, so the key is the identity the cut trusts.\n\n**Seat checkpoints.** After the stack is proven down, the cut writes one checkpoint per retained\nseat under `.cotal/maintenance/v1/checkpoints/<attempt>/<seat>/`, and prints the path, the\ncontinuity class and the generation for each. The path carries the preservation attempt because a\ncheckpoint is immutable once sealed: a shared directory would make the second cut in a root refuse\non the first cut's leftovers, and clearing it would destroy an artifact a rollback still needs. The\ncapture happens only at that point because anything earlier races a harness that is still writing\nits transcript and its working tree.\n\nEach checkpoint directory is created 0700, refuses a destination that already exists, and holds:\n\n- `repo.bundle`, the seat `cwd`'s reachable history, anchored on the base commit the record names\n by full object id;\n- `repo.index.diff` and `repo.worktree.diff`, the staging state as two diffs, base to index and\n index to worktree. Two rather than one because a single combined diff restores a mixed tree with\n the right bytes and the wrong index: a source reporting `MM README` would come back as ` M README`;\n- `repo.untracked.tar`, the untracked files in scope;\n- the harness session pointer, when the seat's connector declares one, and the transcript store\n files the operator named with `--session-store`. Each records where the destination puts it back\n as an anchor (the workspace root, the account home, or the seat's `cwd`) plus a relative path,\n because the destination's root and home are its own and the source host's absolute spelling would\n either miss them or write outside them;\n- `checkpoint.json`, written last, after every digest is computed over the bytes that landed.\n\nThe record carries the manager's resume entry unchanged as its first field, then the space, the seat\nname, the recovered `lifecycleUid`, the writer generation the cut was taken at, `capturedAt`, the\nrecency horizon, the applied profile revision, the seat's `git status --porcelain` as the cut read\nit, and the continuity class. Every captured file is\nlisted with its byte size and sha256, so an operator verifies the whole artifact with `sha256sum`\nand `git bundle verify`. No secret values, no operator keys and no source-host launch material\nenter it.\n\nThe generation is the highest one this root has claimed for the seat in `.cotal/auth`. A\ngeneration entry there that is a symlink, a directory or anything else that is not a regular file\nrefuses the cut, as every other auth record does.\n\nThe continuity class is what the connector declares, capped by what the checkpoint carries. A\nconnector declaring session continuation classifies as `exact`, but reopening a session takes both\nhalves, the pointer that names it and the store that holds its transcript. A checkpoint missing\neither one cannot reopen that session, so it is recorded as `fresh` when the connector declares a\nfresh start and `drain-only` otherwise. A pointer with no store is capped the same way as a cut\ncarrying neither, because it names a session whose bytes the artifact does not contain. A class is a promise the destination is entitled\nto act on, so it never describes bytes the artifact does not contain. The transcript store stays an\noperator input: this repository does not know where a harness keeps its transcript, so `exact`\nrequires `--session-store` to name one.\n\nThe recorded status is read under the same selection rule as the untracked set, so it describes the\nstate the captured bytes can reproduce. The destination re-reads it in the promoted tree and refuses\na difference.\n\nThe untracked selection rule is recorded in the record and is\n`git ls-files --others --exclude-standard -z, excluding .cotal/`. It honors `.gitignore`, so an\nignored file the seat needs does not travel and has to be moved separately. The `.cotal/` exclusion\nis a secrecy boundary rather than a size one: when a seat's `cwd` is also the mesh root, the control\ndirectory is untracked, and without the exclusion the broker trust material, the space account, the\nmanager instance identity's private seed and the seat's own credentials would land inside the\nartifact. A checkpoint carries credential references only; the destination resolves that material\nitself.\n\nA seat whose launch options could not be resolved is refused rather than checkpointed, with the\nmanager's own wording: `imperative launch options have no non-secret durable source (<keys>)`. The\nrefusal arrives at prepare time, so the cut stops before any child does.\n\nA delegated seat (SPEC \xA713.17) is refused at prepare time too, with\n`a delegated seat is not resumed by a later manager; stop it before preserving`. A manager stop\nafter a refused cut retires that seat through its retirement path.\n\n## clean\n\n```bash\ncotal clean <history|store|all> --force\ncotal clean <store|all> --dry-run\ncotal clean restore-attempt --attempt <id> --force\ncotal clean restore-fallback --attempt <id> --force [--discard-history]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | `history`: target mesh |\n| `--dms` | off | `history`: also clear DM history |\n| `--store-dir <dir>` | `.cotal/nats` | `store`/`all`: JetStream store directory |\n| `--force` | none | Required: destructive, no prompting |\n| `--dry-run` | off | `store`/`all`: list what would be removed and remove nothing; needs no `--force` |\n| `--attempt <id>` | none | `restore-attempt`: exact stale pre-commit attempt; `restore-fallback`: matching healthy committed restore |\n| `--discard-history` | off | `restore-fallback`: also delete an old store that holds the only copy of the history or workflow runs the restore left out |\n\nOne configurable cleanup verb; every target requires `--force`, except a `--dry-run` of `store` or `all`.\nIt takes one target and no extra positional arguments. A channel name after `history` refuses\n`USAGE_INVALID` (exit 1); it does not narrow the purge. The dry run quotes an explicit `--store-dir`\nvalue in its printed force command so it stays one shell argument.\nOnly `history` takes `--space`, `--server` or `--creds`. Every other target acts on the root of the\nfolder it runs in, so it refuses them `USAGE_INVALID` (exit 1) before local cleanup inspects or removes\nroot state.\n\n- `history` purges the retained message backlog on the **running** broker (channels, plus DMs\n with `--dms`). The same operation as [`history clear`](#history), which stays as an alias.\n- `store` deletes the **stopped** mesh's JetStream store (`.cotal/nats`): streams, durable\n consumers, and messages. This is the reset for stale on-disk broker state, e.g. durables\n minted by an older, incompatible Cotal generation surviving a `down`/`up` cycle. It removes the\n store's `jetstream` directory, its `.cotal-store-id` marker and the broker's `ocsp` folder, then\n the empty store directory. A\n store directory that holds anything else refuses `CONFLICT` (exit 2), names those entries and\n removes nothing; move them out and run it again.\n- `all` is `store` plus the space identity (`.cotal/auth`), the local creds and markers tied to\n it, any crash residue a normal `down` would have swept (stale pidfiles, `run/`), and the mesh's\n registry entry; the next `cotal up` mints a fresh identity. On a root deployed with `up -f`, plain\n `cotal up` then refuses and names `cotal up -f <manifest>` when the deployment ran at another\n address, mode or space. A full backup is bound to the space trust it was taken under, so full\n backups taken before `clean all` no longer restore into this root. Only the registry of such a\n backup still restores, with `backup restore <dir> --only registry`, and chat and DM history start\n empty. Its dry run and its receipt say so when it removes that trust; take a new full backup after\n the next `cotal up`. On a user-auth space it also removes the space's IdP trust and its actor\n ledger, so every actor grant is lost. Its dry run and its receipt say so, and a later `up` then\n needs `cotal up --user-auth --idp <auth base URL>` and each actor granted again with\n `cotal actor grant`.\n\n`store` and `all` move the store aside to a `.cotal-clean-<id>` folder beside it, then remove its\nentries there, as `reset store` and `reset space` do. A removal that stops partway, such as at a\nfolder in the store this account cannot write, ends `OPERATION_PARTIAL` (exit 4). It names that\nfolder and what is left in it, and its Next makes the folder removable and removes it. Nothing else\nrecords the folder, so a later `clean` or `reset` finds no store and reports nothing to remove.\n\nAn existing store path that is a symbolic link refuses `CONFLICT` (exit 2), naming the link and\nits target. Replace the link with the directory it points to, then run the command again. The\nlink and its target are left unchanged, including for `--dry-run`.\n\n`history` needs the mesh up; `store` and `all` refuse while any recorded mesh process may still be\nrunning or any same-root recorded broker endpoint remains reachable (run `cotal down` first). On a\ncontroller root, a member whose process is gone counts as stopped once a completed `down` recorded it\nas already stopped, so `down --yes` after a crash is enough. A\nrecord counts as stopped only when it is absent, empty, or names a pid the kernel reports gone, so\nunattributable content, a removal reservation, or a pid whose liveness cannot be read refuses. They\nalso refuse outright on a root that holds accounts for several spaces: the store and the broker\ntrust record are shared by every space on the broker, so both targets would take out all of them\nand no `--space` can narrow that. `down`, `backup` and `up --restore` refuse there for the same\nreason. `cotal status` lists the tenants on such a root. Personas\n(`.cotal/agents`) and logs are never touched. Without `--store-dir`, `store` and `all` read the\nstore the root's last broker served, as a start does: while the default `.cotal/nats` holds no\nJetStream data and that store does, such as after `backup restore --store-dir`, they remove that\nstore, and the dry run names it. Custom cleanup targets must contain either the Cotal store-generation marker or a\nreal `jetstream/` store directory; filesystem roots, project roots, and Cotal auth/maintenance trees\nare always refused. A store in the project folder itself, as `up --store-dir .` makes, refuses\n`CONFLICT` (exit 2), since the folder holds the project's own files; its Next names the store's own\nentries to remove by hand once the space is stopped. Its contents are not checked, so a directory that holds only a `jetstream/`\ndirectory is removed as a store, whatever that directory holds. The one exception is another\nspace's data: when another space uses the store folder, as [up](#up) checks it, `store` and `all`\nand their dry runs refuse `CONFLICT` (exit 2), name that space, its root and its streams, and remove\nnothing. Keep the folder while either space needs its history; once neither does, stop both and\nremove it by hand.\n\nIn a folder whose `.cotal` is a link, every target but `history`, and both dry runs, refuse before\nanything reads through it: `CONFLICT`, exit 2, naming the link and where it leads, or\n`TARGET_NOT_FOUND` for a link that leads nowhere, which is not passed over to the records of a\nfolder above it. When the link leads to this folder's own records, moved away and linked back, the\nNext names moving them back, as [up](#up) does.\n\nIn a copy, in a folder moved away from its recorded path, and in the folder a later `up` took its\nspace's name over from, the store may hold that space's only history. There `store` and `all`, and\nboth dry runs, refuse `TARGET_UNAVAILABLE`, exit 2, before the liveness checks, naming the folder\nthe stack runs from and the step the commands that read there name, and remove nothing.\n\n`--dry-run` runs the same checks as `store` or `all` and prints `would remove <path>` for each\nstore, credential, record and registration the command would remove, then removes nothing and takes\nno maintenance lock. A check that would refuse the command refuses its dry run the same way.\n\nCtrl-C or `SIGTERM` during `store` or `all` releases the maintenance lock, reports what the clean\nleft and exits 130 or 143. The outcome is `failed` when nothing was removed yet, and `unknown`, naming\nthe paths removed, once removal began.\n\n`store` and `all` also refuse every maintenance journal state, with `LIFECYCLE_BLOCKED`, exit 2. The\nrefusal names the attempt and, as Next, the step `cotal status` shows for it, such as bare\n`cotal up` to resume a preserved cut; once the root holds no maintenance state and its mesh is\nstopped, the clean runs. After a healthy committed restore,\n`restore-fallback` is the only supported way to remove the recorded unchanged old-store inode; it\nnever deletes the active target, requires both the exact attempt id and `--force`, and retires the\ncompleted restore journal so a later `down --preserve-state` can start a new backup cycle. After a\nrestore that left history or workflow runs out it also requires `--discard-history`, as\n`backup cleanup-fallback` does, and its refusal names the flag without printing the command help.\nAfter an `--accept-missing-source` restore there is no old store, so it only retires the journal.\nAn attempt the root's maintenance record does not name refuses with `TARGET_NOT_FOUND`, exit 2, and\ndeletes nothing. A repeat after a completed cleanup is such an attempt, and its refusal says the\nrestore has settled and names `cotal status`. Without `--force`, `restore-attempt` and\n`restore-fallback` name the command to run with its `--attempt`. `restore-attempt` refuses the same\nway, with `TARGET_NOT_FOUND`, for an attempt the record does not name, and with `LIFECYCLE_BLOCKED`\nfor a recorded attempt past its pre-commit point, which recovers forward. Without `--attempt` both\nverbs refuse with `USAGE_INVALID`. Each refusal's Next names the attempt the root records and its\nstep.\n\n## reset\n\n```bash\ncotal reset store <space> [--dry-run] [-y|--yes] [--json|--plain]\ncotal reset space <space> [--dry-run] [-y|--yes] [--json|--plain]\n```\n\nBoth routes select a space this machine started. A space with a locally started registration is\nselected by it. A controller stack that a completed `down` stopped has no registration; its\nprepared record and stack journal at the same root name its broker address, store and mode, and a\nreset keeps both, so a later `up` starts it again in the same mode. No reset changes a stack's mode;\n[up](#up) names the way to run another one. After `reset space` of a user-auth space, that\n`up` needs `--user-auth --idp <auth base URL>`, as described below. A stack none of whose members ever started has\nno store to reset, so both refuse `TARGET_NOT_FOUND` and name `cotal up`. So does a prepared space\nno stack has run for, which also names `cotal space forget`. A stack whose last\noperation is not a completed `down` refuses `LIFECYCLE_BLOCKED` and names `cotal down --yes`. Each\nrefusal names one reason and the command that clears it. A space that may still run names\n`cotal down` from its root, and lists each process that may run once. A stack member whose\nprocess also holds its component's record, such as the manager's pidfile, is listed as that member\nwith the record's pid. A member that exited without a recorded stop, as after a crash, is\nsettled by `cotal down --yes`: it finds the member gone and records it as already stopped, and the\nreset then accepts it. Only a member that no completed `down` recorded that way needs `cotal up` and\nthen `cotal down --yes`. A legacy root has no stack journal, and its `down` drops the registration\na reset selects, so no reset reaches it once stopped. A space named after its folder keeps that\nregistration, so a reset reaches it stopped. For any other legacy root, the step after `cotal down` is\n`cotal clean store --force` from its root for `reset store`, or `cotal clean all --force` for\n`reset space`, with `--store-dir <dir>` when the registration records a custom store. Once stopped,\nsuch a space has no record here, like a mistyped name, so a reset of either refuses\n`TARGET_NOT_FOUND`, says no registered or prepared space has that exact name, and names `cotal space`\nand then that step.\n\nRemoving the store removes what the broker kept in it: workflow runs and their journals, channel\nhistory, stored messages and every other record. A run still waiting for an answer goes with it and\ncannot be answered or resumed afterwards. Only the broker reads the store, and it is stopped, so\n`cotal down` reads the stack's runs from its manager before stopping it and records them in the\nstack journal with the steps they wait on. The preview, the confirmation and the receipt list those\nruns as of the last down, and `--json` carries them as `storeContents.runsAtLastDown` and\n`removedRuns`. When that down could not read them, or no down recorded them, the preview says so.\nTo see the runs then, run `cotal up` and `cotal run`, then `cotal down --yes`.\n\nThe store dry-run is a local-impact preview that exits 0. It requires a space selected that way, a\nreal project `.cotal` and a root readable as one local tenant. It opens with a verdict: the apply is\noffered, with `cotal reset store <space> --yes` as Next, or it would refuse, with the apply's own\nreason and remedy. The JSON result carries it as `applyVerdict`, and `completeness` stays\n`partial`. Completion suggests locally started names, without proving\neligibility. The preview inspects the store path with the existing guarded\ncleanup path checker, reads the maintenance journal, legacy process records,\nservice-controller instances, supervisor receipts, retained native attempts\nand root-local additive ledgers and attempts plus fresh boot attempts, and\nmakes a credless broker probe. Readable deployment IDs, unsettled attempts and unreadable\nrecord paths remain visible; these records do not\nprove live ownership or a complete broker tenant inventory. An orphan receipt\nor attempt remains visible without an instance record; these space-scoped\nrecords are not proven owned by this root. The preview also lists other local\nregistrations at the exact\nrecorded broker address. A match blocks store apply, while no match does not\nexclude aliases, remote tenants or live attachments. A negative credless probe\ncannot prove absence for TLS-first listeners. A custom store path must be\nabsolute and one real directory. A recorded store path that is a link refuses `CONFLICT` in the\npreview and the apply of both resets, naming the path and the directory it points to, and nothing\nis removed. Replace the link with that directory to reset it. A selected root whose `.cotal` is a\nlink refuses both resets and their previews `CONFLICT`, exit 2, before anything reads through it,\nnaming the link, and nothing is removed; when the records were moved away and linked back, the Next\nnames moving them back. A store in the project folder itself\nrefuses both resets `CONFLICT` as it refuses `clean store`, naming the store's own entries to remove\nby hand. The preview takes no maintenance lock and cannot establish exclusive\ncustody or a complete identity deletion set.\n\nBare `reset store <space>` checks those conditions, displays a scoped terminal\nconfirmation or requires `--yes` in a script, and acquires the maintenance lock. While another\ncommand holds it, such as an `up` started at the same time, `reset store` and `reset space` refuse\n`LIFECYCLE_BLOCKED`, exit 2, name that command's pid and remove nothing.\nUnder the lock it rechecks the exact registration or stopped-stack records, one-tenant root, absent\nmaintenance journal, legacy process records, service instances, supervisor\nreceipts and retained native attempts for that space, additive ledgers and\nattempts plus unsettled fresh boot attempts under the root, credless broker\nnonresponse and pinned filesystem identity before calling the existing\nstore-only cleanup. A readable or unreadable additive ledger or attempt\nblocks reset before confirmation and under the lock. It refuses another\nlocal registration at the exact broker address before confirmation and\nrechecks under the lock. This does not prove\nexclusivity against aliases or remote tenants. An orphan receipt or attempt\nblocks reset even when no instance record remains; a submitted native step may\nhave changed state. A TLS-required or TLS-first target refuses\nbecause a credless probe cannot prove it is down. The route preserves trust,\ncredentials and registration. A cleanup with an unconfirmed outcome is reported\nas unknown and is not retried. Credless nonresponse and the recorded process\nchecks are not proof that no unrecorded process exists. A store directory that holds anything\nbesides the store refuses both routes and their dry runs as it refuses `clean store`, naming those\nentries. A store folder another space uses refuses both routes and their dry runs `CONFLICT` the\nsame way, naming that space, its root and its streams; the store dry-run shows it as its verdict.\n\n`reset space <space>` adds local identity cleanup through the existing native\nclean-all operation. This route currently requires Linux, a space selected as above and a\nplain-NATS loopback listener. It removes a locally started registration; a stopped controller stack\nkeeps its prepared record and stack journal. On a user-auth space it also removes the IdP trust and\nthe actor ledger, so every actor grant is lost. The preview and the receipt say so on their own\nline: a later `up` needs `cotal up --user-auth --idp <auth base URL>`, and each actor needs\n`cotal actor grant` again. TLS-first, remote, shared-root\nand non-Linux targets refuse. It applies the same maintenance, deployment and\nprocess-custody guards as store reset. It does not stop a process to become\neligible.\n\nIts `--dry-run` validates the existing local trust chain and inventories the\nnative identity paths, including provider-declared secret keys, without running\ndeprovisioning hooks. The inventory is bounded to 10,000 paths. Foreign space\nsegments, symlinks, hard-linked files, foreign ownership, group/world-writable\npaths and providers without bounded cleanup metadata refuse. The refusal names the path and its\nreason; for a group- or world-writable path it names `chmod go-w <path>`. Values and content\nfingerprints are not printed. No maintenance lock is taken by the preview. Like the store dry-run, it\nhas a Store contents section when a store is present, naming what removing it removes and the runs\nthe last down recorded, and `--json` carries them as `storeContents`. The apply's confirmation shows\nthe same section. A preview that passes every check opens with a verdict, as the store dry-run\ndoes: the apply is offered, with `cotal reset space <space> --yes` as Next.\n\nApply shows the path inventory and defaults to cancellation, or requires\n`--yes` in a script. Under the maintenance lock it repeats the eligibility\nchecks and compares the reviewed file identities. Native secret deletion is\nrestricted to the planned keys; each filesystem removal checks the remaining\nreviewed tree. Added paths and changed files do not enlarge the confirmed scope.\nThe native operation deprovisions secrets before raw identity removal and\nremoves trust last. After checking the planned targets absent, it removes only\nthe unchanged eligible local registration and releases its matching selection.\nA replacement registration is kept and reported as partial.\n\nThe receipt retains acknowledged deletion paths even if later cleanup fails, and lists each path\nonce.\n\nCtrl-C or `SIGTERM` during either apply releases the maintenance lock, reports what the reset left\nand exits 130 or 143. Before the removal was submitted the outcome is `failed` and the store and\nidentity are as they were. After it, the outcome is `unknown`, `reset space` lists the paths removed\nso far, and Next is the route's `--dry-run`.\nUnconfirmed effects are unknown and are not retried. Personas, saved service\ndefinitions and stopped-instance records, installed packages, login sessions\nand remote data remain. Local cleanup does not revoke a remote credential or\nclear Cloud data.\n\nA saved service definition whose `credentialRef` names a credential this space's account issued,\nsuch as one `cotal auth mint` wrote, would hand its child a credential the next identity rejects.\nReset reads the issuing account from each such file and clears that `credentialRef`, and both the\npreview and the receipt name the service and the file. A file it cannot read as a credential is\nleft as it is. To give the service a credential again, mint one after `cotal up`, then remove the\nservice and add it again with that `credentialRef`.\n\n## Backups\n\n```bash\ncotal down --preserve-state [--store-dir <dir>]\ncotal backup create <dir> [--only full|registry] [--store-dir <dir>] [--json|--plain]\ncotal backup inspect <dir> [--json|--plain]\ncotal backup verify <dir> [--json|--plain]\ncotal backup recover <full-attempt-id> [--yes|--dry-run] [--json|--plain]\ncotal backup cleanup-fallback <full-attempt-id> [--yes|--dry-run] [--discard-history] [--json|--plain]\ncotal backup restore <dir> [--only registry] [--store-dir <dir>] [--accept-missing-source] [--json|--plain]\ncotal up --restore <dir> [--restore-only registry] [--accept-missing-source]\n```\n\nOn a controller root, one with a stack journal, `backup create`, `backup restore` and\n`up --restore` refuse `UNSUPPORTED` (exit 2) in this release. Each needs a preserved cut, and\n`down --preserve-state` cannot make one there. They refuse while validating their arguments,\nbefore the connector seed or any write. Plain `down` and `up` keep a controller stack's store,\nchannels and history. On a full store `down` refuses first, and [down](#down) says how to stop the\nstack there with or without purging that history. Backup and restore of a controller-owned stack\nreturn in a later release. The\nrest of this section applies to a root without a stack journal.\n\nBackup is offline-only. It requires the stable `ready` record from `down --preserve-state`, an exact\nstore match (`--store-dir` defaults to the cut's recorded source store), no recorded process that may still be running (the same test `clean` uses), and an\nunreachable exact endpoint from the recorded cut. That endpoint is probed immediately before cloning, so a live broker with a missing or stale pidfile\nis still refused. `backup restore`, `up --restore` and a bare `up` that resumes the cut probe the\nbroker address they will bind before they move or start anything. While another process listens\nthere they refuse `CONFLICT` (exit 2), name the address and leave the cut unchanged. It claims the cut, reflink/copies the stopped source to a\nprivate attempt clone, and opens only that clone on a random loopback bootstrap broker with an\nindependent parent/deadline watchdog. A bootstrap broker that does not accept connections within\n15 seconds fails the command. Each maintenance-login reload waits for the broker's acknowledgement\nfor up to 15 seconds, bounded by the operation deadline. A rejection, broker exit or missing\nacknowledgement fails the command before the next phase receives its login.\nCreation returns one JSON/plain receipt after cleanup.\nA published artifact plus incomplete cleanup is partial and retains the artifact and original\nattempt; it is not a reason to repeat creation blindly. It validates the canonical stream and pull-consumer inventory,\nwrites native snapshots with consumers excluded, and stores conservative contiguous ACK-floor\ncheckpoints separately. The presence bucket is memory-backed, so it does not survive the cut and\nthe clone may lack it. Every other stream must be present. The original store is never opened by\nthe backup broker, and the stack is not restarted implicitly. Artifact destinations must not overlap\nthe preserved source or maintenance\nattempt tree. Restore artifacts and targets likewise cannot nest inside or contain each other, the\npreserved source, or the maintenance attempt tree.\n\n`backup create` checks its preconditions before it claims the cut or creates any maintenance state.\nA root with no preserved cut refuses and names `cotal down --preserve-state`. A cut in another state\nrefuses and names that state and its space. A create during a restore that still runs refuses\n`LIFECYCLE_BLOCKED` (exit 2), names that restore attempt, its process and when it started, and says\nto wait for it. A create that finds the root's maintenance lock held, such as by another\n`backup create`, names the operation that holds it, its pid and since when it has held the lock.\nNeither refusal journaled anything, so it names no attempt of its own. A destination that already exists is a `CONFLICT`\nrefusal, because create writes only a new directory. A destination whose folder does not exist\nrefuses `TARGET_NOT_FOUND` (exit 2) and names that folder; create makes only the artifact's own\nfolder. A destination check that finds a non-directory parent (`ENOTDIR`) or a symbolic-link loop\n(`ELOOP`) refuses `USAGE_INVALID` (exit 1), naming the destination and its parent. Choose a destination\nwith a directory parent; no attempt was started and the preservation journal is unchanged.\nA destination inside the preserved store refuses `USAGE_INVALID` (exit 1). So does one\ninside a path `clean all` removes, such as `.cotal/auth` or the space's `.cotal/space.<hex>`\nfolder, because that cleanup would remove the backup with them. One in a\nfolder this account cannot write refuses `PERMISSION_DENIED` (exit 2) and names that folder. A `--store-dir` that is not the cut's preserved store, such as an empty folder,\nrefuses `CONFLICT` (exit 2) and names both stores. A process that listens at the cut's recorded\naddress refuses `CONFLICT` (exit 2), as `backup restore` and a resuming `up` refuse it, and its Next\nsays to stop the process that listens there and run the same `backup create` again. Any other\nrefusal or failure shows the check's own sentence as its native detail.\nA failure that removed what it wrote leaves the preserved cut ready, and its Next names the same\n`backup create` to run again once that cause is fixed.\nA create that runs out of disk space (`ENOSPC`) or quota (`EDQUOT`) after it claims the cut removes\nwhat it wrote and fails `OPERATION_FAILED` (exit 5). Its message names the error and where it was\nwriting: the store copy under `.cotal/maintenance/attempts`, or the backup in its destination. It\nalso says that no backup was published and that the preserved cut is still ready. Its Next frees\nspace on that disk and runs the same `backup create` again.\n\nA `backup create` interrupted by SIGINT, SIGTERM or SIGHUP after it claims the cut exits 130, 143\nor 129 and names its attempt, the partial artifact it left and `cotal backup recover <attempt> --yes`.\nThat claim follows the restore claim's rule below: it holds the cut until its backup process and\nbrokers are proven gone, or for 30 minutes when they cannot be. Until then `up`, another create and\n`backup recover` refuse `LIFECYCLE_BLOCKED` and name the backup process and the deadline, and\n`cotal status` names the attempt and its next step. Once it is stale, `backup recover` removes the\npartial artifact, the clone and the broker files, each matched by inode, and a create to another\ndirectory does the same before it starts, then backs up the cut's recorded store, a custom\n`--store-dir` one included. A create naming the partial artifact refuses `CONFLICT`\nand names that recovery. The release's receipt names `cotal backup create <directory>` and\n`cotal up` as its next steps.\n\n`backup inspect` reads the bounded manifest and exact declared file-name inventory. It does not\nhash snapshot or checkpoint contents. `backup verify` additionally reads each named regular file\nand compares its size and SHA-256. Both are read-only: no staging directory, broker, restore or\nsecret upload. A path through a linked folder is read at its real path, as `backup restore` reads\nit, and the report names that path. Reports omit snapshot bytes, configuration bodies and trust\nmaterial. JSON retains\nthe declared metadata; terminal file lists are bounded and state their omissions. When a declared\nfile is missing, an undeclared file is present, a declared file is not a regular file, such as a\nsymbolic link or a FIFO, or a file does not match its declared size and SHA-256, the refusal names\nthat file and the fault, and its JSON target carries `file` and `fault`. A declared name that is\nnot a plain file name in the folder, such as one with `../` or an absolute path, has the fault\n`invalid-name` and is not printed. A `manifest.json` that is not a regular file, is larger than\n1 MiB or is not a valid manifest has the fault `manifest-not-regular`, `manifest-too-large` or\n`manifest-invalid`. `backup restore` and `up --restore` refuse each of these `SCHEMA_INVALID` with\nthe same file and fault, before they stage or change anything.\nA directory with no `manifest.json` that holds files a `backup create` writes is refused as an\nincomplete artifact, the kind an interrupted create leaves, with the fault `manifest-missing`. A\nfile, or a directory that holds neither a manifest nor such a file, is not a backup artifact and\nrefuses `TARGET_NOT_FOUND` (exit 2), naming it and saying whether it is a file, an empty folder or\na folder with other files. The folder of a create in this root that was\ninterrupted before it wrote a file is still an incomplete artifact. A path that names nothing refuses\n`TARGET_NOT_FOUND` (exit 2) and says in `missing` whether its folder or the artifact is missing, as\n`backup restore` does. A full artifact of an authenticated space\nrestores only into a root that still holds the trust state it was taken under, so it cannot move to\nanother machine or a fresh root. `backup create`, `backup inspect` and `backup verify` say so in a\n`Restores into` row, and their JSON carries `trustBound`. A registry restore does not check the trust\nstate, so `--only registry` still restores such an artifact's channel settings. When a full restore\nrefuses `CONFLICT` because the trust state moved, its Next names that `--only registry` restore for an\nartifact that holds the registry.\n\nFile integrity is not a signature or a restore proof. Matching a manifest does not authenticate\nits author, verify snapshot semantics or prove existing trust will accept a restore. `backup\ninspect` and `backup verify` do not compare an artifact's trust state with this root's, and their\n`Authority continuity` row says `not checked`. A full artifact taken on this root before `clean all`\nor `up --rotate-sys` still verifies, and restore refuses it `CONFLICT`. The guarded\nread path refuses unsupported file-protection environments rather than using a weaker reader.\n\n`backup recover <full-attempt-id>` uses the original root's pre-commit restore journal, not a\nmanifest path or selected-space guess. It rolls the attempt back once it is stale: its restore\nprocess and every recorded watchdog and broker are proven dead, and either every broker this root\nstarted is proven gone or the claim's deadline has passed. Commit intent forbids rollback. The\nreceipt names `cotal up`, which resumes the space from its preserved state, and\n`cotal backup restore <directory>`, which tries the restore again, as its next steps.\n`backup cleanup-fallback <full-attempt-id>` requires the matching healthy active restore and\nremoves only its unchanged retained old-store identity; it never removes the restored target.\nIt removes that store by its store entries, as `clean store` does. When the old store also holds\nanything else, such as a file you put in a custom store directory or in the fallback folder, its\n`--dry-run` names those entries, and the cleanup refuses `CONFLICT` (exit 2), names them and deletes\nnothing; move them out and run it again. A restore into the preserved store's own path moves that\nstore aside whole, so it refuses `CONFLICT` (exit 2) the same way before anything moves.\nA restore made with `--accept-missing-source` replaced a missing source and retains no old store,\nso its cleanup removes nothing and only retires the journal. A later `down --preserve-state` then\nstarts a new backup cycle. A registry restore leaves every other stream out, chat and DM history\nand workflow runs included. A full restore of an artifact with no workflow runs leaves the runs out,\nand a full restore of an artifact made before the state it replaced was preserved leaves out what\nwas written after the artifact. In each case its old store holds the only copy of them. Its `--dry-run` warns with\n`HISTORY_ONLY_IN_PREVIOUS_STORE` and names what it left out, and the cleanup refuses\n`USAGE_INVALID` (exit 1) and deletes nothing unless `--discard-history` is given.\nThat refusal names the flag and does not print the command help.\nBoth review full paths and default to Cancel. Scripts need `--yes`; `--dry-run` reads the plan\nwithout taking a maintenance lock or writing. Apply rechecks the reviewed journal revision under\nthe native lock, preserves unknown outcomes and never retries a deletion. No live-owner override\nor process kill is supplied.\nA restore's full attempt id has the form `restore-<uuid>`. `backup recover` also takes an\ninterrupted create's `backup-<uuid>`, as described above. `cotal status` shows the attempt a root\nholds under Maintenance, with its state and next step. For a restore that left history or workflow\nruns out that step includes `--discard-history`. A refusal for the wrong state names the next step\nfor the recorded one. A completed `cleanup-fallback` retires the journal, so a repeated cleanup\nrefuses with `TARGET_NOT_FOUND` and says the root holds no restore attempt.\n\n`backup restore <dir>` is the canonical restore. It runs the same guarded restore as\n`up --restore <dir>`, with the same lock, claim, trust and space proofs and the same resume, and\nreturns one `backup restore` receipt: the restore attempt, space, root, selection, store, broker\nprocess and whether the manager, delivery and auth service started. The restore follows the\nartifact's own selection, and `--only registry` narrows a full artifact to the registry. Any other\n`--only` value is refused, because the restore could not honor it. The space, broker and runtime\ncome from the preserved cut, so no selector can rebind them. An artifact of another space refuses\n`CONFLICT` (exit 2) and names both spaces, and a full artifact taken under another trust state\nrefuses `CONFLICT` the same way. An artifact file that is missing, undeclared or not its declared\nsize and SHA-256 refuses `SCHEMA_INVALID` (exit 1) and names the file, as `backup verify` does. A\nmanifest whose space is longer than 200 characters is invalid, so restore refuses it with the fault\n`manifest-invalid` and does not print that space. In each case the preserved cut is not changed. A\nreceipt means the\nactive journal was written. Its `seatResume` field says whether retained seats were handed to the\nmanager's resume (`requested`) or dropped by a registry restore (`not-requested`). It does not\nconfirm that any seat joined. Its `restored` and `recreatedEmpty` fields name the streams the\nrestore brought back and the ones it recreated empty. A registry restore brings back channel settings only, so chat and DM\nhistory and workflow runs start empty. Its receipt says so with a `HISTORY_NOT_RESTORED` warning, and `previousStore`\nnames the store the restore set aside, which holds the only copy of them. Its next step says\nthat `backup cleanup-fallback` deletes that store only with `--discard-history`. A full restore of\nan artifact made before the state it replaced was preserved, such as a backup taken before a later\n`cotal up` and `down --preserve-state`, leaves out the chat and DM history and workflow runs written\nafter the artifact. Its receipt says so with a `NEWER_HISTORY_NOT_RESTORED` warning that names the\nstore it set aside, which holds their only copy, and its next step includes `--discard-history` too.\nWhen that restore brought the workflow runs back, it also names each run it rolled back and each\nstep that was answered after the artifact and is open again, and says that answer is only in the\nstore it set aside. A reopened step accepts an answer again. `cotal status` and the\n`backup cleanup-fallback` preview name the same runs. To find them, the restore copies the store it\nsets aside and reads its run journals on one more isolated broker, so such a restore takes longer\nand needs disk space for that copy.\n`up --restore --restore-only registry` prints the same two lines. Without `--store-dir`, a restore\ncreates its store at `.cotal/nats`, also when the cut's store is elsewhere, as after\n`up --store-dir`; only a controller root restores into its recorded store. The preserved store stays\nwhere it is until `backup cleanup-fallback` deletes it, so to keep the space on another disk, name a\nnew store there with `--store-dir`. `--store-dir` names a store the restore creates, so a path that\nalready exists, or whose parent directory does not, refuses `USAGE_INVALID` (exit 1) before anything\nis staged. Restore needs the stopped preserved cut from `down --preserve-state`. A root with no\nmaintenance journal refuses before the restore takes its maintenance lock, so the folder keeps no\nstate from the attempt, and `up --restore` refuses the same way. An artifact path that names\nnothing refuses `TARGET_NOT_FOUND` (exit 2) at the same point. It names the path, and its JSON\ntarget says in `missing` whether the folder that should hold the artifact (`folder`) or only the\nartifact (`artifact`) is missing. A path that is not an artifact, or is an incomplete one, refuses\nthere too, as `backup verify` refuses it. A path that is, holds or sits inside the preserved store\nor `.cotal/maintenance`, such as `.` in this folder, refuses `USAGE_INVALID` (exit 1) there, naming\nthat store or folder. The restore checks the path again once it holds the maintenance lock, because a\nlink in it may name another folder by then, and refuses the same way. A restore that fails after it\nbegan names its attempt in the text and in the JSON target, which also carries `journalState`, and\nits remedy gives the next step for that state. `up --restore`\nremains as the equivalent startup form. After its startup lines it names the restore attempt and\ngives the cleanup step that retires it, with `--discard-history` for a restore that left history or\nworkflow runs out. Unlike the other\nbackup modes, `backup restore` runs the connector seed first, as `up` does, because it resumes seats.\n\nThree commands refuse an active restore. `down --preserve-state`, in each of its forms, refuses\n`LIFECYCLE_BLOCKED` (exit 2) and stops nothing: a new cut begins only after the restore journal is\nretired, so it names the attempt and `backup cleanup-fallback` as the next step, with\n`--discard-history` after a restore that left history or workflow runs out. `team boot`\nresumes only a stopped stack whose manager never launched, and a restored stack's manager already\nran, so start it with bare `cotal up` instead. `cotal up -f` boots a fresh mesh, so it starts only\nonce the restore is retired. While the restored broker runs, it refuses `CONFLICT`, exit 2, naming\nthe broker's PID, and its Next is `cotal down`, then `backup cleanup-fallback <attempt> --yes`, then\n`cotal up -f` again. After a restore that left history or workflow runs out, that cleanup step\nincludes `--discard-history` and says it deletes the previous store. Once that stack is stopped, it refuses `LIFECYCLE_BLOCKED`, exit 2, and names\nthe cleanup. Nothing is started either way. While the restored broker runs, any other `cotal up`,\nbare or with `--server`, refuses `CONFLICT` too, naming its PID, and leaves the restore journal as it\nwas. Its Next is `cotal down`, then `cotal up`, which starts the restored stack again.\n\nStopped client-managed KV ordered consumers are ephemeral read residue, not backup state. Backup\nignores only the pinned client's exact stopped shapes: ordinary last-value watchers, a watcher of one\nsubtree of its bucket such as the manager's goal index, and the whole-bucket scanner that uses\nall-history delivery to collapse concurrent tombstones. A bound consumer or any lookalike with a\nfilter outside its bucket, another inbox, lifetime, or other config is still refused.\n\n`full` is the default and indivisible: channel registry, CHAT/DM/TASK/INBOX/DLV, ACL, MEMBERS, on\na static-auth or open mesh the workflow run stores (the WFJ step journal, the records store and the\nrun admission store), and validated durable checkpoints. `registry` is the sole partial artifact. Presence, derived membership\nfeed, leases, native ephemeral/history consumers, credentials, keys, tokens, owner secrets, and actor\nledger files are excluded. The other endpoint stores are nonportable control state: endpoint\nsubmissions, facts, events, timer requests and fires, work pools, contract artifacts, and the\nauth, session and issued-authority stores. Restore recreates those streams empty with their\ncanonical configs before exposing the normal listener, so endpoint sessions do not cross a backup. A\nworkflow run that waited at a checkpoint or a timer at the cut is back after a full restore, and the\nmanager takes it over as after a restart. The records store also holds the endpoint authority rows,\nsuch as lifecycle heads, obligations and policy versions, and a full restore rolls them back to the\ncut with the runs. A user-mode full backup holds no workflow run store, because the records store's\nlifecycle heads name credential rows in the auth store, which a backup does not carry; its restore\nrecreates the run stores empty and says so. An artifact in the earlier `cotal-space-backup/v1`\nformat still restores; it holds no workflow run, so the run stores come back empty and the restore\nsays so. `backup create` says so too when its artifact holds no runs, as a registry or user-mode\nartifact does. After a restore that left the runs out, the store it set aside holds their only copy:\nits receipt says so with a `RUNS_NOT_RESTORED` warning that names that store, and\n`backup cleanup-fallback` deletes it only with `--discard-history`, as for a registry restore. Each\nrestore lists what it restored and what it recreated empty. Artifacts are exclusively created `0700`;\nsnapshot/checkpoint files and\nthe manifest are `0600`; `manifest.json` is written last with exact sizes and SHA-256 values. The\ndirectory is trusted operator input: hashes detect corruption, not malicious rewriting. So\n`backup verify`, `backup restore` and `up --restore` apply the\n[`space create`](#selecting-a-space) rule for other local writers to the artifact directory, each\nfile in it and each directory above it. An artifact another account could write refuses\n`PERMISSION_DENIED`, exit 2, before a restore stages or changes anything, and the refusal names each\nsuch path and one remedy such as `chmod g-w <paths>`. `backup create` refuses the same way, before it\nwrites anything, when a folder that would hold the artifact breaks the rule. `backup inspect` does\nnot check this, and a copy that other accounts can only read is accepted.\n\nRestore validates and stages the exact allowlisted artifact bytes before moving or creating a store.\nIt requires the same space. A full restore also requires the trust state the artifact was taken\nunder; a registry restore, `--only registry` of a full artifact included, does not check it. The\nwhole pre-commit window holds a journaled\nliveness claim (coordinator, watchdogs, brokers, and a deadline 30 minutes after the restore\nbegan). While the claim is live, ordinary `up`, a repeated restore, `backup create` and\n`backup recover` refuse `LIFECYCLE_BLOCKED` (exit 2) and name the restore process, when it started\nand the deadline. A command that finds\nthe root's maintenance lock held refuses the same way and names the pid that holds it. The attempt\nis stale once the restore process and every recorded owner are proven dead on this host, and\neither the deadline has passed or every broker the root started is proven gone. A broker's\nwatchdog stops it when the restore process exits, so a restore stopped by Ctrl-C is stale within\nseconds. A retried restore rolls a stale attempt back and continues; an operator can also roll it\nback with `cotal backup recover <full-attempt-id> --yes`. Nothing ever rolls back a live attempt.\nA restore interrupted by SIGINT, SIGTERM or SIGHUP before it commits exits 130, 143 or 129 and\nprints its attempt id and that recovery command. A registry-only artifact restores as registry-only whether or not\n`--restore-only registry` is passed; omitted infrastructure is always created and the exact\npost-restore stream inventory is asserted before commit intent. Ordinary `up` from a preserved cut\nresumes only the exact recorded source store, file store cap, runtime and broker address. A different\n`--store-dir`, `--max-file-store`, `--runtime` or `--server` refuses `CONFLICT`, exit 2, before\nanything starts, naming the value given and the recorded one. The Next resumes with `cotal up` without that flag, then `cotal down` and\n`cotal up` with it.\n\n**Admitting a seat checkpoint.** An ordinary `up` from a preserved cut admits that cut's seat\ncheckpoints before it journals the resume attempt and before any process starts, so a refusal costs\nnothing. Three gates run in order, each naming what it saw.\n\n1. *Integrity.* Every file the record names must be present, a regular non-symlink file, the\n recorded byte size and the recorded sha256, re-stat'd after the read so a file that moved is a\n refusal. Failure here consults no other gate.\n2. *Identity.* The recorded space must match, the recorded `lifecycleUid` must not belong to a live\n incarnation, and the profile revision must match this host's or be resumed under deliberately\n this host's. A differing revision is refused with both digests and the remedy, and there is no\n override: the checkpoint carries the recorded digest and not the config bytes, so nothing could\n run the seat under the recorded revision, and the manager re-digests the same file and refuses\n drift on its own. This gate has no blanket override, which is the only reason the next one may\n have one.\n3. *Recency.* `capturedAt` is compared to this host's clock against the horizon the record carries.\n Inside it, the seat resumes. Outside it, `up` refuses and prints the capture instant, the clock\n reading and the horizon; `--accept-stale-checkpoint` admits it anyway and the exercised consent\n is printed with the actual age. An unreadable `capturedAt` is refused with no override, because a\n freshness gate that fails open is not a gate.\n\nCustody transfers only after all three pass. The destination claims the recorded generation plus one\nby exclusive create, before it launches anything. A lost create means another destination is already\nclaiming that seat, and it refuses with `seat-writer-generation-create-lost` rather than adopting\nthe winner and becoming a second writer. The recorded `lifecycleUid` is reused and never minted, so\nthe resumed seat binds the same lifecycle-keyed durables.\n\nAdmission is reconciled against the inventory the resume is about to hand the manager, and that\nreconciliation finishes before the restore moves a single tree. A checkpoint whose recorded\n`lifecycleUid` is not the one the retained inventory carries describes a different incarnation of\nthat seat, and it refuses with both uids while every live working tree is still untouched and no\ngeneration is claimed. A retained\nseat with no admitted checkpoint refuses the resume by name: an absent checkpoint directory and an\nabsent record are indistinguishable from a seat that was never checkpointed, and a seat that starts\nwithout passing the gates has claimed no generation. `--accept-stale-checkpoint` is recorded in the\nresume journal with the seat, the capture instant, the admitted age and the horizon, so the consent\nsurvives the terminal it was typed into.\n\nThe whole admission is all or nothing. Coverage is settled first, then every gate runs over every\ncheckpoint, and only then is any generation claimed. A refusal at any point leaves every generation\nunclaimed, including a lost exclusive create during the claim itself: the claims that attempt made\nare removed before the refusal is raised, by the exact paths it wrote, so a generation another\ndestination holds is never touched. A claim is a create that can never be made again, so a refusal\nthat left one behind would consume the retry over the same checkpoint set.\n\n**Restoring a seat checkpoint.** Once every gate has passed over every checkpoint, and before a\nsingle generation is claimed, `up` puts each admitted seat's captured bytes back. A refusal here\ncosts nothing for the same reason a gate failure does: no claim has been made and nothing has\nstarted.\n\nA restore never moves or replaces the destination's own control directory. A checkpoint excludes\n`.cotal/` by design, so a seat whose `cwd` holds one, which is the layout an operator gets by\nrunning `up` and `spawn` in a single directory, is refused before anything is staged: promoting a\ntree that cannot contain `.cotal/` over that `cwd` would carry this host's live trust material and\nmaintenance state away with the superseded tree. The refusal names the control directory it found\nand the remedy, which is to give the seat a working tree that is not a workspace root.\n\nEach seat is staged beside its own `cwd`, in `<cwd>.incoming`:\n\n1. every recorded digest is verified again over the files as they are now;\n2. the bundle is cloned into `<cwd>.incoming`, which is refused when that path already exists;\n3. the recorded base commit is verified in the clone and checked out detached, so a bundle that does\n not contain it stops the resume instead of continuing against a different history;\n4. the index diff is applied with `--index` and the worktree diff without it, both `--binary\n --allow-empty`. That order is what puts staged content back in the index rather than only in the\n worktree, and `--allow-empty` is why a seat with a clean tree is still restorable;\n5. the untracked archive is extracted.\n\nEvery seat stages before any seat is promoted. Promotion moves an existing `cwd` aside to\n`<cwd>.superseded.<timestamp>` and renames the staging directory into place, then puts the session\npointer and store files where the destination's connector reads them, then re-reads\n`git status --porcelain` in the promoted tree and compares it to the status the checkpoint recorded.\nA restore that applied without error and produced a different index is a refusal, not a warning. The\ntwo renames are the only steps that touch the path the seat will use, so a failure anywhere leaves\nevery seat's live `cwd` as it was.\n\nThe rename itself claims the superseded name, and a taken name gets a numeric suffix. The timestamp\nhas one-second resolution, so two promotions of the same seat within one second compute the same\npath; a rename onto a name that already holds a tree fails on every platform, and that failure is\nread as taken. Nothing creates the name ahead of the move, because Windows refuses to rename onto an\nexisting directory at all. A superseded tree is the thing that rename exists to keep.\n\n`git` and `tar` run as child processes with argument arrays, never a shell string.\n\nA leftover `<cwd>.incoming` refuses the resume by name. A staging directory from a failed run is the\nonly record of what failed, so nothing removes one automatically: inspect it, remove it by hand, and\nresume. A pre-existing `cwd` is renamed rather than deleted, so a wrong checkpoint costs a rename\ninstead of a tree. When a promotion fails, the renames that attempt made are undone and the staging\ntree is left where it is, as the evidence for what did not verify.\n\nA session pointer whose recorded `sessionId` is not the one the retained inventory reopens is\nrefused before anything is cloned. A session file already present at its destination is judged by\ncontent: bytes equal to the recorded digest are already restored, and different bytes under the path\nthe connector is about to read are refused with both digests rather than clobbered.\n\n`up --restore <dir>` reaches the same admission and the same restore, after the store is restored\nand validated and before commit intent is journaled. A registry-only restore resumes no seat, so it\nadmits and restores nothing.\n\nOne limit is worth stating plainly. The writer generation is claimed by exclusive create inside one\nworkspace root, so it fences two resumes on the same host and does not fence two independent\ndestinations: copy a checkpoint to two roots and both claim the same successor. A real cross-host\nfence needs a coordinate neither root owns.\n\nAuthenticated restores validate the complete\nspace trust bundle before staging, including nkeys, seed matches, JWTs, signers, and space binding;\nfull restores commit to the validated operator, system-account, data-account, and active-signer root\nchain in addition to the static/user authority fingerprint. Because the system account is part of that\ncommitment, a [`cotal up --rotate-sys`](#up) makes every full artifact taken before it unrestorable\nagainst this root: take a fresh full backup after each rotation. [`cotal clean all`](#clean) removes\nthe space trust itself, so it does the same. Such an artifact still restores its registry with\n`--only registry`, and chat and DM history start empty. The composed commitment is revalidated\nimmediately before store mutation and never includes secret seeds. Restore never creates fresh auth.\nSame-path restores atomically retain the old\nsource at the journaled fallback path; alternate targets retain it in place; a missing canonical\nsource needs explicit `--accept-missing-source`. Without it, the restore refuses `TARGET_NOT_FOUND`\n(exit 2), names the missing path and changes nothing. A default `.cotal/nats` that is a symbolic\nlink to the recorded source is that source, so the restore runs in place at the real path and the\nlink keeps naming it. Quarantine and target restores use current canonical\nconfigs on isolated random-loopback brokers, never expose native snapshot consumers, and publish a\ncommit-intent immediately before the normal listener starts. Archive bytes never instantiate the real\ntarget: after quarantine validation, every stream is re-snapshotted from the validated quarantine\nstate into attempt-owned sanitized files, and the target is restored solely from those. Before that boundary, failure rolls back\nthe attempt-owned target; after it, ambiguity preserves both stores and records forward-repair\nrecourse. The cooperative maintenance lock excludes Cotal commands, not arbitrary raw NATS processes.\n\nBootstrap brokers in every auth mode, including open, mount the store under a local account with\nrandom operation-specific logins only, each carrying the exact per-phase subject permission matrix;\nnormal static credentials and user-auth sentinel/bearer connections are rejected, and no auth\nservice or callout starts. Open mode differs only in its account label, never in authority. Inventory, each stream snapshot,\nrestore initiation, exact upload id, validation, and each checkpoint recreation use separate exact\nauthorities. Every checkpoint carries the source stream's message/first/last sequence state and must\nmatch its snapshot record before mutation; core then derives and validates the only allowed start\npolicy. TASK is not a CLI exception: the same core checkpoint API recreates its canonical `DeliverAll`\nWorkQueue durable because acknowledged tasks are absent from retention and NATS forbids a\nstart-sequence policy there. Registry-only restore creates every omitted canonical stream and transient\nbucket on the isolated target before the normal listener is exposed. It deliberately does not resume\nretained agents or recreate their DM/DLV/TASK/ACL state; their identity material stays retained and\nstopped rather than being reprovisioned into a partial restore.\n\nAfter listener readiness, the manager starts attempt-bound, validates retained credentials/tokens\nwithout granting or reprovisioning, and resumes the exact persisted principals under cleanup\nsuppression. Registry-only restore uses the same flow with an empty agent set. On a user-auth mesh\nthese manager calls run as the logged-in operator's `cli` actor, the caller the preserve cut used, so\nthat actor needs a current `admin` grant. `commitResume` is an\nidempotent validation barrier only: success must be `awaitingFinalize` with an attempt-bound 64-hex\ncommit token and does not release suppression. Under the workspace lock, the CLI first fsyncs that\nexact evidence as `manager-committed` (restore) or `resume-committed` (ordinary resume), then calls\ntoken-bound `finalizeResume`; only an `active` response for the exact token releases suppression. The\nCLI records the same token in finalization evidence before a restore becomes `active`, or before an\nordinary resume retires and consumes the marker. A new launch hands the token to the next manager as\na protected file: the CLI journals its path, writes it at mode 0600 in\n`.cotal/maintenance/v1/handoff/`, then journals its inode and digest before the child starts. The\nchild reads it once with `--resume-commit-file` and `--resume-commit-sha256` and removes it. The\ntoken never appears on a new launch's command line. Before retirement or activation the CLI settles\nthat file. A file it did not record, or a recorded path that now holds another file, holds the\nattempt and is never deleted. `cotal supervise --resume-commit-token` stays accepted for older\ncallers; while such a process runs, its token is visible in its command line. Re-entry from either committed state skips the prior\nidempotent activation/commit phases, retries finalization with the durable token, and finishes the\nworkspace transition. Failure before finalization preserves the committed state and cleanup\nsuppression; it is not rewritten through a degraded transition. Re-entry between any two earlier\nboundaries reuses the same attempt and may retry the idempotent phases without deleting retained state. A missing or\nchanged per-agent dependency is a named fail-closed result; the journal becomes degraded and remains\navailable for forward repair. A retry from `resume-intent`,\n`resume-active`, or `resume-degraded` reuses the same attempt and inventory after the prior listener is\nproven stopped. A retained agent the lost manager already launched can still be running, for example\nin a tmux window, while the journal reads `resume-intent`. On a static mesh the replacement manager\ncloses that seat through the reference the lost manager recorded on the agent's slot, waits for the\nprincipal to leave presence, and launches it again. A live principal with no such record, or one that\nstays live after the seat is closed, is refused. Every normal restore listener has an unguessable\nattempt-bound NATS server name. The CLI fsyncs its exact name/nonce, canonical endpoint, process owner, and generation-bound target identity\nimmediately after spawn. Re-entry accepts a surviving listener only when its INFO server name, live PID\nrecord, endpoint, and target identity all match that proof; degraded restore repair then moves through\nthe guarded workspace transition only after manager commit. If an uncommitted bound owner is provably\ndead, recovery retires that exact proof under the maintenance lock and binds a fresh listener for the\nsame attempt, endpoint, and target with a new nonce and server name. A live foreign/mismatched listener\nor ambiguous owner is preserved and refused, never adopted by reachability alone. Before the\ndaemons start, re-entry recreates the memory-only buckets on an adopted restore or resume listener,\nbecause the interrupted `up` may have stopped before it made them. A reconstructed\ncommit/degraded attempt without either the exact bound proof or a durable dead-listener replacement\nrecord fails closed even when the recorded port is free. A later ordinary startup may pass an `active`\nrestore only when its details prove manager commit and its exact recorded listener is dead.\n\n## Selecting a space\n\n```bash\ncotal space [--json|--plain]\ncotal space show [<name>] [--json|--plain]\ncotal space add <name> --server <url> [--root <dir>] [--mode auth|open] [--tls] [--allow-unencrypted-overlay] [--force] [-y|--yes]\ncotal space add [<name>] (-f|--file <bundle.json> | --from <https url>) [--root <dir>] [--server <url>] [--force] [-y|--yes]\ncotal space create <name> [--root <dir>] [--adopt]\ncotal space forget <name>\ncotal space delete <name> [--dry-run] [-y|--yes] [--json|--plain]\ncotal use [<space> | --clear] [--json|--plain]\n```\n\n`space` lists the spaces this machine can reach: each one's broker state, server, mode, origin\n(`started here`, `registered` or `discovered`) and root, with the selected one marked. Inside a\nproject whose root has one running registered space other than the selection, that space is marked as\nthis folder's, since a command without `--space` targets it there. It probes\neach broker that this machine started or that was registered by hand, and reports one that does not\nanswer as `unreachable`; its registration stays. A discovered space is never dialed by a listing and\nreads `not checked`. `space show [<name>]` shows one space in full and probes only that broker.\nWith no name it is about this folder: the space it has registered, else the one it holds prepared or\nstopped, which `show` names although every other command there targets the selection, else the\nselected one. Neither writes anything. A registry record that cannot be\nread refuses both with `SCHEMA_INVALID`, exit 1, naming its file.\n\n`--json` writes one document. `space` gives `current` (the selected space or null),\n`currentRecorded`, `folder` (this folder's space when it is marked, else null), `items` and\n`prepared`; each item is the row `cotal status --json` gives under\n`meshes.items`: `space`, `server`, `mode`, `root`, `origin`, `tlsRequired`, `eventsRequired`,\n`modeChecked`, `current`, `check` (`reachable`, `unreachable` or `not-checked`) and `catalog`. `space show` adds\n`recordedAt`, `attachHost`, `maxSessions`, `storeDir`, `unencryptedOverlay`, the login pins of a\nuser-auth space under `userAuth` and the catalog's `id`, `slug`, `kind` and `role`. It leaves\ncredential paths out. A name that is not registered is refused with `TARGET_NOT_FOUND`, exit 2, and\nso is a bare `space show` outside a project with nothing selected. Each entry under `prepared` has `space`, `root`,\n`preparedAt`, `current`, `rootMissing`, which is true when its project folder no longer exists,\n`state` and `lastOperation`. `state` is `prepared` for a space that never started, `stopped` for one\nwhose stack ran and was stopped, and `unsettled` when its last `up` or `down` recorded no outcome.\n`lastOperation` names that operation and how it ended, such as `cotal down, completed`, or is null.\n`space show` of a prepared space gives those with the kind `prepared-space`. The listing marks\nsuch a folder `(no longer exists)` and names `cotal space forget <space>` for it instead of `cotal up`.\n\n`space add` registers a space that runs elsewhere, so `--space` and `use` can reach it. A name\n`space create` refuses, such as one that starts with `-` or holds a `*`, given or named by a bundle,\nrefuses with `USAGE_INVALID`, exit 1, before anything is written. With\n`--server`, the trust comes from `--root`'s `.cotal/auth`, and the mode is `auth` when that folder\nholds the space's trust, `open` otherwise. `--root` defaults to this project. Outside one it is\nrequired, and leaving it out is a one-line `USAGE_INVALID` refusal whose remedy says what to pass:\nan open space keeps no credentials there, so any folder will do. With `--file`, a user-auth connection bundle exported\nwhere the space runs supplies the login pins and the server, unless `--server` names another\naddress for that broker; `--from` fetches the space's discovery\ndocument instead, after you agree to the fetch, and needs a terminal. Before anything is written,\n`space add` checks that the address may carry credentials, that the trust composes, that the\nbroker enforces the mode, and that the credentials open the space; a user-auth space's exchange\nmust answer as the issuer it pins. An open broker answers for any name, so an open space must also\nbe one the broker serves, as its streams show. A name it does not serve is refused with\n`TARGET_NOT_FOUND`, exit 2, naming the spaces it does serve. On a terminal it then shows what it will trust and asks; `--yes`\nskips that question, but not the confirmation of fetched pins. It never changes the selection.\n\nA refusal writes nothing and carries its code: `USAGE_INVALID` or `SCHEMA_INVALID` (exit 1) for\ninput, and `CONFLICT`, `TARGET_UNAVAILABLE`, `TARGET_NOT_FOUND`, `DEPENDENCY_UNAVAILABLE`,\n`UNSUPPORTED`, `AUTH_REQUIRED` or `PERMISSION_DENIED` (exit 2) for what the checks found. A name that is already\nregistered is a `CONFLICT`, and its remedy offers `--force` only for a registration added by hand.\nA broker that does not answer is a `TARGET_UNAVAILABLE`, and its remedy offers `--force`.\nA broker that requires auth, with a `--root` that holds another space's trust, is a\n`DEPENDENCY_UNAVAILABLE` that names the space the folder holds.\nWhen no broker answers at `--server`, or at a bundle's server, the `TARGET_UNAVAILABLE` refusal says\nwhether its host name resolves and whether anything took a connection at its port. When a broker\nanswers there under another scheme, such as `nats://` for a `tls://` address, the remedy names that\naddress, and says when it has no TLS. A port that takes no connection gets the port check as its\nremedy. For `--server`, the `--force` route follows either one.\n`--force` replaces a registration added by hand, never one this machine started or a catalog lists,\nand adds a `--server` space without checking its broker, which suits a\nspace that is down right now; a user-auth space is always checked. Such a registration records that\nits mode was not checked: `space` and `space show` show the mode as not checked, with\n`modeChecked: false` in `--json`, and `whoami` does not state what that mode implies as fact. Declining the question is a\n`no-op` with a `USER_CANCELLED` warning. `--json` gives the registration under `space`, in the\nshape `space show` gives, with `verified` and `replaced`.\n\n`space create <name>` prepares a space to start on this machine. It records the name and the root,\nwhich is `--root` or this folder, and makes `<root>/.cotal/`; `cotal up` in that root then starts the\nspace under that name and drops the prepared record. It also writes the `default` persona into\n`<root>/.cotal/agents` when the root has none, as setup does, and leaves one you edited as it is.\n`--json` lists the files it wrote under `result.written`. The project and its ancestors must be physical\ndirectories owned by you or root, with no symlinks. World write refuses. Group write is allowed only\non a directory you own whose group is your user private group, the Debian and Ubuntu default, and\nthat has no extended ACL. On Linux, `getent` must show that the group is your primary group, lists\nno other member and is no other account's primary group, and `getfacl` must show no ACL entries\nbeyond the base ones. That account list counts only when `/etc/nsswitch.conf` names `files` and\n`systemd` alone for passwd and group. A directory source such as LDAP or SSSD can list part of its\naccounts, so with one configured, group write refuses. `getent` and `getfacl` run from your PATH\nwith a 2 second limit each, so the first check in a process can take a few seconds. A proven group\nholds for the life of the process. When a read is unavailable, group write refuses; install the\n`acl` package for `getfacl`, or remove group write. Root-owned sticky temporary directories are\nallowed. New control directories, the persona catalog `.cotal/agents` included, use mode 0700,\neven with a group-writable umask. An unsafe existing path refuses before the\nprepared record is written, without changing its permissions. The refusal names every unsafe path\nin the chain at once, the reason and one remedy such as `chmod g-w <paths>` that lists each path.\nA path that its group and every account can write gets `chmod go-w`, since `chmod o-w` alone would\nleave the group write that refuses next, and the other group and world gaps join that one command.\nWhen a symlink is the gap, the remedy names the physical path the folder resolves to.\nIt starts nothing and leaves the selection as it is. `cotal up` applies the same rule to the root\nit starts in, before it writes anything, so it never starts a stack that `down` would refuse to\nstop from that root. `cotal up` starts a space only under a name of 1 to 64 letters, digits,\nunderscores or hyphens that starts with a letter, digit or underscore, because a name that starts\nwith `-` cannot be passed as a flag value. `space create` refuses any other name, such as `caf\xE9` or\n`-x`, with `USAGE_INVALID`, exit 1, before anything is written, with a `remedy` that says which\ncharacters a name can use, and `cotal up` refuses a `--space`\nthat starts with `-` the same way. The limit is 64 because each local file that carries a space spells its name twice over,\nand a file name holds at most 255 bytes. `cotal up` refuses a longer name that an earlier release\nprepared the same way, before it starts anything, and names `cotal space forget` and a shorter name.\n`space add` and `meshes add` refuse a longer name with `USAGE_INVALID`, exit 1, and write no\nrecord, because a space added from elsewhere is carried by the same files.\nIt refuses with `CONFLICT` a name that is registered or prepared, a folder that is already a\nspace's root, and the home folder, whose `.cotal` holds this machine's own records. It refuses a\nfolder that is, or is in, a control directory, which is a project's `.cotal` or the Cotal home,\njudged by the path given and by the path its links lead to, with `CONFLICT` and nothing written. A\nhome `COTAL_HOME` redirects may hold project folders beside its records, so under it only the home\nitself and its record entries (`meshes`, `prepared`, `services` and the like) are control paths. The\nnext step names the project folder that `.cotal` belongs to. A project folder inside another project,\noutside its `.cotal`, is a project of its own. For a folder\nanother space uses, the next step names creating it in another folder with `--root <dir>`, and\nstarting, selecting or forgetting the space that uses the folder. A folder this\naccount cannot write, such as `/`, refuses `PERMISSION_DENIED`, exit 2, with nothing written, and so\ndoes a Cotal home (`~/.cotal` or `COTAL_HOME`) it cannot write. A `.cotal` that links to a folder that\ndoes not exist refuses `TARGET_NOT_FOUND`, exit 2, naming the link and its target. A folder whose\npath contains a control character, such as a tab, a newline or an escape character, or a bidirectional\nformatting mark, which can show one path as another, refuses `USAGE_INVALID`, exit 1, with nothing\nwritten, in `space create` and in `cotal up`. The refusal names the character and shows the path with\nit escaped. `space` lists prepared spaces under their own heading, below a line that says no space\nhas been started or registered yet when that is so, and `space show`, `use` and `whoami` accept one.\nOnce one of them has run, the heading gets a `State` column, and the line says no space is running\nor registered. A space whose stack ran and was stopped reads as stopped in `space`, `space show` and\n`use`, with its last operation, as `status` reads it.\nA command that\ntargets a prepared space refuses with `TARGET_UNAVAILABLE`, exit 2, naming the root to start it in,\nand so does `cotal up --space <name>` run from another folder. Its Next names\n`cotal up --root <root> --space <name>`, which starts it in that root from where you are, and\n`cotal space forget <name>`, which lets you start it in the current folder. When that root's last `up` or `down`\nwas cut off before it recorded an outcome, as an `up` interrupted before it registered the space\nleaves it, the refusal says so and names `cotal status` in that root, which names the step that\nfinishes or settles it. `cotal up --space <name>` in a folder\nthat is already another space's root refuses with `CONFLICT`, exit 2. `space add` refuses a prepared\nname. A prepared space records its folder by path, and moving the folder leaves nothing in it that\nnames the space. A bare `cotal up` in a folder that names no space, while the selected space is\nprepared in a folder that no longer exists, refuses `TARGET_UNAVAILABLE`, exit 2, rather than start a\nspace named after the folder there. Forget the space with `cotal space forget <name>`, prepare it again in its new\nfolder with `cotal space create <name>`, then run `cotal up`. Until then, `space create` of that name\nsays its recorded folder no longer exists.\n\n`space forget <name>` removes a registration this machine keeps. It stops nothing, deletes nothing on\nthe broker, and keeps the files under its root. It refuses a space this machine is running with\n`LIFECYCLE_BLOCKED`, because `cotal down` stops it, and a discovered space with `CONFLICT`, because\nits catalog keeps it. A stack `cotal up` started stays a prepared space at its root after that\n`down`, so the refusal names `cotal down` and then `space forget` again. A legacy root's `down` drops\nthe record, and the refusal says so, unless the space is named after its folder: that `down` keeps the\nrecord, and the refusal names `space forget` again. A space whose root holds its stack journal counts as\nrunning while any stack member's process may still run. The refusal names one such member and\nwrites nothing: the registration, the selection and the root's files stay. A member whose process\nis gone without its stop receipt does not block, and the result warns with `STACK_MEMBER_UNSETTLED`\nthat it was never stopped cleanly. A prepared space is forgotten the same way, and the `.cotal/`\nthat `space create` made stays. A prepared record that cannot be read names no folder, so forgetting\nits space removes only that file, and the result's `root` is null. Forgetting the selected space clears the selection. Forget also\nremoves the per-space preferences saved under that name, so a later space with the same name starts\nwithout them, and the receipt lists each removed key and value so you can set them again. If that\nremoval fails after the record is gone, the run is `partial` and names the `config unset` to finish it,\nafter the `chmod` when the preferences folder is one another account could write.\nForget also removes the stack definitions `cotal up` saved for the space at its root, with their\nstopped instance records, so another folder can start a stack under the same name. The result lists\nthem. While a member was not stopped cleanly, or the root holds preserved state, they stay and the\nresult warns with `STACK_DEFINITIONS_KEPT`. For a member that was not stopped cleanly, the warning\nnames `cotal down --yes` in the root and then `space forget` again. When the stack's folder is\ngone, it names `cotal down --space <space> --yes`, which stops the stack and removes them.\nForget keeps the other services saved under the name, such as one `cotal service add` saved, with\ntheir instance records. The result names them as `keptServices`, and a later space with the same\nname inherits them: its `cotal service` lists them and its `up` names them. A later space in another\nfolder does not start one, as [Running a service](#running-a-service) describes. `cotal service remove\n<name> --space <space> --yes` in that space forgets one. While no space has the name, `cotal service`\nfinds no space for it, so remove a service before you forget its space to drop it at once.\n`status` in a forgotten space's root then selects no space and probes no broker. When nothing left\nin that root's `.cotal/` names a space, its next step is `cotal space create <name> --root <root>`,\nbecause a bare `cotal up` there would start a space named after that folder. A command run\nthere that finds no broker at the default address says that no space is registered or prepared for\nthe folder and names the same step. That refusal has no `Mesh:` line, and its `target` names only\nthe folder, because no space was resolved there.\n\nIn open or static-auth mode, an unanswered `--server` address that differs from the registration\nretained by target resolution gives `TARGET_UNAVAILABLE`, naming the address that did not answer.\nIts Next names the recorded broker and says to drop `--server` to reach it. If resolution retained\nno registration, the Next says to check the address instead. The request is not sent to the\nrecorded broker in its place. Target resolution given `--server` probes only records at that address,\nincluding when `--space` is omitted. `status --all` still checks every recorded address because\n`--all` asks for that scope. This refusal wording does not describe the infrastructure report from\n`status`.\n\n`space delete <name> --dry-run` reads the registered broker in open or static-auth mode.\nIt requires the broker's mode to match the registration, and a static connection must\nconfirm its current account and user identity. It reports the known first-party\nstreams and bucket backing streams as present or absent, the broker-observed\ntransfer-stream targets under a second exact-name grant in static mode, and the\nentries in the presence bucket. Both static connections must confirm their own\nidentity and the same broker server ID. A failed or incomplete native read refuses\ninstead of showing missing resources as absent. The snapshot is non-atomic and presence is not an\nauthoritative liveness or membership check. User-auth deletion inventory and apply\nare not supported. The dry-run removes no live or local state, and a read that covers its whole scope\nexits 0. A space that is prepared and not running, including one stopped with `cotal down`, has no\nlive broker to read or delete from: `space delete` refuses it with `TARGET_UNAVAILABLE`, exit 2.\nFor a stopped stack it names `cotal reset store <space> --yes`, which removes the stored streams, and\n`space forget`, which removes only its local record. For a space that never ran it names only\n`space forget`, because no streams were stored.\n\nThe dry run also reads the space's workflow runs from its records bucket and lists each run that\nhas not finished, with its recorded state. Deleting the streams removes those runs, and a run still\nwaiting for an answer cannot be answered or resumed afterwards. To read a run's open request first,\nrun `cotal up`, then `cotal run show <run>`. The typed confirmation says how many unfinished runs go\nwith the space, and the receipt lists the runs it removed.\n\nOn a space this machine started, the apply refuses `LIFECYCLE_BLOCKED` while the space's own stack\nstill runs here, before any broker connection. On a controller root that is any service instance of\nthe space other than its broker, and on a legacy root any recorded daemon other than the broker.\nDeleting the streams under them leaves a stack that can neither start nor stop. The refusal names\nthe running services and `cotal down`, then `cotal reset store <space> --yes` on a controller root or\n`cotal clean store --force` on a legacy one to remove the stored streams. When a service that is not\none of the stack's members runs, such as one added with `cotal service add`, `cotal down` refuses\ntoo, so the refusal first names `cotal service stop <name> --instance <id> --space <space> --yes`\nfor each one. With only the broker left after that, it names the delete again in place of\n`cotal down`. The dry-run opens with a verdict, as `reset store`'s does: the apply would refuse,\nwith that refusal's reason and remedy, or it is offered, with `cotal space delete <space> --yes` as\nNext. `--json` carries the refusal as `applyBlockedBy` and as a `LIFECYCLE_BLOCKED` warning.\n\nBare `space delete <name>` displays that impact on an interactive terminal and requires\ntyping the exact space name. Its question, and the message under a wrong answer, wrap to the\nterminal with each continuation line under the prompt's bar. A script supplies the exact\npositional name with `--yes`;\n`--json` without `--yes` refuses before a connection. After confirmation the command\nreads the broker again and refuses if the stream states, transfer names, presence\nidentities, registration, trust or broker identity changed. After a typed confirmation it also\nrefuses `CONFLICT`, exit 2, when a stream's message count differs from the impact it showed, as\nwhen a message is posted while the confirmation waits; the refusal names each changed count and\ndeletes nothing. It opens a separately\nminted exact-target connection, checks each stream state again before the first delete,\nand submits one deletion at a time. A native acknowledgement is reported as deleted;\nprior absence is separate, and the receipt lists only the groups that have a name. It then checks each target's stream info and enumerates\ntransfer names again. A surviving or newly observed resource produces a partial\noutcome if deletions were acknowledged; an incomplete post-read stays unknown.\nA refused or unconfirmed delete stops the sequence without retry, reporting\nacknowledged names and refused, unknown or remaining targets. This is not atomic:\na live process can recreate a stream after the final read. The result\ndoes not claim that processes stopped, or remove registrations, credentials or local\nfiles. Open-mode impact and receipt label that any broker client can delete the space.\n`space forget` is not a substitute for live deletion.\n\nCtrl-C or `SIGTERM` during the apply reports what the command left and exits 130 or 143; with\n`--json` the report is the envelope on stdout. Before any delete was submitted the outcome is\n`failed` and the streams are as they were. Once a delete was acknowledged it is `partial`, listing\nthe acknowledged streams and any delete still unconfirmed, with `cotal space delete <name> --dry-run`\nto see what remains. After every delete was acknowledged and before the check that nothing remains,\nit is `unknown`.\n\nThe receipt's Next names the route back. On a space this machine started, the broker keeps running\nwithout the streams. Until they are created again, `cotal history`, `cotal send` and `cotal run`\nrefuse `TARGET_UNAVAILABLE`, exit 2, on an auth or user space and on an open space this machine\nstarted: the streams are not on the broker, and the remedy names `cotal up` in the space's root.\n`cotal status` shows `Space streams: not on the broker` and puts the same step first in its Next.\nOn a controller stack whose other members are stopped, a bare `cotal up` in its root creates them\nagain, empty, on that broker, the way a broker start does, and starts the other members. Its receipt says\n`Space streams: created again, empty, on the running broker`, and `--json` sets\n`result.streamsRecreated`. It does so only when every one of the space's streams is gone. A broker\nthat lost only some of them keeps them as they are, and `up` refuses `TARGET_NOT_FOUND`, exit 2,\nnaming `cotal down --yes`, then `cotal up`, whose broker start creates the missing ones. While the\nother members run, as when another machine deleted an open space under the stack, a bare `cotal up`\nfinds the stack running and changes nothing. The refusals and `status` then say the space was\ndeleted under its running stack and name `cotal down --yes`, then `cotal up`. They say so only when\nthe broker answers that the chat stream is not found. A broker that answers no JetStream request\nhas not lost its streams: nats-server stops JetStream after a store write it could not make and\nkeeps the streams in its store. `send` and the other commands that read the space first then refuse\n`DEPENDENCY_UNAVAILABLE`, naming the write failure the broker's log records and its repair, and a\nbroker restart brings the history back. To drop\nthe space instead, run `cotal down --yes` in its root, then `space forget`. On a legacy root, run\n`cotal down`, then `cotal up` to start it again with empty streams.\n\n`use <space>` selects the space commands target when none is named, outside another space's\nproject folder. It changes only the selection:\nit starts nothing and does not check the broker, so a stopped or prepared space can be selected and\nthe command that targets it reports that. Selecting the selected space again is a `no-op`. A name that is not\nregistered or prepared is refused with `TARGET_NOT_FOUND`, exit 2, naming the known spaces, and the\nselection stays. That refusal has no `Mesh:` heading, and its remedy names `cotal use --clear` only\nwhile a space is selected. Bare `use` reports\nthe selection. Inside a project whose root has one registered space other than the selection, `use`\nalso says that commands in the folder target that space while it runs, and `--json` gives it as\n`folder`; `use` reads no broker, so it does not say whether that space runs. `cotal space` and\n`cotal status`, which read its broker, give `folder` and `meshes.folder` as null when it does not\nanswer, and the listing does not mark it as this folder's. A selection that names a space no longer registered is reported with a\n`SELECTION_NOT_REGISTERED` warning by `use` and `space`, and stays until `use` changes it.\n`use --clear` removes the selection, so a command targets its folder's space again. Its receipt\nnames the space that was selected, and `--json` gives `current: null` with that `previous`. With\nnothing selected it is a `no-op`, exit 0. It takes no space name.\nA space stays selected through its `cotal down`: `use` then reports it as stopped, with its last\noperation, such as `cotal down, completed`, and names `cotal up` in its root. It stays selected until\n`use` changes it. On a controller root, `up --json` gives\n`result.selection`, with `current`, the space selected after the start, and `changed`, which is true\nwhen this `up` selected its own space because no space was selected.\n\nA command run without `--space` targets the selection. When the selected space's broker is down it\nrefuses with `TARGET_UNAVAILABLE`, exit 2, and when the selection names a space no longer registered\nit refuses with `TARGET_NOT_FOUND`. Neither reaches another mesh.\n\nA folder's space captures the commands run there only while it runs. Inside a project whose root has\none registered space other than the selection, and whose broker answers, the command targets that\nspace, as it does with nothing selected, and the note under its `Mesh:` heading, a refusal's\nincluded, says the selection applies outside the folder. A space `cotal space create` prepared there\nand no `cotal up` started, and one whose stack there is stopped, is the target of the commands about\nthe folder itself only: `cotal up`, `cotal down`, `cotal space show` and the folder part of\n`cotal status`. Every other command run there targets the selection, and the note under its `Mesh:`\nheading says what this folder holds and the `cotal up` that starts it. A refusal there keeps that\nnote whole, as for a selection whose stack is stopped, which names its own `cotal up` under Next.\n`--json` carries the same note in\n`warnings` as `FOLDER_SPACE_NOT_RUNNING`, whose `target` names the folder's `space`, its `state`\n(`prepared` or `stopped`), its `root` and the `selected` space, and `cotal chat`, which prints no\nheading, names the selection it joins with that note on stderr. `cotal space show` there calls a\nregistered folder space whose broker does not answer this folder's stopped space. A folder that holds\nthe records of a stack that runs from another folder or under another Cotal home, as a copy, a moved\nfolder or one whose space name a later `up` took over, is not released to the selection: commands\nthere refuse `TARGET_UNAVAILABLE` and name that folder or home, as they do with nothing selected.\nFor a stack that runs from another folder, the refusal heads with `Mesh: <space>` and carries\n`target.space`, naming the space this folder's records name, as `up` and `down` there do.\nNor is a folder whose own stack's last `up` or `down` ended before it recorded an outcome while a\nmember still runs, as an `up` cut off before it registered the space leaves its broker. That stack\nruns in part, so every command there targets it, as with nothing selected, and the note under its\n`Mesh:` heading says the selection applies outside the folder. `cotal status` there reports it as\n`Half started` or `Half stopped` with its Next, which for that `up` is `cotal down --yes` and then\n`cotal up --root <root>`.\nInside a project whose root\nhas several running spaces, none of them the selection, the command targets the selection. Before it\nacts, it warns on stderr and in its JSON `warnings` with `SELECTION_NOT_FOLDER_SPACE`, naming those\nspaces and `--space <space>`.\nAn empty `--space` value, such as `--space \"$S\"` with `S` unset, is refused with `USAGE_INVALID`,\nexit 1, by every command that takes `--space`, before anything is read or written. It never falls\nback to the selection.\nA `--server` URL with a user name or password in it, such as `nats://<user>:<password>@<host>:<port>`,\nis refused with `USAGE_INVALID`, exit 1, by every command that takes `--server`, before anything is\nread, written or dialed. The refusal does not repeat the value. The NATS client uses only the host\nand port of that URL, so the command would otherwise connect with the credential it resolves. Pass\nthat credential with `--creds`, a login or the space's own trust instead. `up` refuses such a URL\nwith its own message. The manager reads `COTAL_SERVER` when `--server` is unset, and refuses a\n`COTAL_SERVER` with a user name or password the same way, naming `COTAL_SERVER`. `space add` and\n`meshes add` refuse such a server in a connection bundle, and the `meshes add` prompt refuses one\ntyped there, in the same words and with the same next step.\n\nA prepared space whose project folder was deleted cannot start. A command that targets it, `persona`\nincluded, refuses with `TARGET_UNAVAILABLE`, exit 2, saying the folder no longer exists and naming\n`cotal space forget <space>`. `cotal space create <space> --root <dir>` then prepares it again.\n`use`, `space show`, `up --space` and `space forget` also say the folder no longer exists, and\n`space show` names the same forget and create steps in place of `cotal up`. Run from the folder it\nwas moved to, whose stack journal names the old folder, such a command says the folder was moved and\nnames the next step that `up` and `down` name there.\n\nWith nothing selected, a command run in a project targets the space registered for that project's\nroot. A root with no registration whose `.cotal/` holds the stack journal `cotal up` writes is\nrefused with `TARGET_UNAVAILABLE`, exit 2, naming the broker the journal recorded and\n`cotal space create <space> --root <root>`, which prepares the journal's space there again so that\n`cotal up` starts it. That is the state after an `up` that failed to start or a `space forget`, and the\ncommand never dials the default address instead. `status` there reads the machine, selects no space\nand probes no broker. A legacy root after its `down`, which drops its registration, is refused the\nsame way, naming the space its account names, the address in the broker configuration its last start\nrendered and the bare `cotal up` there that starts it at that address again. A space named after its\nfolder keeps its registration, so a command there is refused `TARGET_UNAVAILABLE` naming that root\nand `cotal up`, and `status` names the same step. A selection does not change these refusals: a root\nthat holds such a journal or configuration and no registration, as a moved project does, is refused\nthe same way and never reaches the selected space.\n\nA copied or moved project folder keeps a stack journal that names the folder it was recorded in.\n`space create <name>` there refuses `CONFLICT`, exit 2, and names `cotal space create <name> --adopt\n--root <root>`, and `status` names the same command. `--adopt` takes the journal over: it rewrites it\nto name this folder, moves a broker store that lay under the recorded folder to the same place under\nthis one, and records a stack that never ran here, so the next `cotal up` starts it from this folder\nwith the copied store. It refuses with `CONFLICT`, writing nothing, when the folder's `.cotal` is a\nlink, when the recorded folder still holds the journal, because this folder is then a copy of a stack\nthat runs from there, when this machine records an instance of that stack, because the folder was\nthen moved on this machine and moves back, and when the space is prepared or registered on this\nmachine, where its Next names `cotal space forget` first. A copy brought from another machine and a\nfolder moved after its record was forgotten are adopted. A folder renamed from a path that `cotal up`\nrefuses, such as one with a tab in it, is told only to adopt, since moving it back would not start\nthe stack. Without `--adopt`, nothing rewrites a journal.\n\nWith no `--space`, `--server` or `--creds`, nothing selected and no project in the folder, a command\nthat several running spaces could serve asks which one when it runs in a terminal. The answer is the\ntarget of that one run, and the selection stays as it is; cancelling the question does nothing and\nexits 0. A script, a `--json` caller and `--no-input` are not asked: the command refuses with\n`TARGET_AMBIGUOUS`, exit 2, naming the running spaces. Every refusal of a target puts what to run next\nin `error.remedy`, apart from `error.message`, and names the space and root it concerns under\n`target` when they are known, as for a prepared space that is not started.\n\n`--creds <file>` presents that credential to `--server`. Without `--server`, it goes to the broker\nits space is registered at and keeps that registration's TLS requirement. The space is `--space`,\nelse the selected space, else the space of the project the command runs in, else `main`. Inside a\nproject whose root has one space other than the selection, it is that space. A command never picks the only running space for a credential. A space not registered here is refused with\n`TARGET_NOT_FOUND`, exit 2, before anything is dialed. The refusal says whether it is the selected\nspace, this folder's space or the default space, and a space other than the default is also under\n`target`. The default address is never assumed for a credential.\nThe file is read and checked with the NATS client's own parser before anything is dialed, and the\nrefusal names the file. A missing file is `TARGET_NOT_FOUND`, exit 2. A directory, an empty file and\na file the client cannot parse are `SCHEMA_INVALID`, exit 1, with the cause: no complete JWT block,\nno seed block, an invalid seed, or Windows (CRLF) line endings. A file in a folder that another\naccount can write, or below one, is refused `PERMISSION_DENIED`, exit 2, as `auth mint` refuses to\nwrite one there: the refusal names the folder, and its Next is one `chmod`, such as `chmod go-w <dir>`.\nA file that other accounts can read is still used, with a warning on stderr that gives the\n`chmod 600` command. `join --creds` reads its file the same way. A credential the broker rejects is `PERMISSION_DENIED`, exit 2. When this\nmachine holds the space's account and that account revokes the file's key, the refusal says when it\nwas revoked with `auth revoke-credential`. Its Next is a mint with a new key, which for an agent file\nnames the read and post channels and the role the file held. When the\ncredential's own permissions name one other space, the refusal names that space, such as\n`credentials rejected at <server>: <file> is a credential for space \"<owner>\", not \"<space>\"`, and\nits Next is `--space <owner>`. When `--server` named another broker than the one that space is\nregistered at here, the Next says to use `--space <owner>` without `--server`, naming that broker.\nKeeping `--server` with `--space <owner>` refuses again, saying the space is registered at another\naddress, and the Next is to drop `--server` or point it at that broker.\n\nA manifest's `space:` is the target of native `up -f` and the new team\ncheck/boot/deploy routes. A `--space` that names another space is refused before\nanything is written. The old `spawn -f` manifest spelling no longer deploys;\n`team deploy` applies all-new live channel-only manifests or bounded\nstatic/open agent and mixed manifests. Reapply remains unsupported.\n\n`cotal meshes` still lists, registers and drops meshes. `space`, `space add`, `space create` and\n`space forget` replace it, and its `--json` lines are the old shape. A bare `meshes add` on a terminal is still the\nguided form.\n\n## Mesh registry\n\n```bash\ncotal meshes [--json]\ncotal meshes add # guided, on a terminal\ncotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open|user] [--tls] [--allow-unencrypted-overlay] [--force]\ncotal meshes add <space> --mode user (--user-auth-file <bundle.json> | --from <https url>)\ncotal meshes rm <space> [<space> \u2026] [--force]\ncotal sync [--idp <auth base URL>] [--json|--plain]\ncotal status [--json|--plain] [--all] [-v|--verbose] [--space <s>] [--server <url>]\ncotal status --components [--space <s>] [--server <url>]\n```\n\n`cotal meshes` is a legacy command. It still runs, and `cotal help meshes` still prints its help,\nbut `cotal help` no longer lists it and completion does not offer it. `cotal space` lists the\nspaces, `space add <name>` registers one that runs elsewhere, including a user-auth space with\n`--file` or `--from`, and `space forget <name>` drops a registration.\n\nEveryday command forms use positional agent names: `cotal attach <name>` and `cotal stop <name>`.\nFor one-shot messages, choose one destination with `--to/-t`, `--channel/-c` or `--role/-r`:\n`cotal send -t reviewer \"Please inspect the patch\"`. Message receipts do not repeat the body.\nThey distinguish storage from delivery and say that no reply listener remains active.\n\nBare `cotal` shows a compact list of useful actions. `cotal help` shows the same list, then every\ncommand by group. `cotal help --all`, the spelling from before help listed every command, prints the\nsame. A hidden command, such as a deprecated daemon entry, is left out and still runs. `cotal cfg` is the shorthand for `cotal config`. Its default view\nshows effective values and sources; `config --verbose` adds the storage path and revision.\n\nStatus starts with This machine, lists the spaces this machine knows under Spaces, then shows the\nselected space, this folder's maintenance journal under Maintenance when it holds one (its state,\nattempt and next step; `maintenance` in JSON), unsettled fresh and additive attempts in this folder\nwhen present, owned service records and useful commands under Next. Connection state, local process\nstate and service replies remain separate observations.\n\nThis machine shows what is set up and what needs attention. The version line is always there. Agent\napplications is one line: the applications whose required executables are on PATH, and a count of the\nothers, which `cotal setup` adds. An application's own rows, such as the Claude plugin, Claude\nDesktop and Claude skills, appear for an application that is set up and only when they are not\ncurrent. The broker executable appears when it is missing or when `up` refuses it. The dashboard\nappears when its record cannot be read. The extensions are one line, `11 extensions at 0.103.3`, and\nan extension at another version is the only one named.\n\nSpaces puts the space this report is for first, with its connection state, then one line per space:\nits name, its connection state and how it signs in. Only the space being reported is dialed, so the\nothers carry no state and one note says that unselected spaces are not dialed and\n`cotal status --all` dials each. The broker address is\nnot a group title here; `cotal space show <name>` names it. An account whose space catalog could not\nbe refreshed is one line naming that account.\n\n`status --all` and `status --wide` report every row in full: one row per known application, every\nextension with its version, the spaces grouped under their broker addresses with their origin, and\nthe broker executable and dashboard rows. `--all` also dials every recorded broker; `--wide` dials\nno more than the default report. Use `status --verbose` for installation paths, extension records and\nthe detailed local diagnostics.\nThe default report does not include the agent roster; use `ps` or `who`.\nThe deployment-attempt section lists at most 100 fresh and 100 additive\nlocal IDs, their returned/unsettled state and unreadable paths, with full\nreadable coordinates in JSON.\nIt appears only when there is something to inspect. A missing fresh summary\ndoes not prove the original broker never started; an additive intent does not\nprove which resources changed. This read grants neither retry nor stop\nauthority. A fresh summary that names another folder, as one does after its folder was moved or\ncopied, is listed with its recorded path and is not counted as unreadable. If the folder inherits\nthe machine's home `.cotal`, `status --verbose` says that `up` there makes the folder its own project\nroot. When the home holds a stack an earlier release started there, it also says that commands in the\nfolder reach that stack until then.\n\nA prepared space whose controller stack ran and was stopped reads as stopped, with its last\noperation and how it ended, such as `cotal down, completed`, and Next names `cotal up` in its root.\nA space that never started still reads as prepared but not started. When the stopped stack recorded\na file store cap below 67108865, the least the space's streams start on, the selected space shows it\nin a `Store cap` row, and Next names `cotal up --max-file-store 67108865` or more instead, because a\nbare `up` would fail on that cap. `--json` carries the cap as `selected.storeCap`, and\n`meshes.currentStopped` says whether the selection is such a stopped stack. A stack whose last `up`\nor `down` has no recorded outcome is not stopped, even when a member it launched still runs: the\nselection reads as prepared but not started, and the selected space names that operation.\n\nOwned services use the saved controller definitions and instance records for the selected space.\nInstances are listed by definition name, then instance ID, in the text and in\n`result.services.instances`, so their order holds from one start to the next.\nFull instance IDs and execution hosts stay visible. Process observations and historical readiness\nare separate columns; neither a saved serving state nor a supervisor receipt becomes a fresh\nendpoint response or OS unit-state check. Legacy PID-file rows are labeled separately. On a root\nwhose controller stack serves the selected space, its members are the `stack-` rows here, the\nlegacy rows and the stored-state hint are omitted, and when the stack does not answer or a member's\nprocess is gone, Next offers `cotal up --root <root>` first, with `<root>` quoted when a shell would\nsplit it. On a stack whose last `up` was `--no-manager` it adds `--no-manager`, and a manager record\nan earlier `up` left does not count as a stopped member. When a service of the space outside the stack runs one of this root's records, such as a\n`delivery` preset started after `cotal down stack-delivery`, `up` refuses until it stops, so Next\nfirst names that service's `svc stop` with its instance, as the refusal does, and then\n`up --root`. In open or static-auth mode, an unreachable `--server` address other than the recorded\none says nothing about whether the stack answers. Next offers `up --root` only for a member whose\nprocess is gone, and names the failed address the way other commands' `TARGET_UNAVAILABLE` refusal does.\nWhen an earlier `down`, or a foreground\n`up` whose terminal closed, ended after it signalled the broker or the delivery daemon and before it\nsaw that process exit, `up` and `down` refuse until that stop is abandoned. `status` then says so\nunder the selected space, names the member, and offers `cotal down --abandon-stop <request-id> --yes`\nwith the request ID their refusal names, and the member's `service status`, in place of `up --root`.\n`--json` carries the same as `folder.stackBlock`. A manager stop is decided by the manager's own\nreceipts, which only `up` and `down` read, so `status` does not name it. An `up` or `down` that was\ninterrupted before it recorded an outcome, and left some members running and others stopped, is\nnamed under the selected space as `Half started` or `Half stopped`, with the members on each side.\nNext then offers the command that finishes it first: `cotal up --root <root>` for an `up`, with\nthe root shell-quoted when needed and `--no-manager` kept when it was chosen, or\n`cotal down --yes` for a `down`, followed by `cotal up --root <root>` to restart instead.\nThe restart root is also shell-quoted after an interrupted `down` or when recreating deleted\nspace streams. `--json` carries it as `folder.stackUnfinished`, with `kind`, `id`, `running` and `stopped`. An `up` cut off\nbefore it registered the space leaves the selected space prepared and not started, and `status`\nnames its stack's block or unfinished operation and the same Next under it. When that `up` left\nmembers running, the next `up` cannot finish it, so Next offers `cotal down --yes` and then\n`cotal up --root <root>`. With another space selected, `status` in that folder still reports this\nstack, under `This folder's space`, and so does every other command there while a member runs.\nWhile the stack is blocked, the `Delivery responder` row says that `up`\nrefuses until the block is cleared. The next bare\n`up` or `down` settles the interrupted operation with an `OPERATION_SETTLED` warning. When the\nstack reached that operation's end anyway, every member running after an `up` or stopped after a\n`down`, the warning says it was interrupted and where the stack stands, without calling it failed.\nA running `stack-manager` gets one\nprobe of its manager endpoint, and the selected space's `Manager` row reads `answering`,\n`not answering \xB7 stack-manager pid <pid>`, or `endpoint not checked` when the probe could not be\nmade or another manager instance answered. A manager that does not answer puts `kill -KILL <pid>`\nfirst in Next, because `cotal down` cannot stop it; once that process is gone, `cotal down --yes`\nrecords it stopped. A folder this account cannot read is one warning that names the folder and\n`chmod u+rx` on it, and nothing in it is read. Unreadable\nservice record families make the report partial instead of becoming empty lists. Warnings retain\na classified cause code, such as unsupported host observation, without native diagnostic bodies. These reads\nimport no provider and start, stop, adopt or repair nothing. `--all` extends broker checks; this\nlocal owned-service section retains its explicit selected-space scope.\n\n`meshes` lists the meshes this machine knows; `(default)` after a name marks the `current` default\na bare `cotal spawn` joins. A terminal too narrow for its columns gets one labeled record per mesh,\nas other tables do. Entries learned from a signed-in account are marked `discovered`. Their\nregistration trust is stored under the account's private auth state, and the registry contains no\nsession token or sentinel credential bytes. Commands resolve the catalog `slug`; a different human\n`name` is rendered only as a label.\n\n`meshes --json` prints one JSON object per recorded mesh per line: `space`, `server`, `mode`,\n`root`, `default` (the row marked `(default)`), and `origin` (`up`, `manual`, or `catalog` for a\ndiscovered entry). A local or hand-registered entry also carries `offline`. A discovered entry is never probed, so it has\nno `offline` field. `tlsRequired`, `events: \"required\"` and a discovered entry's `catalogName` appear\nonly when the record has them. An empty registry prints nothing and exits 0. The note about a default\nthat matches no record goes to stderr, so stdout carries only rows, on a first run too. The table is\npresentation and is not a stable parsing target. `meshes add` and `meshes rm` refuse `--json`.\n\nA registry record this build cannot use is refused by name, never rendered and never skipped. One\nthat does not parse, or is missing a field every consumer reads (`server`, `mode`, `root`, `ts`,\n`space`), makes every command that reads the registry refuse with `SCHEMA_INVALID`, exit 1, before\nit acts. The refusal names the file under `target` without repeating its stored values; `meshes`\nalso says what is wrong with it. `status` is the exception: it lists the readable records, names the\nfile and exits 4. Remove the file or restore the record; nothing repairs or invents a field for you.\nA registry directory that exists but cannot be read, such as one without read permission or a\nregular file in its place, refuses the same way, naming the directory, and `meshes` gives the read\nerror. Only an absent directory is an empty registry. The folder of prepared spaces,\n`~/.cotal/prepared`, refuses the same way when it exists and cannot be read, on every command that\nreads it, such as `space`, `space create`, `use <name>` and `up`. A file in its place is named as\n`<path> is a file, not a folder`, with the step that moves it aside. A record in it that does not parse\nor is not a prepared space record refuses only a command about the space its file is named for, such\nas `use <space>`, and names `cotal space forget <space>`. Other commands skip it with the warning `PREPARED_RECORD_SKIPPED`, which names the\nfile and `cotal space forget <space>`, the step that removes it, and `status` lists it under\n`meshes.unreadable` and exits 4. A record removed between listing and reading\nis omitted from that listing. A keyed read also treats a record removed during its read as absent;\nparse, shape and other read failures still refuse by name. Recording or removing a mesh also sweeps\nthe directory for an older-format record of the same space. Any error listing the directory for\nthat sweep, other than an absent directory, fails the operation with that error.\nAn unknown `--space` names the missing registration and points to `cotal space`, which also\nlists prepared spaces.\n\nAn IdP may advertise a same-origin space catalog during login. Cotal reads the complete snapshot and\nadds every valid registration without a separate `meshes add`. A snapshot younger than five seconds\nis used without a request. After that, commands that resolve a mesh target conditionally refresh the\nsaved catalogs. An operation targeting a discovered space refreshes only that space's account and\nrefuses if that account fails. Operations targeting local or manually registered meshes refresh every\naccount and continue. They warn once when an account's catalog starts failing and stay quiet while\nit keeps failing; the next failure after a successful refresh warns again. `cotal status` refreshes every account,\nnever refuses on a refresh failure, and lists each account as `fresh`, `updated`, `not-modified`,\n`no-catalog`, or `failed` with its error. With no cached sign-in, catalog preparation reads without\ncreating a control directory or lock. An explicit IdP without a cached login refuses with\n`AUTH_REQUIRED`. An unreadable sign-in or catalog cache refuses with `DEPENDENCY_UNAVAILABLE`\nbefore refresh or registry application. Both exit 2 and leave the cache untouched; diagnostics\nname the path without printing cache contents or parser excerpts.\n`cotal sync` bypasses freshness and reports added, changed, removed, unchanged, and name collisions. `--idp` limits it to one signed-in account. It never connects\nto a broker. `--json` gives each account under `accounts` with its `idp`, `state`, `catalog`, the\nfive name lists, `selectionCleared` and `error`. An account whose catalog could not be refreshed keeps\nits last snapshot and is named in a `CATALOG_UNAVAILABLE` warning, so the result is partial, exit 4;\nwhen no account could be refreshed, `sync` refuses with `DEPENDENCY_UNAVAILABLE`, exit 2.\n\nThe registry is updated under the same lock that guards the catalog cache, so a command never lists\na discovered space set that another command is still writing. The cache records a fetched snapshot\nas not yet applied before the first registry write and as applied after the last. If a command dies\nor is stopped in between, the next command applies that snapshot again before it can use it, with\nno request inside the freshness window.\n\nThe shared dispatcher applies this preparation to every command that declares both `--space` and\n`--server` as mesh-target flags, including commands registered by other packages and commands that\ndeclare their own equivalent flag objects. Daemon and startup commands that use those names only as\nconfiguration explicitly opt out. Registry-local `meshes add` and `meshes rm` never refresh a catalog.\nWhile the registry holds a record this build cannot use, the preparation neither refreshes nor\napplies a catalog, so the command's own checks run first. A snapshot left unapplied is applied by the\nnext preparation after the record is restored or removed. A command that resolves its target through\nthe registry still refuses the record by name.\n\nRun on a terminal with the space or `--server` missing, **`meshes add` is guided**: it asks for the\none thing that cannot be derived (the broker URL), probes it, and tells you what answered - open or\nrequiring credentials. It then offers the spaces your `--root` already holds credentials for, and\non an open broker the spaces that broker serves, states\nthe mode as a fact about that broker rather than asking, and shows the exact record before writing\nanything. A broker that does not answer, or a space name already registered, becomes a choice rather\nthan an error. For a name added by hand, the first choice is another name. Replacing a registered\nrecord then asks you to type `y`; Enter keeps No. Nothing is written before that last question, and\na cancel at any question says nothing was registered. A name prepared or started on this machine, or\nlisted by a catalog, is not offered for replacement. Anything you pass on the command line is taken as given and not asked again. Without\na terminal - a script, an agent, CI - nothing prompts and the flag form's errors stand\n(`COTAL_NO_PROMPT=1` forces that too).\n\n`cotal up` and `cotal down` maintain their own records. `meshes add` registers a mesh they cannot\nspeak for: one running on another machine, a shared broker, a hosted space. Like `space add`, it\nrefuses a space name `space create` refuses, exit 1, with nothing written, even with `--force`. A\nmesh an earlier release registered under such a name still lists, shows and removes as before. `--root` is the folder\nwhose `.cotal/auth` holds that mesh's credentials and whose `.cotal/agents` holds its personas.\nThe default is the project you run it in. The registry stores that path, never a secret. `--mode`\ndefaults to `auth` when the root holds the space's account record and to `open` otherwise. The\nbroker is probed before anything is recorded, so a wrong address, or credentials that mesh will\nnot accept, fails here instead of at the first `spawn`; `--force` records without verifying (and\nreplaces a record added by hand). A name that is prepared on this machine, or that this machine\nstarted or a catalog lists, is refused with `CONFLICT` (exit 2), with or without `--force`, as\n`space add` refuses it: `cotal down` finds a running stack through that record.\n\nA hostname or public address is registrable only when the connection will **require TLS**. Pass\n`--tls`, or use a `tls://` URL. Without either, the refusal's next step is the `tls://` form of the\nsame address, or `wss://` for a `ws://` one. The scheme is recorded as enforced intent, so every later dial\nthrough the record demands the handshake (and `meshes add tls://\u2026` against a plaintext broker is\nrefused at registration). Without required TLS the fence admits loopback literals, and\nprivate-overlay literals only with `--allow-unencrypted-overlay`, which records your acceptance that\nthe overlay is protected only while its tunnel is up ([Run a mesh](run-a-mesh.md#which-addresses-you-may-register)). RFC1918 addresses are refused in both modes because a cafe LAN is private but does not belong to you.\n\nA **user-auth** mesh registers from supplied pinned trust, never guessed: `--user-auth-file`\ntakes the bundle exported where the mesh runs; `--from` asks before it dials the address at all,\nthen fetches the `/.well-known/cotal-mesh` discovery document under that address (HTTPS only; a URL\nthat already ends in that path is fetched as given), displays the pins, and asks again before\nadopting them. The document must parse as JSON under any content type the host serves it with; an\nanswer that does not is refused with the URL and the content type that came back. Neither fetch follows redirects: a 302 can move a pinned fetch\nonto plaintext or onto another host, so it is refused rather than followed, and the pinned\nexchange must itself be an `https://` URL, except for an exchange on this machine, where plain\n`http://` is accepted for a loopback *literal* (`127.0.0.1`, `::1`, any spelling of them) but not\nfor `localhost`, which is a name rather than an address. Registration verifies that the exchange\nanswers `/health` and `/jwks` as the pinned issuer. It also verifies that the broker refuses a bare\nconnect; that auth-required refusal is the pass. The sentinel credentials land in a 0600 file under\nthe entry's root; the registry records only the path.\n\n`meshes rm` drops records. It never stops a mesh. For a mesh running on this machine `cotal down`\nis the right verb, and `rm` refuses and says so, with or without `--force`, because `cotal down`\nfinds a running mesh by its record. A stack `cotal up` started stays a prepared space at its root\nafter that `down`, which `meshes rm` does not list or remove, so the refusal names `cotal space\nforget <space>` for it. `--force` drops a record only when its folder cannot be checked\nfor a running mesh. A broker record that cannot be proven\nstopped counts as running, as it does for `clean`. A hand-added record is removed by\n`meshes rm`, by an `add --force` replacement, or by a `cotal up` that actually starts the broker for that same space, server and root, which becomes that\nmesh and so takes the record over (a `cotal up` for that space anywhere else refuses instead).\nNothing that merely *infers* a record is stale from a dead broker touches it: an\nunreachable broker is listed `offline` and stays, whether `cotal up` or `cotal meshes add`\nwrote the record; a foreground `up` whose broker exits unexpectedly keeps its record the same way. A broker that answers at a record's address and refuses the connection is read the same way\nwhile another registered space names that address: the broker there is that space's, so the record\nis kept and the refusal is `CONFLICT`, exit 2, naming the space that holds the address, the\n`cotal space forget <space>` that frees it and the `cotal up --server <url>` that moves this one.\n`cotal status` shows that space as `address held`. Deleting the record would leave its folder\nnaming no space, after which nothing there says which space its commands meant.\nA bare command does not treat that offline record as a running mesh;\nname it with `--space` to restart it. An operator command whose target is the selected space, or\nthe space registered at its project's root, probes no other recorded broker. When it picks among\nthe running meshes instead, it probes the recorded brokers once, and its mode check and its\nconnect both read that one result. Managed-seat control resolution and commands that inspect all\nspaces keep their existing probe behavior. `cotal down` / `cotal clean all` still drop an `up` record for the project\nthey tear down; a hand-added one they leave alone even when it shares a root, because nothing\non this machine could write it back.\n\nA discovered entry belongs to the normalized IdP origin and proved subject that supplied it. Local\nteardown, cleanup, and liveness pruning do not remove it. A manual or locally started entry with the\nsame name wins and remains untouched; that discovered name is reported as a collision. Logging out\nremoves only the discovered entries owned by that account.\n\n`cotal meshes` and `cotal status` print `events: required` for a registration carrying\n`policy: { events: \"required\" }`. On that space, foreground spawn, detached spawn and manager\nstarts that host a native harness cannot opt out or launch without an event plane. `--no-events` is refused with\nthe space named. A connector without an event plane is refused with both the space and connector\nnamed. A session whose own grant omits `events.<owner>.<actor>` is refused before joining and the\nmessage names a full-row `actor grant` repair. A running seat whose event plane stops keeps running\nand keeps its mesh connection on that space as on any other; the connector re-establishes the plane\nitself and reports every failure with the delay before the next attempt. A seat the manager resumes\nfrom a preserved cut meets the same rules as a fresh\nspawn: a seat retained with `--no-events`, or on a connector without an event plane, is refused, and\na resumed seat carries the requirement in its launch material.\n\n`join`, one-shot sends and passive protocol views host no native harness turns. They publish no\nAG-UI runs, regardless of who drives their input. Required events do not turn terminal messages\ninto agent runs or copy channel and DM text into an event stream. This launch policy is not an\nadditional broker authorization boundary. The broker still enforces each endpoint's grants.\n\nThe selection `cotal use` sets applies from every directory except the project folder of a running\nspace, where the command targets that folder's space. There `status` reports that space under its\n`Mesh:` heading and a `This folder's space` section, and says the selected space applies outside the\nfolder. `--json` names it in `target.space` and in `meshes.folder`, which `cotal space --json` gives\nas `folder`. In the folder of a space that is prepared or stopped, `status` reports the selection\ninstead, and the line under its heading names what the folder holds and the `cotal up` that starts\nit; `meshes.folder` is then `null`. A folder whose stack is half started or half stopped, with a\nmember still running, counts as a running space's folder here. In a copy or a moved folder, where the commands refuse under the\nspace the folder's records name, `status` reports that space under its `Mesh:` heading, marks it in the\nSpaces list when it is registered, and says what the folder holds in a `This folder` section, with the step those commands\nname under Next. `--json` names that space in `target.space` and `selected.space`, and the recorded\nfolder in `selected.recordedRoot`. `status` is a read-only report in this order: this machine\n(the installed `cotal-ai` version, NATS and each named integration), the installed extensions and\ntheir versions, this folder's `.cotal/`, the recorded meshes, the selected mesh (its connection, the\ndelivery responder, its channels and membership feed), then the hints. It lists no roster; `cotal ps`\nlists managed seats. Stale Claude skills and out-of-date `.agents` skills recommend\n`cotal setup --skills`, not unscoped `cotal setup`. `status` takes `--space` / `--server` to pick the\nmesh to inspect; it starts nothing.\n\nStatus displays broker addresses without URL user information, query parameters or fragments,\nincluding component facts. Saved addresses and connection inputs are unchanged. An address that\ncannot be displayed safely is `null` in JSON, `unavailable` in text, and names a\n`BROKER_ADDRESS_INVALID` warning with a partial result. Its broker component is refused.\n\nOnly the selected mesh is checked. Its row under Spaces dials its address with its recorded TLS\nrequirement, and every other row carries no connection state, under the note that says what was\ndialed. When `--server` names another address, that address is the only broker dialed, and the\nselected mesh's row carries no state either. In `--json` each of those rows is `not-checked`. `--all` checks every recorded mesh, including discovered ones, each with its own\nrecorded TLS requirement. Each row then shows the check of its own address, the selected mesh's\nrow included when `--server` names another.\n\n`status --json` writes one envelope whose `target` is `{space, scope}`, where `space` is the selected\nspace or `null` and `scope` is `selected` or `all`. Its `result` is the report the text draws:\n\n| Field | Holds |\n|---|---|\n| `kind` | `infrastructure` |\n| `machine` | `version`, `install` (`kind` and `root`), `nats` (`path`, `bundled` or `missing`), `integrations` (each `{name, state, text}` a connector's setup reports), `connectors` (each `{name, requires, missing}`), `skills` (`state`: `current`, `not-installed`, `behind` or `unreadable`), and `web` (`installed`, `url` and the error of each when it could not be read) |\n| `extensions` | `root` and `items`, each `{package, label, version}` |\n| `folder` | `root`, `auth` (`state`: `space`, `spaces`, `none` or `unreadable`), `personas` (`root`, `default`: `valid`, `invalid`, `missing` or `unreadable`, `demo` and `error`), `launches` (whether a bare spawn launches from this folder's catalog; `null` when no mesh resolves), `processes` (each `{name, state, pid, detail}`, `state` one of `running`, `stopped`, `not-serving`, `unknown` or `unreadable`), `storedState`, `noManager` (whether this root's controller stack was started with `--no-manager`) and `managerService` (the service of the space, outside that stack, whose process runs this root's manager, as a `manager` preset started there does: its `definition` and `instanceId`, or null), `heldServices` (the services of the space, outside that stack, whose processes run this root's records, as `up` names them before it refuses: each `definition`, `instanceId`, `record` and `pid`; null when those records could not be read) |\n| `meshes` | `current`, `folder` (this folder's running registered space when a command there targets it, else null), `unreadable` (the registry directory, a record, the current-mesh pointer, or the prepared spaces folder or a record in it, that could not be read, or a pointer that is empty, each as `{file, error}`), `items` (each with `space`, `server`, `mode`, `root`, `origin`, `tlsRequired`, `eventsRequired`, `current` and `check`: `reachable`, `unreachable` or `not-checked`) and `catalogs` (each `{idp, account, state, error}`, where `account` is the IdP subject the error line names) |\n| `selected` | `{resolved: false, code, message}` when no mesh resolves, where `message` states why and `hints` carries the step (`code` is `registry-unreadable` when the registry directory or a record could not be read, and a `not-started` prepared space adds its `space` and `root`); otherwise the target's fields, `connection` (`ok` or the reason it failed; on a user-auth mesh `reachable` or `unreachable`), `login`, `deliveryResponder` (`bound`, `unbound`, `stale` or `unknown`, and `null` on an open mesh with no delivery lease), `snapshot`, `channels`, `membershipFeed` and `spaceStreams`. `spaceStreams.state` is `present`, `absent` or `not-checked`; it is read for an auth or user space and an open space this machine started. When `absent`, `root` is this machine's stack of the space and `stack` is `running` or `stopped`, each `null` when unknown. `storedBytes` is what the space's account holds in the broker's JetStream file store, `null` when it was not read |\n| `services` | Selected-space local owned records: `space`, `state` (`read`, `partial` or `not-checked`), non-atomic `observedAt`, public definition/instance projections and explicit scope. An unreadable family is `null`; stored readiness is historical. Older serialized snapshots may omit this observation, which is not an empty inventory. |\n| `stackManager` | The selected space's running stack manager: `instanceId`, `pid` and `endpoint`, which is `answered`, `not-answering` or `unchecked`. Absent when no stack manager runs |\n| `store` | What the selected space's running local broker logged about its JetStream store: `state` `write-failed` with `pid`, `log`, `at`, `cause`, `path` and `jetStreamDisabled`, true when the broker then disabled JetStream; `full` with `used`, `cap` and `path` when the space holds more than its broker's recorded cap; `damaged` with `pid`, `log`, `at` and the broker's `warning` when it logged damage in a stored file; `none-recorded` with `pid` and `log`; or `not-checked` with a `reason`. Absent when no space resolves |\n| `hints` | The hints, in the order the report prints them |\n\n`store` covers only a broker whose log is on this machine: a controller stack's `stack-broker`\ninstance, or the broker a legacy root records in `.cotal/nats.pid` with its log `.cotal/nats.log`.\nnats-server reports a stream's write error natively only through `healthz`, which needs a\nsystem-account user, so `status` reads a bounded tail of that log instead and keeps only lines tagged\nwith the running broker's process ID since its last start. When the broker logged a store write\nfailure, such as a full disk, the selected space shows a `Store` row with its time, cause and file.\nThe broker keeps that error until it restarts, so messages sent meanwhile are not stored. The first\nNext is `cotal down`, to run once the logged cause is fixed: write access to the store again for a\npermission error, or free space for a full disk. Then `cotal up` starts the broker again. When the\nbroker also disabled JetStream, which `store.jetStreamDisabled` shows, the manager's stop cannot be\nrecorded, so the Next starts with `kill -TERM <pid>` for the stack's manager on this host, then\n`cotal down --yes` and `cotal up`.\n\nWhen the broker logged damage in a stored file since it started, the `Store` row reads\n`damage logged` with its time and the broker's own words. The recognized warnings cover a message\nblock whose records do not parse (`indexCacheBuf corrupt`), which the broker rebuilds without the\nmessages from the damage on; a stream whose recovery rolled back past what a consumer had delivered;\nand a stream metafile that fails its checksum, which the broker does not load. It keeps serving what\nit can still read, so `history` holds only that. These warnings do not necessarily populate the\n`STREAM.INFO` lost-data field. For a controller stack the Next is `cotal service logs stack-broker`.\n`status` repairs nothing.\n\nWhen the space holds more bytes than the `--max-file-store` cap this machine recorded for its\nbroker, the broker refuses every write, so the `Store` row reads `full` with the bytes stored, the\ncap and the store's path. The first Next is `cotal history clear --space <space> --yes`, which makes\nroom by purging the space's channel history, followed by `cotal up --root <dir>` when a member of the\nstack stopped. To keep that history, the Next goes on as `up` does: `kill -TERM <pid>` for the stack's\nmanager on this host, as a full store refuses its stop, then `cotal down --yes` and\n`cotal up --max-file-store <bytes>` with a larger cap. From another folder, the Next names the stack's\nfolder to run `cotal down --yes` in, and `up` carries `--root <dir>`. The `Delivery responder` row\nthen says that the full\nstore refuses the daemon's lease, so `up` starts the daemon only once there is room. The bytes are\nthe space account's own, as\n`$JS.API.INFO` reports them; the cap counts every account on the broker. A broker started without\na cap sizes its store from the free disk, and `status` does not compare that.\n\n`deliveryResponder` is `stale` when the ready row in the delivery lease belongs to a daemon that is\ngone. That includes this folder's own daemon once its recorded process has exited without releasing\nthe row, as after a kill. The row expires on its own within 30 seconds. The delivery responder line\nthen says the delivery daemon died and its lease has not expired yet, that spawn, retirement and the\ndurable backstop wait until a new daemon binds while joins, DMs and live posts can still work, and that\n`cotal up` restarts it, since `up` waits out the lease first.\n\nWith no delivery daemon serving, as after `cotal down stack-delivery`, `deliveryResponder` is\n`unbound`. The delivery responder line says spawn, retirement and the durable backstop wait until a\ndaemon binds, while joins, DMs and live posts can still work, and that `cotal up` starts it.\n\nWhen a controller stack's delivery member runs but holds no delivery lease, `deliveryResponder` is\n`unbound`, and the delivery responder line names the member's pid and says it is starting or not\nresponding. `status` cannot ask the daemon which. Next names `cotal service status stack-delivery`,\nthen `cotal down --yes`, which kills a member that does not exit, and `cotal up`.\n\nAn unreachable broker, a stopped process or an unbound responder is a state the report names, and\n`status` exits 0. An open mesh runs no delivery daemon, so when it records no delivery lease its\ndelivery responder row says the responder is not used and suggests no command. A controller stack\nstarted with `--no-manager` gets a Manager row saying it runs none by choice and that bare `cotal up`\nadds it. A part it cannot read is named on its row and as a warning, and the run is\n`partial` with exit 4: a PID record, the folder's trust material, the delivery-aware marker, the\nconnectors, the skills bundle, the web records, a persona catalog, the mesh\nregistry or one of its records, the current-mesh pointer, a user-auth login, or the selected mesh's\nchannels or its owned service definition/instance records. Only an absent catalog, registry or pointer reads as empty or unset; an empty pointer is\nnamed too. A registry directory or record that cannot be read also leaves the selected mesh\nunresolved, because resolution refuses it, and the readable records are still listed. A corrupt or\nunreadable extensions manifest is refused while loading the command surface, before this report\ncan run. The manager\nrow asks the service endpoint once: a live process that does\nnot answer is `not serving`, and a probe that could not be made leaves the row\n`running \xB7 service unchecked`. A broker that takes the connection and does not answer, as a\nstopped one does, leaves it unchecked too, and `status` still exits.\n\nThe local auth reader uses a stripped signer only when both split trust records are absent.\nThe load refuses dangling and looping links at either record even when an older stripped signer is present.\nA process row whose PID record exists but cannot be read, or exists under both its current and a\npre-upgrade name, reads `pidfile unreadable` with the error, and the other rows still print. A live\nmanager whose delivery-aware marker cannot be read keeps its row and names the failure as\n`delivery-aware marker unreadable` with the error. The `Web process` row\nprints the address the selected mesh's dashboard recorded once it was listening, while the PID in\nits `web.pid` is alive. The dashboard that holds `web.pid` picks the record. A dashboard started with\n`cotal service start` writes its session record beside its service startup receipt, and status finds\nthat record through the web service instance whose recorded process is that PID. Any other holder is\nread from `web.session`. Neither record is read in place of the other. A `web.pid`, session record or\nservice instance record that exists but cannot be read is named on the row with the error.\nOtherwise it reads `down`, or `not installed` without the web\nextension. Status and the `cotal setup` card count the web extension as installed only when a\npackage in the extensions manifest provides `web` and that package is on disk. A web package record\nthat cannot be read, is not JSON or has no `version` is named on the `Web extension` row and on the\ncard's web row, after the dashboard's address when it is listening and beside an unreadable\n`web.pid` or session record. The `Web process` row still prints, and reads `down` unless the\ndashboard is listening.\n\nIf a refresh fails, `status` may still show the kept catalog bytes for diagnosis. It labels them\nstale with the last successful snapshot timestamp and the refresh error. It never calls that state\nsynchronized or online. If a selected discovered space vanishes from a successful snapshot, the\nselection is cleared and the command reports that no default is selected.\n\nFor a user-auth mesh the selected-mesh section reports the login `status` works as: the signed-in\nsubject when this machine holds a cached session for the entry's pinned IdP, or the exact `cotal\nlogin --idp <url>` line when it does not, with no network round trip either way. A locally\nprovisioned space also shows the actor grant row; a discovered or registered remote entry reports\nthe grant as not checkable on this machine, because the ledger runs where the space was\nprovisioned. `--components` on a user-mode target probes as that same signed-in login (`ps`'s\ncredential), never a static mint; when the login cannot supply a credential, the row says why\ninstead of printing the broker's refusal of an unauthenticated probe.\n\nPersona rows name the catalog they describe. If this folder and the selected mesh use different\ncatalogs, status names both and marks which one spawn launches from. A green `default` means the file\npasses the same agent-file loader spawn uses; a present but invalid file is reported as invalid.\n\n`cotal status --components` prints the report, then a fail-loud per-component health pass. It takes\nno `--json`, `--plain` or `--all`. It reads **each\ncomponent's own control surface**, rather than treating a PID, a lease, or a successful probe of a\nsibling as proof that the component serves. It prints one of `serving`, `absent`, `not-serving`, or\n`refused` for each component and exits `0`, `1`, `2`, or `3` respectively (the highest observed\nstate wins). An optional component that is not installed prints `not-installed`, which exits `0`.\nThat exit replaces the report's own, so a partial report followed by a clean pass exits 0:\n\n- **manager**: local PID record, its liveness-lease holder and PID, then the manager's own typed\n `status` service reachability from this host. A manager that answers without reporting static\n reconciliation says `static reconciliation not reported by this manager build`; the line stays\n visible even when the manager is otherwise `serving`. A row that reached no manager does not\n carry it.\n- **delivery**: local PID record, its ready lease (`ready` is the daemon's own bound-control\n signal), and the latest `renewal.<spaceKey>.json` adoption verdict, the record of the space the\n command was asked about, keyed per space the way the pidfiles are. A re-signed credential and a\n broker-accepted adoption stay distinct facts. A root-only `renewal.json` left by an older build\n names no space and is never read as any space's verdict (`doctor auth` names it as a leftover).\n- **web**: `not-installed` when the dashboard extension is not installed and no PID record exists.\n Otherwise, the local PID record, then the `/api/meta` response at the address the dashboard\n recorded once it was listening, which must name the same PID. The holder of `web.pid` picks the record, as\n on the `Web process` row: a service-run dashboard's own session record, found through its web\n service instance and named on the row as `service instance <id>`, or `web.session` for any other\n holder. The probe presents the readiness nonce recorded beside that address, the one credential\n the dashboard accepts on `/api/meta`. The probe follows no redirect. An answer with a status\n outside 2xx, a 3xx included, is `not-serving`, and the row names that status, such as `HTTP 401`.\n A live PID with no recorded address (the dashboard is still writing it, or an earlier build\n started it), or an unrecognizable process record, is `refused`, not a green default-port guess. A\n session record or service instance record that exists but cannot be read is also `refused`, and\n the row names the read error.\n- **broker**: the registered mesh URL dialed from this host with its recorded TLS requirement.\n\n`absent` means Cotal has no live local component record (or has a stale record); `not-serving`\nmeans the component record is live but its service/readiness surface did not answer or is not ready.\nThose are intentionally separate exit cases. A failed or unreadable probe is `refused`, never an\nabsent component or a clean zero. A broker that takes the connection and does not answer, as a\nstopped one does, refuses the manager and delivery probes, and the pass still exits. A PID record that exists but cannot be read, or exists under both\nits current and a pre-upgrade name, refuses only its own row. A record that its component removes\nwhile the pass runs reads as `absent`.\n\nEach component prints on one line. A control character inside a reported reason, such as a newline\nin a stored row's field name, prints as its `\\uXXXX` escape.\n\n## spawn\n\n```bash\ncotal spawn [<persona>] [--file <persona-file>] [-d | --foreground] [--dry-run] [options]\n```\n\nA bare `spawn` runs the application in this terminal, on every kind of mesh, and no manager is\ninvolved unless you pick one when it asks where to run, as described below. Its exit, or the terminal's, may end the agent. A manager runs the launch only when you\nask for one. `--detach` (`-d`) hands it to a manager, waits until the agent joins, and returns\nwithout attaching. `--on <instance>` hands it to that manager and attaches this terminal to the\nagent (see [Managed launch view](#managed-launch-view)), when that manager's runtime streams a\nterminal. The built-in `pty` runtime streams one. The `tmux`, `cmux`, `orca` and `herdr` runtimes\nstart the agent in a surface they do not own and stream none, so a launch on one of those managers\nis not attached: it returns once the agent joins, with a receipt that names the runtime and what\nreaches the agent instead. Attaching needs usable input and output\nterminals and refuses `--json` and `--plain`, before an identity is allocated. A spawn in this\nterminal writes no structured receipt, so `--json` and `--plain` need `-d`, except with\n`--dry-run`. `--foreground` names the default and is kept for scripts. A spawn in this terminal\nchecks its persona and connector before it starts the application: a missing persona is refused\nwith `TARGET_NOT_FOUND` and a missing connector with `DEPENDENCY_UNAVAILABLE`, both with exit 2,\nand the connector refusal names the `cotal ext add` that installs it. `--file` (`-f`) names a\npersona file, never a manifest: manifests deploy with `cotal team deploy <file>`, and a manifest\npassed to `--file` is refused with that command.\n\nOn a terminal, a bare `spawn` on a user-auth mesh where several of your managers are live asks\nwhere the agent runs before it does anything else. The managers listed are the ones the exchange\ncounts for a manager selection, so the question comes up only when `spawn -d` would ask which\nmanager to use. Each manager's row gives its instance id, the host it reports, the runtime it\nspawns through and whether a terminal attaches to its seats (`pty \xB7 terminal attach`, or\n`tmux \xB7 no terminal attach`), so the choice is made before anything is launched. A manager whose\npresence does not carry it reads as `tmux \xB7 terminal attach not reported`.\n`This terminal` is the default and runs it as above. Picking a manager launches the\nagent there and attaches this terminal, as `--on` does, and a `Manager chosen` receipt names the\n`--on` that skips the question. No manager credential is held while you answer. `--foreground`,\n`--file` and `--live-only` keep the spawn in this terminal without asking, and so does a command\nthat cannot ask: no terminal on stdin and stdout, `--no-input`, or `COTAL_NO_PROMPT=1`. Cancelling\nthe question does nothing and exits 0.\n\nA managed launch on a terminal, picked at a question or named with `--on`, asks which folder the\nagent works in when you give no `-C` and the manager is not known to run on this host. The host is\nthe one that manager reports, the name `cotal ps` prints as its reported host, so a manager that\nreports this machine's host name is never asked about and one that reports no host is. The mesh and\nthe pin are read before anything is asked, so a stopped mesh or an `--on` that names no running\nmanager is refused first and no question comes up. Enter keeps `Manager's workspace (default)`: no\nfolder is sent, and the manager uses its workspace root, as a launch without `-C` does. A folder you\ntype is sent as typed to that manager, which resolves it on its own host with `resolve-cwd` before\nanything is launched. This terminal does not read or rewrite the path, so a Windows manager takes a\ndrive or UNC path from any client. A folder the manager cannot use is shown with its reason and\nasked again, with your text left to edit. A check that could not be made at all refuses the launch\nand names the command again with the `-C` that sends the folder with it and asks nothing. The\nquestion holds no manager credential: the folder is checked under a view of that manager acquired\nafter you answer, and the launch acquires its own. A\n`Folder chosen` receipt names the folder and the host, and for a typed folder it gives the command\nwith the `--on` and `-C` that skip the questions. Cancelling does nothing and exits 0. `spawn -d`\nwith no `--on`, which the exchange gives to your only live manager, asks about that manager too:\nthe CLI reads which manager the exchange picked, keeps none of that view, and pins the launch to\nthat manager. If it has stopped by the time you answer, the launch is refused and goes to no other\nmanager. A command that cannot ask, or one with `--json`, does not ask.\n\nOn a user-auth mesh that runs elsewhere, a spawn in this terminal provisions its agent with your\nlogin, through the agent-provisioning endpoint the mesh advertises. A mesh that advertises none is\nrefused in one line. The line names the remedies: ask the mesh operator to advertise one, run the\nagent where the mesh runs, or add `-d` to launch it through one of your managers. A one-time\nenrollment, described below, also stands in for the endpoint. An answer from the endpoint other\nthan a 401, which names `cotal login`, or a held name, described next, says whether the agent was\nprovisioned is unknown (`OPERATION_UNKNOWN`, exit 3).\n\nA spawn whose name is already taken checks what holds it. When a live manager of yours runs that\nagent, or presence or the mesh reports it online, the spawn launches nothing, says where it runs,\nand exits 0 with the outcome `no-op`. In a terminal it then attaches to that agent. With `-d`,\n`--json`, or no terminal it prints the `cotal attach <name> --on <manager>` command instead. When\nthe mesh answers that the name is held by a lifecycle that does not run\n([Held agent names](identity-and-auth.md#held-agent-names)), the spawn releases that one lifecycle\nby its UID with your login and asks for the name again, for a managed launch and a spawn in this\nterminal alike. Nothing is adopted: the agent starts under a new lifecycle. While the mesh says the\nname is still retiring, the spawn asks again every 5 seconds for up to 2 minutes, then refuses\n`LIFECYCLE_BLOCKED`. A release the mesh cannot confirm is `OPERATION_UNKNOWN` and is not repeated.\nA lifecycle that a live manager of yours holds without reporting it running is not released: it may\nstill be starting there, so the spawn refuses `CONFLICT` and names that manager. Any other held-name\nrefusal stands as the manager or the mesh gave it, as `CONFLICT`.\n\nOn a user-auth mesh, an ordinary spawn in this terminal without `--name` allocates the first free actor\nin the persona's series: `default_agent`, `default_agent_2`, `default_agent_3`, and so on. Held\nlocal material, existing grants and a name whose agent is online are occupied; local material and\ngrants count even when no presence is visible. A name held only by a lifecycle that does not run is\nreleased and reused, as above. Allocation preserves the existing session and its credentials. An explicit `--name`, a\nsigned enrollment, and a managed handoff pin an identity and never rename it. Authorization,\nnetwork and malformed-provisioning failures are not reasons to try another name.\n\nFor a foreground spawn onto a remote user-auth mesh, a one-time enrollment may stand in for a cached\nhuman login. Prefer a private file:\n\n```bash\ncotal spawn --foreground --file ./seat.md --space main \\\n --enrollment-file /run/secrets/cotal-enrollment\n```\n\nThe file contains only the enrollment URL, ending with at most one line terminator, and must be\nmode `0600` on POSIX. A launcher may name the same file in `COTAL_ENROLLMENT_FILE` instead, and run\n`cotal spawn --config ./seat.md --space main`. An orchestrator that cannot mount a file may set\n`COTAL_ENROLLMENT_URL`; that value is redeemed byte for byte, so a trailing newline in it is refused.\nNaming more than one source is refused, and so is `--enrollment-file` with `--detach`, `--creds` or\n`--on`. Enrollment input requires `--space` and applies only to a foreground persona spawn. If the\nmesh is not registered yet, the enrollment response must carry the stock user-bundle fields and the\ncommand needs a persona file (`--file`, or `--config` from a launcher) because there is no local\nremote-mesh persona catalog to read. The client\nredeems the URL once, registers the returned mesh material, exchanges the returned actor token at the\npinned auth service, and removes both enrollment variables before starting any child process. A\nregistered mesh whose record pins no exchange URL is refused before the URL is redeemed, so the\nenrollment stays unspent; re-register the mesh with `cotal meshes add <space> --from <url> --mode user`.\n\nA cached login for the same IdP and an enrollment are conflicting proofs, so the command refuses\nrather than choosing one. An invalid enrollment never falls back to login provisioning. Unknown,\nexpired, revoked, and already-used enrollments all produce one response: ask the owner for a fresh\none. See [Enrollment redeem](identity-and-auth.md#enrollment-redeem) for the HTTP contract.\n\nA runtime that starts a managed seat outside the manager's filesystem hands the child a managed\nhandoff instead: one `0600` file named by `COTAL_MANAGED_HANDOFF_FILE`, carrying the lifecycle the\nmanager already enrolled, alongside `COTAL_MANAGER_INSTANCE` naming the launching manager. The runtime builds the command with `delegatedSeatCommand`:\n\n```bash\nCOTAL_MANAGED_HANDOFF_FILE=/run/seat/handoff.json COTAL_MANAGER_INSTANCE=<instance-id> \\\n cotal spawn --config ./seat.md --space main --name <actor> --agent claude \\\n --expect-owner <owner> --expect-lifecycle-uid <uid>\n```\n\n`cotal spawn` reads the file, deletes it and drops the variable before it parses flags, prints\nhelp or loads extensions, so every outcome leaves no file. Every other command drops the variable\nwithout reading the file and leaves the file in place. The variable is read under any letter case;\nspellings that name different files are refused after every one of them was deleted. A file spawn\ncannot take, such as a missing one, is refused `USAGE_INVALID`, naming the variable and the path,\nwith `unset COTAL_MANAGED_HANDOFF_FILE` as Next.\n\n`DelegatedSeatLaunch.managerInstanceId` is required. A newer CLI child under an older manager or\nruntime that omits it refuses every delegated launch, so managers and the CLI must be upgraded\ntogether. The spawn refuses an absent `COTAL_MANAGER_INSTANCE`\n(`COTAL_MANAGED_HANDOFF_FILE requires COTAL_MANAGER_INSTANCE`), a malformed value\n(`COTAL_MANAGER_INSTANCE \"<value>\" is not a valid lifecycle token ([a-z0-9]{26,32})`),\na malformed handoff, or one whose space, owner, actor or lifecycle UID differs\nfrom `--space`, `--expect-owner`, `--name` and `--expect-lifecycle-uid`, before any broker\nconnection or exchange request. Every refusal on this path names the field and never a value from\nthe handoff. The registration's server, exchange and enforcement checks, the local state this\nmachine keeps for the space (its mesh record, user-auth state and agent secret files), target\nresolution, the policy refresh, the broker preflight and the agent auth preflight quote the space,\nthe server, the exchange URL, the actor or a path named for one of them in their own diagnostics and\nin the filesystem errors under them. For a handoff each prints one fixed sentence that names the\nfield and the phase instead, whether its check fails or an error is thrown. When the agent auth\npreflight's rollback then fails to remove a secret or file, that sentence is followed by the names of\nthe cleanup steps that failed, without their errors. The event-plane policy\nrefusals name the handoff's space field. An actor outside `[A-Za-z0-9_]` and a space that cannot\nname local state, such as `..`, are refused as malformed before any plane. A handoff conflicts with\nthe enrollment variables, `--detach`, `-f` and `--creds`, and needs `--config <persona-file>` and `COTAL_MANAGER_INSTANCE`. From\nthere it runs the enrollment consumer above without redeeming anything. See\n[Delegated seats](embedding.md#delegated-seats-outside-the-managers-filesystem).\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | resolved mesh | Target space |\n| `--server <url>` | registry entry | Broker URL override |\n| `--creds <path>` | none | Control-caller creds for an off-registry manager. Managed launches only, with `-d` or `--on`; refused for a spawn in this terminal and from a managed seat's own shell |\n| `--name <n>`, `-n` | persona's `name:` | Presence-name override (does not choose the persona) |\n| `--file <persona-file>`, `-f` | none | Persona file to launch, on the execution host. A value with no path separator that does not end in `.md` is read as `./<value>`. Refused together with a positional persona, and when the file is a Mesh manifest |\n| `--config <persona-or-path>` | none | Reserved for internal launchers that set `COTAL_MANAGED_HANDOFF_FILE`, `COTAL_ENROLLMENT_FILE` or `COTAL_ENROLLMENT_URL`; any other use is refused and names `--file` |\n| `--harness <connector>`, `-H` | persona's `agent:`, else the [`harness` preference](config.md#preferences), else `claude` | Connector type (`claude`, `opencode`, `jcode`, `hermes`, and so on). `--agent` is the legacy spelling; giving both is refused |\n| `--role <r>` | persona's `role:` | Role override |\n| `--model <m>`, `-m` | persona's `model:` | Model override |\n| `--variant <v>` | persona's `variant:` | Model variant override (connector-defined; e.g. OpenCode reasoning tiers) |\n| `--cwd <dir>`, `-C` | this cwd; on a managed launch, the serving manager's workspace root | Working directory to root the agent at. On a managed launch, a relative path resolves against the manager's workspace root, not your shell, and a directory that does not exist on the serving manager's host is refused before launch. Without it, a terminal asks for the folder when the manager is not known to run on this host. |\n| `--prompt <text>`, `-p` | none | Initial prompt auto-submitted at start |\n| `--opt <k=v>` | none | Connector-specific launch option, repeatable. Wins per key over the persona's `launchOptions:`; a connector with no option surface refuses it |\n| `--resume <id>` | none | Fork an existing session id into the mesh; only connectors that declare resume support accept it (see [the matrix](connectors.md)). The manager records the source session id, and `ps --wide` shows it. On a managed launch with `--on <instance>`, a Claude session held on this host is carried to that instance first ([Resume a session](connect-claude.md#resume-a-session)); carrying one needs `--on` |\n| `--no-events` | event plane on where supported | Opt out of the session's structured event plane (`--events` only restates the default) |\n| `--share-tools <sel>` | none | Share named operator MCP servers with the agent |\n| `--subscribe <a,b>` | persona's | Channel read-set override; `\"\"` clears it |\n| `--allow-subscribe <a,b>` | = subscribe | Read-ACL override; `\"\"` sets it to the read set |\n| `--allow-publish <a,b>` | deny | Post-ACL override; `\"\"` clears it |\n| `--detach`, `-d` | off | Launch through a manager, wait until the agent joins, and return without attaching (reattach with `cotal attach`) |\n| `--foreground` | on without `-d` or `--on` | Run the application in this terminal, as a spawn without `-d` or `--on` does; kept for scripts. Its exit, or the terminal's, may end the agent. Refuses `--detach`, `--creds`, `--on`, `--restarts`, and `--json` or `--plain` except with `--dry-run` |\n| `--on <instance>` | none | Launch through one manager instance and attach to the agent, or with `-d` return once it joins. Takes the instance id (the whole id, as `ps` prints it), or on a user-auth mesh a manager's name or host, as [`ps`](#managed-seats) takes them. Refused with `--foreground` and when empty. On a static or open mesh an id that names no running manager is refused `TARGET_NOT_FOUND` before anything is sent, naming the managers that run, or `TARGET_UNAVAILABLE` as an unpinned launch is when none runs. A pinned manager that does not answer is refused `TARGET_UNAVAILABLE`, exit 2, after the reply deadline, because the launch was never sent |\n| `--runtime <name>` | the serving manager's runtime | The terminal runtime for this one managed seat (`pty`, or an installed extension such as `tmux`). It needs `-d` or `--on`. A spawn in this terminal refuses it, because that spawn has no manager runtime to choose. `auto` is refused `USAGE_INVALID` before anything is sent. Which host checks any other name is described below this table. Other seats on that manager keep the runtime they already have |\n| `--restarts <n>`, `--restart-window <ms>` | none | Managed launches only, with `-d` or `--on`, and given together: the manager restarts the agent at most `<n>` times within `<ms>`. Each must be a positive integer |\n| `--live-only` | off | A spawn in this terminal only: skip the durable backstop |\n| `--enrollment-file <private-file>` | none | With `--space`, for a spawn in this terminal: a private file holding one seat's enrollment URL (see above) |\n| `--dry-run` | off | Preview the launch without allocating an identity, provisioning anything or starting an application |\n| `--expect-owner <u_\u2026>` | none | With `COTAL_MANAGED_HANDOFF_FILE` only, and required there: the owner the handoff must carry |\n| `--expect-lifecycle-uid <uid>` | none | With `COTAL_MANAGED_HANDOFF_FILE` only, and required there: the lifecycle UID the handoff must carry |\n\n`spawn` refuses `--allow-stale` with \"manifest deployment options belong to cotal team deploy <file>, not spawn\".\n`--runtime` is a per-seat choice on a managed launch. Omitted, the seat uses the runtime that manager\nwas started with. `auto` names a manager's own runtime, so it is refused for one seat. Any other\nruntime has to be installed and reachable on the host the manager runs on, and the CLI can check\nthat only when that host is this machine. When the launch can reach only this root's own manager,\nbecause `--on` names it or it is the only manager running, the CLI resolves the runtime through the\ninstalled-extension lookup `up --runtime` uses. An unknown, uninstalled or unreachable runtime is\nthen refused `DEPENDENCY_UNAVAILABLE`, exit 2, before the launch is sent, and for a known runtime\nthe remedy names its `cotal ext add` package. A launch that may reach a manager on another host\nsends the name unchanged. That manager refuses a runtime it cannot run, the CLI reports\n`LIFECYCLE_BLOCKED`, and nothing starts. No manager substitutes `pty`.\n\nEach session uses its connector's **event plane** by default: a stream of structured events\ndescribing what the agent did, rather than the prose it wrote, on a channel of its own. The channel is named after\nthe agent's principal, `events.<owner>.<actor>`, never after its display name, because two live\nagents are allowed to share a display name and would then share a stream. The launch grants publish\nrights on that channel alone, foreground and detached alike. On an open mesh, which issues no\ncredentials, the launch still allocates the agent an id, so the channel names a stable actor.\n`--no-events` is the explicit opt-out unless the selected registration says\n`policy: { events: \"required\" }`. Required policy makes the\nevent arm and grant mandatory, so `--no-events` and connectors without an event plane are refused.\n\nThe launch decision and the grant are separate on purpose. Holding publish rights on a channel is\nnot a request to publish to it, so writing an event channel into an agent file's `allowPublish`\ndoes not override `--no-events`.\n\nThe persona (`--file` or the positional name, else the [`persona` preference](config.md#preferences),\nelse `default`) is loaded from the target mesh's `.cotal/agents/` when it is a bare name. A reference that contains a path separator or\nends in `.md` is loaded from that file. A relative path resolves against the mesh root, except that\na managed handoff, and an enrollment onto a mesh this machine has not registered yet, resolve the\npersona file against the working directory. A missing\npersona is refused in one line with the catalog directory or the file that was checked, and the\nline names `--file <persona.md>` for a persona file elsewhere. A missing `default` persona also\nnames the command that writes one: `cotal up` in the root of a space this machine started, or\n`cotal persona create default` for any other space. `--dry-run` refuses a missing persona with\nthe same text, as `TARGET_NOT_FOUND` (exit 2). When a managed launch is refused because the chosen\nmanager's catalog has no such persona, the refusal names that manager, its host and the catalog file\nto create there, and the Next gives the spawn again with `--on` for each of your other live managers\nwhose catalog lists it. The launch flags\noverride the file. On a user-auth mesh the\neffective name is also the agent's actor token, so it must match the token grammar (no `-`); the\nspawn is refused with that explanation before any request is sent. A spawn runs the agent attached\nto your terminal; `--detach` and `--on` hand the launch to a manager. Every mode gets the durable\nbackstop on a mesh that runs the delivery daemon; `--live-only` skips it for a spawn in this\nterminal (messages posted while it is disconnected are then not replayed). A foreground exit retires\nthe agent's creds and broker footprint, like a manager despawn. On a static-auth mesh, a spawn that\nfails after its footprint exists, for example because the creds directory cannot be written,\nretires that footprint the same way before it exits with the error. The creds and the footprint are\nremoved as separate steps, so a creds file that cannot be deleted does not leave the broker durables\nbehind, and the failed steps are reported together. On a user-auth mesh the two arms\ndiffer: a spawn against a mesh this machine provisioned revokes the actor row on exit, while a\nremote spawn (an enrollment or the advertised provisioning endpoint) removes only this machine's\ncredential files; its grant stays until the mesh operator revokes it, and the launch line says\nwhich arm you are on. A spawn through the advertised provisioning endpoint against a record that\npins no exchange URL is refused before the grant is requested, so no credential lands on this\nmachine. With no sign-in the spawn is refused with `AUTH_REQUIRED` (exit 2) before any request is\nsent. Any provisioning failure after the request, an endpoint 401 or 403 included, is reported as\n`OPERATION_UNKNOWN` (exit 3): the endpoint belongs to the deployment, and nothing says what its\nrefusal left behind. A foreground `--share-tools` selection that names a server the config does not declare is\nrefused before anything is provisioned. A managed spawn is an\n**action**: the manager accepts it and allocates the identity, and the CLI waits for a real outcome\n(the agent joins, exits, or about 30 seconds pass) before it reports or attaches (see\n[the control surface](control-surface.md)). On a user-auth mesh the manager enrolls the agent with\nthe host first. When the host refuses that enrollment, for example for a role or a channel your grant\ndoes not carry, and the manager removes the secrets it staged for the agent, the spawn is refused\nwith the host's reason and `PERMISSION_DENIED` (exit 2). A host that fails or does not answer leaves\n`OPERATION_UNKNOWN`. See [Connect Claude Code](connect-claude.md) and\n[Agent files](agent-files.md). (`cotal start` was merged into `cotal spawn --detach`.)\nA managed spawn onto a manager from another Cotal release is refused before any request is sent\nwhen the manager's contract does not declare a field this CLI sends. The refusal names the field,\ncalls it version skew, and gives this CLI's version. A field you leave unset is not sent, so it\nnever causes that refusal.\n\nA manager has 50 seat slots, and each seat counts once. A slot is held by a managed seat (a row in\nthat manager's `cotal ps`, including a seat still joining), by a reserved launch the manager accepted\nbut has not started a process for, or by a cooling hold. A seat that ends within 10 seconds of\nstarting leaves its slot cooling until those 10 seconds pass, unless an operator stopped it. Such a\nseat holds only that cooling slot, even while its launch is still reporting the failure. A spawn\nrefused at the limit states that split and whether waiting can free a slot:\n\n```text\nat capacity (50 of 50 slots: 49 managed, 0 reserved, 1 cooling); waiting frees a cooling slot in 7s, or despawn one\n```\n\nA cooling slot frees at the stated time. A launch that has not settled frees its slot only if it\nfails, and a managed seat frees its slot only when it stops. The refusal counts a launch as pending\nonly while it holds a slot, so a launch whose seat already ended is not counted. The roster counts\npresence, which also includes peers no manager owns, so its total is a different number.\n\nThose 50 slots bound the host. A manager started with `--seat-cap <n>` also bounds how many live\nseats it holds for ONE owner, so a manager shared by several owners cannot be filled by the first\none to ask. The default is `unlimited`, which leaves the 50 slots as the only bound. The cap counts\nthe seats that manager supervises for the owner the launch belongs to, plus its launches that have\nnot started a process yet; it does not count cooling slots, or any other owner's seats. A launch\nover the cap is refused before a name is reserved or anything is minted, and the seats already\nrunning are untouched:\n\n```text\nowner <owner> holds 4 of the 4 seats manager <id> admits per owner; stop one of that owner's seats, or ask the operator who runs this manager for a higher seat cap\n```\n\nThe CLI exits 2 with `CONFLICT` and a remedy naming `cotal stop --name <name>` and `--seat-cap`.\nUnder `--json`, `error.cause.code` is `resource-exhausted` and `error.cause.detail` carries\n`owner`, `seats` and `cap`, so a caller can decide without reading the sentence. `cotal_spawn`\nshows the agent the same sentence. Stopping one of that owner's seats admits the next launch.\n\nRun from a managed seat's own shell on a static or open mesh, a managed `cotal spawn` (with `-d`\nor `--on`) launches as that seat when it targets the seat's own space. A bare `spawn` there runs in\nthat shell, as anywhere else. Without `--space` it picks that target the way the\noperator path does, so a recorded mesh that is not running is skipped. The CLI reads the seat's\nlaunch identity (`COTAL_NAME`, `COTAL_ID`, `COTAL_LIFECYCLE_UID`, `COTAL_SPACE`, and on a static\nmesh the seat's own credential), so the manager records the seat as the spawner, the same as for\nthe seat's `cotal_spawn` tool. On a static mesh that credential also proves the seat's space, so a\nlaunch without `COTAL_SPACE` still runs as the seat, and a target space holding no credential for\nthe seat is refused. An open mesh acts as the seat only when `COTAL_SPACE` names its space. The\nseat can then stop the child with `cotal_despawn`, and the manager stops the child when the seat\nexits. On a static mesh a seat whose agent file lacks `capabilities: [spawn]` is refused, because\nits credential holds no spawn subject.\n`--on <instance>` keeps its pin: the seat's own credential has no instance route, so on a static\nmesh the CLI mints a one-shot `manager-caller` view for the seat, pinned to that instance and\ncarrying the spawn subject only when the seat's credential holds it. On an open mesh the call keeps\nthe TLS requirement the mesh records. A managed spawn from a seat's shell never falls back to the\noperator: `--creds` is refused there, and so is a target the seat cannot act on as itself, such as\n`--server` with an unregistered `--space`, a user-auth mesh, or an open mesh that `COTAL_SPACE` does\nnot name.\n\nForeground user-mode launches serialize their local actor material. A duplicate refuses\nwithout replacing the first launch's files. A new local-user foreground launch also refuses\nan existing grant before rotating it. Departure cleanup is conditional on the launch's token\nand lifecycle generation, so an earlier launch cannot revoke a successor's grant. Remote\nprovisioning remains the mesh operator's authority and can reissue material before the\nclient observes an existing same-host consumer.\n\n## models\n\n```bash\ncotal models [--json | --plain] [-H | --harness <connector>] [--refresh] [--on <manager-instance>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--harness <connector>`, `-H` | all registered applications | Application whose catalog to list |\n| `--on <manager-instance>` | native selection | Ask only the named manager instance, or on a user-auth mesh the manager a name or host picks, as [`ps`](#managed-seats) takes them. An unavailable or unauthorized pin refuses without choosing another. On a static or open mesh an id the registry lists for no manager is refused `TARGET_NOT_FOUND` at once, naming the managers that run, as `spawn --on` does. |\n| `--refresh` | off | Ask the connector to refresh its provider cache |\n| `--json` / `-j` | off | One JSON document: `result.items` holds each catalog as returned; the requested manager ID, answering ID and answering epoch are separate, and `result.notAskedManagerInstanceIds` lists the live managers an unpinned read did not ask, or is `null` when no registry was read |\n| `--plain` | off | The same report without color |\n\nAsks the selected manager for each application's model catalog (model ids plus their variants)\nfor connectors that expose one. OpenCode and Codex query harness/provider surfaces; Jcode reads\nproviders that enable `model_catalog = true` in the operator Jcode `config.toml`. Jcode's listed\neffort tiers render as `<tiers> (declared, not provider-verified)`, and launch can still refuse one.\nA connector without a catalog says so with `supported: false`, and one whose install has no\ncatalog it can list, such as OpenCode 2.x, gives the reason in `unavailable`; neither is a failed\nread. A connector whose catalog could not be read, or that the manager could not load, makes the\nread `partial` (exit 4) with one `MODEL_CATALOG_UNREADABLE` warning naming its cause. The\nscreen points to that warning without repeating it inline. The remedy is to correct the stated\ncause before retrying; a refresh cannot repair missing connector configuration. A manager-level\nrefusal is reported separately. When the space's service registry lists no live manager, an\nunpinned operator `models` refuses `TARGET_UNAVAILABLE` (exit 2) at once, as `ps` does, and names\nbare `cotal up` as the next step. An unpinned operator read reads the registry first and goes to\nthe one manager the native selection picks. With several live managers, the report names the others\nin a `Not asked` row, and its Next asks each with `--on`. A manager lists a catalog row for every\napplication its install names, so an empty list says the answering manager has no agent\napplication installed. A connector whose harness the manager did not find at boot\nreports the reason boot recorded, as a spawn does, so restart the manager after installing it. Pick\na result with `cotal spawn --model <id> --variant <v>`, where `<id>` is the model id as the catalog\nprinted it. OpenCode and Codex ids are the full\n`provider/model`; Jcode ids are bare (`opus-5`, not `cliproxy/opus-5`), because the provider is\nselected by the operator's Jcode config and a prefixed id is refused at launch with the bare form\nnamed.\n\n## endpoints\n\n```bash\ncotal endpoints [--space <s>] [--server <url>] [--creds <path>]\n```\n\nLists the mesh presence roster: agents, the manager, and any other protocol endpoint, with each\nendpoint's role, kind, status, and current activity. Unlike `ps`, this is a read-only presence view;\nit is not limited to child processes owned by the manager. The Participant column holds the name\nthat `endpoint describe` takes, and the role has its own column.\n\n## who\n\n```bash\ncotal who [--waiting] [--all] [--json | --plain] [--space <s>] [--server <url>] [--creds <path>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--waiting` | off | Only rows that are waiting or report a condition |\n| `--all` | off | Also list infrastructure endpoints such as the manager |\n| `--json` / `-j` | off | One JSON document: `result.items` holds one row per participant |\n| `--plain` | off | The same report without color or marks |\n\nReads the live presence roster of the selected space. Each row has the participant's name, role, kind, status and condition,\nactivity, attention mode, harness, model and host, as its presence reports them. Infrastructure\nendpoints are hidden unless `--all` asks for them; the report says how many it hid. A `cotal chat`\nsession is a participant a person drives, so it is listed with the agents and never counted as\ninfrastructure. The report adds a Kind column that names it `chat session`, and its JSON row carries\n`sessionKind: \"chat\"`. In a user-auth\nspace, `--all` shows each of your managers as a row whose id ends in `manager_<instanceId>`, the id\n`--on` takes, with its host and its runtime in the activity, such as `supervisor (tmux)`. A manager\nrow reads `manager <name>`, with the name the manager was started with, or `manager <host>/<runtime>`\nfor one started without a name.\n\n`managed` says whether a manager runs the seat. With no manager on the roster it is `false` for\nevery row and no manager is asked. Otherwise every registered manager is asked for its seats, and a\nseat is matched on its lifecycle UID. In a user-auth space each of your live managers is asked, and\neach answers only for the caller's own seats, so other seats show `managed: null` with a\n`MANAGED_SCOPE` warning. An\nidentity the broker does not let ask the managers, such as a user whose grant has no `admin`, sees\nevery seat `null` with a `MANAGERS_DENIED` warning. So does a credential passed with `--creds`: it\ncarries no caller the managers accept from this command, so they are not asked. These two warnings\nsay how far the identity's\ngrant reaches; the roster itself was read in full, so they do not make the run partial. A manager\nthat does not answer leaves every unmatched seat `null` with a warning. In a user-auth space the\nNext names those managers, as `ps` does.\n\nA part `who` could not read makes the run `partial` (exit 4): a roster snapshot that did not finish\n(`ROSTER_INCOMPLETE`), a roster that has gone stale (`ROSTER_STALE`), an error on the roster watch\n(`ROSTER_ERROR`), or a manager census it could not read in full (`MANAGERS_UNREAD`, and the warnings\n`ps` names). A credential that may not read the roster is refused with `PERMISSION_DENIED` (exit 2),\nnever shown an empty roster.\nA mesh where nobody is present is a complete roster: `who` says so and exits 0.\n\n`who` watches the roster and the channel registry on every mesh so it can report those states. On a\nstatic-auth or user-auth mesh the credential `who` connects with may not delete its watch consumers,\nso each one stays on the broker for five minutes after `who` exits. A script that runs `who` in a\ntight loop there can reach the broker's limit of 1000 consumers per stream.\n\n## whoami\n\n```bash\ncotal whoami [--json | --plain] [--space <s>] [--server <url>]\ncotal whoami --creds <file> [--json | --plain]\n```\n\nReports the identity this CLI acts under in the selected space and what it may read, post and do.\nIt reads the mesh registry, the login cache and, when it is on this machine, the user-auth grant\nledger. It does not refresh the remote space catalog or contact the IdP. A one-shot command has no\nsession, so there is no unread count or connection state to report.\n\n| Mesh | `identity.kind` | What it may do |\n|---|---|---|\n| Open | `open` | Everything; the broker authenticates nothing |\n| Static auth, signer on this machine | `space-signer` | Everything; each command mints its own short-lived credential for its task, so there is no standing principal |\n| Static auth, no signer here | `no-credential` | Nothing without `--creds <file>` |\n| User auth | `user` | The signed-in user's `cli` actor: `read`, `post` and `capabilities` are its ledger row |\n| A `--server` the space is not registered at, whether or not this folder holds its trust | `unknown` | Unknown: `mode` and the access fields are `null`, and the run is `partial` (exit 4) with `BROKER_NOT_PROBED` |\n| A space `space add --force` registered without checking its broker | the kind its recorded mode gives | What that mode allows, if the broker enforces it: `modeChecked` is `false`, and the run is `partial` (exit 4) with `MODE_NOT_CHECKED`, naming how to add it again with the check |\n\nA user who is not signed in has no principal. A signed-in user with no ledger row (`grant:\n\"not-granted\"`) may read and post nothing. When the grant ledger is on another machine the grant\ncannot be read here: `read`, `post` and `capabilities` are `null`, the run is `partial` (exit 4) and\nthe `GRANT_NOT_LOCAL` warning says so. A remote registration can hold a cached login without the\nsecret that derives its owner and principal. Those fields stay `null`; the report calls the\nprincipal unknown on this machine, rather than calling the user signed out. The cached expiry is\nadvisory and the command does not verify the login with the IdP. `dms` says whether the identity\nmay read DM history: on an open mesh, or with the space signer's audit credential on an authenticated mesh. The report\nreads local configuration and does not contact the broker to check its authentication requirements.\nSo an address no registration names is reported as unknown, never as open; adding the space with\n`cotal space add` checks the broker. The same holds for the space's own folder: its signer does not\nsay whether a broker at another address serves the space, and the remedy names `cotal whoami --space\n<space>` for the address it is registered at.\nA space that is prepared and not started has no broker yet. `whoami` reports the identity its folder's\ntrust gives it: `space-signer` when the root holds the space's signer, as after `space forget` and\n`space create` again, and `no-credential` before its first `up`. `server` and `mode` are `null`, the\n`target` names the root, and the report says to start it with `cotal up` there. With `--server`, or\nwhen the prepared folder no longer exists, it still refuses with `TARGET_UNAVAILABLE`.\nIn a folder whose `.cotal/` names no space, as after `space forget`, the held default `main` is\nassumed. The report keeps `Mesh: main` and says under it that the default was assumed, and the JSON\n`target` is `{\"root\": <folder>, \"assumed\": \"main\"}`.\n`--creds <file>` reports what a credential file may do instead, with `kind: \"credential\"`: the space\nits permissions name, the profile it was minted with, its principal, the agent lifecycle its\ndelivery and history grants are keyed to (none for the `observer` and `admin` profiles), when it was\nissued and when it expires, the channels it may read and post to, and whether it may read every DM\nin the space, only its own, or none. `revokedAt` is when the space's account on this machine revoked\nthe file's key with `auth revoke-credential`, and `null` when no revocation of it is recorded here. It reads the file as every `--creds` file is read and decodes\nthe claims without checking the signature or contacting the broker, which enforces them. When the\npermissions name no single space, `space`, `read`, `post` and `dms` are `null`, and the run is\n`partial` (exit 4) with `CREDENTIAL_SPACE_UNKNOWN`. `--creds` takes no `--space` or `--server`.\nAn unregistered `--space` is refused even with `--server`. Server addresses in the identity report\nand connection refusals omit URL user information, query strings and fragments, and escape terminal\ncontrols. Addresses need a host and a supported broker scheme. TCP addresses cannot have a path;\nWebSocket routes and bare host addresses remain supported. Invalid addresses make `whoami` refuse\nrather than print the raw value. Recovery instructions use a `<url>` placeholder rather than a\nsecret-stripped connection address.\n\nBoth reads refuse an unusable local mesh registry with `SCHEMA_INVALID` (exit 1) before\nconnecting. The diagnostic names the file without repeating its stored field values.\n\n`endpoint invoke <target> <command> --cast` uses the native one-way verb. Only an ephemeral\ncommand can be cast; journaled commands and `--wait` combinations refuse before publication.\nThe receipt records the original request, resolved binding and broker flush, not a responder or\nconfirmed execution. A class cast is not retried or repaired if another instance refuses its\nbinding. A broker publish denial remains a refusal. A lost publication outcome remains unknown.\n\n## service-endpoint\n\n```bash\ncotal service-endpoint approve <proposalId> [--caller-ceiling a,b | --request-id <hex32>] [--space <s>] [--json | --plain]\ncotal service-endpoint refuse <proposalId> [--request-id <hex32>] [--space <s>] [--json | --plain]\ncotal service-endpoint proposal <proposalId> | operation <operationId> | name <endpoint> [--space <s>]\ncotal service-endpoint name-bind <endpoint> (--owner <u_\u2026> --claim <ref> | --request-id <hex32>) [--space <s>]\n```\n\nDecides and inspects service endpoint registrations on a user-auth space whose auth service runs\nwith `--service-endpoints`. A static-credential or open space has no service endpoint host, so\nthere every subcommand refuses `UNSUPPORTED` (exit 2), says so, sends nothing and records no\nrequest. A root that does not hold the space's user-auth service refuses the same way and names the\nroot that started it. Every subcommand is one request to the service's\n`/service-endpoint-host` route as the signed-in user: the route verifies your IdP token and derives\nthe granter itself, so no command names one. `approve` and `refuse` are the owner's: the command\nreads the proposal and its name first and decides at those revisions, so a proposal or name that\nmoved in between is a conflict the host reports, never a decision on something else.\n`--caller-ceiling` names capabilities the proposed commands declare, such as `read`. Each one\nbecomes a ceiling entry, pinned to the proposed instance, for every proposed command that declares\nit, and callers can later be granted only commands inside that ceiling. A name that no proposed\ncommand declares is refused with nothing decided, and so is one a targeted command declares,\nbecause a capability name does not choose a target mode. Without the flag the ceiling is empty. `proposal`,\n`operation` and `name` read as the owner: another owner's record reads absent. `name-bind` is the\noperator's: it needs the auth service on this machine and runs on its loopback face under the\nexchange capability, with your signed-in subject as the audit id. The host verifies no domain\ncontrol, so `--claim` is your own reference for the namespace you checked. The answer is the host's\nown. A decision or a binding is recorded under its request id at\n`.cotal/auth/space.<key>/service-endpoint-requests/<id>.json` in this root before it is sent, and\nthe command prints that id, or names the exact resend command in the failure when no answer came\nback. A run interrupted before any answer prints nothing: the record's file name is the id.\nRun the same subcommand and subject again with `--request-id <hex32>` to send the recorded request as it was,\nwhich the host replays with the same approval or binding; a fresh read would carry the revisions\nthe first attempt moved, so the replay never rebuilds the request, and it takes no\n`--caller-ceiling`, `--owner` or `--claim`. An id this root never recorded, or one recorded for a\ndifferent command or subject, is refused with nothing sent. A run without `--request-id` is a new\nrequest, and a proposal already being approved answers `approval-in-progress`.\n\n## Handle inspection\n\n```bash\ncotal auth handle inspect <private-file> [--space <space>] [--json|--plain]\n```\n\nThis reads one protected native handle artifact or a bounded array of artifacts once. Without\n`--space`, it shows unverified public scope, holder and declared expiry claims. Signatures,\nissuer authority, parent links, holder currency and revocation remain not checked. No broker\nis contacted or credential issued. Signature bytes and original signed artifacts are never printed.\nA handle is a signed JSON grant, so a file that is not JSON, such as a NATS credential file, refuses\n`SCHEMA_INVALID` and says what it is. For a credential file, the Next names\n[`cotal whoami --creds <file>`](#whoami), which reports what it may do. A path that names nothing\nrefuses `TARGET_NOT_FOUND` (exit 2) and names the path.\nThe human report shows at most 50 scope rows and names omissions; JSON retains public metadata.\n\nAn explicit `--space` selects online inspection on a registered static-auth space whose root\nholds matching native operator authority. One ephemeral provisioner reader is minted without\npersisting it. Its broker account and identity must match before leader-served signer and\nissuer-namespaced revocation reads. The inspector checks signatures, issuer scope, presented\nparent digests and containment. Every required sturdy-handle status must be explicitly unrevoked;\nmissing, malformed, unreadable or stale spec/status pairs fail closed. Reads share one bounded\nchain budget. No records are created or repaired, and no alternate signer or broker is tried.\n\nThese non-atomic observations are signature inspection, not a usable presentation context.\nDeclared expiry, presenter identity, holder/process currency and contract-dependent use authority\nremain separate. No holder credential is conferred and no handle is redeemed. Open and user-auth\nspaces refuse this static reader rather than borrowing an operator identity. Reader-close\nuncertainty is reported separately.\n\n## Account signer export\n\n```bash\ncotal auth signer --out <private-file> [--space <space>] [--force] [--json|--plain]\n```\n\nThis exports the selected space's existing data-account signer through the native stripped-signer\nformat. It does not create a caller, rotate a key, start a service or export the operator root.\nA cached login is not signing authority. The selected root must hold validated static trust;\nconflicting current-folder and selected-root account authorities refuse before publication. A space\nregistered as open refuses `UNSUPPORTED`: it issues no credentials, so it has no signer to export.\nA per-user-auth space refuses `UNSUPPORTED` as `auth mint` does there, naming the login or\nenrollment path, and nothing is written.\n\n`--out` is required. Its parent must exist and pass the protected physical-directory checks. A\nparent that does not exist refuses `TARGET_NOT_FOUND`, naming the folder, before anything is\nwritten.\nManaged `.cotal` and workstation state are not valid explicit export destinations. The file is private, and secret contents\nnever appear on stdout, in the receipt or in diagnostics. Keep this account-wide signer out of\nlogs and support bundles; it is not a least-privilege endpoint credential.\n\nAn existing path refuses by default. `--force` first inspects an owned private regular file and\naccepts only the stripped format for the same space and account. It refuses full trust bundles,\nother accounts, symlinks and concurrent changes. An identical export is a no-op. New files use\nnative create-only private publication; replacement uses the inspected snapshot under a writer\nlock. An unconfirmed publication remains unknown and is not retried. Platforms without the\nprotected file policy refuse rather than substituting a weaker writer.\n\n## Endpoint authoring\n\n```bash\ncotal endpoint init reviews [--dir <directory>] [--json|--plain]\ncotal endpoint check <contract-file> [--json|--plain]\n```\n\n`init` creates `contract.json` and an executable `handler.mjs` template. The default directory is\nthe supplied name; `--dir` chooses another one. Its parent must exist. Files are created\nexclusively, without following a pre-planted symlink or replacing an existing file. A partial\nwrite retains the named created material for review rather than deleting it automatically.\n\nA simple scaffold name such as `reviews` declares `com.example.reviews`. A full reverse-DNS name\nis kept. Choose a namespace you control before requesting serving authority. Scaffolding creates\nno service definition, reference, credential, registration or process. The handler accepts one\nJSON object with `command` and `args` on stdin and writes one JSON result on stdout.\n\nThe version-1 local contract file has `schemaVersion`, `endpoint` and `cluster`. The cluster has\nnative `urn`, `revision`, `attributes`, `commands` and `events` fields. Each command carries its\nnative declaration plus `input` and `output` schema bundles, each `{root, members?}`. The installed\nnative compiler derives their closure digests; authors do not hand-maintain a second digest next\nto an edited schema. `members` maps native digest references to supplied JSON documents. Unknown\nwrapper or command fields are refused. This file is local authoring input, not a new wire kind.\n\n`check` reads at most 1 MiB, validates the native cluster declaration, compiles every input/output\nwith the installed schema profile and reports the derived cluster closure. All schema references\nmust resolve inside their bundle; no URL or adjacent file is fetched. Attribute and event\nsemantics remain opaque to the native parser, so a file declaring them produces a partial result,\nexit 4, rather than a claim that those semantics were checked.\n\nNeither command contacts a broker, loads the handler, starts a service or proves serving\npermission. Authoring takes no space, broker, caller or invocation flags.\n\nServe the edited contract through the independent service controller:\n\n```bash\ncotal service add reviews --preset exec-endpoint --space <s> --root <absolute-root> --contract <absolute-contract.json> --handler <absolute-node-executable> --handler-args '[\"<absolute-handler.mjs>\"]'\ncotal service start reviews --space <s>\ncotal service status reviews --space <s>\ncotal service logs reviews --space <s>\ncotal service stop reviews --space <s>\n```\n\nThis preset supports Linux and an existing registered static-auth root. The executable is an\nabsolute protected regular file, with argv supplied separately. A symlink on the root, contract or\nexecutable path refuses, because a link could be repointed after the check, and the refusal names\nthe physical path to use instead, such as `/usr/bin/python3.14` for `/usr/bin/python3`. There is no shell, PATH lookup,\nimplicit installation, account switch or open/user-auth credential fallback. For a Node handler,\nselect the Node executable and pass the script path in `--handler-args`. The runtime Node in\nCotal's install root qualifies; if an earlier installer left its folders group-writable, the\nrefusal names `cotal update`, which removes that write. That JSON array has at\nmost 64 fixed nonsecret strings of at most 4096 characters each, and its serialized form has at\nmost 8192 characters. A longer string refuses `USAGE_INVALID`, exit 1, naming its position and that\nlimit. The handler\nruns in the contract directory with an empty environment. It is trusted local code with the\noperator's OS permissions, not a sandbox; it must not daemonize or leave independent children.\n\nStart recompiles one protected contract snapshot and pins its cluster closure and the executable\nfile identity across the controller handoff. Changes before launch refuse; changes to the\nexecutable before a later call also refuse. Only ordinary untargeted ephemeral commands are\nsupported. Targeted, journal, trait, attribute and event declarations refuse before registration.\nThe host publishes the schema roots, members, closure manifests and cluster to the native\ncontract store, registers one fresh native instance, and uses the native issuance gate and\nbranded serve grant. The handler receives no signer, executor credential or broker environment.\n\nThe native serving boundary validates input before spawning and output before replying. Each\ncall receives one `command`/`args` JSON object on stdin and must return one UTF-8 JSON value on\nstdout with exit code zero. Calls and casts need a native deadline; the handler budget never\nexceeds it or 120 seconds. At most 32 handlers run concurrently. Input is bounded to 64 KiB,\nstdout to 256 KiB and discarded stderr to 64 KiB. Deadline, pipe, process, JSON and schema failures\ndo not replay effects. Handler payloads and stderr are not forwarded to service logs.\n\nInitialization is recorded only after the real serve subscriptions flush and the native ready\nstatus returns. Controller readiness remains a process-bound initialization observation, not an\nongoing endpoint health probe. Logs and private launch records name the full native instance;\nuse that instance for an attributed `endpoint describe` or `endpoint invoke`. Normal renewal\nuses the same issuer, identity and registration, with one immediate same-broker reconnect. An\nunconfirmed renewal withdraws the service rather than starting a replacement.\n\nStop first prevents new handlers, requests termination of its owned handler groups, waits for\nprocess/pipe closure, drains serving and closes the connection. Native deregistration pins the\noriginal registration revision and preserves successor or in-flight governance state. Private\ncreate-only intent, registration and removal records retain the original coordinates. A later\nlaunch of the same local instance refuses an unsettled or malformed attempt instead of silently\nrepeating it. Unconfirmed initialization or cleanup remains unknown; neither process disappearance\nnor an initialization receipt proves native cleanup. When the earlier host process is gone, as\nafter a `kill -9`, the refusal names its native registration and gives the way out: remove it with\n`cotal endpoint deregister <endpoint> <instance> --yes`, then start a fresh instance with\n`cotal service start <name> --new-instance --space <s>`. A `service restart` of that instance\ngives the same commands. The separate `endpoint deregister` route retains its broker-gone and\nrevision guards.\n\n## Endpoint control\n\n```bash\ncotal describe <endpoint> [--on <instance>] [--space <s>] [--server <url>] [--creds <path>]\ncotal invoke <endpoint> <command> [--args '<json>'] [--timeout <ms>] [--space <s>] [--server <url>] [--creds <path>]\ncotal invoke <endpoint> <command> --name <agent> [--admin] [--space <s>]\n```\n\nThe generic v0.4 service surface. `describe` resolves a registered endpoint's command set off the\nwire - the reserved `describe` command answers the registered contract digests, the schemas are\nfetched from the space's content-addressed contract store, recompiled, and verified against those\ndigests - and prints each command with its capability class and targeting shape. `--on <instance>`\npins `describe` to one manager instance's rail (the whole id, as `ps` prints it under its\n`manager <id>` headers), so an operator can read what that instance serves in a multi-manager space;\nunpinned, the class queue answers and the attribution line names whichever instance did. `invoke`\ncalls one command by name: `--args` is a JSON object validated against the fetched input schema\n*before*\npublish, and `--timeout` sets the reply deadline in milliseconds (default 10000). `--server` overrides the\nregistry's broker URL and `--creds` names a creds file for an off-registry connection. A targeted command takes `--name <agent>` (resolved to the agent's current principal through\n`inspect`) or `--self`. `--admin` uses the admin instrument credential, whose cross-agent reach rides\nthe operator-only `any` authorization mode. Neither command has compile-time knowledge of any\nendpoint's schemas - this is the same trust chain every built-in control command now uses. Needs an\nauth mesh: the manager registers its service on both static and per-user meshes (a signed-in user\nrides their bearer; each visible or invoked command still requires its existing grant, and cross-agent\nreach needs the `admin` scope). An open mesh has no service registry.\n\nOn a static-auth root the operator's own credential sends requests to the manager endpoint only, so\nthe broker refuses its `invoke` of any other endpoint before the call runs. When the call and the\nendpoint's whole command surface are untargeted synchronous ephemeral commands, the refusal names\n`cotal auth mint <name> --endpoint <endpoint> --space <space> --out <private-file>`, with the space\nthe call resolved and the `--server` it was given, if any, so the caller is issued for the space that\nrefused even when another space is current. Run the same `invoke` again with `--creds <private-file>`. An `invoke --on <instance>` of another endpoint is refused the same\nway and names no remedy: with `--on` the operator's credential still carries manager rows only, and\na paired caller file takes no instance pin.\n\n## Managed seats\n\n```bash\ncotal ps [--json | --plain] [--on <instance>] [--wide] [--slots] [--space <s>]\ncotal stop <name> [--force] [--on <instance>] [--space <s>]\ncotal stop --self [--force]\ncotal attach [<name>] [--on <instance>] [--no-reconnect] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `<name>` / `--name <n>` | none | Managed agent to stop or attach, named positionally or with `--name`. `stop` needs one unless `--self` is given. `attach` without one lists your agents to choose from on a terminal, and refuses `USAGE_INVALID` where it cannot ask |\n| `--self` (`stop`) | off | Stop only the verified managed seat running this command (see [Stopping an agent](#stopping-an-agent)) |\n| `--force` (`stop`) | off | Request the native hard despawn (`graceful:false`); it does not bypass authority |\n| `--on <instance>` | class anycast (`ps`: class scatter) | Pin to one manager instance id (multi-manager space); takes the whole id as `ps` prints it, not a prefix. On a user-auth mesh it also takes a manager's name, its host, or the `<host>/<runtime>` an unnamed manager shows as (its runtime alone when its presence names no host), when only one of your live managers matches it; otherwise it refuses as `TARGET_AMBIGUOUS` or `TARGET_NOT_FOUND`, lists your live managers and gives the command again on each. On a static or open mesh a manager's presence does not carry its instance id, so a name is refused as `USAGE_INVALID` and the Next asks for the instance id. An empty value (`--on \"\"`, an unset shell variable) is refused, never treated as absent. A roster principal id (`local.\u2026`) is refused with a message naming the instance id `ps` prints |\n| `--wide` (`ps`) | off | Print each seat as a labeled record that adds the operational facts the manager records: the provider the connector reported serving the model, `cwd`, `pid`, spawner, lifecycle uid, the owning manager's instance id and host, and for a `--resume` seat the session it forked (`forked from <id>`, with the source title and transcript SHA-256 once a Hermes or Jcode seat has recorded its fork; a carried Claude session prints `forked from <host>:<id>` with its title, SHA-256 and `carried <time>`, the time its bytes reached the manager). Model and requested variant stay in the identity row rather than printing twice. A fact the manager did not record (for example a runtime with no real process, a Windows seat whose process ConPTY has not created yet, or a connector that reported no provider) prints nothing, never a placeholder |\n| `--json` / `-j` (`ps`) | off | One JSON document (see below). Mutually exclusive with `--wide` and `--plain` |\n| `--plain` (`ps`) | off | The same report without color or marks |\n| `--slots` (`ps`) | off | List the durable static slot rows this manager owns instead of live seats, through the `slots` command. Mutually exclusive with `--wide`. A row that is not in the live roster still prints, with `LIVE` false; a retired row never prints |\n| `--no-reconnect` (`attach`) | off | End the attach when its session ends, instead of re-establishing it. For scripts that want one run and one exit code |\n\nOn a controller stack started with `--no-manager`, `ps` refuses `TARGET_UNAVAILABLE`: no manager\nruns, so there are no managed agents to list. Bare `cotal up` adds the manager. A `manager` preset\nservice started at the root answers instead, and `status` names it on its Manager row. Bare `up`\nthen refuses `LIFECYCLE_BLOCKED`, exit 2, naming that service and the\n`cotal svc stop <name> --instance <id> --space <space> --yes` that stops it; once it is stopped,\nbare `up` starts the stack's own manager. A stack manager whose process died stays registered, so\n`ps` waits out the manager's answer deadline first, then refuses the same way and names bare\n`cotal up`, which starts it again. While an `up` is still starting the\nstack's manager, `ps` refuses `TARGET_UNAVAILABLE` saying the manager is starting, and its Next is\nto run `cotal ps` again once `up` reports it started. That `up` holds the root's maintenance lock;\nonce it has exited, `ps` gives the refusal above.\n\nA raw `--creds` file is refused by `ps`, `stop`, `attach` and the other control commands, because\nthat route mints no endpoint-caller triple; the project folder, or `--space` against the registry\nentry, is the route that does.\n\n`ps` shows the instance id `--on` takes for each manager that answered, with or without agents. One\nmanager is listed under `Manager` as its `instance`, and with several each gets a `manager <id>`\nheader. With no agents its Next is `cotal spawn`, or `cotal setup` when no agent application is set\nup here, as `status` reports it.\n\nWhen `ps` asks every manager, it then asks each manager that answered for the host name in its own\nstatus reply. The host shows as `\xB7 reports host <host>` after the id, or as a `reported host` row\nunder `Manager`. It is that manager's own claim, and `ps` does not check it. Two managers on one\nhost report the same name, and `--on` never takes it on a static or open mesh. These asks share one\n2-second limit. A manager that has not answered by then is listed without a host, and the read\nstays as complete as the census found it. `ps` does not show a manager's name. On a user-auth mesh\nwith several of your managers live, the host is the one each manager's presence reports.\n\nThe human `ps` report is presentation text and is not a stable parsing target. Scripts use `--json`,\none envelope whose `result` is:\n\n| Field | Meaning |\n|---|---|\n| `kind` | `seats`, `slots` with `--slots`, or `presence` when no manager of yours answered on a user-auth mesh |\n| `items` | Each seat (or slot) row as its manager returned it, unchanged |\n| `managers` | Each manager instance asked: `{instanceId, state}`, where `state` is `answered`, `silent` (registered, no answer within the deadline, with the `liveness` this command established), `refused` (with its `error` and `code`) or `contract-mismatch`. An `answered` manager that gave a host name in time also has `reportedHost` |\n\n`target` is `{space, scope: \"all-managers\"}` for the class scatter,\n`{space, scope: \"your-managers\"}` for a user-auth mesh with several of your managers live,\n`{space, scope: \"presence\"}` when none of them answered, and\n`{space, scope: \"manager\", managerInstanceId}` for `--on` or a user-auth mesh with one. A manager that is not\n`answered` makes the read `partial` (exit 4), with a `MANAGER_SILENT`, `MANAGER_REFUSED` or\n`MANAGER_CONTRACT_MISMATCH` warning naming it. A `silent` manager whose `liveness` is `gone` is a\nregistration its host never removed. On an auth space its row names `cotal endpoint deregister\nmanager <id> --yes`, run from the space's own root. On any other space only that manager's own clean\nstop removes it, so the row names starting it again and stopping it if its host is still available,\nand says no supported command removes the record if the host is gone. The row stays and the read\nstays `partial`.\n\n`--slots --wide` is refused: `--slots` lists durable static slot rows, `--wide` prints live seat facts, and the two answer different questions. Across a multi-manager scatter, `--slots` prints each manager's rows under its own instance header, the same way the plain `ps` scatter does.\n\nA revision-32 manager also supports a bounded native `slots` query through\n`endpoint invoke manager slots --on <instance> --args '{\"names\":[\"worker\"],\"includeRetired\":true}'`.\nIt point-reads up to 100 distinct aliases and can return their retired slot/head and\ncleanup observations. The default `ps --slots` query is unchanged and excludes retired\nrows. Omitted aliases, live-map absence and stop acknowledgements are not retirement\nproof. Match the returned principal and lifecycle UID before interpreting any terminal.\n\nThese are operator clients over the running manager's control plane. The default row includes the\nconnector, model pin, optional requested variant, and runtime as operational descriptors for the\nmanaged row. They do not make a shared display name a unique protocol identity; use `--json` when\nunambiguous owner+actor attribution is required. An omitted variant means no override was requested;\nCotal does not invent an effective provider default it cannot observe. `ps` also prints two state\nfacts per managed agent, because they answer different questions: the process fact from the manager's\nown runtime handle (`running` with its uptime, or `exited` with how long it ran), and the mesh fact\nfrom the roster (`idle` / `working` / `waiting` / `mesh offline`, or `not in roster` when the seat has\nno presence row at all: a seat that has not joined yet, or one that never did). When the seat's\nconnector relays a harness-reported condition, the mesh fact carries its code and how long it has\nheld, so a seat whose turn died on a provider rate limit reads `waiting (rate_limit for 40m)` rather\nthan a bare `waiting`, and `--json` carries the whole `condition` object. When the connector reports\nthe seat's last work event (presence `activeAt`), the mesh fact ends with its age, such as\n`\xB7 active 3s ago`, and `--json` carries `activeAt`. A seat whose turn stopped advancing keeps\nheartbeating, so its presence row stays fresh and this age is what shows the stall. A seat can be\n`running` and `mesh offline` at once: the process is alive and its presence has lapsed. That row says\nhow long, as in `mesh offline for <age>` with an age such as `3.5h`, counted from the seat's last\npresence heartbeat, which `--json` carries as `offlineSince` (epoch ms). The age is read only from\nthe seat's own presence record, matched on its principal and lifecycle uid, so a same-named peer or\nan older lifecycle never dates it. The manager log names each managed seat that is offline on the\nmesh while its slot is held\n(`seat offline on the mesh: <name> - last heartbeat <time>; process <state>`), including one its\nwatch first sees offline after a reconnect, and each one that comes back\n(`seat back on the mesh: <name>`), so a watchdog that only checks process liveness has a line to\nact on. The manager does not reap or re-key such a seat. The mesh fact is only a verdict while the\nmanager's own presence watch is fresh: when that watch has been silent past the liveness window, or\nhas not replayed the bucket yet, every row prints `mesh unknown` with the reason instead (`--json`\ncarries it as `meshView: stale | unpopulated`), because `offline` and `not in roster` would then\ndescribe the manager's watch rather than the seat. The manager rebinds a watch that goes\nsilent under a live connection on its own, so `mesh unknown` normally clears within a liveness window.\nOn a user-auth mesh `ps` also renders each managed agent's last credential-refresh outcome, fail-closed.\n\n**Mode split (chosen up front, never try-scatter-then-degrade):**\n\n- **Static / open mesh.** Bare `ps` is a **class scatter**: it freezes the live manager class from\n the records registry, merges every registered instance's agents grouped and attributed per\n instance, and a non-answering instance is shown as `registered, no answer within the deadline`\n (never silently omitted). A refused list or a missing answer makes the census incomplete: rows\n from other instances remain visible, but `ps` prints an incomplete-census warning on stderr and\n exits 4 (`partial`), including with `--json`. Those rows are not a complete seat count. A contract\n mismatch prints one plain comparison of the requested and served input/output digest pairs and\n advises aligning manager versions. The no-answer label means only that the instance is registered\n and did not answer. It does not say the host is down, because a dead host never deregisters itself and a\n live one can be slow; if it is gone, deregister it.\n `--on <instance>` pins the read to one exact instance id instead. A wrong pin fails loud\n rather than falling through. On a static or open mesh a well-formed id that the registry lists for\n no manager is refused `TARGET_NOT_FOUND` at once, naming the managers that run, as `spawn --on`\n does; a registered one that does not answer is reported as `manager instance <id> did not answer`\n (nothing else is asked). A credential without that\n instance's rail is reported as refused by the broker, not as an unresponsive manager. A manager\n that answers with a refusal is shown with its own cause; \"no manager reachable\" is said only when\n nothing answered at all. If the scatter's own registry read fails (the freeze or the reconcile),\n `ps` says the manager registry could not be read rather than pronouncing on the managers, which\n may all be up. When nothing answered and this root's stack manager process still runs, as a hung\n one does, `ps` refuses `TARGET_UNAVAILABLE` naming its pid, and its Next ends that process so bare\n `cotal up` starts the manager again.\n\n**The verdict is scoped to the endpoint rail the request rode.** An issued caller rides the\nversioned `ep.v1` rail, a separate subject space from the legacy `ep` rail, and an endpoint serves\nboth (SPEC 13.15). A manager older than the versioned rail serves `ep` alone, so it can be running,\nregistered and answering while an issued caller's request reaches nobody. Silence on `ep.v1` is\nreported as `no manager answered on the <rail> rail` with `ep.v1` as the rail, and names both causes\nit is consistent with: no manager running, or one older than the rail. The CLI cannot tell them\napart, because the service registry records no package version, so check whether a manager is\nrunning and, if it is, its version. The same scoping applies to `cotal run`'s hosted verbs, which\ndrop the `--local` suggestion there, since `--local` drives the run from the calling process and\nnames the caller as its answerer. A hosted verb that no manager answered refuses\n`TARGET_UNAVAILABLE`, exit 2, leading with `the manager registered for this space did not answer`,\nand its Next is `cotal status`, which names a stack manager that runs but does not answer, with its\npid.\n\n**`stop` and `attach` route by seat locality.** A seat can only be stopped or attached by the\nmanager actually running it, and the class queue does not know which one that is. So on a\nstatic/open mesh both verbs first ask every registered instance which one hosts the named seat, then\naddress that instance directly. This happens by default; you do not need `--on`. When every instance\nanswers and none hosts the name, `stop`, `attach` and `input` refuse `TARGET_NOT_FOUND` (exit 2)\nbefore anything is sent, as they do with `--on`, naming the managers that answered, with `cotal ps`\nas Next. When an instance did not answer for itself, the seat may run there, so they refuse\n`TARGET_UNAVAILABLE` (exit 2) and name that instance. When more than one instance runs the name,\nthey refuse `TARGET_AMBIGUOUS` (exit 2) and name each of them. `--json` carries each refusal. On a\nterminal the last two ask instead, listing only the managers that hold the name or did not answer,\nand the refusal's Next gives the command again with `--on` for each.\n\n`--on <instance>` remains the override, for when you already know where the seat lives or the\nlookup itself is degraded. On a static or open mesh, `stop --on`, `attach --on` and `input --on`\nwith an id the registry lists for no manager are refused `TARGET_NOT_FOUND` at once, naming the\nmanagers that run, as `spawn --on` is.\nOn a **user-auth mesh**, the exchange selects one authorized manager\nfor a short-lived `manager-caller` view. `--on` requests a specific instance. Without it, a command\nrun from a managed seat's shell selects the manager that launched the seat (`COTAL_MANAGER_INSTANCE`),\nand any other caller's selection must be unique. Discovery and the command use that instance route.\nThe caller gains no registry read or scatter permission. An absent, ambiguous or unauthorized\nselection refuses before sending the command. With several of your managers live, the refusal lists\neach one on its own row with its name, instance id, host, activity and the time its process started.\nA manager started without a name is listed as `<host>/<runtime>`. A manager started by a cotal\nbefore 0.100.0 puts no host in its presence, so its host reads\n`not reported (started by an older cotal)` and it is listed by its runtime, such as `tmux`.\nRestarting it with the current cotal shows its host. Its Next gives your command again with `--on`\nfor each, using the name, `<host>/<runtime>` or runtime when that picks the manager out, and the\ninstance id otherwise. The code stays\n`PERMISSION_DENIED` and the exit 2. `who --all` lists the same managers at any time. By default, when every manager you registered has\nexited, the refusal says to start one with [`manager start`](#manager); the command is not sent to the local manager.\n\n`ps` and `status <agent>` do not refuse: they read each of your live managers, as the user-auth\nmode below describes. `stop`, `attach` and `input` with an agent name read them the same way and\nrun on the manager that holds the name, without asking. A name held on several of them, or found on\nnone that answered, is settled as on a static mesh above, among those managers only, and a manager\nwhose read fails counts as one that did not answer. A name every manager answered without is\nrefused `TARGET_NOT_FOUND`, naming the managers searched. On a terminal, `spawn -d`, `models` and\n`persona` ask instead of refusing. Every manager the exchange counts is one of yours in the space the command\nresolved, from the folder or `--space`, so the question lists those managers with the most recently\nstarted first, and that one is the default. The command then runs on the manager you pick, as the\nNext would run it. The question and a `Manager chosen` receipt, with the manager, its instance id\nand the `--on` that skips the question, go to stderr. Cancelling the question does nothing and\nexits 0. Without a terminal on stdin and stdout, with `--no-input`, with `--json`, which implies\n`--no-input`, or with `COTAL_NO_PROMPT=1`, the command refuses as above. A `--json` caller passes\n`--on`, and the result's `target.managerInstanceId` names the manager that answered. A bare `spawn` asks where the agent runs\nbefore any of this, and a managed launch on a manager that is not known to run on this host then\nasks for its folder (see [spawn](#spawn)).\n\nA trusted hosted composition may opt into an [interactive local primary](embedding.md#interactive-manager-primary).\nUnder that policy, unpinned human control selects the genuine local Manager up front, even when your\nown remote Manager is live; an unavailable local Manager refuses without remote substitution. This\naffects all interactive Manager verbs, not only `ps`. Select your remote Manager explicitly for its\nspawn, run or terminal operations. Explicit pins and managed-seat launching bindings keep their rules.\nThere is no user flag for the policy, and stock `up` and recovery defaults do not enable it.\n\nA connection that the broker refuses, or that does not open within 5 seconds, sends nothing. A manager\ncommand then connects again, as do `send` and the other commands that open a one-shot connection, up\nto 4 attempts 1 second apart. A view the auth service refused for a moment, or an auth service that\nanswered late, then just works. When every attempt fails the command says nothing was sent and is\nrefused, exit 2: `PERMISSION_DENIED` when the broker refused each attempt, `TARGET_UNAVAILABLE` when\nnone opened in time. The broker's own words stay in the native detail. On a user-auth mesh the space's\nauth service logs why it refused.\n\nA seat is reported as **not found** only when every reachable instance answered for itself. An\ninstance that stayed silent past the deadline, or that refused the read rather than answering, said\nnothing about which seats it hosts, so the seat may be running on it. That case reports that the\nlocation could not be established, names the instances that did not answer, and states outright\nthat it is not a report that the seat is gone. Read it as unknown and retry with\n`--on <instance>`; a retry loop that treats it as \"already gone\" stops looking for a seat that is\nstill running. A single manager cannot tell \"hosted elsewhere\" from \"does not exist\": it answers\n`not-found` for both, which is why the search asks all of them and why an incomplete search\nconcludes nothing.\n- **User-auth mesh.** With one of your managers live, `cotal ps` reports what that manager knows\n about your agents (an instance-addressed read against its in-memory roster, owner-filtered). With\n several live, `ps` asks nothing: it reads each manager the exchange counts live through its own\n one-instance view, one after another, and lists each under its own `manager <id>` header, as the\n class scatter does. A manager that does not answer is listed as silent and makes the read\n `partial` (exit 4), and the Next names each one by instance id: a manager that ended without a\n clean stop stays registered under its id. Managers of other owners are not asked, so completeness\n across the space is not claimed. `status <agent>` and `who` read your managers the same way.\n When none of your managers answers, or none is live, `ps` lists your agents as presence shows\n them, read-only, with their state, application and host, and exits 0. It says that no manager\n answered, and its Next starts one: bare `cotal up` on the root of the space's own stack, as for a\n static stack above, and `cotal manager start` anywhere else. `--on` and `--slots` keep refusing\n `TARGET_UNAVAILABLE`, exit 2, when their manager does not answer. Your ledger row needs the `admin` scope to\n reach `ps` at all; `spawn` alone is refused by the broker (the ep tier boundary). A broker-refused\n read keeps that refusal and adds the documented scope requirement and `cotal auth grants` remedy.\n The broker does not identify whether the missing permission is for the route, command or target;\n the CLI does not substitute another caller or retry on a different route.\n\n`attach` streams and drives an agent's terminal on the `pty` runtime; detach with the escape key\n(Ctrl-] by default; see the [`detach-key` preference](config.md#preferences)). The key is recognised\nas the legacy control byte and as the kitty keyboard protocol and xterm modifyOtherKeys encodings of\nthe same press, so a terminal with either protocol enabled detaches too. It does so over a one-use, holder-bound\nmesh session ([SPEC](../SPEC.md) \xA713.6): the manager replies with a signed session grant (never a\n`127.0.0.1` URL), the CLI redeems it once over the broker, and the browser console (`cotal console`)\ndrives the same session. `stop` and `attach` need a running manager to talk to. On a static mesh\nthey are cross-agent admin operations. On a user-auth mesh, your own agents (any agent under your\nowner) need only the `spawn` scope; another owner's agent needs `admin` on your ledger row\n([identity & auth](identity-and-auth.md)). Launch detached agents with [`spawn --detach`](#spawn).\n\n`pty` is the only runtime that streams a terminal. A seat started by the `tmux`, `cmux`, `orca` or\n`herdr` runtime runs in a surface the manager does not own, so there is no stream to give and\n`attach` refuses once with `UNSUPPORTED`, exit 2, carrying that runtime's own guidance for watching\nthe agent where it runs, and a Next that names `cotal send` and `cotal watch`. The reconnect loop\nbelow does not retry that refusal, because asking again cannot change it.\n\n**`attach` reconnects when the link dies.** A session lives on a network link, and a laptop that\nsleeps, a VPN that drops or a wifi handover kills it. When that happens `attach` prints\n`[cotal: connection lost, reconnecting]` on stderr and starts asking the manager for a new session:\na fresh grant, a fresh per-session credential, a fresh connection, so every attempt re-runs the same\nauthorization the first attach did. On success it prints `[cotal: reconnected]`, the manager repaints\nthe seat's current screen the way it does for any attach, and you carry on in the same terminal.\nRetries wait 1s, 2s, 5s, 10s, then 30s, for as long as the seat exists. The detach key is read the\nwhole time the loop runs, the waits and the attempts alike, so a reconnect never traps you: press it\nwhile a session is being established and the attach ends there, and a session that lands behind the\npress is handed back to the manager rather than left holding a slot. Everything else you type while\nthere is no session is dropped rather than queued, so keystrokes aimed at a terminal that turned out\nto be frozen, Ctrl-C included, are not delivered to the agent by a reconnect you did not know had\nhappened. That starts before the first session, not at the first reconnect: at a terminal, `attach`\nreads and drops what you type while it is still resolving the mesh, so a key struck at a prompt that\nhas not come up yet does not reach the agent when it does.\nThe terminal is in raw mode for the whole reconnect, including when the link died before the first\nsession finished opening, so the detach key works there too instead of echoing as `^]`.\n\nA **pipe** carries script input. For example, `printf 'ls\\n' | cotal attach --name web` is\nbuffered until the session opens. Buffering continues across reconnects, so\n`tail -f log | cotal attach --name web` does not lose the part of its feed written while the link was\ndown. Only a terminal gets the reader; `--no-reconnect` keeps the old behaviour on both.\n\nIt stops on its own when reconnecting cannot help, and says why: a manager that refuses the attach\nexits non-zero with the manager's own message, and a reconnect that finds the seat no longer there\n(despawned, or its agent exited while the link was down) exits cleanly with `seat <name> is gone`.\nIt stops the same way on a refusal the CLI raises itself, such as a broker that denies the request.\nA local connect refusal that retrying cannot fix, such as a static-auth mesh whose seed is now\nmissing, also exits non-zero with the refusal's own sentence. A broker that is still unreachable,\nor a user-auth service that does not answer, keeps the loop trying in silence.\nA refusal that could still pass, such as a manager at its session ceiling, is relayed in the\nmanager's own words while the loop keeps trying, once per refusal rather than once per attempt.\nPressing the detach key ends the view with `Detached`. A remote terminal-session end, including\nprocess exit or manager shutdown, prints `Attach session ended`. Neither report claims the agent\nis still running or that its cleanup completed. Use `cotal ps` to check its current state.\n`--no-reconnect` restores the single-session behaviour.\n\nEach reconnect also hands the abandoned session back to the manager, over the first link that can\ncarry the message, so an attach that flaps does not eat the manager's session slots one outage at a\ntime. If that message never gets a link, the attach says so when it ends. The live-session ceiling\ndefaults to 64 concurrent sessions (`--max-sessions`); the browser console opens one session per\npane, so a dashboard over a large mesh should size for agents \xD7 panes. Hitting the ceiling refuses\nbefore a credential is minted and names `--max-sessions`.\n\nWhich mesh `attach` resolves also decides **how it redeems the grant**. On a registered open mesh\nthere is no local seed. The CLI connects bare, the same way other control commands already do, and\nthe session rail is the caller rail that a real open-mode connection already reaches. Telling the\noperator to re-register the root is false: the registered root is already the contract. On a\nstatic-auth mesh the grant is still redeemed by minting a short-lived\nsession-scoped credential from the seed at the root the mesh resolved to, never from a `.cotal`\nfound by walking up from whichever directory you happen to be standing in. The difference is not\nhypothetical: `~/.cotal` exists on every install because the mesh registry lives there, so a command\nrun anywhere under your home directory but outside a project used to mint from your home\ndirectory's trust and present it to a broker that trusts a different chain, which surfaced as a\nbare authorization failure that named nothing. A directory that does hold another chain for the\nsame space is now reported on the way past, and not obeyed:\n\n```text\n! this directory resolves to /Users/you, whose .cotal/auth holds a DIFFERENT trust chain for space \"team\".\n attach used /Users/you/projects/app, the root this mesh resolved to. The other one is not being used, and is worth a look.\n```\n\nWhen a **static-auth** mesh holds no seed at the resolved root, `attach` refuses and names what it\nresolved, the broker and the root, instead of describing a directory it did not use and instead of\ntaking the open-mode path. An authenticated registry entry with a missing seed is still\nauthenticated. On a USER-AUTH mesh `attach` reads no seed. It sends your login and the session grant to\nthe auth service, which issues a `session-caller` bearer only if your owner and actor hold that\nsession. The connection it opens expires with the session grant.\n\nTerminal bytes stream over the mesh; the manager's own HTTP/WS face serves the console. That endpoint binds\n**loopback by default**, so nothing is exposed by accident; `cotal up --host <addr>` passes its bind\naddress down, which is what lets you reach the browser console (`cotal console`) for an agent whose manager runs on another machine.\n`attach` does not use that face: it redeems a signed mesh session grant over the broker instead (see above), so it reaches a\nremote manager regardless of the bind address. A\nbare `cotal supervise` and an embedded manager stay machine-local. Set it directly with\n`supervise --console-host <host>`.\n\nThat address is **recorded on the mesh** and carried forward, because it is a decision rather than\nsomething later commands can work out for themselves (a broker dial address is not a manager bind\naddress). Every later manager launch for the same mesh reuses it, including a same-root `cotal up` repair\nand an adopted preserved or restored listener. A manager replacement\ndoes not quietly move a reachable attach face back to loopback. Passing `--host` again overrides it,\nso you can widen or narrow exposure whenever you like; a mesh that never asked stays loopback-only\nand records nothing.\n\nBecause that face mints terminal read and write authority for every managed agent's browser session, it is credentialed in two\ntiers. A mesh caller receives a **ticket** bound to the single agent the manager just authorized,\nsingle-use and short-lived, so one authorized attach can never be re-pointed at someone else's\nagent. The **console token** is the operator's own, reaches every agent, and is printed only to the\nmanager's output. The roster, the live feed, and the PTY stream all answer `401` without one; the\nstatic console shell is served openly, since it describes no agent.\n\n## input\n\n```bash\ncotal input --name <n> --text <text> [--no-enter] [--on <instance>] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which manager to reach |\n| `--name <n>` | | Managed agent to type into (required) |\n| `--text <text>` | | The text to type, taken verbatim (required) |\n| `--no-enter` | off | Type the text and stop there, without pressing Enter |\n| `--on <instance>` | class anycast | Pin to one manager instance id using the same rules as [`attach`](#managed-seats) |\n\nTypes one line into a running agent's terminal, as if you had typed it there, and returns. This is\nthe half of [`attach`](#managed-seats) that a program wants: `attach` is a live stream that holds a\nsession open and expects a terminal on your side, so a script, a cron job or a web UI cannot use it\nto send a single line. `input` is one authorized call.\n\nWhat it is for is **harness commands**. A line beginning with `/` is not chat and not a message: it\nis something the agent's own harness handles, and the only way in is the keyboard.\n\n```bash\ncotal input --name reviewer --text \"/compact\" # ask the harness to compact its context\ncotal input --name reviewer --text \"/model opus\" # switch its model\ncotal input --name reviewer --text \"hold on that PR\" # ordinary typing works too\n```\n\n**Quoting.** `--text` takes a value, so a payload starting with `/` survives as written. A payload\nstarting with a dash needs the `=` form, because the shell-style `--text --foo` is ambiguous and is\nrefused rather than guessed:\n\n```bash\ncotal input --name reviewer --text=--verbose # dash-leading text: use --text=<value>\n```\n\nEnter is pressed by default, since a command typed but never submitted has not been delivered.\n`--no-enter` types the text and leaves it sitting at the prompt, which is how you stage a line and\nsend it later.\n\nNothing comes back but a `Terminal input written` receipt. It names the agent, space and manager,\nthe bytes the manager reported writing (counting the trailing carriage return) and whether Enter was\nrequested. It confirms the write to the terminal only. Whatever the agent does next shows up where\nits output already goes: the mesh, its transcript, or an `attach`.\n\nA write the manager cannot confirm, such as a terminal write that failed partway, exits 3 with\n`Outcome unconfirmed` and is not typed again. Part of the text may already be at the prompt, so check\nthe seat before you send it again.\n\nWith no manager running, as after `cotal service stop stack-manager`, `input` refuses\n`TARGET_UNAVAILABLE` (exit 2) before it sends anything and names bare `cotal up`, as `ps` does.\n`stop` and `attach` find their seat the same way and refuse the same. A manager that stays\nregistered but does not answer, such as one that crashed, leaves `input` and `stop` waiting out its\nanswer deadline. They then refuse `TARGET_UNAVAILABLE` too, since nothing was sent, with\n`cotal status` as the next step.\n\n**This one is operator-only, and more narrowly than `stop` or `attach`.** Those two are granted to\nanything holding `spawn`, so an agent can stop and attach to seats under its own owner. `input` is\nnot: it is granted only to operator credentials, which on a user-auth mesh means your ledger row\nneeds the `admin` scope, the same scope [`ps`](#managed-seats) already needs there. The reason is\nthat a write into a terminal is control of whatever is running in it, and on a user-auth mesh the\nown-owner rule covers every seat under you, not only the ones you launched: a `spawn`-scoped agent\ncould otherwise type into a sibling it never started. Seat locality is still resolved for you.\n\nOnly the `pty` runtime can be typed into. The external terminal runtimes (`tmux`, `cmux`, `orca`,\n`herdr`) attach to a process they do not own, so they have no input stream for it and the command\nrefuses by name rather than dropping the keystroke.\n\n## persona\n\n```bash\ncotal persona [--json|--plain]\ncotal persona show <name> [--json|--plain]\ncotal persona create <name> --file <persona.md>\ncotal persona create <name> --prompt <text> --subscribe <a,b> [--allow-subscribe <a,b>] [--allow-publish <a,b>]\ncotal persona edit <name>\ncotal persona remove <name> --yes\n```\n\n`cotal personas` is the earlier parser for the same catalog and is a legacy command. It still\nruns, and `cotal help personas` still prints its help, but `cotal help` no longer lists it and\ncompletion does not offer it.\n\n`p` aliases `persona`; `persona rm` aliases `persona remove`. These commands manage the\nselected space's local `.cotal/agents` catalog without connecting, refreshing a remote catalog\nor seeding connectors. `--space` and `--server` select the same root used by launch. A prepared\nspace has an offline catalog before it is started. Failed target resolution never falls back\nto the current directory: a `--server` that no registration names reads the project in this\nfolder, under the space prepared there, as launch does, and outside one it refuses with `TARGET_NOT_FOUND` and writes nothing. With\nno space selected and no project here, the refusal says so. With `--on <manager>`, list, show and\ncreate work on that manager's catalog instead, and edit and remove refuse there, as\n[Personas on an exact manager](#personas-on-an-exact-manager) describes. `--announce <channel>`\nbelongs to `persona create --on`; without `--on` it refuses with `USAGE_INVALID` and changes no file.\n\nA persona name uses letters, digits, `_` and `-`, up to 128 characters, and a name refusal says\nwhich rule it broke.\nCreation accepts one of `--file/-f` (a complete [agent file](agent-files.md)), `--prompt`, or\n`--from` (body file, or `-` for explicit stdin). Complete files validate all supported fields,\nmust name the requested persona and keep their original bytes. A file that names another persona is\nrefused with `SCHEMA_INVALID`, naming the file's own name and the `persona create` that keeps it.\nA file the agent-file rules refuse is `SCHEMA_INVALID` naming the file, the field and the reason,\nsuch as a missing frontmatter block, the renamed `channels` key or a `subscribe` channel outside\n`allowSubscribe`. A YAML error names only its line and column, not the parser's text.\nThey cannot be combined with\n`--role`, `--model`, `--harness`, `--subscribe`, `--allow-subscribe` or `--allow-publish`. Body\ninput requires an explicit `--subscribe` declaration, including `--subscribe \"\"` for no channels,\nand accepts `--role`, `--model` and `--harness`. `--allow-subscribe` sets the channels the persona\nmay read, and an omitted or empty one reads as the `--subscribe` set. `--allow-publish` sets the\nchannels it may post to, and without it the persona posts nowhere. Both take a comma list, as the\n[agent file](agent-files.md) fields `allowSubscribe` and `allowPublish` do. Both refuse control\ncharacters with `USAGE_INVALID`. An empty `--model` is refused with `USAGE_INVALID` too, and so is a\n`--role` or `--model` with a control character, before any other rule reads it. A refusal that quotes\na flag's value escapes its control characters. A role is an address `send -r` reaches, so a role\nthat is not one\ntoken of letters, digits, `_` and `-` is refused, `USAGE_INVALID` from `--role` and `SCHEMA_INVALID`\nfrom a file's `role`. Each entry of `--subscribe`, `--allow-subscribe` and\n`--allow-publish` must be a valid channel name, written without `#`, and each `--subscribe` channel\nmust be within `--allow-subscribe` when it is given, or creation refuses with `USAGE_INVALID`,\nnaming the flag and the entry. `--harness` must name a harness this machine knows, as\n`config set harness` requires, or creation refuses with `USAGE_INVALID` and writes nothing.\nCreate and edit refuse a definition over 1 MiB with `SCHEMA_INVALID`, the bound a create on a\nmanager applies to its input, and an edit keeps that draft and names it.\nCreation refuses an existing definition, and with `CONFLICT` a name that differs from an existing\none only in case, since a case-insensitive file system holds the two as one file. Mutation receipts contain names and paths, not bodies.\nA persona file that is a symbolic link is listed with a `PERMISSION_DENIED` warning that names the\nfile it links to. `spawn` reads that file, but `persona show`, `edit` and `remove` refuse the link\nwith `PERMISSION_DENIED`, and the next step names the file to copy in its place.\nA `.cotal/agents` that is a file or a link that loops holds no personas, so the listings, `show`,\n`create`, `edit` and `remove` refuse it with `CONFLICT`, naming it and what it is, and the next step\nis to move it out of the way.\nShow and edit accept a definition whose `name` is not its catalog name. The seeded `default`\npersona is one: its file is `default.md` and its agents launch as `default_agent`. That name is\nreserved, so a create under it is refused with `USAGE_INVALID`.\n\nEditing opens a private draft using the editor preference (`VISUAL`, then `EDITOR`, then saved\npreferences). A value that is the path of an existing file runs as that program, spaces and all.\nAny other value runs as a shell command. With no editor set, or one that cannot be found or run,\nediting refuses with `DEPENDENCY_UNAVAILABLE` (exit 2)\nand names the setting to fix. The draft's private folder is made in the temporary directory\n(`TMPDIR`). One this account cannot write refuses with `PERMISSION_DENIED`, and one that is missing\nor not a directory with `TARGET_UNAVAILABLE`, both exit 2 and naming it before anything is made. A\nsuccessful editor exit and valid definition are required before saving. A draft that fails\nvalidation is refused with `SCHEMA_INVALID` and the reason, keeping the draft and naming its path,\nso the edit is not lost. An editor that exits non-zero, as vim's `:cq` does, cancels the edit: it\nexits 0 with a `USER_CANCELLED` warning, saves nothing, and keeps and names a draft it changed. An\neditor ended by a signal fails with `OPERATION_FAILED` and keeps the draft. A SIGINT, SIGTERM or\nSIGHUP that reaches the CLI while the editor is open is passed to the editor, and the CLI waits for\nit to exit. SIGTERM and SIGHUP then stop the edit, as does a Ctrl-C that ends the editor: nothing is\nsaved, an unchanged draft is removed, a changed one is kept and named, and the CLI exits 130, 143 or\n129. An editor such as vim that takes Ctrl-C as a key keeps the edit going. A draft left as it was\nsaves nothing and says the persona is unchanged. A changed\nor removed source refuses with `CONFLICT` (exit 2), leaving the current definition intact. The\nrefusal says which one happened and keeps your edited draft in its private folder, naming it, so\nthe edit is not lost. Local\nCLI create, edit and remove share a per-persona lock during comparison and commit, not while an\neditor or confirmation is open. Manager and external writes do not participate in that lock.\nCreate, edit and remove refuse a catalog folder you cannot write, such as one set to mode 0500,\nwith `PERMISSION_DENIED`, and leave it and its files as they were.\n`--json` and `--no-input` refuse editing before an editor opens. That refusal, and a removal\nrefused for want of `--yes`, give the fix without the command's help.\n\nA catalog file that is not a regular file, such as a FIFO, is not opened: show, edit and remove\nrefuse it with `PERMISSION_DENIED`, and the list marks it unreadable and names it in a\n`PERMISSION_DENIED` warning, exit 4.\n\nList, show and mutation receipts support one-document `--json` and line-oriented `--plain`.\nShow explicitly returns the full definition and source. List does not return the body or launch\noptions. A malformed entry produces a partial inventory (exit 4); an unreadable catalog refuses\nrather than reporting it empty. Remove asks with cancellation as the default in a terminal.\nRemoving the seeded `default` persona says so in the question and the receipt, since a bare\n`cotal spawn` launches it, and names what writes a fresh copy: a local `cotal up` in its root,\n`cotal space create` or `cotal setup`.\nPlain confirmation uses numbered choices. Scripts and JSON removal require `--yes/-y`.\n\nThe legacy `personas` parser remains available during migration: `list [-v] [--running]`,\n`show`, `edit`, `new` and `rm --force`. Its `new` requires `--subscribe`, reads `--from -` from\nstdin, and overwrites an existing persona only with `--force`. Its local writes share the same\ndraft validation and commit fence. Its `list` escapes control characters in the names, fields and\nbodies it prints, as `persona show` does, and shows each persona's description or first prompt line\nwhole, wrapped to the terminal's width. Its `show` does the same on a terminal, under the file's\npath, and writes only the file, byte for byte, when its output is piped or redirected, so\n`personas show <name> > <file>` copies it. It refuses a missing persona, an invalid definition and a\nname no persona can have with the codes `persona show` uses. Only its explicit `--running` overlay\nconnects to the mesh.\nThat overlay heads the list with the mesh it read and marks each persona `running` or `not running`.\n\n## manager\n\n```bash\ncotal manager start [--runtime <name>] [--roster <file>] [--name <name>] [--foreground|--dry-run] [--space <s>]\ncotal manager stop [--with-agents] [--yes|--dry-run] [--space <s>]\ncotal manager restart [--with-agents] [--yes|--dry-run] [--space <s>]\ncotal manager status [--space <s>]\ncotal manager logs [--tail <n>] [--follow] [--space <s>]\ncotal manager install [--dry-run] [--space <s>]\ncotal manager uninstall [--yes|--dry-run] [--space <s>]\ncotal manager reconcile ...\ncotal manager seats [--drain]\n```\n\nThe manager starts and keeps your agents running. `cotal up` starts one with the rest of the stack;\n`cotal manager start` starts one on its own, for this folder's space or the one `cotal use`\nselected, with the `pty` runtime and the broker the space is registered at. Nothing has to be added\nfirst, and no other service is started for it. `mgr` is an alias.\n\n`cotal manager <verb>` is `cotal service <verb> manager`: every [`service`](#service) rule applies,\nand its flags and the manager preset's options (`--runtime`, `--roster`, `--name`, `--server`,\n`--console-port`, `--console-host`, `--ws-port`, `--max-sessions`) are taken on the line. The first\nstart saves the space's `manager` service from the preset's defaults and says so on stderr; `cotal\nservice show manager` reads it. A start that names another `--runtime` or other option for a\nstopped manager saves it again with that option, and says so. A running one refuses\n`LIFECYCLE_BLOCKED` and names `cotal manager stop` first.\n\nOn a user-auth space this folder hosts, `start` and `restart` need your signed-in, granted login,\nas `cotal up` does for its manager. Without one they refuse `AUTH_REQUIRED` (exit 2) before\nanything is saved or launched, and Next names `cotal login`, then the command again. `stop` and\n`cotal down` need no sign-in there: the broker, the records and the process belong to this folder,\nso the stop reads the manager's own local receipt.\n\nThe manager these verbs act on is the space's one manager service: `manager` when `cotal manager\nstart` saved it, or `stack-manager` when `cotal up` did. Stderr names the saved service when it is\nnot `manager`. So after `cotal up`, `cotal manager status` and `cotal manager stop` act on the\nstack's manager, and `cotal manager start` reports it running and starts no second one. Two saved\nmanager services in one space refuse `TARGET_AMBIGUOUS`, naming each with its `cotal service`\ncommand. A manager that `cotal up` or `cotal supervise` started without a saved service is reported\nby `start` and `status` with its PID, and starts nothing beside it. `logs` reads the log `cotal up`\nopened for it, `<root>/.cotal/manager.<spaceKey>.log`, and changes nothing. It takes `--tail`,\n`--follow` and `--json` as a service's `logs` does, and a follow ends when that manager's process is\ngone. After `cotal down` stops that manager, `logs` still reads the log it left: the text says the\nmanager is stopped, with its PID while its record still names one, and names `cotal up` under Next,\nand `--json` carries `recordedProcess` with `state` set to `stopped`. With no such log, `logs`\nrefuses `TARGET_NOT_FOUND`, naming the file and `cotal up`. `cotal service logs stack-manager` reads\nthe same file when no `stack-manager` is saved. A\nmanager that `cotal supervise` runs in a terminal writes to that terminal instead. `stop`, `install`\nand the other verbs refuse it while it runs, because no saved service owns it; it stops with its\nstack through `cotal down`, or where `cotal supervise` runs it.\n\n`--foreground` keeps the manager in this terminal until Ctrl-C, which stops it through its native\nstop. A unit or container you write that runs a manager uses `cotal manager start --foreground`.\n`cotal manager install` refuses `UNSUPPORTED` in this release, because a Cotal-written unit runs\nonly a direct-process service. It names the `ExecStart=` line for a unit of your own instead, whether\nor not a manager was started, with one exception: while a manager that `cotal up` started with no\nsaved service runs, `install` refuses it as `stop` does and names `cotal down`.\n\n`manager start` and `manager restart` run the first-run connector seed, as `cotal supervise` does,\nso a fresh machine or container has its connectors before the manager boots; `status`, `logs`,\n`stop` and the maintenance verbs install nothing.\n\n`reconcile` and `seats` are the [manager maintenance](#manager-maintenance) commands. Bare\n`cotal manager` lists every verb and starts nothing.\n\n## Start a manager for a hosted space\n\nA hosted space runs its broker elsewhere, so nothing on your machine starts a manager for it. Start\none:\n\n```bash\ncotal manager start --space <space> --runtime <orca|tmux|pty>\n```\n\nIt needs you signed in to the space (`cotal login`) and the space registered on this machine\n(`cotal meshes add`). With the space selected by `cotal use <space>`, a bare `cotal manager start`\nis enough: it uses the `pty` runtime, which every platform has built in. `orca` and `tmux` need\ntheir extension first, such as `cotal ext add @cotal-ai/orca`. The command returns once the manager\nanswers; `--foreground` keeps it in this terminal instead. `cotal ps` then lists it with the agents\nit runs, and `cotal spawn -d <persona>` launches an agent through it.\n\n## supervise\n\n```bash\ncotal supervise [--execution runtime|none] [--runtime <name>] [--space <s>] [--server <url>] [--name <name>] [--spawn <names>]\n```\n\n`supervise` runs a manager in this terminal until Ctrl-C. It is deprecated, hidden from help and\ncompletion, and removed in `1.0.0`, with the other [deprecated daemon\nentries](UPGRADING.md#deprecated-daemon-entries): every run prints one stderr warning naming\n`cotal manager start`, and `cotal manager start --foreground` for a terminal or a unit. On a\nterminal it wraps like any other warning. A run given\n`--execution`, `--spawn`, `--seat-cap` or a resume flag, which `manager start` does not take yet,\nsays to keep `supervise` for that run instead. It keeps its flags and its run for existing units,\nscripts and containers. The manager that `cotal up` starts\nruns the same code through a hidden entry. Without `--space` it\nsupervises the space other commands target here: `COTAL_SPACE`, else the space this folder hosts\nor is registered for, else the one `cotal use` selected. A space it did not get from `--space`,\n`COTAL_SPACE` or a folder that hosts it keeps its manager records in the folder that space is\nregistered at, so a start from any folder is the same manager, with the same instance and name.\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | `COTAL_SPACE`, this folder's space, or the `cotal use` selection | Space to supervise. On a root that hosts several spaces, name one of them here |\n| `--server <url>` | hosting mesh, or matching registered mesh | Broker URL. A registered mesh supplies it when omitted; a different explicit value is refused before anything is dialed. |\n| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |\n| `--execution <runtime\\|none>` | `runtime` | `none` starts a control instance for a hosting user-auth root, with no runtime, connector inspection, workload or terminal execution |\n| `--console-port <n>` | none | Protocol-console port |\n| `--console-host <host>` | loopback | Bind host for the console endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets the browser console reach this manager from another machine. `cotal attach` does not use this face: it redeems a mesh session grant over the broker |\n| `--max-sessions <n>` | 64 | Live-session ceiling. Each console pane and each `cotal attach` is one session, so size for agents \xD7 panes, not agent count. A capacity refusal names this flag. `cotal up --max-sessions` records the same number on the mesh so a later `supervise` started by repair keeps it |\n| `--seat-cap <n\\|unlimited>` | `unlimited` | Live seats this manager admits for one owner. `unlimited` leaves the manager's own 50-slot ceiling as the only bound, so a mesh with one owner keeps every slot. Set a number where several owners share a manager, and one owner cannot take the host. A launch over it is refused before anything is reserved, naming the owner's count, the cap and the remedy. It is recorded in this root, so a later start without the flag keeps it |\n| `--name <name>` | none | The manager's name. Its presence card carries it, `who --all` and the manager choice show it, and `--on` takes it on a user-auth mesh. It is recorded in this root, so a later start without `--name` keeps it. Without a name, the manager shows as `<host>/<runtime>` |\n| `--roster <file>` | none | Declarative roster to boot at startup. See [Roster files](define-a-team.md#roster-files) |\n| `--launch <spec>` | none | Resolved manifest launch spec (from `up -f` / `team boot`) |\n| `--spawn <names>` | none | Comma-separated personas to pre-spawn at startup |\n\n`--ws-port`, `--resume-attempt`, `--resume-commit-file` and `--resume-commit-sha256` are passed by\n`cotal up` when it starts the manager, for the console session client and for resuming preserved\nseats. They are not for direct use. `--resume-commit-token` is the deprecated argv form of the\nresume commit, still accepted for older callers (see [Backups](#backups)).\n\nThe manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`,\n`attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise`\ndirectly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an\noptional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, `@cotal-ai/cmux`, or `@cotal-ai/herdr`) and\nselect it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.md).\nBoot inventory decides whether this process takes unpinned `spawn`/`launch` on the class rail:\nif every declared connector is unavailable, those commands stay on this instance rail only\n(`status` reports `classSpawn: false`). `describe` still answers on the class rail, so an\nunpinned spawn can bind-fence against a skip member; re-issue, or pin `--on`. With no member\non the class rail, an unpinned spawn is refused and names the agent application to add. A partial\ninventory keeps the class rail and names `--on` on a harness refusal, because sibling\ninventories are not readable from the serve credential. See [control surface](control-surface.md#instance-routing).\nThe manager prints what its boot inventory found once it is up, so a start that is refused prints\nno boot line.\n\nUse `--execution none` only for a fresh control instance in an existing hosting user-auth root.\nIt requires that space's signing trust, provider pin and user-mode mesh record, without remote\nmanager authority. It rejects `--runtime`, startup workload flags and participant registrations\nbefore resolving a Runtime or connector. The mode needs no connector seeding. An existing\nunclassified identity or retained workload refuses admission; a recorded NONE instance cannot\nrestart as runtime. Do not delete identity or capability files to change modes.\n\nNONE retains native registration, status and the selected Manager's empty managed-seat ps.\nIt does not adopt foreground agents, roster peers or HTTP-hosted sessions. Workload operations\nstill need an explicitly selected execution-owning participant Manager and its existing authority.\nStarting a control instance does not enable an auth service's interactive local-primary policy.\nNONE also refuses model inspection and caller-driven persona, purge, transcript, preservation,\nresume, cancellation, turn-yield and self-stop writes. It starts no transcript receiver or sweep.\nIts native registration, lease, renewal, session-ledger and goal maintenance can still write.\n\nLocal runtime startup now records identity and execution mode during admission. A later failed\nauthentication or registration can leave those files classified as runtime. Invalid supervise\narguments refuse before connector seeding, with one error line rather than seeding first.\nA refusal names the caught value's `message`, or the value itself when it has none, so an auth\nprovider that rejects with `null` or a string still gets its refusal line and exit status 1.\nWhen the broker refuses the credential the manager presents, such as one this folder's signer\nminted for a space another folder hosts, `supervise` exits 2 with a `PERMISSION_DENIED` refusal\nunder a `Mesh:` heading that names the credential and the broker, and its Next names the folder\nthat hosts the space. Before it connects, `supervise` refuses the same way, with exit 2 under a\n`Mesh:` heading, and writes nothing in this folder. A space whose own stack in this folder is\nstopped or not started gets `TARGET_UNAVAILABLE` and `cotal up` there, and with `--server` also the\nfolder whose `cotal up` started that broker. A user-auth space that another folder hosts gets\n`PERMISSION_DENIED` and the folder that hosts it.\n\nOn a normal `SIGINT`/`SIGTERM`, the manager stops every seat and requires the selected runtime to\nprove the seat is gone before it releases the manager lease or service registration. A stop that\ncannot prove exit fails loud and keeps manager authority instead of reporting a clean shutdown while\nan orphan still holds broker rails. When the runtime refuses the stop itself, the shutdown fails at\nonce and its error names the refusal as `stop failed: <message>`. A manager that a controller stack\nor `service start` runs stops this way on a plain `SIGTERM` that carries no stop request, such as\none from `kill`, a systemd stop or a system shutdown, with the default agent policy. Its log shows\n`received SIGTERM with no stop request`, it writes no stop receipt, and the next `cotal down`\nrecords it as already gone. It ignores a `SIGINT` that carries no stop request and logs one\nwarning line, because Ctrl-C reaches it together with the CLI that publishes the request. A\nmanager that does not answer its endpoint, such as a hung or stopped process, cannot report its\nstop impact, so `cotal down`, `service stop` and `service restart` refuse it `LIFECYCLE_BLOCKED`,\nexit 2, submit no stop and name its pid. Once that process is ended, as with `kill -KILL <pid>`, the\nsame command finds it gone and goes on. After an abrupt manager death, the same logical successor\nterminalizes only its own durable static slots, verify-evicts the predecessor's broker principal,\nrecords that result in the lifecycle's caller-readable audit detail, reaps the predecessor's seat\nprocess through the runtime's custody reference recorded on the slot (the pty runtime verifies the\nprocess start identity in its seat record, so a reused pid is never signalled), and only then\nretires the lifecycle and frees the alias. A seat whose spawn carried `supervise` with restarts left\nis restarted instead of retired: the successor reaps any seat the slot still names, relaunches it\nunder the same name, lifecycle uid, credential, persona and cwd, and counts the restart against the\npolicy. A seat on a runtime that can adopt a live seat (tmux) and still running when the successor\nstarts is adopted in place: the successor checks that the recorded tmux server, window and pane are\nthe ones the slot names and that the pane's process still runs, then manages that seat again under\nits existing credential, without a restart. Both paths read the record the manager writes to\n`.cotal/seat-records/<space>/<name>.json` once a supervised or adoptable seat is ready, rewrites at\nevery restart and credential renewal, and removes when the seat is freed. A spent budget, a missing\nrecord, an expired credential, a persona file or credential that no longer matches the record, or\nan adoptable seat whose pane has exited retires the lifecycle as above. A runtime that custodies its seats reserves that\nreference before it launches one, and the manager records it on the slot's first durable row, so a\nmanager that dies part-way through a spawn also leaves a seat its successor can address. A\nsame-lifecycle restart or a resume records the new seat's reference on the slot the same way, and\nwhen the slot does not take it the restart or resume fails and stops any seat it started, so the\nslot never names a seat that has already exited while its replacement runs. A resumed seat keeps\nits retained credentials, so the resume frees it only once its exit is proved; a seat whose stop\ncannot be proved stays managed, and the resume's error says so, naming a stop the runtime refused\nas `stop failed: <message>`. A spawn\nthat launched its seat and then failed is rolled back by the manager that launched it, and that\nrollback reaps the seat through the same reserved reference before the lifecycle retires. Missing or unverified broker evidence keeps the slot\nterminalizing, and so does a runtime that cannot reap by reference. While the name stays held, a\nsame-name spawn or a resume re-drives that teardown with the same recorded reference, so it reaps\nthe seat again or keeps the name held.\n\nA `meshes add --mode user` entry is a **participant** registration, not hosting authority. A\nparticipant may run `supervise` only when the host advertises the remote manager authority service\nand the signed-in actor has the dedicated `supervise` ledger scope. The CLI obtains the closed,\nloopback-only `manager-service` view; `spawn` and `admin` do not substitute for that scope. The\nhost issues the manager's public-nkey JWT material through its lifecycle-bound prepare \u2192 activate\n\u2192 renew protocol, never by handing the participant a signer or static provisioner credential.\nThe host also performs instance-scoped eviction and guarded gate reconciliation. A remote manager\nrefreshes its short-lived registration executor before clean deregistration, so a long-running\nprocess removes its service row on `SIGINT` or `SIGTERM`. After an unclean stop, the same instance\nverify-evicts its superseded family and advances the process epoch. If an abandoned frozen gate\nholds the manager governance slot, a different supervise-scoped manager asks the host to reconcile\nthat holder after a complete gone verdict, then retries its registration once.\n\nA remote supervise never uses local signing trust: with host-issued authority in hand, the\nmanager mints from that authority alone and consults local records only to refuse a conflict,\nnamely the supervised space's own trust records under the cwd root. A root that hosts another\nstatic space beside the sign-in is a normal configuration and is never read as this space's\ntrust.\n\nThe broker URL in the registry entry decides the transport. A remote broker is often published\nover a `wss://` edge rather than a raw `nats://` port, and `supervise` dials whichever scheme the\nrecord holds, starting with the manager-authority registration it runs before the manager exists.\nThe record also decides whether that registration requires TLS, so a participant never downgrades\nthe credential exchange to a plaintext connection the registry did not describe.\n\nWithout that advertised host service or scope, `supervise` refuses before it starts a manager.\nRun `cotal spawn` without `--detach` to launch a foreground agent, or ask the space host to enable\nthe authority service and grant `supervise` for detached agents. If a running remote manager loses\nrenewal, it reports degraded state and refuses unsafe new starts and restarts; live agents are not\nsilently replaced. Do not run `cotal down` or `cotal up` on a participant machine to repair this\ncondition.\n\n## daemon\n\n```bash\ncotal daemon serve [--daemon-home <dir>] [--json|--plain]\ncotal daemon status [--daemon-home <dir>] [--json|--plain]\ncotal daemon login --idp <auth base URL> [--client-id <id>] [--daemon-home <dir>] [--json|--plain]\ncotal daemon connect-check --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon register --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-connect --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-disconnect --space <s> [--daemon-home <dir>] [--json|--plain]\ncotal daemon service-status --space <s> [--daemon-home <dir>] [--json|--plain]\n```\n\nThis release serves a local endpoint for status, sign-in, a connection check, a registration request\nand the daemon's own service connection. It does not start a Manager, serve any command on its\nservice, connect to Cloud, or install a system service. It supports Linux only. On other\nplatforms, an unset `XDG_RUNTIME_DIR` produces\n`TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`); with it set, the platform check refuses `UNSUPPORTED`.\n\n`serve` runs in the foreground and prints `daemon ready at <socket>` once the endpoint is bound\nand checked. That success line is plain text; `--json` shapes its refusals. `status` prints the\nstate, version, protocol, process ID, start time and uptime. `state: \"ready\"` means only that the\nlocal control listener is bound and verified, not that a mesh or Cloud connection exists.\n\nThe endpoint is `$XDG_RUNTIME_DIR/cotal/daemon.sock`. Without `XDG_RUNTIME_DIR` both forms refuse\n`TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`). Both refuse `PERMISSION_DENIED` for a runtime directory\nthat is a symlink, not a directory, not owned by you, or not mode `0700`. `status` also refuses\n`PERMISSION_DENIED` for an endpoint entry that is not a socket, not yours, or not mode `0600`.\nThe daemon drops connections from other users. The kernel separates users, not processes of one\nuser: any process running as you can use the local controls, including sign-in and cancellation.\n\n`status` with no daemon running refuses `TARGET_UNAVAILABLE` (`NOT_RUNNING`). `serve` refuses\n`CONFLICT` (`ALREADY_RUNNING`) for any existing socket, including one left after a crash, and\n`PERMISSION_DENIED` (`SOCKET_NOT_SOCKET`) for a non-socket entry. It removes neither.\nSIGTERM and SIGINT shut down cleanly and remove only the daemon's own socket. SIGKILL or SIGHUP,\nincluding closing the terminal, leaves a stale socket. To recover, run `cotal daemon status`.\nIf it reports `NOT_RUNNING`, confirm no daemon process is running, then remove the named socket\npath by hand and start again.\n\n`--daemon-home <dir>` runs or reaches a separate daemon whose own state lives in `<dir>`: its\nsign-in session, its per-space identity, register request and service store, all at the paths\nbelow with `<dir>` in place of `<COTAL_HOME>`. Spaces are still read from the shared Cotal home's\nmesh registry, which this daemon never writes. Its socket is\n`$XDG_RUNTIME_DIR/cotal/home-<16 hex>/daemon.sock`, derived from the directory, so it runs beside\nthe default daemon. `<dir>` must already exist as an absolute directory you own with mode `0700`,\nand cannot be the Cotal home. A relative path or the Cotal home refuses `USAGE_INVALID`, a missing\ndirectory `TARGET_NOT_FOUND`, and a symlinked, foreign or loose one `PERMISSION_DENIED`. Nothing is\ncreated or repaired.\n\n`serve` holds the lifetime lock `<dir>/daemon.lock` while it runs. A second `serve` for the same\ndirectory refuses `CONFLICT` (`LOCK_HELD`), naming the live holder. A start that fails for any other\nreason releases the lock again. A killed daemon leaves its socket behind, which still refuses\n`CONFLICT` (`ALREADY_RUNNING`) and is never removed, so recover as above. Every client command with\n`--daemon-home` sends nothing unless the process at that socket is the lock's live owner (same pid\nand process start). Otherwise it refuses `PERMISSION_DENIED` (`PEER_NOT_LOCK_OWNER`).\n\nThis daemon is a different machine identity. Nothing is copied from the default daemon: it needs\nits own `login`, its own `register`, and the space owner's approval of its own instance. Without\n`--daemon-home` every command behaves as before.\n\n`login` signs the running daemon in with device approval. The daemon runs the same sign-in as\n`cotal login` and keeps the session in the machine's Cotal home, where `cotal login` keeps it. The\ncommand prints only the approval link, code and expiry to stderr, then the signed-in account. No\nsession token crosses the endpoint or reaches the terminal. `--idp` takes an HTTPS auth URL, or an\nHTTP loopback IP URL; `--client-id` defaults to `cotal-cli`.\n\nOne sign-in runs at a time: a second `login` refuses `CONFLICT` (`LOGIN_IN_PROGRESS`) without\ncontacting the provider. Ctrl-C, or the command's connection closing, requests cancellation on this\nmachine. If cancellation takes effect before persistence, nothing is saved, even if device approval\nalready happened. An already-completed attempt remains completed; cancellation revokes nothing\nat the provider. A cancelled attempt reports `USAGE_INVALID` (`CANCELLED`). A denied or expired\napproval refuses `PERMISSION_DENIED` (`DENIED` or `EXPIRED`), and an unreachable provider refuses\n`DEPENDENCY_UNAVAILABLE`\n(`UNREACHABLE`); none of these saves a session. Any other provider answer fails `OPERATION_FAILED`\n(`REFUSED`). Such a failure can come after the session was saved, so run `login` again. A failure\nis reported with a fixed sentence; the provider's own error text is never relayed. `status`\nkeeps answering while a sign-in waits.\n\n`connect-check` asks the running daemon whether it could register with a space as its own service.\nThe daemon is not a Manager and never borrows a Manager's identity or authority. The check reads\nthe space's registration on this machine and the daemon's sign-in, then the space's public account\nidentity from its auth service (`GET <exchange>/service-endpoint-authority`, no redirects). It sends\nno registration and holds no credential. A space whose auth service does not host service endpoints\nrefuses `UNSUPPORTED` (`UNSUPPORTED_BY_SPACE`). The daemon does not fall back to anything else.\n\nThe other refusals are `TARGET_NOT_FOUND` (`NOT_REGISTERED`) for a space this machine has not\nregistered, `UNSUPPORTED` (`NOT_USER_AUTH`) for a space without user sign-in, `AUTH_REQUIRED`\n(`NOT_SIGNED_IN`) when the machine is not signed in to the space's provider, and\n`DEPENDENCY_UNAVAILABLE` (`UNREACHABLE` or `REFUSED`) when the auth service cannot be reached or\nanswers with something unusable.\n\nOn success it reports `ready-to-register` with the space's account, the daemon's service name\n`ai.cotal.machine` and the daemon's own instance ID. Nothing has been registered. The space owner must\nstill approve the daemon, and the service name needs the space operator's binding. The first\nsuccess creates the daemon's identity for that space at\n`<COTAL_HOME>/daemon/<space segment>/service-identity.json` (mode `0600`). It holds the instance ID\nand the account, and no credential. Later checks reuse it. If the space later answers with a\ndifferent account, the check refuses `CONFLICT` (`ACCOUNT_MISMATCH`) and changes nothing: confirm\nthe space was not replaced before you remove that file by hand.\n\n`register` asks the running daemon to request its own service registration with a space. It first\nruns `connect-check` and returns any outcome other than `ready-to-register` unchanged, without\ncontacting the space again. Otherwise it sends one registration request for the daemon's instance\nwith this machine's sign-in, through the space's stock service authority. The daemon never sees or\nrelays that sign-in proof. The request ID is saved before the first send, at\n`<COTAL_HOME>/daemon/<space segment>/register-request.json`, and every later `register`, including\nafter a restart, replays the same request unchanged. The stock writer does not guarantee durability\nacross power loss.\n\nThe fixed service name binds to one owner per space. Different owners in a shared space need distinct\noperator-approved endpoint names; this command does not yet select one. The operator must verify or\ndelegate the namespace before binding it.\n\nThe space answers in one of three ways, each reported with exit 0:\n\n- `approval-required`: the space owner must approve this daemon and the space operator must bind its\n service name `ai.cotal.machine`.\n- `pending`: the space is processing an approved registration.\n- `registered`: the registration is recorded. It is not activated or serving, so the machine is\n still not connected.\n\nNo release ships a command or page that lets an owner approve the daemon or an operator bind its\nname. A refusal by the space is `PERMISSION_DENIED` with the space's reason as the native code, such\nas `PROPOSAL_REFUSED`. A reason the daemon does not recognize is reported as `UNRECOGNIZED`, and the\nspace's own text is never shown. A new proposal after a refusal needs a new request, which `register`\ndoes not send. A sign-in the provider rejects (expired or revoked) is `AUTH_REQUIRED`: run\n`cotal daemon login` again. A failure before anything was sent is `DEPENDENCY_UNAVAILABLE`. Only a\nrequest whose outcome is unconfirmed is `OPERATION_UNKNOWN`: run the same command again\nto replay it. A changed registration for the same instance is refused `CONFLICT` and nothing is sent.\n\n`service-connect` connects the running daemon to the space as its own registered service. It replays\nthe saved register request unchanged to read its registration, gets its service credential from the\nspace's auth provider, verifies the bearer signature and witness against the pinned authority's\nissuer keys, checks the serving snapshot against its embedded contract, and connects to the broker\nas that service. It binds the registered\nresponder before reporting ready. It succeeds, exit 0, only after the broker accepts the connection,\nthe responder subscriptions are flushed and the space accepts the daemon as ready. The renewal and ready request IDs are kept in\n`<COTAL_HOME>/daemon/<space segment>/service-store/`, mode `0600`, and reused after a restart.\n\nEvery other answer is a refusal and nothing is connected. Not yet approved is `LIFECYCLE_BLOCKED`\n(`APPROVAL_PENDING`). A refused registration is `PERMISSION_DENIED`, and so is a space that ended the\ndaemon's authority, with its code as the native code, such as `APPROVAL_REVOKED`; connecting again\ndoes not reconnect it. A space or broker that cannot be reached is `DEPENDENCY_UNAVAILABLE`, and\nconnecting again retries the same requests. Without a register request the answer is\n`TARGET_NOT_FOUND`.\n\nWhile the daemon runs, the connection renews itself and reconnects on its own. The access bearer\nlives at most 60 seconds, so the broker drops the connection when each one expires and the daemon\nreconnects with the next. While the broker connection is down, for that reason or any other, the\nanswer is `connecting`, not connected; it is connected again only after the broker is back and the\nspace accepts the daemon as ready again. This is not a persistent service: a restarted daemon starts\nwith no service connection and stays disconnected until you run `service-connect` again, and an\nended connection is remembered only until the daemon stops.\n`service-status` reads the daemon's current answer without contacting the space. `service-disconnect`\ndrains the responder and closes the connection, and stopping the daemon closes all of them.\n\nThe service answers its existing `machine-status` command with `null`. This is a liveness response;\nit contains no vitals, hostname or machine metadata. Its stock Describe answer is explicitly public:\nit identifies the endpoint and derived owner token and includes the protocol and command schemas.\nCallers still need broker-enforced invoke access to the selected instance and command. Connecting the\ndaemon grants no caller access. Owner approval, operator name binding and caller grants remain\nseparate actions. An authority that does not publish its issuer identity cannot support this\nserving connection; the daemon refuses rather than trusting a decoded token or using Manager\ncredentials. The registration contract is unchanged.\n\n## service\n\n```bash\ncotal service [--space <s>] [--json|--plain]\ncotal service show <name>\ncotal service add <name> --exec \"<command>\" [--cwd <dir>]\ncotal service add <name> --preset <preset> <preset-options>\ncotal service add <name> --file <definition.json>\ncotal service start <name> [<preset-options>] [--instance <id>|--new-instance] [--foreground|--dry-run]\ncotal service stop <name> [--instance <id>] [--with-agents] [--yes|--dry-run]\ncotal service restart <name> [--instance <id>] [--with-agents] [--yes|--dry-run]\ncotal service status <name> [--instance <id>]\ncotal service logs <name> [--instance <id>] [--tail <n>] [--follow]\ncotal service install <name> [--instance <id>] [--dry-run]\ncotal service uninstall <name> [--instance <id>] [--yes|--dry-run]\ncotal service remove <name> [--instance <id>] [--yes]\n```\n\n`svc` and `services` are aliases. Every form takes `--space`, `--json` and `--plain`.\n\n| Flag | Meaning |\n|---|---|\n| `-s, --space <s>` | The space whose services to use, resolved as in [Selecting a space](#selecting-a-space). A space that is prepared but not started can be named |\n| `--exec <command>` | add: your own program and its arguments, quoted as in a shell but run without one |\n| `-C, --cwd <dir>` | add `--exec`: the folder it runs in (default: this folder) |\n| `--preset <name>` | add: save a declared preset. Its options become flags of `add` |\n| `-f, --file <definition.json>` | add: import a version-1 definition file |\n| `--instance <full-id>` | Select one instance by its full ID, not a prefix |\n| `--new-instance` | start: allocate another instance of a process definition |\n| `--foreground` | start: keep the process in this terminal |\n| `--with-agents` | stop, restart: request the manager's native agent-stop policy |\n| `--dry-run` | start, stop, restart: preview the effect. install, uninstall: inspect the unit |\n| `-y, --yes` | stop, restart, remove, uninstall: confirm in a script |\n| `--tail <1..1000>` | logs: how many records to read (default 100) |\n| `--follow` | logs: keep reading |\n\nA service is a saved definition in one space plus the instances started from it. Bare `service`\nlists every service you can name here with its state and the one command that moves it on: the\nbuilt-in services (the presets), whether started or not, and the ones you added. Nothing starts a\ndependency for you: starting the manager does not start its broker.\n\nA built-in service needs no `add`. `cotal service start <preset>` saves a definition of that name\nfrom the preset's defaults and starts it; stderr says what it saved. A preset that needs something\nonly you can give, such as the `web` dashboard's credential file or the intake's keys, refuses with\nthose flags named, and takes them on the start line: `cotal service start web --creds <file>`. A\nstart refused before anything launched forgets the definition it saved. A preset name acts on the\nspace's one service built on that preset, so after `cotal up` the name `delivery` reaches\n`stack-delivery`. The manager has its own [`manager`](#manager) commands. `--json` writes one envelope for every form,\nand `logs --follow --json` writes one record per line. Without `--json`, a follow's last line says\nin a sentence why it ended. `logs` refuses a log file this account\ncannot read with `PERMISSION_DENIED` (exit 2), naming the file and `chmod u+rw`, quoted when a\nshell would split the path. The failed read changes no permissions or service state. With `--follow`\nthe refusal is the stream's `error` record, which carries that step as `remedy`, and the stream ends.\n\n### Adding a service\n\nYour own program is one line:\n\n```bash\ncotal service add reviews --exec \"node /srv/reviews/server.mjs --port 8080\"\ncotal service start reviews\n```\n\n`--exec` splits its text into the program and its arguments as a shell quotes words, but runs no\nshell. Single and double quotes and backslashes work as in a terminal. A pipe, a redirection, a\n`;` or `&`, a variable, a glob or a `~` outside quotes refuses `USAGE_INVALID` (exit 1), because it\nwould not mean here what it means in a terminal; for those, run a shell yourself with\n`--exec \"sh -c '<command line>'\"`. A bare program name is looked up on `PATH` as `add` saves it, and\na relative path is saved as the absolute path it names in `--cwd`, which defaults to the folder you\nrun `add` in. The service is ready once its process runs. `--file` keeps the full definition shape\nbelow, for readiness over HTTP, environment references or a credential file.\n\nThe presets built into the CLI are `auth-service`, `broker`, `delivery`, `exec-endpoint`,\n`feedback-intake` and `manager`. The `linear` and `web` presets come with the `@cotal-ai/linear`\nand `@cotal-ai/web` extensions, which `cotal ext add <package>` installs. Bare `service` lists the\npresets this machine has, and a name it does not have is refused as `DEPENDENCY_UNAVAILABLE`.\n\n`cotal service add <name> --preset <preset> --help` lists a preset's options. An option the preset\nfills itself shows `(default: ...)`, such as the space's root for `--root` and `pty` for the\nmanager's `--runtime`. A required option with no default is marked `(required)`, and an option that\ntakes a path shows `<dir>` or `<file>`. A missing required option is refused with each one named,\nand the refusal prints the command line with them.\n\nA preset's options describe the service and never choose its space. `add` saves into the space\n`--space` names, or else the selected space or the folder's, as other commands resolve it.\n\n`add` checks the options against the preset's schema, and checks that each declared path exists\nwith its required protection. It does not check that a root or credential suits the preset, such\nas a root's auth mode or which credential a file holds. Those are checked when the service starts.\nA refusal names the option or field at fault. Option values are not printed, except the `broker`\npreset's `server`: `add` checks it against the address rules a start applies, and its refusal quotes\nthe address unless it holds an `@`, which may be a credential.\n\n`--file` imports a version-1 definition. This one runs a program and treats it as ready once its\nloopback health URL answers 200:\n\n```json\n{\n \"schemaVersion\": 1,\n \"space\": \"main\",\n \"launch\": { \"type\": \"process\", \"executable\": \"/usr/bin/node\", \"args\": [\"/srv/reviews/server.mjs\"], \"cwd\": \"/srv/reviews\" },\n \"readiness\": { \"type\": \"http\", \"url\": \"http://127.0.0.1:8080/health\", \"expectedStatus\": 200, \"timeoutMs\": 10000 },\n \"envRefs\": { \"REVIEWS_TOKEN\": \"REVIEWS_TOKEN\" },\n \"credentialRef\": { \"kind\": \"file\", \"path\": \"/srv/reviews/reviews.creds\" }\n}\n```\n\n| Field | Meaning |\n|---|---|\n| `schemaVersion` | `1` |\n| `space` | The selected space's name. Another name is refused |\n| `launch` | `{type: \"process\", executable, args, cwd}`: an executable by absolute path, by a path relative to `cwd`, or by a bare name that `add` finds on `PATH`, at most 256 arguments of at most 8192 characters each, and an absolute working directory. Or `{type: \"adapter\", adapter, options}` for an installed adapter, which a preset writes for you |\n| `readiness` | `{type: \"process\", timeoutMs}`: ready once the process runs. `{type: \"http\", url, expectedStatus, timeoutMs}`: ready when a loopback `http://127.0.0.1` or `http://[::1]` URL answers that status. An adapter definition uses `{type: \"adapter\", timeoutMs}`. `timeoutMs` is 1 to 120000 |\n| `envRefs` | Optional. Child variable name to the name of a variable in the environment `start` runs in. `CLAUDECODE` and names starting `COTAL_` or `CLAUDE_CODE_` are refused |\n| `credentialRef` | Optional. `{kind: \"file\", path}`: an absolute, protected credential file. The child receives its path as `COTAL_CREDS`, never its contents |\n\nA `space` that names another space is refused `SCHEMA_INVALID`, exit 1, before `add` checks whether\nthe selected space already has a service of that name. No other field is accepted. `add` saves the\ndefinition with the space's root, which `show` lists as `Added in`; a file that sets `root` itself\nis refused `SCHEMA_INVALID`, exit 1, and nothing is saved. The file is at\nmost 256 KiB. A `readiness` value that breaks these rules refuses `SCHEMA_INVALID`, exit 1, naming\nthe field and its rule, such as a `timeoutMs` outside 1 to 120000, a field the chosen type does not\ntake, or a `url` that is not on loopback. A launch argument over 8192 characters refuses\n`SCHEMA_INVALID`, exit 1, naming its position and that limit.\n\nA process launch runs its executable as you, so the executable follows the\n[`space create`](#selecting-a-space) rule for other local writers. The file it names, where a link\npoints, every folder above that, and the folder a link is in must be writable only by you or root.\n`add` checks this before it saves, and `start`, `restart` and an installed unit check it again\nbefore anything launches or stops. An unsafe path refuses `PERMISSION_DENIED` (exit 2), and the\nrefusal names each unsafe path and one remedy such as `chmod g-w <paths>`.\n\n`add` looks a bare executable name up on its own `PATH` once and saves the absolute path it finds,\nwhich its receipt shows as `Executable` and `--json` as `result.executable`. Every later `start`\nruns that file, whatever its own `PATH` is. A `PATH` with a relative or empty entry refuses that\nlookup `UNSUPPORTED` (exit 2), as a unit install does, because the entry would resolve against\nwhichever folder `add` runs in. A name not on `PATH` refuses `DEPENDENCY_UNAVAILABLE` (exit 2). A\ndefinition an earlier release saved with a bare name refuses to start `SCHEMA_INVALID` (exit 1);\nremove it and add it again.\n\n### Running a service\n\n`start` records the instance before it launches anything and prints its full ID. A start that fails\nbefore its process exists records the instance as failed. Its refusal names the cause, such as a\nspace that is prepared but not started. `start` can then try again on the same instance, and\n`stop`, `remove` and `cotal down` find nothing to stop.\n\nA definition starts only in the root it was added in. One a forgotten space of the same name left in\nanother folder refuses `start`, `restart` and `install` `CONFLICT` (exit 2) before anything launches.\nThe refusal names the root it was added in and `cotal service remove <name> --space <space> --yes`,\nwhich forgets it so you can add it again for this project. Bare `service` marks it `inherited from\n<root>` in its `State` column and names that `remove` as its Next, and `--json` gives each\ndefinition its `root` and a `rootMatch` of `this-root`, `inherited` or `not-recorded`. A definition an earlier release saved has no recorded\nroot: it starts as before and is listed as `root not recorded`, as are a controller stack's own\nmember definitions.\n\nA `start` or `restart` whose environment does not set a variable that `envRefs` reads refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2) before anything launches or stops. It names each missing variable\nand the child variable it feeds, never a value. `show` lists the references by name.\n\nA `start`, `restart` or `install`, or an installed unit, whose executable or working directory was\nmoved, removed or changed mode since `add` refuses `DEPENDENCY_UNAVAILABLE` (exit 2) before anything\nlaunches or stops. The refusal names the path and what is wrong with it, with its mode when the\nmode is the cause. The Next names the fix, such as `chmod u+x <path>`, quoting paths that need it\nfor executable and working-directory permission repairs. No permissions change automatically.\nFor a path that is gone, it\nalso names `cotal service remove <name> --space <space> --yes`, after which you can add the service\nagain with another path. `add` refuses a path that does not exist the same way and names the field\nto correct.\n\n`stop` and `restart` ask before they act in a terminal and need `--yes` in a script. Enter, Ctrl-C,\nCtrl-D or closed input at that question leaves the service unchanged and exits 0. Without an\ninstalled unit, a `restart` whose process is already gone stops nothing, so it does not ask. A\nstart or stop that finds nothing to do says so and reports `no-op`.\n\n`status` names the file a running instance's process executes, as the kernel reports it, so the\n`stack-broker` row shows which `nats-server` serves the stack. `--json` carries it as `executable`.\nA stopped instance shows no running process, and a host with no `/proc` shows it as not readable.\nFor a background instance with no recorded supervisor, when its recorded state is running but its\nprocess is gone, `status` keeps its historical readiness and records no exit code or signal.\nIts Next names `cotal service logs <name> --space <space> --instance <id>`, where the process's own\noutput is kept.\n\n`remove` forgets a definition once every instance is stopped and no unit is installed. It deletes\nthose instances' logs and lists each file it deleted.\nWith `--instance <id>` it forgets only that stopped instance record, and keeps the definition,\nits other instances and its logs. A command that names no instance while several records exist refuses\n`TARGET_AMBIGUOUS`; its Next lists each instance with its state and the `remove --instance` command\nfor each stopped one, so a finished record stops blocking the bare form. A `start` that selects a\nrecord of an earlier revision of its definition, as a definition moved aside and added again leaves,\nrefuses `CONFLICT` (exit 2) and launches nothing. Its Next names\n`cotal service remove <name> --instance <id> --space <space> --yes` and then `start` again, after a\n`cotal service stop` of that instance when it still runs. A controller stack's\nmember definitions and records are not removed this way, since its `up` and `down` read them:\n`remove` refuses `UNSUPPORTED`, exit 2, and names `cotal down --yes`, then `cotal space forget <space>`,\nwhich removes them with the space.\n\n`install` writes an owned systemd user unit for a saved direct-process definition with no\n`envRefs` or `credentialRef`, and enables it without starting it. It is Linux only. `uninstall`\nremoves that unit. A preset is not installed as a unit in this release, the manager included:\n`install` of one refuses `UNSUPPORTED`, allocates nothing, and for a preset that runs in the\nforeground names the `ExecStart=` line, `cotal service start <name> --foreground`, for a unit of\nyour own.\n\nThe sections [Local service definitions](#local-service-definitions),\n[Service presets](#service-presets) and\n[Owned process services](#owned-process-services)\ndescribe each preset and the process driver in detail.\n\n## reconcile-gate\n\n```bash\ncotal manager reconcile [--space <s>] [--server <url>] [--endpoint <e>] [--instance <id>] [--yes|--dry-run] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | this folder's auth space | Space the frozen gate lives in |\n| `--server <url>` | the local mesh | Broker URL |\n| `--endpoint <e>` | `manager` | Endpoint whose gate is frozen |\n| `--instance <id>` | this folder's persisted manager instance | Instance id |\n| `--yes` | ask and default to cancellation | Confirm this selected gate repair outside a terminal |\n| `--dry-run` | off | Resolve the local coordinate without minting or checking native guards |\n| `--json`, `--plain` | terminal report | Finite structured receipt or plain shared report |\n\n**When you need this.** A manager restart killed after deregistration begins but before the new\nincarnation finishes leaves the endpoint's issuance gate *frozen*, held by a\nprocess that no longer exists. The freeze is what stops two incarnations serving at once, which is\ncorrect. The successor manager now completes that dead registration itself on boot, including on\nthe remote user-auth path. A foreign remote manager blocked by this gate also asks the host to repair\nit before one registration retry. Both use the same guard this command uses: they act only when the freeze-holder is affirmatively gone under a complete\nCONNZ sweep (`gone` and `sweepComplete=true`). If that registration's spec write already committed,\nit finishes the same freeze at the committed registration revision. If the spec did not advance, it\nabort-reopens the gate at generation+1 with processEpoch unchanged and continues the normal takeover.\nLive, unknown, unestablishable, and\nwrong-op-kind still refuse; there is no TTL.\n\nUse this command when the automatic path cannot run: the delivery daemon is down, the repair targets a\nnon-manager endpoint, or you want to lift the freeze without starting a manager. It checks that the\nholder really is gone, then finishes the dead operation the same way as the\ninterrupted restart would have: revoke the old credentials, evict their holders with verification,\nand reopen the gate.\n\nThe command revokes the old credentials 16 at a time. It then verifies the holders' eviction in\nshared sweeps of up to 256 holders on the delivery daemon. Each sweep scans the broker a fixed number\nof times and kicks live connections 16 at a time, so holders that are already gone add almost\nnothing and live ones add one broker round trip per 16 connections. The daemon must serve the\n`evictPrincipals` verb; an older daemon refuses it and the gate stays frozen.\n\nEach sweep durably records the holders it verified before the next sweep starts. If a holder is not\nverified gone, the command leaves the gate frozen with those records kept. An interrupted sweep\nrecords nothing, and the sweeps before it stay recorded. A retry still repeats the freeze-holder\nliveness check, then skips only progress bound to the same registration operation, frozen-gate\nrevision, and holder set.\nThe output reports holders completed before this attempt, completed now, and still remaining. A new\nfreeze or changed holder set starts from zero. Cursor cleanup happens only after reopen; a retained\ncursor is harmless because its old gate revision cannot authorize a later freeze. The governance slot\nrelease after the reopen is cleanup in the same way: a slot it fails to release does not fail the\nrepair, because the reopened gate is already past the slot's generation.\n\n**It refuses far more often than it acts, on purpose**, and always says which check stopped it:\n\n| Refusal | What it means | What to do |\n|---|---|---|\n| `holder-alive` | The freeze-holder still has a live connection: a manager *is* running | Stop that process first. Reconciling would evict a live manager's credentials |\n| `holder-unknown` | The connection sweep could not prove the holder absent | Not safe to proceed: an unprovable holder is treated as a live one. Re-run once the broker answers completely |\n| `liveness-unestablishable` | The delivery daemon gave no verdict: it was unreachable, timed out, or refused | Act on the delivery lease line in the refusal (below). Silence is never read as death |\n| `not-frozen` / `no-gate` | The gate is open, or there is no gate at that coordinate | Nothing to repair: check `--endpoint` / `--instance` |\n| `wrong-op-kind` | Frozen under a takeover or retirement, not a registration | Out of scope for this command; it will not reinterpret another operation's intent |\n| `eviction-unverified` | The holder looked gone but eviction could not be verified | The gate is left frozen, unchanged. Investigate the broker before retrying |\n| `raced` | A newer manager moved the gate mid-repair, and nothing was reopened | Run the command again, which reads the gate afresh |\n\nWhen the daemon gives no verdict, the refusal also reads the delivery lease (`lease.0`) and names\nwhat is blocking the rail:\n\n| Lease reading | What to do |\n|---|---|\n| absent | No daemon is running. Start it (`cotal up` runs it) and re-run |\n| unreadable | The daemon cannot be named, so do not assume none is running. Fix the lease read, then re-run |\n| held, not ready | That holder claimed the shard and has not bound its rails. Wait for it, or stop it so its lease lapses |\n| held, ready, no answer | The query may have gone to another daemon still subscribed to the rail, such as a stopped one whose lease lapsed. Re-run before stopping anything. If no run gets an answer, stop any other delivery daemon for the space, then stop or restart the holder |\n| changed hands | The holder took the shard after the query was sent, so it was never asked. Re-run before stopping anything |\n\nThe command reads the lease before it sends the query and again after the query fails. It names a\nholder as the blocker only when the same run of the same daemon held the lease both times, and two\nrows from a daemon too old to record its run never count as the same run. Even then a ready holder\nmay not have been asked: the rail is queue-grouped, so any daemon still subscribed to it can take\nthe query. A row whose times are not valid dates reads as unreadable.\n\nA daemon that answered and refused keeps its own reason, followed by the same lease line. The lease\nline names the holder, whether it is ready, the space account that holds the lease bucket, when that\nholder acquired the shard, and when the row was last written. A ready holder rewrites the row on\nevery renewal and keeps its acquisition time, which only a successful acquisition sets. A row\nwritten by a daemon that predates the acquisition time reports it as unknown. The lease reads never\nchange the outcome: the gate stays frozen and the command exits 2. A manager's boot self-heal uses\nthe same check and reports the same line.\n\n`raced` means the repair's own reopen lost its compare-and-set and wrote nothing. A reopen whose\noutcome is unknown, such as one whose acknowledgement was lost, fails with an `unavailable` error\nthat says it may have committed. Re-run the command: it refuses `not-frozen` if that reopen landed and\nfinishes the repair if it did not.\n\nThere is no `--force`, and no path that discards gate state: the only way this reopens a gate is by\nproving the holder is gone and then completing the operation properly.\n\n**What reopening the gate does for the endpoint's governance slot.** A registration takes the\nendpoint-wide governance slot before it publishes its spec, and holds it until its gate reopens. An\ninstance that died between those two points leaves the slot held with no registration behind it.\nThis command does not write that slot and never has; the registration path is its only writer. What\nthe reopen does is advance the holder's gate past the generation the slot is stamped with, which is\nwhat marks the slot abandoned. The next registration for that endpoint then reclaims it as part of\nits ordinary start. So the repair here is still one command followed by starting the manager, and\nthe slot needs no separate step.\n\n## endpoint deregister\n\n```bash\ncotal endpoint deregister <class> <full-instance-id> [--space <space>] [--server <url>] [--yes|--dry-run] [--json|--plain]\n```\n\nThis removes a departed instance's registration, not a process or an agent. Both the native\nclass and full instance ID are required; there is no default instance, prefix match or saved\nreference. The selected static space must have validated local signing authority. Open and\nuser-auth targets refuse instead of borrowing a static credential path.\n\nThe command resolves one root, account and broker policy, including its TLS requirement.\nA terminal reviews that full target and defaults to Cancel. Scripts need `--yes`.\n`--dry-run` reports the offline target and intended guard without contacting the broker,\nissuing an operation credential or deleting anything. It does not prove apply will be allowed.\n\nApply delegates to the existing native guard. An answering instance refuses. An unanswered\ninstance also refuses unless the broker affirmatively reports that instance rail empty.\nAn unavailable probe or an in-flight registration refuses. After that evidence, the native\nhelper checks governance and deletes status before spec, pinning both revisions. The issuance\ngate is not changed and no process is stopped; the same instance may register on a later start.\n\nThe JSON/plain receipt retains the full coordinate and acknowledged removed revisions.\nA registration that moves during deletion is not success: its spec was not removed, but an\nolder status may already have been removed. That uncertain outcome retains the target and is\nnot retried. Raw probe diagnostics and credential material are not copied into the receipt.\n\nThe old `deregister-instance` spelling is a hidden migration refusal. It starts no operation\nand names the literal replacement, and says that the replacement needs a static space's signing\nauthority, since an open or user-auth space has no command that removes a registration. Its former\ndefault-to-this-folder instance is not retained.\n\n## runtimes\n\n```bash\ncotal runtimes [--json | --plain]\n```\n\nLists every agent runtime the manager can spawn through: the built-in `pty`, the official providers\n(`orca`, `tmux`, `cmux`, `herdr`), and any custom provider installed via `cotal ext add`. Each installed\nprovider is probed so you can see what is actually reachable on this machine before selecting it:\n\n```\nRUNTIME STATE PACKAGE\npty built in\norca installed \xB7 reachable @cotal-ai/orca\ntmux not installed @cotal-ai/tmux\ncmux not installed @cotal-ai/cmux\nherdr not installed @cotal-ai/herdr\nadd one that is not installed with `cotal ext add <package>`\n```\n\n`installed \xB7 reachable` / `unreachable` is the provider's own `available()` probe; `not installed`\nmeans it is a known runtime you can add with `cotal ext add` and the package shown. `--json` writes\none document whose `result.items` are `{name, state, package, reachable, error}`, with `state` one of\n`built-in`, `installed` or `not-installed` and `reachable` null when the provider was not asked. An installed provider\nwhose package fails to load is shown with its error and makes the read `partial` (exit 4). The command\nreads this machine only, so its `target` is null. Selecting an unknown or uninstalled runtime\nvia `up --runtime <name>` fails loud and, for a known one, points at the exact `cotal ext add`\npackage. `spawn --runtime <name>` refuses the same way when the launch goes to this root's own\nmanager. A launch that may reach a manager on another host leaves the check to that manager, as\n[`spawn`](#spawn) describes. `up --runtime` chooses the runtime every seat uses unless that seat names its own. `spawn --runtime`\nchooses the runtime of that one managed seat, and the manager keeps every other seat where it is. There is no silent fallback to `pty`.\n\n## seats\n\n```bash\ncotal manager seats [--drain [--yes|--dry-run]] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--drain` | off | Retire every seat whose agent has exited. A seat whose agent still runs is kept |\n\n`cotal seats` is the older spelling, hidden from `cotal help`, and runs the same command. A drain\nasks first on a terminal and needs `--yes` in a script, as [Manager maintenance](#manager-maintenance)\ndescribes.\n\nThe pty runtime used to start a detached custodian process for every Linux seat. It now spawns\nin-process, but custodians that an earlier manager started keep running, and one whose agent has\nexited stays resident while a manager still holds its connection. This command lists the custody\nrecords under `COTAL_SEAT_ROOT` (default `~/.cotal/seats`), one line per seat:\n\n| State | Meaning |\n|---|---|\n| `live-child` | The agent process still runs. The seat is never signalled, and a manager can still adopt it |\n| `childless` | The agent has exited, or the record comes from an earlier boot. `--drain` retires the seat |\n| `drained` | `--drain` proved the custodian and the agent gone and removed the record |\n| `refused` | The record cannot be read, carries no start or boot identity, this host publishes no boot identity, or the reap could not prove the processes gone. The record stays on disk |\n\nA drain signals only a custodian whose recorded start identity still matches the live process, so\na reused pid is never touched. No process outlives a reboot, so a record from an earlier boot is\nreported childless and `--drain` removes it without signalling anything. A record with no start or\nboot identity is refused with or without `--drain`, and is never reported as running or exited.\nOn a host that publishes no boot identity (`/proc/sys/kernel/random/boot_id`) every record is\nrefused the same way, because no record can be tied to this boot.\nThat refusal and an unreadable record signal nothing. A refusal from the reap itself can come after the drain already\nsent `SIGKILL` to the custodian. Its detail names the pid or process group the reap could not prove\ngone, so check those processes before you retry. The command exits non-zero when any record is\nrefused. It is Linux-only, and on other platforms it refuses `UNSUPPORTED`.\n\n## send\n\n```bash\ncotal send -t <agent> \"<text>\" [--space <s>] [--server <url>] [--creds <path>]\ncotal send -c <channel> \"<text>\"\ncotal send -r <role> \"<text>\"\n```\n\nChoose one destination: `--to/-t` for a direct message, `--channel/-c` for a channel,\nor `--role/-r` for the role's native work queue. Choose one body source: a quoted positional\nargument, `--file/-f <path>`, or explicit `--stdin`; `--file -` reads stdin as `--stdin` does. There\nare no implicit stdin reads. Files and stdin must contain nonempty UTF-8 text; their content is\npreserved and never printed in the receipt. A file that cannot be used is refused with its path:\n`TARGET_NOT_FOUND` when it does not exist, `PERMISSION_DENIED` when this account may not read it,\nand `USAGE_INVALID` (exit 1, without the command's help) for a directory or text that is not UTF-8.\nThe refusal names the mesh the send was aimed at when this machine's records name one, and its\nremedy keeps the destination with a quoted body. No destination, several destinations, several body sources or an empty message is refused. A lone\n`-` is refused too, because stdin is read only through `--stdin` or `--file -`. An empty\n`--to`, `--channel` or `--role` value is refused for that flag, and the remedy keeps it.\nThe recipient grammar is checked before connector installation or target preparation. Channel\nand role names use the native subject grammar. A channel name holding `*` or `>` is refused with\n`name one concrete channel, without wildcards`, and a channel or role name outside the grammar is\nrefused with the rule it breaks.\nA direct target resolves by its full identity or an unambiguous name. Names match without regard\nto case, so `bob`, `Bob` and `BOB` are one name, and a participant whose name another shares in any\ncase is reached by its full identity. A name that no participant\ncan carry, such as one holding `\\` or longer than 128 characters, is refused with the name rule\nbefore any presence lookup. A name collision refuses\nwith `TARGET_AMBIGUOUS` and lists the full identities that carry the name, each with its display\nname, its kind (`agent`, `endpoint` or `chat session`) and the local time its process started, and a target that is not\npresent refuses with `TARGET_NOT_FOUND` and points at `cotal who --all`. No fuzzy match selects an\nagent for you.\n\nFor a channel post, repeat `--mention <agent>` to add explicit wake hints without changing the\ndestination. Each one resolves like a direct target and is sent as that agent's name, which is what\na receiving session compares. An instance id is refused when its name also resolves to another\nparticipant, and a shared name refuses with the full identities to send to with `--to` instead.\nDirect and role sends have no native mention field and refuse that option as `USAGE_INVALID`\n(exit 1), before connecting.\nA direct send may use `--reply-to <message-id>` to identify the message it answers. The sender\nstill has no inbox and does not promise thread continuity.\n\nThe receipt names the destination and message identifier without repeating the body. Direct and\nrole sends include the broker's stored sequence. Storage does not confirm delivery, acceptance\nor completion. A direct send to an agent the roster shows offline also warns on stderr:\n`<agent> is offline. The message is stored and delivered when <agent> reconnects; a new agent spawned as <agent> does not receive it.`\n`--json` lists it as the `RECIPIENT_OFFLINE` warning. Use `--json/-j` for the same receipt as one versioned envelope, `--plain` for a text report, or\n`--verbose/-v` to show the actual sending principal. A post to an unregistered channel warns\n`CHANNEL_UNREGISTERED`: it says whether the channel already has messages or this message starts it,\nand lists the space's registered channels, so a mistyped name stands out. It reads that count as\n`cotal history` lists channels. Its native space policy still controls publication and no registry\nmetadata is created. Metadata or a count it cannot read stays not checked. A role send is stored\nwhether or not anyone holds the role. When no present participant holds it, the send warns\n`ROLE_NOT_HELD`: the message waits on that role's queue until one takes it, and the warning names the\nclosest role a present participant holds, or lists the roles present when none is close. A roster\nthat is not current within two seconds adds no warning. A message over the broker's message limit, envelope included, refuses\n`UNSUPPORTED` (exit 2) with its size and the limit, and nothing is sent. A file or stdin body is read\nonly to one byte past that limit. A pipe or a device such as `/dev/zero` has no size to report, so\nits refusal names the limit alone. A channel post the broker\nrefuses was not stored, so it refuses `PERMISSION_DENIED` (exit 2): the channel does not exist in\nthe space or this identity may not post to it. The remedy lists the channels this identity may post\nto, read from the grant ledger as `cotal whoami` reads it, or with `--creds` from an agent file's own\nclaims. When the ledger is on another machine it says so and lists the space's registered channels\ninstead. A broker whose JetStream store holds more\nthan its cap refuses every write with JetStream error 10023 and stores nothing, so `send` refuses\n`DEPENDENCY_UNAVAILABLE` (exit 2), naming the store and the `--max-file-store` cap this machine\nrecorded for it. Its Next is `cotal history clear --yes`, which purges the space's channel history;\na larger cap applies after that through `cotal down` and `cotal up --max-file-store <bytes>`. Any other unconfirmed publish outcome is\nunknown and never retried automatically. A broker that cannot write its store sends no\nacknowledgement at all, so when such a publish times out and the space's broker log on this machine\nrecords a store write failure, the `OPERATION_UNKNOWN` message names its time, cause and file and\nsays the message was probably not stored. Its Next names the fix for that cause, write access to\nthe store for a permission error or free space for a full disk, and restarts the broker before the\nmessage is sent again. When the broker disabled JetStream, it adds that `cotal down` names the\nmanager's process to end first if the manager's stop cannot be recorded.\nThe command sends once and exits. It does not wait for an answer, retain an inbox\nor transfer replies to a later chat session.\n\nThe sending principal comes from the selected space's credentials or user login. Its display name\nis the OS login and host, not `COTAL_NAME`. A one-shot sender never registers as a participant.\nThe broker and native channel policy still decide which publications are authorized.\n\nThe transient endpoint never joins the roster and binds no inbox. With the static credential this\nmachine records for the space, or on an open mesh, it reads the roster and the channel registry once\nwhen it connects and binds no watch consumer, so it leaves nothing on the broker when it exits. On a\nuser-auth mesh it runs on your user bearer, which has no batched read of those buckets, so it watches\nthem instead, and each watch's consumer stays on the broker for five minutes after the send exits.\nA `--creds` credential reads once when its own grant allows the batched read, and watches otherwise,\nas an agent credential from `cotal auth mint` does. A sustained send rate on a watch can reach the\nbroker's limit of 1000 consumers per stream, and the broker refuses further sends until the old\nconsumers expire. A channel send also reads that channel's policy, with one Direct Get where the\ncredential allows it and with one more scan consumer otherwise. A `--creds` credential that may not\nread the roster and the channel registry at all refuses with `PERMISSION_DENIED` (exit 2) before\nanything is sent, and the remedy says to run without `--creds`.\n\nA recipient can still answer a `send --to` or `send --role` with `cotal_dm` or from a `chat`\nsession. On a static or open mesh every send has its own principal under the same display name, so\nthe sender's name works only while it names one principal among the senders the recipient has heard\nfrom and the present peers. Once the recipient has heard two sends from the same login and host, a\nreply by that name refuses with `TARGET_AMBIGUOUS` and lists the principals. When none of them is\npresent on a current roster, the refusal says they are no longer present and names how to answer one\nmessage. Answer by the id on the message instead (`from.id` in `chat --stdio`, with `replyTo` set to\nthe message id), or with `/reply` in `chat`. The reply is stored\nunder the sender's id in the space's DM history, which an operator's DM view such as the\ndashboard's Direct messages lens shows. The `cotal send` that asked has already exited, so the reply\nnever reaches that shell.\n\n## chat\n\n```bash\ncotal chat [<channel>] [--name <n>] [--plain | --stdio] [--space <s>] [--server <url>]\n```\n\n`chat` opens one live session in the selected space, under an identity of its own. Each opening is\na new principal with its own inbox and presence, so two windows are two sessions, and leaving ends\nthe session. `--name` sets its display label, 1 to 128 characters, not blank and without control\ncharacters; a longer label is refused as `USAGE_INVALID`. The default is your OS login and host with\na short suffix. The label carries no authority. When another live participant already carries the label,\nthe session still opens and warns on stderr, naming that participant, because a direct message by the\nname refuses as ambiguous while both are present.\n\nThe mesh is resolved once, and its recorded auth mode picks the admission. No other mode is tried.\n\n| Mode | Admission | Lifetime |\n|---|---|---|\n| open | Routing identity only, with no credential | Until you leave |\n| static | The space's manager admits the session and mints its own credential ([Static chat sessions](control-surface.md#static-chat-sessions)) | 15 minutes, never renewed |\n| user | The auth service admits it under your own `cli` grant; sign in with `cotal login` first | Up to 8 hours, never extended |\n\nOn a static mesh, `chat` reads the space's service registry before it asks the manager for a\nsession. When the registry lists no live manager, as after `cotal service stop stack-manager`, it\nrefuses `TARGET_UNAVAILABLE` (exit 2) at once and names bare `cotal up`, as the run verbs do. A\nmanager that stays registered but does not answer, such as one that crashed, is refused\n`TARGET_UNAVAILABLE` too once its answer deadline passes, with `cotal status` as the next step,\nbecause no session was asked for.\n\nA static or user session shows its end time in the banner, and says when it ends a minute before.\nAfter a session ends, `--plain` answers every line with how to leave, and `/quit` or the end of\ninput leaves.\n\nA named channel is the session's initial channel. On a static or user-auth mesh it is also the\nsession's whole read and post list, so a session opened without one can send and receive direct\nmessages only. A send, join or channel mode outside that list refuses with `PERMISSION_DENIED`\nbefore anything reaches the broker. `/leave` does not change that list, so `/join` returns the\nsession to a channel it left. On a session opened without a channel, the refusal says to\nleave and run `cotal chat <channel>` with the flags this session was started with, such as\n`--stdio` and `--space`. Before the session is admitted, `chat` reads the channel registry on the resolved\nmesh the way `send` does, under the same one-shot identity, and says on stderr whether the channel\nis registered, unregistered, or could not be read. The space's native policy decides whether a send\nis allowed, and nothing creates registry metadata.\n\nWhen the channel's replay is on, the session holds its retained messages in the replay window in\nits inbox. The inbox keeps at most 500 unread messages, so a channel that holds more replays only\nits newest 500 and the oldest are dropped. One `inbox` gap reports how many were dropped.\n`cotal history <channel> --limit <n>` reads further back. `channel show` names the bound in its\n`Chat replay` row.\n\nThe terminal view needs a terminal for input and output; without one, `chat` refuses and names\n`--plain` and `--stdio`. `--plain` is the line flow, with a separate confirmation for each send,\nand it writes its punctuation in ASCII as every plain report does.\n`--stdio` runs the NDJSON participant protocol for programs: requests on stdin, records on stdout,\ndiagnostics on stderr. Closing stdin ends the session. `--stdio` does not combine with `--plain`,\nand `chat` has no `--json`. Under `--no-input` only `--stdio` runs.\n\nIn `--plain`, a line you type becomes the draft, and it is sent only when you answer `y` to the\nquestion that follows. Any other answer keeps the draft unsent. A slash command typed as the answer\nruns, and the question is asked again. A new draft that replaces a kept one says so, with the kept\ndraft's length. At the end of input an unsent draft or an unfinished `:multi` message is reported\nby its length and never sent. `:to` lists the recipients by number, and `:to <target>` sets one directly:\n`#<channel>`, `@<name>`, `@<owner>.<actor>` or `role:<role>`. A target in another form, such as a\nname without `@`, is refused with that list of forms, and the numbered list opens. An `@<name>`\nthat names no present peer and no sender the session heard from is refused there with the reason a\nsend to it would get, and the numbered list opens. A `#<channel>`\nholding `*` or `>` is refused with the sentence `cotal send -c` gives. In the list, an empty line\nkeeps the current recipient, and `/quit` leaves. `:multi` starts a message of several lines, which a line\nholding only `.` ends. A line starting with `/` runs a slash command, and `//` sends a line that\nstarts with `/`. `/help` lists the slash commands, in `--plain` and in the terminal view. A slash\ncommand that takes a channel reads it with or without its `#`.\n`/history` reads the current channel's retained messages. On an open mesh, which runs no delivery\ndaemon, it reads them the way `cotal history` does.\n\nA direct message to an agent the roster shows offline is stored, and the view adds a notice that\nthe agent receives it when it reconnects, as [`send`](#send) warns. `--stdio` carries that roster\nstatus as `recipientStatusAtSend` in the stored result.\n\nMessage times show in local time, with the date when it is not today, the same way `history`\nshows them.\n\n`/reply` answers the latest direct message or role task the session received. It addresses the\nsender's principal and quotes the message id, with no name lookup. A sender shows by display name,\nand with its principal when another principal in the session carries the same name, ignoring case.\n`--plain` cannot relabel a line it already printed, so it prints one line naming those principals\nwhen a name becomes shared. A typed `@<name>` must name one principal among the present peers and\nthe senders the session heard from. A name that more than one of them carries refuses and lists\nthem, so type `@<owner>.<actor>` instead. A target that parses as `<owner>.<actor>` goes to that\nprincipal whether or not it is present or was heard from, and is never matched against display\nnames. A peer whose display name parses as a principal therefore cannot be reached by that name;\n`/reply` and its full principal still reach it. Typing the session's own principal refuses with\n`USAGE_INVALID`, and so does its own name when no other participant carries it. A `--stdio` `dm` request resolves its `to` the same way.\n\nEvery admission presents the session as an endpoint, so `--kind endpoint` is accepted and changes\nnothing. `--kind agent` refuses with exit 1 because a chat session never presents as an agent, and\nso does any other kind.\n\n`--role`, `--link`, `--token`, `--creds` and `--lifecycle-uid` are declared but no admission\nsupports them yet, so each refuses with exit 2. A role would put the session on that role's work\nqueue, and the view has no way yet to answer the sender of a task. The static and user-auth\nadmissions take no role.\n\nA session that ends by leaving, by end of input or at its lifetime exits 0 when its connection\nclose was confirmed and it was retired, or was an open-mesh session with nothing to retire. It exits\n4 when its connection close was confirmed and its retirement was requested but not confirmed. It\nexits 3 when the retirement outcome is unknown, its connection close was not confirmed, or no close\nwas observed. A session that cannot reconnect because its inbox is gone from the broker, as when the\nspace restarted and retired it while the connection was down, ends by itself with that reason and\nexits 5 when its close is confirmed. In the terminal view Ctrl-C and Ctrl-D leave. With `--plain` or `--stdio`, Ctrl-C ends\ninput and the session closes, then exits 130. A terminate signal exits 143 and a hangup 129. A\nsignal that arrives before the view starts is held: no session is opened if admission has not\nbegun, and a session admitted meanwhile is closed at once. The exit status is still the signal's.\nWhen the reader of `--plain` or `--stdio` output closes it, as `| head` does, input ends the same\nway and the session closes, then exits with its close status: 0, 3 or 4.\n\nBefore a session is admitted, an error prints on stderr only, since `chat` has no `--json`. An\nerror that ends `chat` exits 1 for `USAGE_INVALID` or `SCHEMA_INVALID`, 2 for any other refusal, 3\nwhen the outcome is unknown and 5 when it failed.\n\n### The --stdio protocol\n\nstdin carries requests and stdout carries records, one JSON object per line, in UTF-8 with LF line\nends. A CR before the LF is dropped, a blank line is ignored, and a last line with no LF still\ncounts when input ends. A line longer than 1048576 UTF-16 code units is discarded up to its next LF\nand reported once with an `error` record. Input is decoded as UTF-8 with replacement, so bytes\nthat are not valid UTF-8 become U+FFFD. In ordinary string content the line can still parse: a\n`text` value is sent with U+FFFD in their place, and a field that accepts only some characters,\nsuch as `id`, refuses. In an escape sequence such as `\\` or `\\u` and its four digits, or outside a\nstring, they make that line's JSON parse refuse. stdout carries records and\nnothing else. The session's diagnostics go to stderr, prefixed `cotal chat: `, and never repeat a\nmessage body.\n\nEvery request and record carries `\"schemaVersion\":1`. A request names its `type` and an `id` you\nchoose, 1 to 128 printable ASCII characters, and its `result` record repeats that id. A request that\nreuses the id of one still in flight is refused with `USAGE_INVALID`, in a result under that same\nid that follows the first request's result. Requests run one at a time in arrival order, and a\nrefusal waits its turn too, so every `result` and `error` record comes back in the order its line\narrived. `message`, `presence` and `gap` records can arrive between results, so match each result\nby its id. A missing field, a field the type does not take or a malformed value refuses with\n`USAGE_INVALID` before the session sees the request. A channel must be a valid channel name with no `*` or `>`, and is refused with the sentence `cotal send -c` gives for that name. A role must be a\nvalid role name. A `text` that is empty or only whitespace is refused with `the message is empty`,\nas `cotal send` refuses an empty message.\n\n| `type` | Fields | Result |\n|---|---|---|\n| `send` | `channel`, `text`, optional `mentions` (names of 1 to 128 characters) | `stored`: `messageId`, `channel`, `registered` |\n| `dm` | `to` (a display name or `<owner>.<actor>`, 1 to 512 characters), `text`, optional `replyTo` (a message id, 1 to 512 characters) | `stored`: `messageId`, `seq`, `duplicate`, `recipient` (`id`, `name`) |\n| `anycast` | `role`, `text` | `stored`: `messageId`, `seq`, `duplicate` |\n| `status` | `status` (`idle`, `working` or `waiting`), optional `activity` | `ok` |\n| `attention` | `mode` (`open`, `dnd` or `focus`) | `ok` |\n| `channelMode` | `channel`, `mode` (`normal`, `quiet` or `muted`) | `ok` |\n| `join` | `channel` | `ok`: `joined`, `backfilled`, `durable`, optional `reason` |\n| `leave` | `channel` | `ok`: `left` |\n| `inbox` | optional `peek` (default `false`) | `ok`: `peek`, `messages` |\n| `roster` | none | `ok`: `rows`, `view` |\n| `channelInfo` | `channel` | `ok`: `channel`, `registered`, `replay`, `readable`, `postable`, optional `description` and `instructions` |\n| `connection` | none | `ok`: `state`, `identity`, `bufferedInbox`, `joinedChannels`, `attention`, `channelModes` |\n| `invoke` | `endpoint` (1 to 200 characters), `command` (1 to 128), optional `args` (an object) and `timeoutMs` (an integer from 1 to 120000, default 30000) | `ok`: the endpoint's data, or `null` when it returned none |\n\n`identity` in the `connection` result names the session, and no other record names its space. It\nhas `space`, `mode` (`open`, `static` or `user`), `principal` (`<owner>.<actor>`), `lifecycleUid`,\n`label`, the `allowSubscribe` and `allowPublish` ceilings, and `expiresAt` in epoch milliseconds\nwhen the session has a set end. A roster's `view` is `current`, `unpopulated` or `stale`, and its\n`rows` are complete only when it is `current`. A row has `id`, `name`, `kind` and `status`, and\n`role`, `activity` and `sessionKind` when they are set. A `status` request with an `activity` sets\nboth in one presence write, so a peer gets one `updated` record carrying both.\n\nA `result` record has `type`, `id` and `outcome`. With `ok` or `stored` it carries `result`, which\nis `null` when the request returns nothing. With `refused`, `unknown` or `failed` it carries `error`\nwith `code`, `message` and, when the native layer gave one, `cause` with its own `code` and\n`detail`. `refused` means nothing happened. `unknown` (`OPERATION_UNKNOWN`) means the effect may\nhave happened. The session never retries it, and it reports an unexpected failure of its own the\nsame way. `failed` (`OPERATION_FAILED`) is a step that did not complete and had no effect. `stored`\nmeans the broker's stream accepted the message. It does not say anyone read it.\n\nThe session also writes these records on its own:\n\n| `type` | Fields | When |\n|---|---|---|\n| `message` | `message`: `id`, `ts` (epoch milliseconds), `kind` (`channel`, `dm` or `anycast`), `from` (`id`, `name`, optional `role`), `channel` or `service`, `text`, optional `replyTo`, `mentionsMe`, `historical`, optional `deliveryCount` | A message arrived |\n| `presence` | `change` (`joined`, `updated` or `left`), `row` (a roster row) | A roster row changed, this session's own row included. As the session opens, its own row and each member already online come as `joined` |\n| `gap` | `scope` (`live` or `inbox`), `reason` | Messages may have been missed |\n| `error` | `id` (`null`), `error` | A line was refused before an id could be read from it, or was too long |\n| `end` | `reason`, `close` | The session ended. Nothing follows it |\n\nA `presence` record's `joined` is a member coming online and `left` one going offline. `updated`\nmeans a field the row shows changed, such as `status` or `activity`. A presence change the row does\nnot show, such as a member's attention or channel modes, writes no record. `cotal who --all --json`\nshows attention.\n\nA message's `text` is untrusted. JSON escapes its control characters, so a body can never start\nanother record. `channel` is set on a channel message and `service` on an anycast. `mentionsMe`\nmeans the message's mention list holds this session's label, ignoring case. The sender writes that\nlist, so treat it as a hint. `deliveryCount` appears only on a durable delivery and is 1 the first\ntime. Above 1, an earlier copy went unacknowledged and may already have been shown.\n\nA message is acknowledged once its record is handed to stdout. A DM or anycast is written when it\narrives. A channel message is held in the inbox instead when its channel is quiet, when attention is\n`focus`, or under `dnd` unless it mentions the session, and join backfill is held too. A muted\nchannel's messages are dropped. `inbox` with `peek` lists the held messages and leaves them held.\nWithout `peek` it returns them, and they are acknowledged once its result is handed to stdout. A\nmessage still unacknowledged when the session closes is left unacknowledged and does not come back\nto this session.\n\n`join`'s `backfilled` counts the messages the join read back from the channel's retained history:\nthose sent before the join and within the channel's replay window, not counting this session's own.\nIt is 0 when the session is already on the channel or replay is off for it. Each of them then follows\nthe rules above. One the session already handed over, holds or dropped is not held again, and one on\na muted channel is dropped, so the inbox can gain fewer messages than `backfilled` says. The session\nremembers at least the last 4096 messages it handled for this.\n\nA `live` gap means the connection dropped or is reconnecting, so live channel messages may be\nmissed until it returns. Its reason defaults to\n`connection <state>: live channel messages may be missed until it returns`. An `inbox` gap means\nthe held buffer was full: channel messages were dropped and direct messages were left for the broker\nto redeliver. Its reason counts each kind that happened. One gap covers the drops of up to a second,\nand a replay past the bound is one gap when the session starts. When a write waits for stdout to\ndrain, or more than 4 MiB of records are waiting, the session stops reading stdin until stdout drains.\n\n`end` is the last record. Its `reason` is `input closed` when stdin ended, `interrupted`,\n`terminated` or `hangup` when SIGINT, SIGTERM or SIGHUP ended input, and `session ended` when the\nsession ended by itself, for example at a static session's expiry. A session that could not\nreconnect gives that reason instead, and its `close` carries it as `lost`.\nWhen stdin ends, the requests already read finish first. `close` has `retirement` (`retired`,\n`requested`, `unknown` or `not-applicable`), `connectionClosed` (`confirmed` or `not-confirmed`) and\n`message`. A static session's `close` can carry `credentialValidUntil` in epoch milliseconds: the\nbroker accepts its credential until then unless its eviction was verified. Once the session has\nended by itself and before the `end` record, a request refuses with `LIFECYCLE_BLOCKED` and\n`the session is ending; no new requests are accepted`. When the reader closes stdout, stderr says\n`stdout closed by the reader; ending the session`, and no later record is written, `end` included.\nAny other failed write, such as to a full disk, says `stdout could not be written; ending the\nsession` and the one line about lost output, and the session ends the same way with exit 3.\n\n## channel\n\n```bash\ncotal channel\ncotal channel set <name> [--replay | --no-replay] [--window <duration>] [--delivery <live|durable>] [--desc <text>] [--instructions <text>]\ncotal channel defaults [--replay | --no-replay] [--window <duration>] [--delivery <live|durable>]\ncotal channel show <name>\ncotal channel members <name> [--follow] [--json | --plain]\ncotal channel remove <name> [--purge] [--yes]\n```\n\n`channel` has the shorthand `ch`; `remove` also accepts `rm`. The inventory shows configured\nmetadata, not every channel with traffic. `show` includes inherited replay and delivery settings.\n`members` reads the broker-derived membership feed. Its timestamp is the last successful feed\npoll, not proof of current activity or access. With `--follow --json`, it emits membership NDJSON\nrecords. A missing feed remains unknown rather than an empty confirmed census. Without `--follow`, a\ncensus with no member for a name that has no saved entry and no retained messages is refused with\n`TARGET_NOT_FOUND`, listing the registered channels, or naming the registered spelling when only the\ncase differs. A channel with a saved entry or retained messages and no member reads as empty. A\nfollow shows the census again only when it changes: a poll that observes the same members writes no\nrecord and prints nothing, and the `end` record's `lastAsOf` is the last poll the follow read. A follow's\nview offers no command to follow. A lost connection is\none `TARGET_UNAVAILABLE` error record, or one line, while the follow re-establishes it, and a `gap`\nrecord and the current census follow once the feed answers again, so a follow outlives a broker\nrestart. A credential that can no longer connect ends the follow with the refusal a one-shot\ncommand with that credential gets, as [`history --follow`](#history) does. The delivery service\nwrites that feed, and an open mesh runs none, so `members` refuses an open mesh with `UNSUPPORTED`.\n\nRemoval keeps retained history unless `--purge` is present. The confirmation names the channel,\nspace and history scope. Scripts pass `--yes`. A name with no saved entry and no retained messages\nis refused with `TARGET_NOT_FOUND` before the confirmation, listing the registered channels, or\nnaming the registered spelling when only the case differs. Removing a channel that has retained\nmessages but no saved entry is a `no-op`, and its Metadata row says `no saved entry`; `--purge`\nstill deletes that channel's retained messages. In `--json`, `result.removed` says whether a saved entry was removed. Neither\nremoval nor replay settings revoke grants or stop participants; future posts can create retained\nhistory again. A `set`, `defaults` or `remove` that the broker refuses for the credential's grant\napplied nothing, so it is refused with `PERMISSION_DENIED` (exit 2), naming the credential's profile,\nthe write it lacks and the subject the broker refused. With `--creds`, the Next is to run it without\n`--creds`, on the operator credential this machine records. A remove whose credential may not read\nthe registry skips the name check, and the broker's answer to the remove decides. A change that the\nroot's default `.cotal/channels.json` would undo at the next start warns that it will, as the\n[channel seed file](#channel-seed-file) section says.\n\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--replay` / `--no-replay` | none | `set`/`defaults`: replay history to new joiners, or not. They take no value: `--replay off` is refused with the flag to use |\n| `--window <duration>` | none | `set`/`defaults`: replay window, for example `24h` |\n| `--delivery <live\\|durable>` | none | `set`/`defaults`: choose the live-only or durable-backstop delivery class; history retention is separate. Shell completion offers both values |\n| `--desc <text>` | none | `set`: channel description |\n| `--instructions <text>` | none | `set`: instructions shown to joiners |\n| `--purge` | off | `remove`: also delete the channel's retained history |\n| `--yes` / `-y` | off | `remove`: confirm the stated channel and deletion scope without a question |\n| `--follow` | off | `members`: follow broker-observed membership changes |\n| `--json` / `-j` | off | One JSON document; `members --follow --json` emits NDJSON records |\n| `--plain` | off | The same report without color |\n\nInspects and edits the channel registry: replay and delivery policy, description, and joiner instructions. `live` has no per-member backstop; it does not disable ACL-bounded retained history. Changing a channel's delivery class does not settle already pending durable copies. `--delivery durable` requires a registered static-auth or user-auth space; an open or unregistered target has no established delivery-service support. A recorded auth mode does not prove that daemon is currently healthy. ACL\nsemantics (who may read or post) are set at mint / provision time, not here; see\n[Channels and permissions](channels-and-permissions.md). On a user-auth mesh, `list` rides your\nown login as is; `set` and `defaults` edit the registry over a short-lived\nchannel-writer view, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\nOn a remote user-auth mesh that view is served by the public exchange; space-history `purger`\nand the read-only admin view are not.\n\n`set` and `defaults` change only the fields you name. Each write lands on the registry entry as it\nwas read, so writers changing different fields of one channel at once all keep their change: one\nthat loses the race reads the entry again and merges again. A write that keeps losing to other\nwriters refuses with `CONFLICT` (exit 2) and saves nothing. Two writers setting the same field leave\nthe value of whichever wrote last. When the entry already holds every value you name, nothing is\nwritten: the receipt is `Channel unchanged` or `Channel defaults unchanged`, and the outcome is\n`no-op` (exit 0).\n\nWith the static credential this machine records for the space, or on an open mesh, `list` reads the\nregistry with one Direct Get and leaves no consumer on the broker. On a user-auth mesh your login has\nno Direct Get row on the registry, and neither has an agent credential passed with `--creds`, so\n`list` scans it with a consumer that stays on the broker for five minutes after the command exits.\n\n\n## history\n\n```bash\ncotal history [<channel>] [--dms] [--since <time|seq>] [--limit <n>] [--from <name>] [--grep <text>] [--follow] [--json | --plain]\ncotal history clear [--dms] [--yes | --dry-run] [--json | --plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Target mesh |\n| `--dms` | off | Also read DM history, which needs a DM read grant. With `clear`, also clear DM history |\n| `--since <time\\|seq>` | none | Only messages after an ISO 8601 date and time, an age such as `30m`, `2h` or `1d`, or a chat stream sequence. A date, or a date and time without an offset, is local time |\n| `--limit <n>` | 50 | How many of the newest retained messages to read, 1 to 1000 |\n| `--from <name>` | none | Keep messages from this sender name or id |\n| `--grep <text>` | none | Keep messages whose text contains this, ignoring case |\n| `--follow` | off | Keep listening and print each new message |\n| `--json` / `-j` | off | One JSON document, or with `--follow` one JSON record per line |\n| `--plain` | off | The same report without color or marks |\n| `--yes` / `-y` | off | `clear`: confirm without prompting. Scripts and `--json` require it |\n| `--dry-run` | off | `clear`: show the selected target and streams without connecting, and purge nothing |\n\nReads the newest retained messages in the selected space, newest last: one channel, or every\nchannel the identity may read. The channel may be written as `#general`, and a name holding `*` or\n`>` is refused with `name one concrete channel, without wildcards` before connecting. A channel with\nno retained messages and no saved entry warns `CHANNEL_NOT_FOUND`, names the channels that hold\nmessages, and reports no replay policy for it. When the space's broker runs on this machine and logged\ndamage in a stored file since it started, `history` warns `STORE_DAMAGE_LOGGED` with the broker's\nwords, because it serves only what it can still read. The read stays complete, as it read the stream\nthe broker serves. A broker whose log is not on this machine is not checked. `--limit` bounds the read, and `--since`, `--from` and `--grep` then\nfilter that window on this machine. They do not search the whole stream, and with older retained\nmessages behind the window the report says that `--from` and `--grep` searched only the messages\nread. At `--limit 1000`, the most it reads, the report says older retained messages are not shown and\nnames no larger limit. `result.window` says how many messages were read, whether older retained\nmessages exist that the window did not reach, and whether the window goes back to `--since`. The report names the local time and the ISO instant a\ntime `--since` resolved to, or the chat stream sequence a bare number was read as. A four-digit\nnumber from 1900 to 2099 is still a sequence, and the report says to write a date in full. A bare\nnumber past the chat stream's newest sequence names no message, so it is a `USAGE_INVALID` refusal\nthat names that sequence and how to write a date. An age outside the supported date range, a date\nthat is not on the calendar, or a sequence that cannot be represented without rounding is a\n`USAGE_INVALID` refusal before connecting. A time is an ISO 8601 date such as `2026-10-23`, or a date and time such\nas `2026-10-23T09:00:00Z`, so `-5` and `10/23/2026` are refused. A date alone, or a date and time\nwith no offset, is local time, so `--since 2026-10-23` starts at local midnight.\nThe report shows each message's local time, with its date when it is not today; `result.items[].ts`\nis the ISO time. A DM row shows its recipient's name when the roster carries that principal or a\nmessage in the window came from it, and the principal otherwise. `--dms` reads the roster for those\nnames, waiting up to two seconds for it. `result.items[].to` is the principal.\nA line break or other control character in a message's text shows as its `\\uXXXX` escape in the\ntext and `--plain` reports, so a message cannot clear the screen or start a row of its own.\nA row shows the first 500 characters of a longer message and marks the cut with the message's size.\n`--json` carries the text as it was sent, in `result.items[].text`.\n\nRetained history is not every message sent. The chat stream keeps a fixed number of messages per\nsender on each channel, so a busy sender's oldest messages are discarded while other senders'\nmessages from the same time remain. `result.retention` names the limit and each sender at it on the\nchannels read, and the report prints a `gap` line for each. A gap is a fact about the stream, not a\npartial read. `result.replay` gives each channel's replay policy for new members, read from the\nchannel registry. DM gaps are not reported.\n\nA channel the identity may not read is refused with `PERMISSION_DENIED` (exit 2), never shown as\nempty. On a user-auth mesh each channel read uses the connection's broker-enforced channel grant.\nRevocation follows the effective credential and connection retirement, as it does for live channel\nreads. Concurrent commands sharing one identity wait for the broker to release its transient history\nreader; no command deletes another reader. Each channel has a ten-second read budget, with a\nthirty-second budget for the whole channel walk. A busy or cut-short read fails with `OPERATION_FAILED` (exit 5) rather than showing\nan empty conversation. Reading every channel leaves out channels the grant does not cover and names\nthem in a `CHANNELS_DENIED` warning. DM history and `--since` by sequence are not available to a\nuser-auth identity. An agent's `--creds` credential reads the same way, one channel at a time under\nthe lifecycle its own history grant names, with the same limits. A channel that grant leaves out is\nrefused with `PERMISSION_DENIED` before it is read, quoting the channels the grant does name, or\n\"no channel\" for a grant that names none, and its `CHANNELS_DENIED` warning names them too. An\nobserver or admin credential reads across channels.\nA part that could not be read is a warning and a `partial` run (exit 4): the\nretention, a replay policy, the channel list when a channel is named, or one channel of several.\n\n`--follow` prints the window and then each new message until interrupted, each as one line in the\nform `watch --plain` uses: `[<space>] <time> <sender> <destination> <text>`, with a 24-hour local\ntime and `#<channel>` or `DM to <principal>` as the destination. A purge by `history clear` is not\nannounced to a follow or to `watch`; messages sent after it keep arriving. A lost connection is\nreported in one line while the follow re-establishes it, so a follow outlives a broker restart. A\ncredential that can no longer connect ends the follow. It may have expired, or the broker may now\nreject it, as after `reset space`. The follow then ends with the refusal a one-shot command with that\ncredential gets, `AUTH_REQUIRED` or `PERMISSION_DENIED`, and its Next. Without `--creds` on a\nstatic-auth space the follow mints its own credential from the space's trust, so when the broker\nrejects it the Next says to run the follow again, which mints one from the current trust. With\n`--json` each line is one record: `{\"schemaVersion\":1,\"stream\":\"history\",\"type\":<type>,\"target\":\u2026,\"receivedAt\":\u2026,\"data\":\u2026}`.\nThe first record is a `snapshot` holding the window, then one `event` per message. An event has\nno `seq`, because the live feed carries no stream sequence; the snapshot's items keep theirs. To\nread on from a follow, give `--since` the last event's `ts`. An `error`\nrecord names a failure on the live feed, and a lost connection is one `TARGET_UNAVAILABLE` error. A\n`gap` record says the connection was re-established and messages sent while it was down are not\nshown. An `end` record always closes the stream. The exit code is the snapshot's, the refusal's\nwhen the credential ended the follow, or 130 or 143 when SIGINT or SIGTERM ended it.\n\n`history clear` purges retained history from every channel in the space it targets; `--dms` extends\nit to direct-message history. It refuses extra channel names, read filters and `--follow` before\npurging. These inputs cannot narrow a clear operation. It is the same operation as [`clean history`](#clean). On a user-auth mesh the purge rides a short-lived\npurger view over your login, which needs ledger scope `admin` ([Identity & auth](identity-and-auth.md)).\nA purge the broker refuses for the credential's grant purged nothing, so it is refused with\n`PERMISSION_DENIED` (exit 2), naming the credential's profile; with `--creds`, the Next is to run it\nwithout `--creds`, on the operator credential this machine records.\n\n## console\n\n```bash\ncotal console [--plain] [--space <s>]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Space to watch |\n| `--plain` | off | Line stream instead of the TUI |\n\nA live protocol view for a space: a lazygit-style TUI, or a plain line stream on `--plain`. The\nline stream is also what a pipe gets. On `--plain`, and under a locale that is not UTF-8, it writes\nno status marks and its punctuation in ASCII. The line stream reports a lost connection in one line\nwhile it re-establishes it. A credential that can no longer connect ends it with the refusal a\none-shot command with that credential gets, as [`history --follow`](#history) does. Ctrl-C or\nSIGTERM ends either view at any time. On a\nuser-auth mesh it rides the read-only admin view over your login, which needs ledger scope\n`admin`. Inside the TUI, operator control (`D` kill, `:spawn`, `:status`, `:purge`) rides the\nsame per-action instrument path as `cotal stop` and `cotal ps`, never the observer; a raw\n`--creds` file cannot drive it. `a` (or `:attach <agent>`) runs\n[`cotal attach`](#managed-seats) in place and returns to the console on detach. See\n[Watch a mesh](watch-a-mesh.md).\n\nMessage bodies, names and other text from peers are untrusted. Both views show a control character\nin them as its `\\uXXXX` escape, so a message cannot clear the screen, retitle the terminal or plant\na hyperlink. The TUI keeps a body's line breaks, and `--plain` prints each message on one line.\n\n<a id=\"web\"></a>\n\n## Web dashboard\n\nThe dashboard is an owned service preset from `@cotal-ai/web`, not a top-level `web` command.\nIt is not started by `up`. An absent extension requires the explicit `cotal ext add @cotal-ai/web`;\nstarting a service never installs it or creates a caller credential.\n\n```bash\ncotal service add web --preset web --space main --root /absolute/registered/root --creds /absolute/private/dashboard.creds --read-only\ncotal service start web --space main\ncotal service status web --space main\ncotal service logs web --space main\ncotal service stop web --space main --yes\n```\n\nThis route requires a registered static-auth target, an existing protected credential file\nand explicit read-only intent. `--host` chooses a concrete bind/browser address, default\n`127.0.0.1`; wildcard binds refuse. `--port` is 1 to 65535, default 7799. Broker coordinates and\nrequired TLS come from the registered target. Broker TLS does not encrypt the dashboard's HTTP\nlistener. The file supplies the only connection authority.\nNo cached human login, managed-seat identity, operator signer or channel-purger credential is\nsubstituted. User-auth/open targets refuse, because no supported caller handoff is connected for them.\nThe page marks this service read-only and hides channel delete; the server also refuses it before\nreading a delete body or opening a mutation connection. The broker still enforces the file's scope.\n\nThe real child exclusively claims native PID custody. After the HTTP listener and signal handlers\nexist, it writes a create-only owner-private session record for the full instance and launch, then\na private process-initialization phase. This is not a fresh authenticated endpoint-health reply.\nLogs print only the session file's reference, never its bootstrap URL or readiness credential. A\nbackground `service start` ends with the `service logs` command for the new instance, which is where\nthat reference appears. An endpoint error or warning in that log names only the error's class, its\nname and code, never its message. Open the private file locally and use its single-use launch link in the\nintended browser. There is no automatic browser action. The existing cookie and origin gates remain in use. Confirmed close\nremoves only this child's unchanged session/PID records; uncertainty retains custody.\n\nForeground service start uses the shared terminal lifetime. OS installation and full native caller\nparity are not supported. An old cached `command:web` descriptor is hidden and refuses with the matching\nextension-update remedy before package loading or repair. It is not resolved by removing the\ndashboard, starting a second process or silently reinstalling it.\n\n## linear\n\n```bash\ncotal ext add @cotal-ai/linear\ncotal linear account add <name> --mode <write|readonly> (--token-stdin | --token-file <path>)\ncotal linear account login <name> --mode <write|readonly>\ncotal linear account show <name>\ncotal linear inventory <account> [--json|--plain]\ncotal linear call <account> <tool> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]\ncotal linear resource <account> <uri> [--inventory <digest>] [--timeout <ms>]\ncotal linear prompt <account> <name> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]\ncotal linear serve <account> --endpoint <reverse-dns-name> [--space <s>] [--server <url>]\ncotal linear caller <name> --endpoint <reverse-dns-name> --out <path> [--channels <a,b>] [--expires-in <s>] [--json|--plain]\n```\n\nServes one Linear account as a registered Cotal endpoint that agents call with `cotal_describe` and\n`cotal_invoke`, and gives the operator a direct client for the same server. Until the extension is\nadded, `cotal linear` and `cotal help linear` name the `cotal ext add` that adds it. It only talks to `https://mcp.linear.app/mcp`\n(mode `write`) or `https://mcp.linear.app/mcp/readonly` (mode `readonly`); there is no URL or header\noption. Use a separate account per Linear workspace, and prefer a `readonly` account with a\nrestricted read key wherever writes are not needed.\n\nAn account's credential is sent as `Authorization: Bearer` and is never taken from argv. `account add`\ntakes a Linear API key: `--token-stdin` stores it under the cotal home as a 0600 file, and\n`--token-file` points at an existing file that must not be readable by other users. The file is read\nat each use, so rotating it needs no restart. `account login` runs Linear's OAuth flow instead: it\nregisters a client, prints the authorize URL, and waits on a loopback redirect. It asks for scope\n`read` in `readonly` mode and `read write` in `write` mode, keeps the tokens in a 0600 file, and\nrefreshes them before a request is sent. OAuth requests only go to `https://mcp.linear.app`.\nRedirects are refused so the credential is never forwarded.\n\n`inventory` reads the server's capabilities and every page of its tools, plus resources, resource\ntemplates and prompts when the server advertises them. Names and schemas are printed as the server\nsent them. The digest covers the whole inventory; pass it to `--inventory` so a call is refused\nbefore dispatch if the inventory changed. Capabilities this command does not represent, such as\nresource subscriptions or logging, are listed under `unsupported`.\n\nBare `linear` and `linear account` show their scoped operations without opening an account or\ncontacting the upstream. Account add/show, inventory and the native call/resource/prompt forms\nsupport shared rich/plain reports and version-1 JSON envelopes. Scoped help and completion use\nthe same operation/flag declarations as validation, and each operation's help shows its own usage\nline. Extra operands and wrong-mode flags refuse before account access; a flag the operation does\nnot take is named, with the flags it does take and its usage. Positional values are bounded to 8192 characters without terminal controls.\nPiped tokens are bounded to 1 MiB of strict UTF-8; their contents and argument parse errors are\nnot echoed. Account reports describe recorded configuration or a native save acknowledgement,\nnot current access to Linear. An account name with no record refuses with `TARGET_NOT_FOUND` and\nthe `account add` route. A record that exists but cannot be read refuses with\n`DEPENDENCY_UNAVAILABLE`.\n\n`inventory --json` retains the full native inventory inside the result envelope. Human reports\nshow at most 60 whole tool rows. Read-only hints remain upstream declarations, not enforcement or\nwork progress. `call`, `resource` and `prompt` retain the complete native reply under\n`result.reply` with `--json`; rich/plain output shows at most 60 whole result lines. The bearer\nis scrubbed by the native client; other upstream content is not claimed to be secret-redacted.\n\nA tool result with `isError: true` remains an MCP result (exit 0), with a visible warning. This does\nnot assert that the requested work succeeded. A native refusal with outcome `not-executed` returns\nexit 2; that includes a deadline that expires while queued, discovering or connecting. An unknown\nprotocol/transport outcome returns exit 3 without replay, including for read-only requests. The\nnative outcome and reason remain in the result. Errors carry a status or class, not HTTP response\nbodies. An interrupted account save or OAuth flow can return unknown because private state or an\nexternal authorization may already have changed.\n\nOAuth login keeps the native human authorization flow, so it offers plain output but not JSON\nand refuses when input is disabled. The existing foreground `serve` route keeps its native authority and human output; it does not\naccept finite-output flags, and `--json` or `--plain` there refuses as a usage error (exit 1) that\nsays so. Its refusals carry their identifiers in the text report instead, such as the proposal to\napprove on a user-auth mesh. The existing `caller` route\nkeeps its native static issuer but supports shared JSON/plain private-file receipts. Its output\nmust be outside managed state in an existing protected physical parent. An existing destination\nrefuses before provisioning. Publication is create-only under the cooperative output lock, and\nuncertain provisioning/publication is not retried. It prints neither credentials nor environment-\nexport launch recipes. The preferred supported standalone path is now `auth mint --endpoint`\nafter a live digest-verified surface is available. No caller or managed launch is inferred from an\naccount, inventory or reply.\n\n### Serving the endpoint\n\n`serve` registers the account as an endpoint named by `--endpoint` and serves it until Ctrl-C. The\nname must be a reverse-DNS name in a namespace you own, such as `com.example.linear`; it is\nauthorized for a static-auth mesh's local owner because you configured it. `serve` reads the inventory\nfirst, so an account that cannot be reached is never registered. On a static-auth mesh, it publishes\nthe contract to the contract store, opens the issuance gate,\nregisters a fresh instance, writes its ready status, and mints a scoped serve credential through\nthe gate. Every step runs on short-lived executor credentials scoped to this one endpoint instance.\nThe space signer stays in the `serve` process for renewal at 75% of the credential's life and is\nnever written out, printed, or handed to the serving connection. Ctrl-C stops serving, closes the\nLinear session and removes the instance's service record, each within 10 seconds. A start that\nfails after registration removes the record before it exits and says whether that removal\ncompleted; a registration that fails part-way rolls its own record back and reports when it could not.\n\nThe endpoint serves five fixed commands: `inventory`, `call-tool`, `read-resource`, `get-prompt` and\n`complete`. Each needs the one `linear.mcp` capability. That capability is permission to call the\nendpoint; which tools a call can reach is decided by the Linear account and its mode. `inventory`\nreplies in pages that fit a broker message: the first page carries the server, its capabilities,\nits instructions and per-section counts, and every page carries entries verbatim with an opaque\ncursor bound to the inventory digest. An entry is never cut: discovery refuses an inventory whose\nfirst-page head (server, instructions, capabilities) or any entry prints over 48 KiB the way an\nagent tool prints it (indented JSON, which grows with nesting), naming the entry, so the first page\nand every entry it accepts fit one reply and the tool an agent reads it with. A cursor or `inventoryDigest` from an older inventory is refused before anything is sent to\nLinear. `call-tool`, `read-resource`, `get-prompt` and `complete` require the digest the caller read.\n\n### Callers\n\n`caller` provisions an isolated caller on a static-auth mesh: an `agent` credential with its own\nmailboxes and request rows for only the five Linear commands on that endpoint, plus the channels\nin `--channels`. It carries no spawn, run, admin or provisioner capability. The credential is\nwritten 0600 to a create-only `--out` outside managed state and never printed. Its physical parent\nmust already exist and be protected. The shared receipt contains the native identity, lifecycle,\nexpiry and acknowledged footprint, not credential bytes. An existing output refuses before\nprovisioning. A failure after native issuance starts stays unknown with confirmed identity/file\nreceipts retained, and is not retried. The command does not print environment-export launch recipes.\n\nThis remains the native standalone caller exporter, not a connector setup or generic caller bundle.\nA supported bootstrap must carry its actual credential and minted lifecycle together; a file alone\nis not a native caller identity. No seat is started, and managed static `cotal spawn` agents still\nrefuse endpoint capabilities. `auth mint --endpoint` now covers supported untargeted synchronous\nsurfaces through live native resolution. Targeted/journaled/action issuance and caller/launch\nadapters are not supported.\n\nOn a user-auth mesh, `serve` uses the registered auth provider's service authority. The owner must\napprove the original service proposal and the operator must bind its endpoint name. Until then\n`serve` refuses with `PERMISSION_DENIED` (exit 2), and its Next line names the proposal and the\ncommand that approves it:\n\n```bash\ncotal service-endpoint approve <proposalId> --caller-ceiling linear.mcp --space <space>\n```\n\nThe `linear.mcp` ceiling covers the five Linear commands, so callers can be granted them later; an\napproval without it has an empty ceiling and no caller can be granted. Repeat the\nsame command after approval. Its instance, registration body and request identities are retained;\na changed account, target or embedded contract refuses instead of replacing them. The shared\nrenewal source activates and renews the access bearer. The service verifies the signed bearer and\nembedded cluster before serving, and reports ready only after the broker flush and host acknowledgement.\nNo local signer or ordinary enrolled agent is substituted.\n\nStopping a user-auth service closes its serving connection, renewal source and Linear session.\nIt retains the approved registration for a later start; stopping does not claim remote revocation.\nUse the space's service approval and exact caller-grant controls to revoke access. Callers need\nexplicit instance-scoped grants through that remote authority. An account or visible registration\ndoes not grant invocation rights. The standalone `caller` credential exporter remains static-auth\nonly. Open meshes refuse because they have no credential system to scope.\n\nNot represented yet, and listed under `unsupported` when the server advertises them: resource\nsubscriptions, logging, progress notifications and experimental capabilities. The server cannot\ncall back for sampling, elicitation or roots. Every call acts as the account's one Linear identity.\n\n## pending\n\n```bash\ncotal pending <agent> [--durable <exact-dm-durable>] [--limit <1..100>] [--space <s>] [--json|--plain]\n```\n\nThe finite read reports the selected DM durable's native pending count, ack\nfloor, delivered sequence, frontier, retention limits and up to 20 candidate\nIDs by default. JSON and plain output use the common one-document result. A\nname must match one observed roster entry; an exact principal ID wins over a\ndisplay-name match, and an ambiguous display name refuses. `--durable` reads a\nprevious lifecycle by its complete durable name without substituting a\nsame-named current agent. An absent or unreadable durable is not `pending 0`.\nThe caller needs the native admin read authority; user-auth has no equivalent\nview here and refuses without a static or bare fallback. A failed read does\nnot prune a locally saved mesh registration.\n\nCandidates are read without acknowledging DMs. The transient read consumer is\ndeleted after the bounded pass; an unconfirmed cleanup or candidate read is\nreported as partial. Candidate IDs are not evidence of delivery, a missing\nmessage or complete history. Recipient contents are not printed. The retained\n`deliver pending` spelling keeps its earlier output shape.\n\n## deliver\n\n```bash\ncotal deliver [--space <s>] [--server <url>] [--tls] [--creds <file>] [--root <dir>] [--shard <n>] [--shards <n>] [--dev-mint]\ncotal deliver pending <name> [--limit <n>] [--durable <name>] [--json]\n```\n\nWith no positional, `cotal deliver` runs the delivery daemon (see\n[the delivery daemon](delivery-daemon.md)). `deliver pending <name>` never starts the daemon: it\nis an operator-only read over one recipient's DM durable, for the moment after a send when the\nquestion is \"what does the broker actually hold for them.\" It resolves `<name>` against a short\npresence watch (an `offline` card still counts, since the recipient may be dead, that is what\nthe verb exists to inspect); when neither a card nor the durable can be found, it prints\n`\u2717 not-found: no agent \"<name>\" and no DM durable for it in space <s>` and exits non-zero, never\n`pending 0`. On a match it prints the durable name and one fact per line: `pending`,\n`ack-pending`, `delivered`, `ack-floor`, `created`, `frontier`, and the stream's `max_age` /\n`max_msgs_per_subject` / `discard` limits (`--json` prints the same facts as one object), followed\nby a bounded, unacked read of up to `--limit` (default 20) recent candidate message ids under the\nheading `recent candidate ids (from the ack floor; not proof of a hole)`, a list of what is\nthere, not proof that nothing was lost.\n\nThe verb needs the `admin` credential profile: it runs through the same static-mesh route as\n`cotal auth mint --profile admin`, and refuses a user-mode mesh, naming the retired static credential,\nbecause there is no user-mode inspection authority yet. Pass `--creds <file>` for an off-registry\nadmin credential. A same-name respawn never inherits a predecessor's held DMs (the durable is\nlifecycle-keyed); an old lifecycle's durable is reachable only by the name a live read printed\n(the `<durable>` line on the first line of this verb's output). Pass that name with `--durable\n<name>` to read it directly once the lifecycle's card is gone from the roster. This skips the\npresence watch on `<name>` entirely, so `<name>` is required but only echoed in error text.\n\n## auth mint\n\n```bash\ncotal auth mint <name> [--profile agent|observer|admin] [--out <private-file>] [--json|--plain]\ncotal auth mint <name> --provision [--role <role>] [--space <space>] [--server <url>]\ncotal auth mint <name> [--expires-in <seconds> | --expires-at <unix-seconds> | --no-expiry]\ncotal auth mint <name> --identity <private-creds> --out <private-file>\ncotal auth mint <name> --replace\ncotal auth mint <name> --endpoint <class> --out <private-file> [--channels <a,b>] [--expires-in <seconds>]\n```\n\nThe fixed-profile forms use the existing native static credential profiles. No generic host\nprofile is exposed.\nA space registered as open refuses `UNSUPPORTED`, since it authenticates no one and issues no\ncredentials. Per-user-auth spaces refuse too; a cached login does not replace signing authority. The\nresolved root supplies the persona ACL, signer and default storage together. A root that cannot be\nresolved refuses with its next step, such as `cotal up`. Conflicting current-folder and\nselected-root accounts refuse before issuance.\n\nA label takes letters, digits and `_`, since `-` separates the parts of a principal's name. `--role`\ntakes letters, digits, `_` and `-`. A label or role with any other character, or an empty one,\nrefuses `USAGE_INVALID` without the command's help. The refusal names the character, the value and\nthe characters allowed, and its Next names the value with each such character replaced by `_`.\n\n`agent` is the default profile. Its channel ACLs and role come from the selected root's\n`<name>.md` persona file, when present. It does not fall back to `default.md` for a different name.\nExplicit `--allow-subscribe`, `--allow-publish` and `--role` override the file's grants. A persona\nfile of that name that cannot be read as a persona refuses the mint `SCHEMA_INVALID`, exit 1, even\nwhen the flags name every grant. The mint refusal does not print file values or parser text.\nIt names the file and `cotal persona show <name>` to inspect it. A role from the file that is not one word\nof letters, digits, `_` and `-` refuses the same way, naming `cotal persona edit <name>`. With\n`--identity` naming an agent credential this space issued, the new credential keeps that\ncredential's read channels, post channels and role in place of the persona's, and each flag still\nreplaces its own grant. The receipt lists the grants as May read, May post and Role, marking those\ncarried from `--identity`; JSON has them under `result.grants`, with `fromIdentity` naming the\ncarried ones. An empty ACL flag means no explicit entries. The native read-ACL resolver still enforces boot membership.\nWithout a matching persona, `--identity` or grant flags, the agent credential has no channel grants\nor role. Its validity does not establish that it can perform a particular task.\nObserver and admin have fixed native scope and reject agent ACL, role and provisioning flags\nrather than implying those inputs narrowed anything.\n\nThe default credential is created through its canonical secret-store kind. An existing label is\nnot overwritten. Use another label or an explicit private output. `--replace` replaces the label's\nstanding credential when it has expired, has no expiry claim or has a key that\n`auth revoke-credential` revoked. It mints under a new identity unless `--identity` names one, which\na revoked key never is, and removes a no-expiry record beside it unless `--no-expiry` writes a new\none. The receipt names what it replaced and its public key under `result.replaced`. A replacement\ndoes not revoke the credential it replaced, so a copy of one with no expiry claim stays valid. The\nreceipt then warns `REPLACED_CREDENTIAL_VALID`, naming that key and the\n[`auth revoke-credential`](#auth-revoke-credential) route that stops it. A standing credential that is still\nvalid refuses `CONFLICT`, naming its expiry, and so does one that is not a native credential.\n`--profile` still defaults to `agent`, so a replacement of an observer or admin credential needs\n`--profile observer` or `--profile admin`; a replacement with another profile than the one it\nreplaced warns `REPLACED_PROFILE_CHANGED`. A replacement holds only the grants its persona file,\n`--identity` or grant flags name, not those of the agent credential it replaced. When both are\nagent-profile credentials, the old one names this space's account as issuer, and their grants\ndiffer, the receipt warns `REPLACED_GRANTS_CHANGED`, naming both and the\n`--allow-subscribe`, `--allow-publish` and\n`--role` flags that name the replaced ones. With no standing credential under the label, `--replace`\nmints a new one and warns `NOTHING_REPLACED`. Exported files stay unchanged; the warning names\n`doctor auth --space <space>` to inspect their state and the guidance for each file, and the\nspace's root to run it in, since `doctor auth` reads the root it runs in.\n`--replace` takes no `--out`. An exported file stays outside\nmanaged `.cotal` and workstation state. Its path, label, profile and public key, without the\ncredential, are recorded under `credentials/` in the Cotal home so `doctor auth` lists the file. A pre-existing explicit output must be an owned private\nnative credential matching the nkey deliberately supplied through `--identity`; unrelated files\nand identities refuse. New outputs use create-only publication. The output's directories follow\nthe [`space create`](#selecting-a-space) path rule, which also accepts a root-owned sticky directory\nsuch as `/tmp`, and the file is owner-only. Unsupported platforms refuse rather than weakening it.\nA credential is never written through a symbolic link: a link among the output's folders refuses\n`PERMISSION_DENIED`, naming the link, the folder it points to and the same file named without the\nlink. A path through a file refuses `USAGE_INVALID`, and a folder this account cannot create\nrefuses `PERMISSION_DENIED`, naming the folder. None of them writes anything.\n\n`--identity` reuses the native nkey. The out-of-band agent mint reports its newly allocated\nlifecycle UID, with a warning that it did not reuse the old inbox/delivery footprint. Use that\nreported UID for the resulting client; do not infer it from the label or assume the old UID was\npreserved. No private nkey or JWT appears in a receipt. A key revoked with `auth revoke-credential`\nrefuses `CONFLICT`, because a credential signed now would postdate the revocation and the broker\nwould accept the key again; mint a fresh key without `--identity`.\n\nWithout `--provision`, fixed-profile minting writes credentials only. `--provision` uses one native provisioner\nconnection to pre-create that lifecycle's bind-only DM/delivery and role footprint. It performs\nthe authenticated preflight against the same recorded broker and TLS requirement, without\npruning a registration. An acknowledgement proves neither a joined agent nor task completion.\nA partial or unknown result retains the allocated public identity and lifecycle for diagnosis\nand does not retry the mint.\n\nA fixed-profile credential expires 24 hours after it is minted unless `--expires-in` or\n`--expires-at` sets another time, in native seconds. `--no-expiry` issues one with no expiry claim and\nrecords that request beside the file, in `<file>.no-expiry.json`, so `doctor auth` reports it as a\nwarning rather than a finding. The receipt reports the resulting JWT expiry. The three flags are\nmutually exclusive, and an `--expires-at` that is not in the future refuses before anything is written. So does an expiry\nafter the last date Cotal can represent, in the year 275760. JSON is\none finite document; plain output uses the same public receipt. Signing material is exported by\n[auth signer](#account-signer-export), not a signer flag on mint. The old top-level `mint` spelling\nis refused with the literal namespace replacement.\n\nA minted credential stays valid until it expires, and one minted with `--no-expiry` until it is\nrevoked or the space identity rotates. [auth revoke-credential](#auth-revoke-credential) stops one\ncredential and keeps the store. Rotating the space identity stops every credential the space issued:\n`cotal down`, then `cotal reset space <space>` (or `cotal clean all --force` from the root when\n`down` left no registration), then `cotal up`. That reset also removes the space's store, with its\nworkflow runs and their journals, channel history and stored messages, so the space starts again\nwith no history. `cotal reset space <space> --dry-run` lists what the store holds first. It also stops every\nearlier full backup of the space from restoring into this root: only its registry restores, and\nchat and DM history start empty (see [Backups](#backups)).\n\n### Endpoint caller credentials\n\n`--endpoint` is a separate closed mode, not an ACL override on an observer or admin profile. It\nrequires a private `--out` in an existing protected physical parent outside managed state. Only\n`--channels`, `--expires-in`, space/server and presentation flags combine with it. It has no default\nlifetime: without `--expires-in` the caller carries no expiry claim. An empty or\nomitted channel list grants no explicit read/post channels. Supplied channels deliberately enter\nboth ACLs, as in the native standalone Linear caller form. No persona ACL or role is inferred.\n\nThe same static-root/account checks run first. The existing native control instrument then resolves\na live endpoint and digest-verifies its visible command surface. This issuer accepts only nonempty,\nbounded untargeted synchronous ephemeral surfaces. A targeted, journaled or action command refuses\nthe whole issuance before footprint work; unsupported rows are not silently dropped. The result\nrecords the observed responder and contract digests, not a promise that every future incarnation\nhas the same implementation. The caller is scoped to the class's observed command names, not that\nsingle answering instance.\n\nThe native agent provisioner creates the new lifecycle's bind-only footprint and additional endpoint\nrequest rows. No spawn, run or admin capability is added. The ordinary native agent baseline remains;\nthis is not a new hand-written permission profile or a managed-seat launch adapter. User-auth/open\nspaces still refuse. The selected broker's required TLS is retained for discovery and provisioning.\n\nAn immutable owner-private `<out>.issuance.json` intent precedes the first footprint operation.\nIt contains public identity/lifecycle, requested scope and contract coordinates, never credentials.\nIts presence is not a grant or successful submission receipt. An existing output or retained intent\nblocks another issuance, even if no output was published. Partial/unknown provisioning retains that\noriginal intent and does not replay the native call. Success reports native footprint acknowledgement\nand the exact privately published file; provisioning is not presence, task completion or cleanup.\nTemporary provisioner close is reported separately and can leave a partial result.\n\nRich/plain receipts show the actual caller/lifecycle, described endpoint owner and responder,\nexplicit channel scope and native expiry. Command tables show at most 40 rows with an omission\ncount; JSON retains every command and contract digest. The local issuance request is not a broker\nrequest acknowledgement.\n\nNo credential bytes or environment-export recipe are printed. Endpoint describe/invoke consume\nthis paired file under the same registered static root:\n\n```bash\ncotal ep describe ep:<class> --space <space> --creds /absolute/private/caller.creds\ncotal ep invoke ep:<class> <command> --space <space> --creds /absolute/private/caller.creds --args '<json>'\n```\n\nKeep the matching `<private-file>.issuance.json` beside the credential. The consumer checks both\nprotected snapshots and validates the selected root's full static trust chain. The native JWT\nsignature, matching user seed, current validity window, account and current delegated signing key\nare checked before connecting. A retained older signing key is not guessed from a different root.\nThe caller coordinates and recorded commands must reproduce the signed publish/subscribe/response\npermissions and principal tags through the native builder. The unsigned local intent selects that\ncomparison; it grants nothing and proves no submission, lifecycle currency or completion. Original\nresponder/contract metadata remains an issuance observation, not an instance or future-code pin.\n\nThe connection retains registered TLS and uses only that file. Its broker admission is separate\nfrom local checks. Invocation accepts only an included untargeted synchronous ephemeral method\nfrom the fresh digest-verified surface. A changed targeted, journaled or action shape refuses before\npublication. Instance pins, scatter, admin, agent targeting and goal-follow flags are unsupported\nfor this caller mode. Calls do not repair a binding, switch authority or resubmit an unknown effect.\n`--server`, when supplied, must repeat the registered broker. Target/reference selection is\nread-only, including when a paired file is selected. Receipts retain the public caller binding;\ndescribe shows methods outside the file scope without granting them. Issuance next steps keep\nexplicit file selection instead of switching back to the normal operator instrument.\n\nArbitrary raw files, issued-generation files and different native profiles are not interpreted as\nthis standalone caller. User-auth issuance and managed static launch are not supported.\n\n## auth revoke-credential\n\n```bash\ncotal auth revoke-credential <public-key | creds-file> [--space <space>] [--yes] [--json|--plain]\n```\n\nIt stops one credential that `auth mint` wrote and keeps the space's store and every other\ncredential. Name the credential by its user public key, which the mint receipt reports as its\nidentity, or by its creds file, which must be private to you and issued by this space's account. A\nword that is not a user public key and names no file refuses `TARGET_NOT_FOUND` and says both. A\nfile from another account refuses `CONFLICT`. A file the space's system account issued, such as the\nstack's `connection-evictor` and `membership-observer` credentials, refuses `UNSUPPORTED`. Its Next\nis `cotal down --yes`, then `cotal up --rotate-sys`, which issues a new system account and retires\nthe old one's credentials, and after which earlier full backups no longer restore (see [up](#up)). The command records the key in the space's account,\nre-signed by the space's operator, and the broker refuses that key from its next start.\n\nThe broker loads a revocation only when it starts, so the space must be stopped: run\n`cotal down --yes`, then this command, then `cotal up`. On a running space it refuses\n`LIFECYCLE_BLOCKED` and names that route, and it refuses the same way while the root records a\nmaintenance attempt. A connection held with the credential ends at the `down` and is refused after\nthe `up`. It asks before it revokes, or takes `--yes`; `--json` without `--yes` refuses. A key\nalready revoked reports the time it was first revoked and changes nothing. `--json` reports\n`publicKey`, `revokedAt` in Unix seconds, `alreadyRevoked` and `takesEffect`.\n\n`auth mint --identity` refuses a revoked key. A credential the stack renews itself, such as its\ndaemons' or a managed agent's, is signed again for the same key at renewal, and the broker accepts\nthat newer credential, so stop or despawn that agent instead. A per-user-auth space refuses\n`UNSUPPORTED` and names `auth revoke <actor>`, and an open space issues no credentials to revoke.\n\n## Login\n\n```bash\ncotal login --idp <auth base URL> [--client-id <id>] [--plain]\ncotal logout --idp <auth base URL> [--local]\n```\n\nSigns you in to a per-user-auth mesh's IdP (device code flow) and caches the session; run it\nonce per machine. `--plain` presents the approval prompt and signed-in result without color.\nDevice approval is still interactive and has no native noninteractive or JSON result in this\nclient; `--no-input` refuses before starting a provider request. The IdP URL must use `https://`.\nPlain `http://` is accepted only on a loopback IP literal such as `127.0.0.1` or `::1`, for an IdP on this machine. `localhost` is refused because it\nis a name: a hosts entry would choose the IdP. It prints your IdP subject, the id the operator\ngrants against. When the trusted\n`/token` response advertises a same-origin space catalog, login validates and records that account's\nspaces immediately, lists them under `Your spaces`, and names `cotal use <name>` to select one. A\nspace registered from elsewhere that has not granted your account refuses with those discovered\nspaces as its remedy, because only that space's operator can grant you. On the root that holds the\nspace's grant ledger, the refusal names the operator's `cotal actor grant` command instead. After a\nlogin, every command on that mesh works under your identity: each connect takes a fresh IdP\nproof, exchanges it locally for a short-lived bearer, and is authorized against the actor\nledger at connect time. A provider that cannot be reached, or that stops answering, refuses with\n`DEPENDENCY_UNAVAILABLE` (exit 2) and caches no session. `logout` revokes the IdP session, clears\nits cache, and removes only that\naccount's discovered registry entries. See\n[identity & auth](identity-and-auth.md).\n\n## actor\n\n```bash\n# an upsert of the WHOLE row: name all three ACL flags, or pass --full for the wide defaults below\ncotal actor grant <actor> --sub <IdP subject> --scope a,b --allow-subscribe a,b --allow-publish a,b [--role <r>] [--label <l>] [--json|--plain]\ncotal actor grant <actor> --sub <IdP subject> --full [--scope a,b] [--allow-subscribe a,b] [--allow-publish a,b] [--role <r>] [--label <l>] [--json|--plain]\ncotal actor revoke <actor> (--sub <IdP subject> | --owner <u_\u2026>) [--json|--plain]\ncotal actor label <actor> (--sub <IdP subject> | --owner <u_\u2026>) <label> [--json|--plain]\ncotal actor list [--sub <IdP subject>] [--json|--plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` | the resolved mesh | Space whose ledger to manage |\n| `--sub <subject>` | none | The IdP subject (shown by `cotal login`) the actor belongs to. With `list`, only that subject's rows are listed |\n| `--owner <u_\u2026>` | none | The derived owner token (alternative to `--sub`) |\n| `--full` | off | Fill each ACL flag left off with its wide default; without it, `grant` refuses unless all three are named |\n| `--scope <a,b>` | `spawn,role:default` with `--full` | Capability scope (`''` = none; `spawn` = may run agents; `run` = may start workflow runs; `role:<r>` = may delegate role r; `admin` = cross-agent control; `supervise` = eligible for the closed remote manager-service view when the host enables it). Any other name is refused |\n| `--allow-subscribe <a,b>` | `>` (all channels) with `--full` | Channel read ACL; the user's envelope, their agents can never read beyond it |\n| `--allow-publish <a,b>` | `>` (all channels) with `--full` | Channel post ACL; also the envelope for their agents' posting |\n| `--role <r>` | none | Role (scopes the task-queue consumer) |\n| `--label <l>` | none | Display label for `actor list` (never the IdP subject) |\n| `--parent <owner.actor>` | none | Audit link to the spawning principal. An operator grant is the authority, so the link does not narrow the row |\n| `--json`, `-j`, `--plain` | off | Print one JSON document or line-oriented text, as `auth grants`, `auth grant` and `auth revoke` do |\n\nThe actor ledger is the single authorization source of a user-auth space: no row, no access.\nThe ledger stores no IdP subject. `actor list --sub <subject>` and `auth grants --sub <subject>`\nderive the subject's owner as `grant --sub` does, with this root's owner secret and IdP pin, and list\nthat owner's rows, interactive and managed-agent alike. The report is titled\n`Rows for <subject> (owner <u_\u2026>)`, and JSON adds `subject` and `owner` to its result and names the\nowner in its target. A subject with no rows lists none. An empty `--sub` refuses `USAGE_INVALID`.\n`actor label <actor> <label>` sets only an interactive row's label. The label is outside the\nauthorization digest, so the row keeps its lifecycle, grant time, capabilities and channels, and a\nbearer issued under it keeps working. The receipt names the earlier label, and JSON carries it as\n`previousLabel`. An actor with no row refuses `TARGET_NOT_FOUND`, and a managed agent's row refuses\n`UNSUPPORTED`. Neither writes anything.\n`cotal help actor` lists `grant`, `revoke`, `label` and `list`, and shell completion offers them.\n`actor list`, `grant` and `revoke` resolve their space and root as `auth grants`, `auth grant` and\n`auth revoke` do, and refuse the same way. With no space to resolve they refuse with a next step,\nsuch as `cotal up`, rather than assume one, and a root that does not hold the space's user-auth\nledger refuses `list` rather than report no rows. On a static or open space the refusal says that\nactor grants apply only to a space started with `cotal up --user-auth`, and `grant` and `revoke`\nrefuse that way before asking for `--sub`, `--owner` or the ACL flags.\nOtherwise a usage error refuses with exit 1 before any authority is read, and one refusal names every\nmissing input: `--sub` or `--owner`, and for `grant` the ACL flags left off without `--full`. An\ninput that is not valid refuses the same way, naming each field and why: the actor, `--owner`,\n`--role`, `--parent`, a channel pattern, or a `--scope` name other than `admin`, `run`, `spawn`,\n`supervise` and `role:<r>`. Nothing is written. A channel needs no creation, so a grant may name one\nthat has no message or registry entry yet.\n`grant --full` is the **full** envelope (all channels; scope `spawn,role:default`, so it may spawn and may delegate the default role). A\n`grant` that leaves off `--scope`, `--allow-subscribe` or `--allow-publish` without `--full` is\nrefused and writes nothing. A re-grant **replaces the whole row**, not the one field you name, so to add a capability spell\nevery field out: the new scope plus the row's current read set, post set, role and label\n(`cotal actor list` and `cotal auth grants` show what a row holds: its capabilities, read and post\nsets, role, label and parent). Under `--full`, a field left off does not stay as it\nwas: it reverts to the wide default in the table above. A re-grant retires the current interactive lifecycle through the running auth\nservice before it rotates the row, so copied bearers cannot cross an authorization update. The\nreceipt names the earlier grant as none, the same fields (titled `Actor grant stored unchanged`) or\nreplaced, and says when its lifecycle was retired; JSON carries `previous` and\n`previousLifecycleRetired`. If that\nretirement cannot be confirmed, the row is left unchanged and the command fails with the recovery\naction. `revoke` uses the same retirement before deleting the row, which lets a later grant create a\nreal successor instead of colliding with a live predecessor. `supervise` is separate from `spawn` and `admin`: it only makes a signed-in\nperson eligible for the host-provided closed remote manager-service view; it does not grant\nmanagement of another owner or a general host profile. `revoke` denies the next exchange and\nthe next connect with no restart, and evicts the principal's live connections. It asks nothing and\ntakes no `--yes`, because a later grant gives the actor access again under a new lifecycle. It reports\n`verified gone` only when the delivery daemon's eviction result names the principal and shows a\ncomplete scan with no connection left; any other result prints `live-connection eviction INCOMPLETE`\nwith its counts, or with the reason the result was not accepted. Managed-agent rows\n(written by the spawn path) live in a disjoint row space this command never touches. See\n[identity & auth](identity-and-auth.md).\n\n## doctor\n\n```bash\ncotal doctor auth [--space <space>] [--fix] [--json|--plain]\n```\n\nThis inspects the current root's local credential claims and recorded renewal state. It does not\nprove current broker authorization, daemon adoption, connected agents or task progress. The shared\nreport keeps full credential paths and public issue/expiry metadata; JSON retains all diagnostic\nrows without JWTs, seeds, fingerprints or raw parser/renewal error bodies. On a root that holds several\nspaces, `--space` names one; it defaults to the sole space. Without `--space`, a root with several\nspaces refuses with `TARGET_AMBIGUOUS` (exit 2) and names them. In a folder whose root holds no space,\nthe report is partial (exit 4) and its next step is to run it in a space's root, which `cotal space`\nlists. In the root of a space registered as open, the report says that the space issues no\ncredentials, so there are none to check, and the result is a no-op (exit 0). With `--space` naming\na space whose signing trust this root does not hold, it refuses `AUTH_REQUIRED` (exit 2) and reads\nno other root. Its next step names the folder that space is registered at, where the same command\nruns, or else a space's root, which `cotal space` lists.\n\nPlain diagnosis starts no repair or connection. A required legacy credential or user-auth directory\nmigration refuses instead of silently moving files or reading canonical absence as an empty scope.\nA credential file that other accounts can open is a finding that names its mode, with `chmod 600`\non the file as its repair. That includes the space's own store credentials, and the other files are\nstill checked. Each file is also parsed the way `--creds` checks one, so a file the NATS\nclient cannot parse is a finding even when its claims are legible, such as one with Windows (CRLF)\nline endings.\nOnce the root holds the space's trust, a missing `membership-observer` or `connection-evictor`\ncredential is a finding too, with `cotal down` then `cotal up --rotate-sys` as its repair, because\nnothing else mints that pair again. A missing delivery or membership-rw credential is only noted,\nsince `cotal up` mints it.\nA credential with no expiry claim is a finding whatever its kind, since a copy stays valid until it\nis revoked or its signer rotates. `--fix` re-signs only the static daemon credentials, so an unbounded agent credential\nstays a finding until its file is gone. The exception is one `auth mint --no-expiry` wrote, whose\nrecord beside it still names the file's bytes: someone asked for it, so it is a\n`CREDENTIAL_UNBOUNDED_BY_REQUEST` warning instead, and the run ends ok, exit 0. Both get guidance that\nnames the ways out. `cotal auth revoke-credential <file> --yes` between `cotal down` and `cotal up`\nstops that credential and keeps the store. Replacing\na label's file with `cotal auth mint <label> --replace` once nothing uses it bounds it from then on,\nand so does removing any other file and its record and minting a replacement without `--no-expiry`;\na copy made before stays valid until it is revoked. Rotating the space identity (`cotal down`,\n`cotal reset space <space>`, then `cotal up`) retires every credential the space issued and removes\nthe file, and the reset removes the space's store with its history. An expired label's file is a finding too, and\nits guidance names that same `--replace`, next to the managed renewal that re-signs an agent a\nmanager runs. An agent file whose key was revoked is a finding in state `revoked`, with\n`result.items[].revokedAt`, however far off its expiry is. A revoked key is not signed again, so\nfor a label's file the guidance names `--replace`, and for any other agent file it names stopping\nthe agent that uses it. Each `--replace` this guidance names, for an unbounded, expired or revoked\nfile, carries the file's profile as `--profile observer` or `--profile admin`, and an agent file's\nread and post channels and its role as `--allow-subscribe`, `--allow-publish` and `--role`, since\nthe replacement is a new key, which holds only the profile and grants its mint names. For an\nobserver or admin file it names no managed renewal, as no manager runs one.\n\nEach file `auth mint --out` wrote for the space from this root and that is still there is listed\nunder Exported credential files, and in `result.exported`, with its state and expiry. These rows\nare not findings. An expired one is an `EXPORTED_CREDENTIAL_EXPIRED` warning. One near expiry or\nexpired names `cotal auth mint <label> --identity <file> --out <file>`, which signs the same key\nagain with the same grants. One whose key was revoked, or that an earlier space identity issued,\nnames a mint with a new key instead, because the broker refuses that key and `auth mint --identity`\nrefuses a revoked one. For an agent file, that mint names the file's read and post channels and its\nrole with `--allow-subscribe`, `--allow-publish` and `--role`, since a new key holds only the grants\nits mint names.\nA run with no findings says so in its heading only when nothing else is printed. While an exported\nfile needs attention, or a credential is unbounded by request, the heading starts `No findings;` and\nnames each, such as `2 exported credential files need attention`. The exit code stays 0.\nMissing local signing trust is not inferred to mean an open or healthy mesh. An unreadable renewal\nrecord prevents a complete healthy verdict. Locally ordered older incarnation files are labeled as\nfile observations, never proof of a live successor or permission to delete or respawn an agent.\n\n`--fix` explicitly permits native local material migration and the supported static daemon re-sign\nphase. Its path-move and re-sign acknowledgements are retained separately. It holds the native\nrenewal lease when using the broker and refuses another holder. Offline repair is labeled as\nuncoordinated; an authenticated preflight failure is not called an offline target. User-auth roots\ncannot receive a static-signer repair fallback. No system account is rotated and no agent is\nrespawned or silently given a new identity.\n\nA delivery or membership-rw credential the NATS client cannot parse holds no key to re-sign, and\nthe manager cannot renew it either. When no broker answers, `--fix` mints it again under a new\nidentity, as `cotal up` mints a missing one, and its result row has `reminted: true`. While its\nbroker answers it stays a finding, and its guidance and row name `cotal down` first. A refusal\nbecause the manager holds the renewal lease names the same step. Every result row that is not a\nplain re-sign has a `reason`. When `cotal up` starts with such a membership-rw credential, the\ndelivery daemon runs without it, and `up` warns `DAEMON_CREDENTIAL_UNPARSABLE`, naming the same step.\n\nA local re-sign does not erase a recorded broker refusal or claim that a daemon adopted it.\nA recorded refusal names the space's manager, the renewal owner, as the next step. A pre-per-space\n`.cotal/renewal.json` names no space, so it is reported as a leftover and never read as the space's\nverdict.\nRemaining findings, incomplete audit reads and partial/unknown repair outcomes keep classified\nfinite receipts. Inspect the original renewal owner and lifecycle rather than repeating a mutation\nor creating replacement agents merely to obtain a different report.\n\n## join\n\n```bash\ncotal join --space <s> --name <n> [--role <r>] [--channel <c>]\ncotal join --link <url> | --token <t>\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--space <s>` / `--server <url>` / `--creds <path>` | resolved mesh | Which mesh, and which credential |\n| `--name <n>` | none | Your presence name |\n| `--role <r>` | none | Your role |\n| `--channel <c>` | none | Channel to join |\n| `--kind <k>` | `agent` | Endpoint kind |\n| `--link <url>` | none | Join link (`cotal://\u2026`) |\n| `--token <t>` | none | Join token |\n| `--lifecycle-uid <uid>` | none | 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 |\n| `--tls` | off | Connect over TLS |\n\nAn interactive presence: join a space under your own name and role, without launching an agent\nharness. A `--link` supplies the where and the auth in one value. See\n[Spaces](spaces.md) and [Identity and auth](identity-and-auth.md).\n\nA `--token` or `--creds` goes to `--server`, or else to the broker the space is registered at,\nkeeping that registration's TLS requirement. With no `--space`, that is the selected space, else\nthis folder's, and inside a project whose root has one space other than the selection, that space.\nA space not registered here is refused before anything is dialed. The default address is never\nassumed for a credential.\n\nA `--creds` join binds the inbox provisioned for its lifecycle, so the space's owner mints the\ncredential with `cotal auth mint <name> --provision` and hands over the lifecycle UID that mint\nreports. A credential minted without `--provision` has no inbox there, and `join` refuses, naming\nthat lifecycle and the mint that makes one.\n\nOn a registered user-auth space, sign in with `cotal login` first. `join` uses your existing `cli`\nactor bearer and renews it through the same login and exchange while the session runs. Its channel\nand DM resources must already be provisioned by the space operator. It never mints static\ncredentials or widens your grants. A changed principal, lifecycle or connection trust refuses\nrenewal and requires leaving and joining again. A revoked login is reported by renewal, and the\ncurrent connection can last only until its already-issued bearer expires.\n\nFor `nats://` and `tls://` addresses, if TLS is required but the broker does not offer it, `join`\nrefuses with a request for the operator's TLS-enabled broker address. It never retries over\nplaintext. A `ws://` address uses plaintext WebSocket. Use `wss://` for TLS.\n\n`join` has no `--json`. A refusal before the session starts prints on stderr with its code and\nexits 1 for `USAGE_INVALID` and 2 for any other refusal: `TARGET_NOT_FOUND` for a space not\nregistered here, a creds file that does not exist, a lifecycle with no inbox or a space not\nprovisioned for a console session, `PERMISSION_DENIED` for credentials the broker rejects,\n`TARGET_UNAVAILABLE` for a broker that does not answer, and `UNSUPPORTED` for a broker without the\nTLS the join requires.\n\nA running join that cannot come back ends the same way, with its reason and its Next. When it\nre-establishes its connection and finds its inbox gone from the broker, as after `reset store` while\nit was disconnected, it ends `TARGET_NOT_FOUND` and says to run the same `cotal join` again, or, for\na `--creds` join, to mint the credential again with `--provision`. When the broker rejects its\ncredential for good, as after `reset space`, it ends with the refusal a one-shot command with that\ncredential gets, as `history --follow` does. A user-auth join keeps re-establishing in both cases,\nand an open or `--token` join when the broker rejects it.\n\nA broker that comes back on an empty store soon enough for the client to reconnect by itself does\nnot end the connection, so the join keeps running without its DM inbox, and direct messages to it\nare not delivered. After a second check it prints one warning that its DM inbox is missing, on a\nline that starts with `!` like its other warnings, and keeps checking. If the inbox does not come\nback, interrupt the session and join again. A `--creds` join first needs the space's owner to mint\nthe credential again with `--provision`.\n\n## Manifest deploys\n\nA `cotal.yaml` manifest declares a whole mesh (channels, personas, roles, and ACLs) in one file.\nThese commands consume it:\n\n```bash\ncotal up -f cotal.yaml # boot a fresh mesh from the manifest\ncotal team deploy cotal.yaml # deploy the manifest additively onto a running mesh\ncotal team remove <deployment-id> # retire a returned deployment\ncotal topology view -f cotal.yaml # validate + view the access graph, change nothing\n```\n\n`up -f` brings up a new broker and applies the manifest. It returns once the broker and manager are\nup; the manager then launches the agents one at a time, and `up -f` does not wait for them to join.\nIts launch line says so and names `cotal ps --space <space>`, which lists the agents that run, and\nthe manager's log, where a failed launch is recorded with its reason. When the manifest's space is already\nrunning from the same folder, it exits 1 and names `cotal team deploy <file>`, which adds all-new\nchannels and agents, and `cotal down` before another `up -f`. On a root that holds a stack preserved by\n`down --preserve-state`, it refuses `LIFECYCLE_BLOCKED` (exit 2) and starts nothing; bare\n`cotal up` resumes that stack. On a root whose records hold a controller stack, running or stopped,\n`up -f` and its dry run refuse `CONFLICT` (exit 2) and start nothing. The refusal names\n`cotal team deploy <file>` for the running stack, and `cotal down --yes`, then\n`cotal team boot <file>`. `team deploy` requires a registered,\nrunning mesh and applies additively: all-new live channel-only manifests, or bounded all-new agent\nand mixed manifests on static-auth or open meshes through one native manager. It does not launch\nuser-auth agents. `team remove` retires a returned deployment by its full ID within the limits in\n[Recorded team views](#recorded-team-views). `spawn -f` no longer deploys a manifest; an additive\nledger written by that older path is torn down with `cotal down -f <file> --run <id>`. `up -f`,\n`team deploy` and `team remove` take `--dry-run` to print the plan without mutating anything.\n`topology` validates the manifest and renders its channel / role / ACL graph. Given a folder\ninstead of a manifest file, `topology view`, `up -f`, `down -f` and the `team` commands that take a\nmanifest refuse with exit 1, naming the path and that it is a directory. See [Define a team](define-a-team.md) and the\n[manifest reference](manifest.md).\n\n## ext\n\n```bash\ncotal ext [list] [--json | --plain]\ncotal ext add <npm-package>\ncotal ext remove <name>\ncotal ext root # print just the install prefix (scriptable)\ncotal ext seed [--repair|--reset|--force]\n```\n\nOperator-installed extensions: `add` installs an npm package into a cotal-owned prefix and records\nevery registry provider it contributes. Commands appear in help, completion, and dispatch; runtime\nproviders are loaded on demand by commands such as `supervise`, and by a running manager the first\ntime a managed seat names one with `spawn --runtime <name>`, so a `pty` manager launches a seat on\nan installed `tmux` without a restart; local process providers participate in `status` and\nselective `down`. Removing an additional runtime extension, one the manager was not started with,\nrefuses the next seat that names it; the manager's own runtime and the seats already running stay\nas they are. `remove` and `list` manage them. The `@cotal-ai/web` dashboard is the\ncanonical service preset and local-process example. Installed packages and their location are described in\n[config](config.md). When a package needs an export its linked `@cotal-ai/*` peer does not have,\n`add` rolls back and names which install is behind, as a later load of an installed one does.\n\nBare `cotal ext` lists the inventory, headed by the install prefix. That prefix is a cotal-owned npm\nroot kept **separate** from npm's own global tree. These packages never show up in `npm list -g`,\n`cotal ext` (or the Extensions section of `cotal status`) is the canonical inventory. `cotal ext root`\nprints only the path, for scripts. The versions shown are the manifest pin recorded at add time.\nOn POSIX, cotal creates a missing Cotal config directory with mode `0700`, even with a\ngroup-writable umask, whichever command writes there first. The extension writer creates a missing\ninstall prefix with mode `0755` and its `package.json` with mode `0644`. Its npm children start with 022 added to your umask. The\nextension writer leaves existing directory permissions as they are, and [`update`](#update) removes\ngroup and other write from the config directory and everything you own under it. Completion still refuses a pre-existing writable ancestor\nrather than weakening its shell-file ownership checks.\n\nBare `ext --json` (or `ext list --json`) writes one JSON document: `result.root` is the install\nprefix and `result.items` holds one row per installed extension with `pkg`, `version`, `spec` (what\n`ext add` was given), `seeded` (`true` for an entry the built-in seed installed) and `provides` (the\n`kind:name` refs the table shows). An empty prefix is an empty list. `--plain` writes the same report\nwithout color. The other `ext` subcommands refuse `--json` and `--plain` with `USAGE_INVALID`.\n`ext root` stays a raw path for `$(cotal ext root)`.\n\nAn installed package whose directory is gone is reported as not installed. One whose `package.json`\nis not JSON or has no `version` is reported as corrupt, naming the package and its `package.json`.\n\nRemoving an extension that owns a running local process is refused with the mesh root and its\n`cotal down <component>` command; stop it first so uninstalling the package never strands a process\nwhose lifecycle provider is gone. While it uninstalls, `remove` reserves each process record the package\ndeclares under every mesh root, and a record folder it creates there is mode `0700`, even with a\ngroup-writable umask.\n\nLoading an installed extension takes the same locks as `add`, `remove` and `cotal update`. When another\n`cotal` process holds them, the load waits up to five seconds in total (one deadline for both locks)\nwithout blocking the waiting process, then refuses. A running `cotal update`, `cotal ext add` or\n`cotal ext remove` holds the update-pass lock as well as the install/remove lock, so the load refuses with\n\"another extension update or mutation is in progress (pid N) - retry once it finishes\". The install/remove\ntext, \"extension install/remove is in progress (pid N) - retry after the active `cotal ext` command\nfinishes\", appears only when the update-pass lock is free but a live process still holds the\ninstall/remove lock itself, for example an update child that outlived its `cotal update` parent. Re-run\nthe command once the other one has finished.\n\n### Built-in connectors are seeded extensions\n\nThe first-party agent connectors (`claude`, `opencode`, `codex`, `hermes`, `jcode`, `pi`) are not compiled into\nthe binary. They are seeded on first run through the **same** `ext add` path a third party uses, and\nappear in `cotal ext list` like any other extension. So you can remove one you do not want\n(`cotal ext remove @cotal-ai/connector-hermes`), and a deliberately-removed connector STAYS removed\nacross upgrades. `cotal ext add <your-package>` adds a third-party connector the same way. The web\ndashboard (`@cotal-ai/web`, providing `service-preset:web`) is the seventh built-in seeded on the\nsame path.\n\n`cotal ext seed` is the maintenance entry for that seeding (it runs automatically before the first\ncommand of each boot that is not a read, so you rarely call it; `cotal up` runs it when it first\nstarts a process). A read, such as `status`, `ps`,\nbare `use`, `ext list` or `docs`, installs and refreshes nothing. An installed extension's `reads` declaration is\ncached when it is added. A bare read and a read subcommand skip that seed prelude; a subcommand\nwithout the declaration still seeds. After an upgrade, a read that would load a\nbuilt-in still at another version names the package and its version with `cotal ext seed` instead\nof loading it. Each seeded connector's `\u2713 added` line goes to stderr, so the command that triggered\nthe seed keeps stdout to itself:\n\n| Flag | Meaning |\n|---|---|\n| (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. |\n| `--repair` | Recover after a seed that failed or was interrupted, or a lost authority (rebuilds that seed's connector; restores the removed-vs-never-seeded record from its durable backup). Also reinstalls a seeded built-in whose entry file is gone or whose `package.json` is not JSON or has no `version`. |\n| `--reset` | Discard the record and re-seed all seven built-ins (the six connectors plus the web dashboard). **Resurrects any you removed.** Rebuilds cleanly over corrupt seed state. |\n| `--force` | Re-seed the built-ins even when the version stamp is current. Refuses to downgrade a newer shared store. |\n\nA seed that fails names the connector and the reason, such as npm missing from PATH. Until\n`cotal ext seed --repair` finishes it, each command that seeds refuses. The refusal says whether\nthe last seed failed, and why, or was interrupted.\n\nWhen a newer `cotal` advances the operator-global seed store to its generation, it prints one\nmigration line naming the old and new generations, the exact CLI entry that wrote the store, the\ncommit timestamp, and `seed/stamp.json`. That writer and timestamp are kept in the stamp, so a later\nolder CLI refusal can say which executable wrote the generation it will not overwrite and when.\nLegacy generation-only stamps remain readable; their refusal simply has no writer provenance to add.\n\nAn older `cotal` refuses a seed store written by a newer version. The refusal names the store's\n`seed` folder and the `extensions` folder beside it, and says to restore both from a backup taken\nbefore the store moved to the newer generation; the two versions are context. When it can verify a\nsufficient `cotal` executable on PATH or at the installer's `~/.local/bin/cotal` location, the\nrefusal also names that absolute path, which uses the store as it is, so a reduced service PATH\ndoes not select the older binary again. Every maintenance mode, including `--force` and `--reset`,\nrefuses to replace a newer shared generation. Same-generation force remains supported. Reset still repairs corrupt state and resurrects\nremoved connectors. Older released executables may still allow a backward rebuild; do not use\ntheir force advice to recover from selecting the wrong installation.\n\nA source-checkout CLI (`pnpm cotal`, `tsx bin/cotal.ts`, `node bin/cotal.ts`, or a suite child of\nthose) refuses to write or garbage-collect that store. The refusal names the path, the generation\nit declined, and `COTAL_SKIP_CONNECTOR_SEED=1` as the way to run other commands from a checkout,\nbecause pointing `$XDG_CONFIG_HOME` at a scratch dir alone does not lift it. With the skip set, a fresh\nconfig gets no built-in connectors (`cotal ext list` shows none), so add one from the checkout with\n`cotal ext add <checkout>/extensions/connector-claude-code` against that scratch `$XDG_CONFIG_HOME`. `COTAL_HOME` does not relocate this\nstore. An entry that cannot be proven as a released install is refused the same way. Isolated\nrelease tests that must seed from a checkout-shaped `bin/` set `COTAL_ALLOW_CHECKOUT_SEED=1` after\npointing `$XDG_CONFIG_HOME` at a scratch dir; that override is documented here, not on the refusal\nline. An opt-in write still records the checkout path in `seed/stamp.json` as `writtenBy`.\n\nThe default connector for a bare `cotal spawn` (no `--agent`) is the persona's `agent:` pin if it\nhas one, else `claude`; set `COTAL_DEFAULT_AGENT` or `cotal config set harness` (e.g. `opencode`)\nto change the fallback. It is a default, so a persona that pins its harness still wins over it. An `--agent` naming a removed\nconnector fails loud with the exact\n`cotal ext add` to restore it. Set `COTAL_SKIP_CONNECTOR_SEED=1` to turn off the automatic first-run\nseed/refresh entirely (for a controlled or offline setup that manages connectors by hand); `cotal ext\nseed` still runs on request. `cotal agent-bearer` never takes the seed at all: it is exec'd by\nspawned seats on every bearer refresh, so it neither reconciles nor is refused by the store's\ngeneration (see [Plumbing](#plumbing)).\n`cotal completion` skips the seed as well, so a shell profile that sources its stub never reconciles\nand is never refused by the store's generation or the source-checkout guard. Printing a stub writes\nnothing, and `completion install` writes only shell configuration.\n\n## docs\n\n```bash\ncotal docs [<page>] [--refresh] [--json | --plain]\ncotal docs --search <query> [--json | --plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--search <query>` | none | Show the sections that best match a query, each naming its page |\n| `--refresh` | off | With a page: also try the version-pinned copy on docs.cotal.ai |\n| `--json` / `-j` | off | One JSON document |\n| `--plain` | off | The same report without color, and a page as its Markdown |\n\nReads the docs bundled with this installed version, offline: the same pages agents read with\n`cotal_docs`. Bare `docs` lists the pages by category (`Start here`, `Guide`, `Concept`, `Reference`,\n`Project`, the order of the docs index), each with its title; `result.pages` holds each page's slug,\ntitle, kind (its classification line, such as `Concept (informative)`), category (the heading it\nlists under) and summary, in that order, and\n`result.sources` the normative `spec`, `lang` and `schema`. A page is named by its slug,\nignoring case and a trailing `.md`; `result.body` holds its Markdown. On a terminal a page is drawn\nfor reading: each heading is a title, paragraphs and lists wrap to the terminal, a table is text that\nbecomes one record per row when its columns do not fit, and a code block keeps its lines. To a pipe\nor a file, and with `--plain`, the page is its Markdown. An unknown page is refused with\n`USAGE_INVALID`, naming every page.\n\n`--refresh` fetches the page from the immutable path for this version on docs.cotal.ai.\n`result.source` says which copy was served, `bundled` or `docs.cotal.ai`, and `result.refresh` says\nwhat the fetch did: `served`, `not-published` (the path has no copy) or `unreachable`. The bundled\ncopy is served whenever the fetch does not succeed. `--refresh` applies only to a page.\n\n`--search` ranks sections by the words of the query. A search with no match is an empty result.\n\n## config\n\n```bash\ncotal config [--user | --space <name>] [-v | --verbose] [--json | --plain]\ncotal config get <key> [--user | --space <name>] [--json | --plain]\ncotal config set <key> <value> [--user | --space <name>] [--json | --plain]\ncotal config unset <key> [--user | --space <name>] [--json | --plain]\n```\n\n| Flag | Default | Meaning |\n|---|---|---|\n| `--user` | your defaults for a write | Your own values, with no space's values |\n| `--space <name>` | the selected space for a read | Your values for one space |\n| `--verbose` / `-v` | off | Bare `config` also shows the preferences file and its revision, which its JSON result always holds |\n| `--json` / `-j` | off | One JSON document |\n| `--plain` | off | The same report without color |\n\nYour [preferences](config.md#preferences): `harness`, `persona`, `detach-key`, `color` and `editor`.\nBare `config` lists every key with the value a command uses, where it came from (a variable, your\nvalue for the space, your own value, or the default) and the values it outranks. `get` shows one\nkey. A read looks through `--space`, else the space a launch here uses: the space selected with\n`cotal use`, or with nothing selected the space `spawn` resolves from this folder, which is this\nfolder's space or the only running one. With none of those it reads your defaults. `--user` reads\nyour defaults alone.\n\n`set` and `unset` change your defaults, or your values for one space with `--space`. Only `harness`\nand `persona` have per-space values. `set --space` takes a space registered or prepared on this\nmachine, and refuses any other name with `TARGET_NOT_FOUND` (exit 2) and the known spaces.\nA read and `unset --space` also take a space that still holds a saved value, so a value left for\na space you removed can be read and removed, and refuse any other name the same way. A write that\nanother value outranks in this shell is still saved, with a `PREFERENCE_SHADOWED` warning naming\nthat value: a variable, or your value for the space a command here uses when you write your\ndefaults, with or without `--user`. The receipt's effective value is the one a command here uses,\nread through `--space`, else that space. A `persona` that the space's catalog does not hold is saved with a `PERSONA_NOT_FOUND`\nwarning naming the catalog. A variable whose value `config set` would refuse still wins, and\n`config`, `config get` and `status` name it in a `PREFERENCE_INVALID` warning with the reason.\nSetting a value that is already saved, or\nunsetting one that is not, is a `no-op` and exits 0. An unknown key or an invalid value is refused\nwith `USAGE_INVALID` and the valid choices, and a write that lost a race with another writer is\nrefused with `CONFLICT`. A preferences folder you cannot write is refused with `PERMISSION_DENIED`\n(exit 2), naming the folder, before anything is changed. So is a folder another account could write,\nwith the `chmod` that fixes it, and a linked `preferences.json`, which `config set` never replaces\n(see [Preferences](config.md#preferences)).\n\n`result.settings` (or `result.setting` for `get`) holds each key's `value`, its `winner`\n(`{source, name, value}`, where `source` is `environment`, `space`, `user` or `default`) and the\n`shadowed` layers below it. A write's result has the `previous` and new `value`, the file's new\n`revision`, and `effective`, the key as a command there now resolves it.\n\n## completion\n\n```bash\ncotal completion [--json|--plain]\ncotal completion script <bash|zsh|fish|powershell>\ncotal completion install [shell] [--json|--plain]\ncotal completion uninstall [shell] [--json|--plain]\n```\n\nBare `completion` reads local integration files. It reports the script version, startup reference\nand recorded ownership separately. It does not inspect or load the running shell. An unreadable\nfile or ownership record produces a warning and a partial result, exit 4. The warning names the\nfile and why. A record wired into another startup file, as an install under another `ZDOTDIR` leaves\none, names that file and the `ZDOTDIR` to read it with. A folder above the files that is a symlink,\nowned by another user, or group- or other-writable is named once, with the `chmod go-w` that fixes\nit when one does. The files under it are not opened, and one that does not exist is reported missing,\nso a report with nothing installed stays complete, exit 0. Next offers that `chmod` first, offers\n`install` and `uninstall` only when nothing stops them, and offers `uninstall` only when something is\ninstalled. A relative `ZDOTDIR`\nis resolved from the current directory for this report, as zsh resolves it.\n\nIn zsh, initialize its completion system with `autoload -Uz compinit && compinit` before\nsourcing the native zsh stub.\n\nLoad completion in the current shell:\n\n```bash\nsource <(cotal completion script bash)\nsource <(cotal completion script zsh)\ncotal completion script fish | source\ncotal completion script powershell | Out-String | Invoke-Expression\n```\n\n`completion script` writes raw shell code and takes no `--json` or `--plain`. Generation is\noffline. The stable hidden `__complete` entry reads registered command specifications, the spaces\nregistered or prepared on this machine, the extension manifest, the bundled docs index and local\npersona files. It does not contact the mesh or refresh extensions. Every `--space` offers the spaces\n`cotal use` offers, and every `--creds` lets the shell complete a file name. `space show`, `forget`\nand `delete` offer the spaces registered or prepared here. `config` offers its keys, and `config set color` and `config set harness` the\nvalues a write accepts. `docs` offers its pages, `watch --view` its views and `space add --mode` its\nmodes. A channel name for `send -c`, `history` or `channel show`, `members`, `set` and `remove` comes\nfrom the channels the target space's personas declare, as `spawn` reads them, so a space prepared\nand not started offers its own. The broker keeps the others, so a persona that declares only `>`\noffers none. A\ncandidate that holds a control character, such as a persona file name with an escape sequence or a\nnewline in it, is not offered. `persona` still lists that file with the characters escaped. A\npersona catalog folder that cannot be read fails the completion of a persona name, so the shell\noffers none, and with `COTAL_COMPLETE_DEBUG=1` it prints the folder. A record it skips, such as a\nprepared space record that does not parse, is named on stderr only under that flag too. The stubs\ndiscard stderr, so run the completion by hand to see it, as\n`COTAL_COMPLETE_DEBUG=1 cotal __complete persona show ''` does. The bash\nand zsh stubs complete file names where a value takes a path. The fish stub offers only cotal's own\ncandidates.\n\nIn bash and zsh, a file argument such as `team check <file>`, `up -f <file>`, `--channels <path>`,\n`--creds <path>`, `run start --file <program>` or `auth mint --out <private-file>` completes from the\nshell's own file names, and so does a folder such as `space add --root <dir>`.\nA value with a fixed set of choices, a duration or free text does not offer files.\nA stub installed by an earlier release offers no files. `cotal completion` reports it as differing\nfrom this version, and `completion install` refreshes it.\n\n`install` and `uninstall` take a shell name or detect it from `SHELL`. Pass `powershell`\nexplicitly when PowerShell inherited another shell's `SHELL`. A `SHELL` that names a shell\ncompletion does not support, such as tcsh, is refused with `UNSUPPORTED` (exit 2), naming it. On supported POSIX hosts:\n\n- Bash uses `~/.config/cotal/completion.bash` and an owned block in `~/.bashrc`.\n- Zsh uses `~/.config/cotal/completion.zsh` and an owned block in `$ZDOTDIR/.zshrc`, or\n `~/.zshrc` when `ZDOTDIR` is unset.\n- Fish uses `~/.config/fish/completions/cotal.fish`, which fish loads automatically.\n- PowerShell on Linux or macOS asks the installed `pwsh -NoProfile` for its current-user\n all-hosts profile and wires an owned `completion.ps1` stub into that file. A missing shell,\n invalid profile reply or missing profile directory refuses without guessing a path.\n\nAn absolute `XDG_CONFIG_HOME` replaces `~/.config` for the owned stubs; a relative value is\nignored. Mutation refuses a relative `ZDOTDIR`, missing startup directory, symlink, untrusted\nowner or unsafe file access before it creates anything. A missing startup directory is named, with\n`ZDOTDIR` when that variable chose it, and with the `mkdir` that creates it. One refusal names every refused startup\nfile, stub, record and directory on the way to them with its reason, and a group- or other-writable\none with its mode and one `chmod go-w <paths>` that fixes them all. A startup file that is a\nsymlink, as a dotfile manager such as stow links it, is never written through: `install` refuses\nit, exit 2, names it and gives the line to add to the file it points to yourself. Bare `completion`\nwarns that it is a symlink and does not read it. A startup file its owner made read-only is never\nreplaced. `install`\nstill writes its stub, then fails, exit 5, and names the line to add to that file yourself.\n`uninstall` refuses it, exit 2, and changes nothing until you remove that line. On Windows, PowerShell script generation works but automatic\nprofile editing remains unsupported: the guarded POSIX file writer cannot safely replace\nWindows reparse-point paths. Installing files does not establish that the host runs its\nprofile or permits scripts under its execution policy.\n\nInstallation writes a private `.ownership.json` record beside its stub. Reinstall refreshes only\nan unchanged recorded stub. Uninstall removes only that stub and the unchanged recorded startup\nblock, keeping other startup bytes. A startup file that `install` created is removed when nothing\nelse is left in it; one that was there before stays, even empty. Modified or unrecorded material is preserved with a refusal.\nFor a recorded stub or startup block you edited, the refusal names `--force`: `install --force`\nrewrites the stub and each block from `# >>> cotal completion >>>` to `# <<< cotal completion <<<`,\nand `uninstall --force` removes them. Startup content outside those markers is kept. A block whose\nend marker is gone is still refused, with the file named, because only you can tell which lines\nare cotal's; remove them there yourself. For an integration you modified, bare `completion` offers\n`install` and `uninstall` only with `--force`.\nA stub from an earlier release without an ownership record needs manual review; matching\ngenerated text alone is not treated as ownership. An unowned startup reference to a managed stub must be reviewed\nbefore removing the stub. These commands cannot unload functions in a parent shell.\n\nCotal writers use a per-shell lock and recheck file identity and bytes before changing them.\nAn unconfirmed filesystem mutation is reported as unknown and is not retried. A confirmed change\nfollowed by another refusal is partial, with the acknowledged file paths retained. This is not\na filesystem compare-and-swap against an editor that ignores the lock.\n\n`update` refreshes existing unchanged recorded stubs through its current-generation CLI. It never\nadds startup wiring or reinstalls a removed stub. Modified and unrecorded integrations are kept;\nany guarded refresh it cannot complete is reported. `update --check` does not refresh completion.\n\nThe old `completion <shell>` form is a usage error. Custom startup commands need manual migration;\nsee [Upgrading](UPGRADING.md#shell-completion-script-in-0910). No command silently\nrepairs custom wiring. Install and uninstall support one finite JSON document or the plain report.\n\n## feedback\n\n```bash\ncotal feedback \"<summary>\" [--type <type>] [--email <contact>] [--details <text>] [--json|--plain]\ncotal feedback \"<summary>\" --key-file <private-file> [--json|--plain]\n```\n\nThe type is bug, idea, friction, praise or other; severity is low, medium or high. Optional area\nand contact name are included only when supplied. The public intake requires a traceable email,\nfrom `--email`, the existing configured contact or local git email. No environment setup is\nrequired. Do not put credentials, private transcripts or unrelated customer data in feedback.\n\nA protected `--key-file` selects keyed intake without placing a credential on argv. The file holds\none key: an opaque bearer token with no whitespace, optionally followed by one newline. An existing\ninternal intake configuration remains supported. The old raw `--key` input refuses before the\nrequest and does not echo its value. Private key files must meet the shared file-ownership and\nprotection policy; unsupported environments refuse rather than weakening that policy.\n\n`--url` can select a self-hosted intake. A Cotal intake accepts reports only at `POST /v1/feedback`,\nso the URL names that path, such as `https://intake.example/v1/feedback`; the origin alone gets\nHTTP 404. Remote intake requires HTTPS; HTTP is limited to literal loopback development hosts. URL\ncredentials and fragments are refused, and redirects are not followed. One request has a 20-second\ndeadline and is never automatically retried or rerouted to a different credential/intake.\n\nA connection that never opens sends nothing: a refused connection, a failed DNS lookup or a failed\nTLS handshake refuses with `TARGET_UNAVAILABLE` and the native error code. A timeout or a broken\nconnection after the request was written, or an unconfirmed server outcome, can mean the report\narrived: inspect the original intake before sending it again. An intake refusal names its cause and\na next step. HTTP 401 means the key is missing or refused, 404 means the URL is not the intake's\nfeedback path, and 429 means the intake is rate limiting submissions. A 429 names the retry time\nwhen the intake's `Retry-After` header gives one.\n\nJSON/plain receipts report the intake origin, HTTP acceptance and a bounded record ID when the\nserver provides one. They do not repeat the submitted summary, details, contact values, key or\nresponse body. Acceptance does not prove that anyone read the report or acted on it. A success\nresponse without an established ID is reported with that limitation, not retried for a new ID.\nRun a self-hosted intake with [`feedback-intake`](#server-daemons).\n\n## telemetry\n\n```bash\ncotal telemetry [--json|--plain]\ncotal telemetry off|minimal|regular [--json|--plain]\n```\n\nThe CLI sends anonymous usage data: one small event per command as it exits, and at the default\n`regular` level one install summary per day. [Telemetry](telemetry.md) lists every field and what\nis never sent. The bare form prints the level in force, what set it (the settings file,\n`COTAL_TELEMETRY`, `DO_NOT_TRACK` or `COTAL_TELEMETRY_DISABLED`), the install id, the intake URL and\nthe settings file. A level argument writes the file; `on` is an alias of `regular`, and writing the\nlevel the file already holds is a `no-op`. A level the environment forces still wins, and the\nreport says so. Under `DO_NOT_TRACK`, `COTAL_TELEMETRY_DISABLED` or `COTAL_TELEMETRY=off` the file\nis neither read nor created, so a level argument saves nothing and is a `no-op` with an\n`ENVIRONMENT_OPT_OUT` warning. Any other word is refused `USAGE_INVALID`, naming the levels.\n`cotal telemetry` itself sends nothing.\n\nThe first interactive run at `regular` prints one notice to stderr. `COTAL_TELEMETRY` set to\nanything but a level refuses `USAGE_INVALID`. A settings file that does not validate refuses\n`SCHEMA_INVALID` on `cotal telemetry` and on every command that reports usage, until it is fixed or\nmoved away. Under the environment opt-outs above nothing reads it, so it stops no command.\n\n## run\n\nOperate durable workflow runs (cotal-lang programs) from the terminal.\n\n```bash\ncotal run start --file <program> [--timeout <dur>] [--local --admit-read <channels> --admit-publish <channels>] [--on <instance>]\ncotal run resume <runId> [--local --file <program>] [--on <instance>]\ncotal run [--endpoint <ep>] [--json] [--on <instance>]\ncotal run --page --on <instance> [--limit <n>] [--cursor <cursor>] [--endpoint <ep>]\ncotal run show <runId> [--endpoint <ep>] [--limit <n>] [--json] [--on <instance>]\ncotal run answer <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]\ncotal run amend <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]\ncotal run revoke <runId> --local --by <who> --reason <text> [--endpoint <ep>]\ncotal run check <runId> <program> [--endpoint <ep>]\n```\n\n`ps` (or `list`), `journal` and `migrate` still work as compatibility aliases of bare `run`, `show`\nand `check`.\n\nEvery verb also takes `--space <s>`, `--server <url>` and `--creds <path>` to pick the mesh and the\ncredential. A hosted verb refuses a raw `--creds` file `UNSUPPORTED`, exit 2, because the file\ncarries no endpoint-caller triple for the manager call and that route cannot mint one. Its next step\nis to run the verb again without `--creds`, in the mesh's project folder or with `--space`, where\nthe CLI mints the one-shot caller itself.\n\nHosted run verbs accept `--on <manager-instance>` to choose one full manager id\nas `cotal ps` prints it. A managed seat defaults to the manager that launched\nit; an unbound user-auth caller with several managers must choose one. Without\n`--on` its call is refused with exit 2 before any request, and the refusal names\neach live manager.\n`--on` does not apply to local, offline or seat-only run commands, and a failed\npin never falls back to another manager. Rich run reports label an explicit pin\nas the requested manager, and the JSON target carries `requestedManagerInstance`.\nFollow-up commands printed by the CLI retain the same explicit pin and fill\nin the run ID the command named or the manager returned, only when it matches the native\nrun-token shape. Keep the pin on later status, answer and resume calls.\n\nA run ID that is not 1 to 64 letters, digits, `_` or `-` is refused as `USAGE_INVALID`, exit 1,\non every verb that takes one, before anything is contacted.\nA hosted verb the manager refuses reports the outcome and code as other commands do, and with\n`--json` as one refusal envelope on stdout. A refusal the manager makes before acting is refused: a\ndenied credential or a step in the wrong state exits 2, and a program that does not validate is\n`SCHEMA_INVALID`, exit 1, like other invalid input. A run or step it holds no record of is `TARGET_NOT_FOUND`, with `cotal run` or\n`cotal run show <runId>` as the next step. A manager activates at most 8 runs at once, counting\neach `start` or `resume` until its run is recorded. One more is refused `LIFECYCLE_BLOCKED`, exit 2,\nbefore anything is written; the refusal names the runs still activating, and the next step is to\nwait until one has activated and run it again. Any other refusal of `start`, `resume`, `answer` or\n`amend` may follow a recorded change, so it is unknown, exit 3, and is not retried.\n\nAn unpinned hosted verb run by the operator reads the space's service registry first. When it\nlists no live manager, as after `cotal down stack-manager`, the verb refuses `TARGET_UNAVAILABLE`\n(exit 2) at once, as `ps` does, and names bare `cotal up` as the next step, instead of waiting out\nthe manager's answer deadline.\n\nA mesh a run verb cannot resolve or connect to is refused before any request under the refusal's\nown code, hosted or `--local`, as `ps` refuses it. With no mesh running that is `TARGET_NOT_FOUND`,\nexit 2, and with `--json` one refusal envelope on stdout.\n\n`start` hands the program to the mesh's manager, which validates it, mints the run id (the record\nnever takes a caller-supplied one), drives it in its own process, and answers with the id once the\nrun is recorded; a program that does not validate is refused with every problem listed, each\ncontrol character in it shown as its `\\uXXXX` escape. Every verb\nthat takes a program file refuses a missing one with `TARGET_NOT_FOUND` and one this account cannot\nread with `PERMISSION_DENIED`, exit 2, and a folder with `USAGE_INVALID`, exit 1.\nA `start` interrupted with Ctrl-C or `SIGTERM` after its request went out reports outcome `unknown`,\nbecause the manager may already have recorded the run, and exits 130 or 143. List the runs with\n`cotal run` before starting it again.\n`resume` asks the manager to take an existing run back and continue it from its step journal; the\nsource is the recorded program, so no `--file` is taken. A run recorded `completed` or `failed` is\nrefused `LIFECYCLE_BLOCKED` (exit 2) and gets no new activation, because nothing is left to continue;\nthe next step is `cotal run show <runId>`. Neither takes `--endpoint`: the manager records\nits runs under its own endpoint, and naming another is refused. Bare `run` lists the run records and\n`show` renders one run's durable records; both only inspect. `show` reads the\njournal from the manager in pages, each one reply, so a journal larger than the broker's payload\nlimit stays readable. A record or a journal row too large for one reply refuses `UNSUPPORTED`,\nexit 2, and names `cotal run show <run-id> --local`, which reads the run's stream from its\nproject folder with no reply. A manager from release 0.99.3 or earlier reads no journal page, so\nthrough it both refuse `UNSUPPORTED` and name the same local read. An open pause prints its question\nunder `Needs an answer`, and only then does Next offer `answer`. A checkpoint with a `to` prints\nbeside its question whom it escalates to, and once it has escalated, until when that attempt is open. A pause counts as open while its\nlast journal row is pending, so a settled or expired one is not listed there.\nA pause settled with an accepted answer prints its value as JSON plus the recorded answerer,\nartifact when present, time, and answer id, then one `amended` line per later amendment, in the\norder the store committed them, so the last is the current position. A value longer than 120\ncharacters is cut there, with a line that says so; `--json` carries it whole.\nExpired pauses and ordinary steps print no answer line.\nA step whose last journal row recorded an error prints its outcome and the recorded message under\n`Step errors`, a caught failure included, with each control character shown as its `\\uXXXX`\nescape. A run that failed records its cause on its status. Its recorded state reads\n`failed (<code>)` in the run list and the run view when the error carries a language code, and the run\nview prints the message under `Failure`. When a step's error is the run's failure, `Failure` names\nthat step and the message stays under `Step errors`. A run that failed before its status recorded a\ncause shows only `failed`. A failure raised by program code outside any step is recorded in no\njournal row. When the status recorded no cause either, the view says so under `Failure`. Either way\nit names `cotal service logs stack-manager --space <space>`, because a hosted run's cause is in its\nmanager's log.\nThe failure records no line or column in the program: a source position belongs to a problem\n`run validate` reports before the program runs.\n\nA run recorded `released` has no recorded reason. Its `Released` section points to the executing\nmanager's log for a hosted run or the driving process's output for a locally driven run. A journal\nread does not establish that execution host or log location, so the view does not guess one.\nA journal entry larger than the broker's configured `max_payload`, such as a large checkpoint\nquestion, can release a run with `L5010 Journal append rejected`. Repeating `resume` while the\ncause remains can release it again; this view does not change that state or prevent the retry.\n\nA program's `log(...)` lines are not journalled, so `show` does not print them. A hosted\nrun writes each line to its manager's log as `run <run-id>: log <text>`, which\n`cotal service logs stack-manager --space <space>` reads, and a resumed run writes the lines before\nits resume point again. `--local` prints them in the terminal that drives the run.\n`--json` on the run list or `show` writes one envelope on stdout, as other commands do, and nothing on\nstderr, whether the manager answers or `--local` reads. The list puts its rows under `result.items`.\n`show` puts the run under `result.view`: its `runId`, `endpoint`, recorded `spec` and `status`,\nits `programHash` as the list row carries it, and its rows under `result.view.journal`. A program\nrecord that cannot be read gives `programUnreadable` with its reason, and no `programHash`. A list row carries `runId`, `endpoint`, `state`, `holder`, `epoch`,\n`journalHigh`, `forkedFrom`, `startedAt` and `programHash` (the values the program's `run()`\nreports; `programHash` is absent for a run with no recorded program), `failure` on a failed run\nwith its `code` and `message`, and `revoked` or `revocationUnreadable` when the marker says so. A journal row is an `activation` or a `step`. A\nstep row carries its `step` key, the `effect` kind and its `name`, `state`, `outcome`, the recorded\n`status` and `errorCode` once settled, the recorded `errorMessage` of a failed or refused step,\nand `startedAt` and `endedAt` in epoch milliseconds. An open\npause adds its `asks`, its `deadlineAt`, and for a checkpoint the `onExpiry` it was armed with and\nthe `addressee` its `to` names. A checkpoint's escalation is armed with `proceed`, so it never\nescalates again. A settled pause adds its `answer` and its `amendments`, as the text view prints\nthem. A field the\njournal does not record is absent: a checkpoint opened before `onExpiry` was recorded carries none.\nAn unreadable revocation marker makes the result partial, exit 4, with a `REVOCATION_NOT_CHECKED`\nwarning naming the run. A `run --endpoint <endpoint>` that matches no run lists nothing and says so,\nnaming the endpoints runs are recorded under, with an `ENDPOINT_HAS_NO_RUNS` warning. The text view is presentation and is not a stable parsing target. With\n`--local`, only the list, `show`, `answer` and `amend` take `--json` or `--plain`. Any other local form\nrefuses either flag `USAGE_INVALID`, exit 1: `start --local` and `resume --local` name the\nmanager's form, which takes it, and `revoke` and `check`, which have no other form, name the same\ncommand without it.\n`run --page`, which is not in release 0.88.0 or earlier, asks one manager for a single bounded\ndiagnostic JSON page instead of the full list:\n`--limit` takes 1 to 16 rows (default 8), and `--cursor` continues a scan from a returned\n`nextCursor`. It requires `--on` and refuses `--local`, and a page proves neither absence nor\nownership ([Diagnostic inventory](workflows.md#diagnostic-inventory)).\n`revoke` writes the run's revocation marker from the project folder; `--by` and `--reason` are\nrequired ([workflows](workflows.md)).\n`answer` resolves an open\ncheckpoint through the manager, presenting as the holder that armed it; the manager records the\nanswerer from your credential, so no `--by` is taken there. A settled step refuses a second\n`answer` with `TARGET_NOT_FOUND`, also when it is sent before the run records the first, and a\nrevoked run refuses every answer with `LIFECYCLE_BLOCKED` and files nothing. A step the run holds no\nopen request at is also `TARGET_NOT_FOUND`; when an open checkpoint or `ask` has the name you typed,\nas `frage` names `/checkpoint:frage#0`, the next step gives its full key, and several matches are\nlisted for you to pick from. A run ID that no run is recorded under is `TARGET_NOT_FOUND` too, with\nor without `--local`, and the refusal says no run is recorded under that ID. An\nanswer without `--value` carries no value: the step settles as answered, and the\nprogram's checkpoint result has no `value`. `--value` is refused with `SCHEMA_INVALID` before\nanything is sent when the record would not hold what you typed: an object that names a key twice,\na number outside the range JSON carries (such as `1e999`), or a value nested more than 1000 levels\ndeep. An empty or blank `--artifact` is refused with `USAGE_INVALID`. The receipt of `answer` and\n`amend` names the step, the value it filed or that none was given, any artifact and the answer id. A value longer than\n120 characters is cut there too, with a line that says so. With `--json` they are in\n`result.answer`, whole, where `valueGiven` says whether a value was sent. `amend` records a changed\nposition on a settled checkpoint or `ask`: it files a new answer beside the accepted one, naming it, and the journal lists it under the step. The pause stays\nsettled and the run keeps the answer it acted on. A step that is still open or settled without an\nanswer refuses an amend. So does a run ID that no run is recorded under, and that refusal says no\nrun is recorded under that ID. With `--local` these refusals are `TARGET_NOT_FOUND`, exit 2, as the\nhosted ones are, since nothing was written. The run records an accepted answer a moment after `answer` returns, and an\namend sent in between is refused with a message saying the answer is not recorded yet; amend again\nonce `run show` lists it. A spawned seat may amend only an answer recorded under its own name.\n`check` replays a run's journal against an edited program, from this terminal under a read\ncredential (`--local` only; the manager serves no run-migrate command). It prints whether the\nmigration is admissible, every orphaned step with its verdict and code, and exits 0 on admissible\nand non-zero on not. An edited program that does not validate is refused before the replay, with\neach problem's code, position, cause and fix. It writes nothing: the commit that would file the\nmigration is not reachable yet, and the report says so. A run ID with no record on the endpoint is refused\n`TARGET_NOT_FOUND`, exit 2, as `run show` refuses it. `check` refuses `--adopt`, `--release` and `--discard-approvals`, since they decide what a\ncommit does and the check files nothing. `--timeout` sets the default\nlimit of a checkpoint, `ask` or turn that sets none of its own (default 1h). A checkpoint that\nexpires under `onExpiry: \"fail\"` says in its L4007 whether the deadline came from its own `timeout`\nor from this default. It takes a duration\nlonger than zero, such as `30m`, and refuses anything else with `USAGE_INVALID` before the run starts. `--on <instance>` sends a hosted verb to one manager\ninstance, with the same rules as [`ps`](#managed-seats). On a mesh whose managers this terminal can\nlist, an `--on` that names none of the running ones is refused `TARGET_NOT_FOUND` at once, naming\nthem, and nothing is sent. On a user-auth mesh where you run more than one\nlive manager, an unpinned hosted verb is refused and `--on` picks one. `--local` takes no `--on`. `--local` drives in this process instead, over one\nconnection per invocation under the run's own credential minted from the project folder's trust\nmaterial, and is the path on a bare broker with no manager or for a run with no recorded program\n(`cotal run resume <runId> --local --file <program>`); `answer --local` and `amend --local` take\n`--by <who>` and print the same receipt as the hosted path, and `start --local` requires\n`--admit-read` and `--admit-publish` (comma-separated\npatterns or `none`), the run's channel ceiling. An entry that is not a channel or a pattern, such as\n`a b`, is refused `USAGE_INVALID` naming it. When the run's status records that its program failed\nunder this drive, `start --local` and `resume --local` fail `OPERATION_FAILED`, exit 5, naming the\nrun and its language code with that code's title. A failure whose code has no title shows the first\nline of its recorded message instead, with control characters escaped. Their Next is\n`cotal run show <runId> --local`, which prints the whole message. Any other error the drive ends on\nstays `unknown`, exit 3. On a\nuser-auth mesh the host's own manager refuses the family by name, and `--local` has no credential\nthere. A participant's manager started with `cotal supervise` hosts a logged-in user's runs through\nits issuing host: the auth callout issues the user's manager connection, and every `run` verb rides\nthe versioned rail under that issuance.\n[User-auth run start](design/user-auth-run-start.md)\nrecords the path. The guide is [workflows](workflows.md).\n\n## Server daemons\n\nThree long-lived infrastructure roles ship with the CLI: the delivery daemon, the auth service and\nthe feedback intake. They are not part of everyday operation. The delivery daemon comes up\nautomatically with `cotal up --detach` in auth mode.\n\n`deliver`, `auth-service` and `feedback-intake` are deprecated entries kept for existing units and\nscripts; help and completion omit them. Each entry's own help\nmarks it deprecated, with its removal release and the preset that replaces it. Each accepts its existing flags and\nprints a warning on every run naming its service preset and the release that removes it. The\ndelivery daemon and auth service that `cotal up` starts run the same code through hidden entries\nand print no warning. New setups use `cotal service start <preset>`; see\n[deprecated daemon entries](UPGRADING.md#deprecated-daemon-entries). [`supervise`](#supervise), the\nmanager's entry, is deprecated the same way and names `cotal manager start`.\n\n```bash\ncotal deliver --space <s> [--server <url>] [--creds <file>] [--root <dir>]\ncotal auth-service --space <s> --server <url> [--port <n>] [--exchange-public-port <n>] [--exchange-public-url <https://\u2026>] [--exchange-trusted-proxy] [--advertised-server <url>] [--agent-provisioning-url <https://\u2026>] [--auth-timeout <seconds>]\ncotal auth-service --space <s> --server <url> --callout-only [--auth-timeout <seconds>]\ncotal auth-service --server <url> --callout-only --spaces <manifest> [--auth-timeout <seconds>]\ncotal service add intake --preset feedback-intake --space <s> --root <absolute-root> --keys <private-keys.json> --creds <private-creds>\ncotal service start intake --space <s>\n```\n\n`auth-service` runs a user-auth space's identity plane: the NATS auth callout, the\ncapability-gated local exchange and JWKS, and, when `--exchange-public-port` is set, the closed public\nexchange/discovery face forwarded by an HTTPS reverse proxy. `--exchange-public-url` is the proxy URL\nadvertised to clients; `--exchange-trusted-proxy` opts into last-hop `X-Forwarded-For` attribution.\n`--advertised-server` and `--agent-provisioning-url` set the broker address and agent-provisioning\nendpoint the public bundle advertises, as on [`up`](#up). `--auth-timeout` is the broker's\n`authorization { timeout }` in whole seconds, which the callout answers inside; give it the value the\nbroker config renders, which `cotal up` passes from `--auth-timeout`.\n`cotal up --user-auth` starts and supervises the service for you, so you run it directly only to\nrecover one by hand.\n`--callout-only` runs a callout replica beside the full service: it answers connects alongside it\nand serves no HTTP, so it refuses `--port`, the public-face flags and `--service-endpoints`. It\nrefuses to start in a space that admits service endpoints. It never writes `auth-service.json`, and\nit exits non-zero when it loses a store connection or the broker. Give it the same `--auth-timeout`\nas the full service, the value the broker config renders.\nSee [callout replicas](identity-and-auth.md#per-user-authentication).\n\n`--spaces` serves several spaces from one callout process, one lane each, and stands in for\n`--space`. The two are alternatives: naming both refuses, and `--spaces` without `--callout-only`\nrefuses. The manifest is a JSON file holding the spaces this process serves:\n\n```json\n{ \"version\": 1, \"spaces\": [\"<s>\", \"<s>\"] }\n```\n\nAn empty list, a repeated space, an unknown field, a version other than 1 and an entry that is not\na space name all refuse. Every lane dials the one `--server`, so a deployment whose spaces live on\ndifferent brokers runs one host process per broker rather than naming a server per space.\n\nEach lane applies the per-space refusals a single-space replica applies, so a space with missing\nmaterial, a mismatched issuer pin, an unreachable broker or open service-endpoint admission refuses.\nAt startup the host serves every space in the manifest or none: one that cannot be served is an\nerror naming that space, and no lane is left running.\n\nThe host rereads the manifest when it changes on disk, on `SIGHUP`, and on a slow backstop interval,\nand opens or closes the difference. Write a replacement and rename it into place: a manifest that\ndoes not read or parse whole is reported and the lanes already open are kept. A lane that loses its\nstore connection or the broker closes on its own and the other lanes keep answering; the host\nreopens it, retrying every 5 seconds while it fails.\n\n`deliver` runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL\nauthority. It is auth-mode-only and single-instance (`--shard`/`--shards` accept only `N=1`);\n`--dev-mint` mints a scoped cred for standalone dev, signed by the local account of the space the\ndaemon serves. `--creds` can start a daemon that already looks healthy, but production renewal is\nnot that file alone: the manager and the daemon must address one credential store. On a stock split host with two project roots, a\ndirect `deliver` is not an independent repair; keep the daemon under `cotal up` on the broker\nhost, or inject the same store into both processes ([embedding](embedding.md#supervisor-signing-authority)).\nTyped by hand on the workstation, `deliver` dials the broker recorded for `--space` in the mesh\nregistry (a mismatching `--server` is refused before any dial, and a record for a different\nworkspace root is refused outright); with no record for the space it falls back to the local mesh.\nThe daemon serves the workspace root that `--root <dir>` names, which must hold `.cotal/`, or else\nthe nearest `.cotal/` above its working directory. With neither, it refuses at start and names the\ndirectory it searched from, before it reads a credential or dials a broker.\nSee the [delivery daemon](delivery-daemon.md). `feedback-intake` runs a self-hosted feedback server\nat `POST /v1/feedback` (requires `--keys`, in the format the `feedback-intake` service preset\ndescribes, and a scoped `--creds`), announcing submissions into a space channel; flags\ninclude `--host`/`--port`, `--store`, `--space`/`--channel`, `--max-bytes`, and `--rate-limit`.\nWithout `--server` it dials the broker its `--space` is registered at, and refuses an unregistered\nspace before dialing.\n`--port` takes a decimal port from 1 to 65535 and is checked before the intake dials the broker.\n`--max-bytes` and `--rate-limit` are checked at the same point. Each must be a positive integer no\ngreater than 9007199254740991, and a refusal names the value it rejected.\n\n## Plumbing\n\n`cotal __complete <words\u2026>` is the internal entry the shell-completion stubs call to emit candidates\nfor the current command line; you run it directly only to debug a completion, with\n`COTAL_COMPLETE_DEBUG=1`. `cotal agent-bearer` is machine-facing\nplumbing on user-auth meshes: spawned agents exec it to print a fresh short-lived bearer from their\nspawn-time secret; you never run it directly either, so `cotal help` leaves it out. Its local arm uses `--dir` to discover the\ncapability-gated loopback service. A remotely enrolled, already-granted agent instead receives\n`--exchange-url <https://base>` in its launch argv: that arm sends `{owner, actor, actorToken}` to the\npinned public exchange with no local capability, follows no redirects, and refuses every non-HTTPS\nURL because the actor token is the credential in the request body. Because a seat execs it on every\nbearer refresh, it skips the connector-seed boot gate entirely: it reads one 0600 token file,\nexchanges it and prints the bearer without consulting or writing the operator-global seed store, so\na newer store generation cannot refuse a live seat's refresh. For the same reason the seat's command\nnames the CLI entry file the `cotal` bin link resolved to at spawn, not the link: an install that\nlater replaces the link, such as the get.cotal.ai launcher over an `npm i -g` link, leaves running\nseats able to refresh. The command runs before the rest of the CLI loads and reads none of its\npreferences, extensions or config folder, so a refresh does not pay the CLI's start-up time. A command line it\ncannot parse goes on to the full CLI, which refuses it with the command's help. `--manager-call` asks for the\ninstance-bound `manager-caller` view; `--manager-instance <id>` selects an explicit live candidate,\nand an owner with more than one live candidate is refused with their ids until it names one.\nThat mode still prints only the raw token and does not update `--health-file`. A spawn runs it once as the agent auth\npreflight. When it fails there without printing a sentence of its own, the refusal names the cause: the 30 second\ntimeout, the signal that killed it, or its exit code. (`cotal start` is a removed tombstone: it\nerrors and points you to `cotal spawn --detach`.)\n\n`cotal app-identity` is plumbing for a Claude app conversation, and you never run it directly\neither. The Claude connector's local server runs `cotal app-identity provision --name <name>` when\na conversation connects. It picks the mesh as `cotal spawn` does (`--space` selects one) and prints\none JSON line. A new identity opens only after a person approves it at the mesh's authority, so\nprovision needs a signed-in mesh whose authority advertises connection approval v1, and the\nconversation's native `--session`. It keeps the request and its secret in a private record under\n`~/.cotal/app-identities`. Until the person decides, it answers\n`{\"ok\":false,\"pending\":true,\"requestId\":\"<id>\",\"approval\":{\"id\":\"<id>\",\"url\":\"<url>\",\"expiresAt\":\"<time>\"},\"proposed\":<selection>}`,\nwith no credential, secret or path. `proposed` is `null` for a request without a selection.\nProvisioning again sends the same request: it answers the same approval while pending, and the\nlaunch env and the reference that `retire` takes once approved. The grant is what the person\napproved, which can differ from the request. A denial or an expiry drops the record, and the next\nprovision makes a new request. `--selection <json>` proposes channels, a role and capabilities for\nthat conversation. A mesh with static credentials, an open mesh, and a signed-in mesh whose\nauthority this machine holds have no approval page, so provision opens no identity on them.\nThe connector passes `--require-approval`. Every provision needs approval with or without it, and\nan earlier CLI that opened identities without approval refuses the unknown flag before doing\nanything. A saved request that was never issued needs the person's decision even when an earlier\nversion saved it.\n`cotal app-identity retire --dir <dir>` still removes an identity an earlier version opened on a\nstatic mesh: its broker footprint, then its files and directory. When the broker is not answering it\nkeeps them. Each provision also retires those directories whose owning process has exited. For a\nsigned-in identity, `retire` keeps the record until the mesh confirms retirement, and names the\nmesh's phase while it is pending. A request still pending is dropped.\n`cotal app-identity reap` retires the selected mesh's saved conversations whose server process no\nlonger exists. The connector runs it after each signed-in connection, so a connection never waits\nfor that cleanup. Like `agent-bearer`, it skips the connector-seed boot gate.\n\n## watch\n\n`cotal watch` opens the read-only activity screen for the selected space. `w` is its shorthand.\nIt uses the existing observer view authority; a user-auth space requires the corresponding\nadmin view grant. It does not register a participant, bind an inbox or expose local process\ncontrols. Leaving with `q`, Esc or Ctrl-C stops only this observer.\n\nUse `--view events` or `--view messages` to select a feed, or press `f` to change it in the\nterminal. Arrow keys scroll; `g` returns to the latest observations. The feed retains at most\n300 entries. A reported turn ending is not task completion. An interrupted feed keeps its\nlast observations and reports the gap instead of showing them as current. When the connection\ncomes back, after a broker restart too, the feed resumes and reports the transition as a gap.\n\n`--plain` or a noninteractive invocation uses the attributed line stream. Interactive filters\nare not supported by that stream. A line carries at most 16,384 code units of a message's text, the\nobserver stream's row bound. A longer message is cut there, and its line ends with the count not\nshown and the read that shows it whole: `cotal history <channel>`, or `cotal history --dms` for a\ndirect message. Authenticated observer views keep direct messages out of\nthe channel tap. Opening this view never launches, stops or attaches an agent. If the reader\nof the stream closes it, as `| head` does, the observer stops at its next line, so on a quiet mesh it\nruns until a message arrives or Ctrl-C ends it with exit 130.\n\nA target this observer cannot open, such as a stopped project's space, is refused before any view\nexists, under the refusal's own code and exit 2, as `history`, `channel` and `chat` refuse it in that\nfolder. The terminal gets the refusal's report, with the mesh it resolved and its next step;\n`--json` gets that code, message and next step in the stream's `error` record, ahead of its `end`.\n\n## Named agent status\n\n`cotal status <agent>` reads the authorized manager census and separates Identity,\nExecution, Mesh and Last reported activity. JSON has `identity`, `execution`, `mesh`\nand `activity` fields; missing optional observations are null with source annotations.\n`--on <manager-instance>` restricts the read. It takes no `--all` or `--components`.\n`cotal status manager`, or an answering manager's instance id, names the manager itself, which runs no\nrow of its own: it refuses `TARGET_NOT_FOUND` like any unknown name, and its Next says the manager is\nanswering and points to `cotal status` and `cotal endpoint describe manager`.\nA requested model is not provider-confirmed. Connector-reported provider\ninformation is labeled exact or name-matched under the same proof. A new manager\nrow marks a roster observation exact only when its principal and lifecycle UID\nmatch the managed seat and its presence view is current. Older or mismatched rows leave exact presence\nand activity null and label any name-matched values separately. A verified\ncondition and its reported start appear with activity; an unverified condition\nstays in a separate name-match field. A waiting observation is not a measured\nblocked duration, and a work event is not task completion. Attach and stop\nhints retain the answering manager's instance when it is a valid native id.\n\nA same-name match on several managers refuses as ambiguous. An unanswered manager leaves\nthe search partial; no matching row in that partial read is not proof the agent is gone.\nThe command starts, stops and attaches nothing.\n\n## Manager maintenance\n\n`cotal manager`, also `cotal mgr`, shows its two scoped operations. The group and its operations\nsupport `--json` and `--plain` through the shared reports. Neither operation installs connectors\nor starts a manager.\n\n`manager seats` lists retained local PTY custody records. `--drain --dry-run` reads the census\nwithout submitting a drain. A real drain reviews the local store and defaults to cancellation;\nscripts require `--drain --yes`. Its scope is all safely childless records in that store when\nthe native pass runs, not a frozen list from the review. Each native reap rechecks process\ncreation identity. Live children stay. An unresolved preflight blocks draining; a partial read\nreturns exit 4. An unconfirmed effect during a submitted drain returns exit 3 and keeps confirmed\nremovals in the result. Raw native diagnostic bodies are omitted.\n\n`manager reconcile` uses the existing guarded issuance-gate repair from the owning local mesh\nroot. It resolves the registered broker without an alternate-target fallback and preserves its\nTLS requirement across repair, liveness and eviction connections. It requires that root's auth\nmaterial and verified holder liveness. Review defaults to cancellation; scripts require `--yes`.\n`--dry-run` checks only the local coordinate and issues no credential or native guard query.\n\nA pre-effect native guard refusal returns exit 2. Its error names the guard's condition and keeps\nthe guard's own sentence as its native cause, which names the freeze-holder and the next step.\nA failure after the repair may have submitted\nwork returns exit 3, without replaying mutations or hiding possible revocation/eviction progress.\nRe-observe that gate and its operation-bound durable cursor before another explicit repair.\nThe final receipt reports the native operation and before/after coordinates, not a running-manager\nverdict. A reopened gate with retained cursor cleanup returns exit 4.\n\n\n## Workflow command forms\n\n`cotal run` (or `cotal r`) lists the authorized workflow records. `run show <run-id>` reads\nrecorded state, open requests, each step's recorded error and the last 20 journal rows. `--limit <n>`\nprints the last `n` rows instead, from 1 to 1000, and is refused with `--json`. A settled\n`race` row names the branch it took, and a cancelled row in a branch that lost a race names the\nbranch that won and the race. An answer whose step was in a branch that lost a race, or\nwhose pause was cancelled after accepting it, is listed as `not acted on`. Its JSON result contains\nthe full returned status and journal. Revoked admission and unreadable revocation markers remain distinct\nfrom the record's state. Neither a record nor a receipt is a live agent-progress report.\n\n```bash\ncotal r\ncotal r start ./workflow.cotal.js\ncotal r show <run-id> --json\ncotal r resume <run-id>\ncotal r answer <run-id> '<step-key>' --value '<json>'\ncotal r amend <run-id> '<step-key>' --value '<json>'\ncotal r check <run-id> ./edited.cotal.js\ncotal r revoke <run-id> --local --by <actor> --reason '<text>'\n```\n\nStart takes a positional source file. A source file a `run` verb cannot read is refused naming\nits path and the cause: `TARGET_NOT_FOUND` for a missing file or a link whose target does not exist,\n`USAGE_INVALID` for a directory and `PERMISSION_DENIED` for a file it may not read. `-` is a file\nname there, since `run` does not read a program from standard input. A run verb that reads a source\nfile refuses an empty one or one that is not valid UTF-8 with `SCHEMA_INVALID`, before it checks or\nsends anything, because a run's source must not be empty and its text is never replaced. A hosted\nstart sends the program to the manager in one message, so a program over the broker's message limit\n(`max_payload`) is refused `UNSUPPORTED` with its size and the limit, and nothing is sent. Resume uses the host's recorded program. Hosted answers\nand amendments are attributed to the authenticated caller, never a supplied name. Local answers and\namendments require `--by <who>`; a missing or empty value refuses `USAGE_INVALID` before contacting\nthe broker, with a JSON refusal under `--json`. `check <run-id> <file>`\nreplays the run's recorded journal\nagainst the edited program and prints whether the migration is admissible, and it files no migration. Revocation keeps\nthe existing local trust and explicit actor/reason requirements and does not cancel a task.\nHosted start, resume, answer and amend have JSON and plain receipts. The local drive and local\ncheck retain their native output rather than claiming an unsupported structured format.\n\n```bash\ncotal r validate ./workflow.cotal.js\ncotal r plan ./workflow.cotal.js\ncotal r simulate ./workflow.cotal.js --handler ./results.json\n```\n\nThese three forms are offline and reject mesh or caller selection. Validation checks the\nlanguage and nothing a broker decides, so a valid program over the broker's message limit is still\nrefused by a hosted `start`. A program that does not validate is refused by all three with every problem listed: its\ncode, position, cause and fix, as a hosted `start` lists them. `--json` carries them under\n`result.problems`. A warning leaves the program valid. All three print it on stderr with its code,\nposition, cause and fix, and `validate --json` lists it under `result.warnings`. One is L3021, a `fanOut` over a list written in the call whose items carry no\n`id` and that passes no `key`, which the run refuses when it reaches it. A list held in a variable\ngets no warning, because it can change before the call; the run refuses it the same way. Plan lists validated static call sites in source order; it does not evaluate branches,\nloop counts or an execution schedule. Simulation uses the native simulator with a JSON results\nscript. It executes no JavaScript handler and invents no unscripted outcome. A scripted\ncheckpoint result needs a `status` of `resolved` or `expired` and may carry `value`, `by` and\n`artifact`; any other shape, a scripted `at` included, is refused `SCHEMA_INVALID`, because the\nsimulator stamps `at` from virtual time. A scripted turn result needs a `status` of `done`, `blocked` or\n`handoff` and may carry `to` and `note`; any other shape, a scripted `at` included, is refused the\nsame way. When the script has no result for an effect, the\nsimulation does not complete: it exits 5 with `OPERATION_FAILED`, the message names the step and the\nentry to add, and `target.step` carries the step. When the program itself fails under the script,\nas a checkpoint scripted `expired` under the default `onExpiry: \"fail\"` does with L4007, the\nsimulation completes and exits 0. Its program outcome is failed, with the step and that step's\noutcome, or the last step the program settled before its own code failed, and the language's error.\nJSON carries this as `result.outcome` and `result.failure`. A simulation does not establish live authority, installed applications or readiness. All three support JSON and\nplain output and contact no broker.\n\nThe `--handler` file is one JSON object. Each table maps a step's name to its result: one value\nfor every occurrence, or a list used in order. A step with no name is keyed by its effect, such as\n`ask`. `spawn`, `sleep`, `notify`, `monitor`, `observe` and conclaves need no entry, and a sleep\nmoves the virtual clock forward.\n\n```json\n{\n \"checkpoints\": { \"approve-plan\": { \"status\": \"resolved\", \"value\": true, \"by\": \"ana\" } },\n \"asks\": { \"plan\": { \"steps\": [\"design\", \"build\"] } },\n \"turns\": { \"build\": [{ \"status\": \"blocked\", \"note\": \"needs a key\" }, { \"status\": \"done\" }] },\n \"events\": { \"await-reply\": null }\n}\n```\n\n| Field | Meaning |\n|---|---|\n| `turns` | `turn` results: `{status, to, note}`, where `status` is `done`, `blocked` or `handoff` |\n| `asks` | `ask` replies, each checked against the step's `schema` |\n| `checkpoints` | `checkpoint` results: `{status, value, by, artifact}`, where `status` is `resolved` or `expired` |\n| `events` | `wait` results. `null` is a timeout |\n| `clock` | `{start, turn, ask, checkpoint, wait}`: the start time in milliseconds and how long each effect takes, such as `\"5m\"`. A turn takes 5 minutes and the others 1 minute unless set |\n| `faults` | `[{at, kind, code, message}]`: the step at `at`, a step key such as `turn:build#1`, fails with that error |\n\nThe simulation's `log(...)` lines print under `Log`, and `--json` carries them in `result.log`, each\nwith its `scope` and `values`. A step with no entry, or a list shorter than the step's occurrences,\nstops the simulation with L6001 and exit 5. The failure names the table, the step's name and its step key,\nand the remedy names the entry to add. For a list that is too short, it also says which result the step\nneeds, counted from 1, while the step key counts occurrences from 0. A failure in the program itself\nnames its code and cause.\n\nInside a manager-launched static or open seat, `run turns` reads that seat's assigned turns and\n`run yield <turn-id> --status <done|blocked|handoff>` reports an outcome. Handoff also requires\n`--to <agent>`; `--note <text>` carries context. The native launcher supplies the caller context.\nThese forms verify the seat's own credential and manager, never substitute an operator caller,\nand refuse user-auth self impersonation. They skip operator catalog and installation repair.\nYielding one turn does not complete the entire workflow.\n\n\n## Update availability\n\n`cotal update --check` queries the latest registry version and reads recorded extension versions.\nIt performs no install, reconcile or process restart and takes no mesh selection. It supports\n`--json/-j` and `--plain`. The registry read runs npm with a temporary cache that is removed\nafterwards, no log files and no npm update notifier, so it leaves nothing in npm's cache, log\ndirectory or notifier stamp. Missing registry data or an unreadable extension inventory remains\nunknown or partial, never an empty successful check.\n\nThe report compares first-party recorded versions with the running CLI. It does not inspect\npackage contents, signatures or loaded process generations. `cotal update` and `update --self`\nkeep the existing guarded update, continuity checks, download verification and current-generation\nreconciliation. Their installer progress is native output, not a structured check receipt.\n\n\n## Account screens\n\nBare `login` chooses a known provider from cached account metadata or registered spaces. When\nnone is known, an interactive terminal asks for the provider auth URL. `--idp` selects it directly.\nThe device-approval link and code are written only to stderr. Session tokens are never printed.\nThe sign-in receipt separates account identity from each space's grants. This client's device\nflow requires approval and refuses `--no-input` before requesting a device session.\n\nBare `logout` chooses among cached providers. A single cached provider is unambiguous; scripts\nwith multiple providers must select one with `--idp`. `logout --json` and `--plain` report the\nsame result. No cached session is a local no-op, not proof that all provider sessions are gone.\nRevocation must return before local cleanup. If revocation is unconfirmed the login is retained\nfor retry, and the refusal names `--local`; a later cleanup failure is partial and keeps the\nconfirmed server outcome. `logout --local` clears this machine's cached login and that account's\ndiscovered registry entries without contacting the provider, for a provider that is down or does\nnot confirm sign-out. The provider session stays valid until it expires or is revoked at the\nprovider, and the receipt says so.\n\n\n`endpoints --json` and `endpoints --plain` show the authorized live presence snapshot, including\nagents and infrastructure. This legacy live read is labeled as presence, not a saved-reference\ninventory or proof that a service registration is active. Native contract resolution remains a\nseparate read. Each `--json` item carries the presence values: `reportedStatus`, `condition`,\n`activity`, and the ISO 8601 times `statusSince`, `activitySince` and `activeAt`, each null when the\nrecord has none. The report works out the ages it prints from those times.\n\n\n## Managed launch view\n\n`spawn [<persona>] --on <instance>` launches through that manager and attaches after its native\nreadiness result, when that manager's runtime streams a terminal. The attachment is pinned to the attributed accepting manager and the allocated identity/incarnation,\nnot a second display-name guess. `--detach/-d` returns a finite JSON/plain-capable receipt after\nthe same readiness result. While it waits, it prints the launch it asked for, with the space as a\nfield, and one line that says the manager answers when it refuses the launch, or when the agent\njoins or exits, within about 30 seconds. The receipt or refusal that follows names the mesh in its\n`Mesh:` heading. A launch the manager reports failed, such as an agent whose process exits at once,\nfails `OPERATION_FAILED`, exit 5, with the manager's reason. The terminal receipt shows the full lifecycle, original request, accepted goal where reported,\nrole and model request separately from any provider acceptance claim. When the host granted the\nagent other read or post channels than it asked for, as a host that narrows a request to your grant\ndoes, the receipt adds a `Granted` line with the channels it got and the ones it asked for, and\n`--json` carries both as `acl.requested` and `acl.effective`. Its Next section names the actual agent, space and accepting manager\nin attach/status commands when their tokens are safe shell words; it otherwise retains\nplaceholders beside the full identity fields.\n\nA manager on a runtime that streams no terminal, which is `tmux`, `cmux`, `orca` and `herdr`,\nanswers with that fact and the launch stops there: no attach is asked for, nothing is partial, and\nthe command exits 0 with the agent running. Its `Runtime` row reads `<runtime> \xB7 no terminal\nstream`, its `View` row reads `not attached; the seat runs in the manager's <runtime> session`, and\nits Next gives `cotal watch` and `cotal send -t <name>` with one line saying `attach` is not\navailable there. `--json` carries the same answer as `result.streamsTerminal`, beside the\n`result.runtime` that names the backend. A manager from before 0.104.2 reports neither, so\n`streamsTerminal` is null and the launch asks for an attach. When that manager refuses it because\nthe runtime streams no terminal, the launch ends there too, with one line that names the runtime,\nand exits 0. Such a manager does not mark that refusal, so the CLI accepts it only for a runtime\nwhose provider, registered or installed with this CLI, declares no terminal. Any other failed\nattach is partial, exit 4, with the manager's reason as `Native detail` and a Next that gives\n`cotal ps` and `cotal attach` with the space and manager instance. Without usable input/output terminals, an attaching\n`--on` launch refuses before allocation and names `-d` or a spawn in this terminal. A shell tool's\ninherited parent PTY is not enough.\n\nA launch whose agent application waits for you before it joins is ready once its process runs.\nClaude with `--opt resume=` and no session id opens its session picker, so the CLI says the agent\nis running on its manager and host and waiting for you at Claude's session picker. In a terminal it\nattaches to the agent at once, `-d` included; otherwise it prints the attach command. When the\nreadiness window passes and the manager reports the agent's process still running, the CLI says\nthe agent has not joined the mesh yet, attaches when the spawn was not detached, and otherwise\nprints the attach command. Both exit 0, and `--json` reports them as `managed-launch-running` with\n`waitingFor` naming the screen or null. The outcome stays unknown when the manager cannot say\nwhether the process runs.\n\nA managed launch prints its `Managed launch` plan on stdout before its receipt, so\n`cotal spawn <persona> -d > out.txt` keeps both, and a refusal prints on stderr. With `--json`,\nstdout holds only the envelope and the plan stays on stderr. The manager that refuses a launch\nwrites one line to its own log naming the original request, the persona and the reason, so\n`cotal service logs <definition>` on its host shows why a spawn failed after the terminal is gone.\nAn unpinned launch that no manager received has no such line, because no manager answered it.\n\n`--harness/-H`, `--model/-m`, `--name/-n`, `--prompt/-p` and `--cwd/-C` retain their declared\nmeanings. The legacy `--agent` spelling remains accepted; using both harness spellings refuses.\nA `--file <persona-file>` launch uses this route too unless `--foreground` is given. A manifest is\ndeployed with `cotal team deploy <file>`, not `spawn`.\n\n`--foreground` runs the native application directly. It does not support a structured receipt,\nmanager pin or manager control credential. Native exit or terminal loss may end that process;\nno detachable-view or reboot-survival claim applies. Protected internal handoff/enrollment\nchildren keep their existing direct execution and native authorization checks.\n\nIf the agent joins but attachment fails, the result is partial with its original identity and\nmanager. No second launch occurs. A view exit sends no stop request. This route does not offer\nno-manager startup choices or imperative planning.\n\n\n## Observer stream\n\n`watch --json` emits versioned watch NDJSON records from the same MeshView as the terminal.\nActivity, events and messages filters use the shared classifier. Metadata snapshots identify\nscope, reported roster state, native channel counts and membership-read failures. Feed rows keep\nnative message IDs and timestamps; coalesced updates are identified as observed rows, not new\nmessage publications or proof of task completion. No global sequence is invented.\n\nThe feed has a 300-entry window and bounded per-channel prefill, not a complete history export.\nWindow loss, connection transitions and text beyond the 16,384-code-unit row bound produce gap\nrecords, and a cut row carries `textTruncated` and `omittedCodeUnits`. Identity fields are not clipped. The stream does not claim that arbitrary publisher\ncontent is secret-redacted. Leaving the observer stops its own connection and sends no agent\ncontrol request. Connection or authorization failure is an error, not an empty conversation.\n\n\n## Recorded team views\n\n`team` (`t`) reads fresh launch summaries, additive ownership ledgers and their\nroot-local attempt records at the selected local root. A team read or `--dry-run` preview that\ncompletes exits 0. Its JSON marks `completeness` as `partial`, and its report has a line\nsaying what it does not establish. A report names each record's kind, recorded state, next action\nand auth mode in words, such as `boot returned` and `static auth`. The JSON result keeps the\nrecorded values, such as `broker-boot-returned` and `auth`. A deployment ID that no record in the\nroot names is refused with `TARGET_NOT_FOUND`, which names the ID, by `team show` and by\n`team remove`; `team show` adds that an ID is 16 hex characters when the value has another shape.\nWhen native `up -f` returns from its broker start, it writes a creation-only fresh\nsummary with its full run ID, initial broker PID, declared agent/channel names\nand the initial control-plane, auth-service and delivery-responder results.\nOlder summaries without a broker PID, auth-mode pin or responder observation\nremain readable; `team show` labels their absent facts not recorded and\nsays the record proves no current ownership. A PID\nor a responder bound when boot returned is not current process or listener\nownership proof. A private create-only fresh boot attempt is written and fsynced\nbefore the launch spec and broker start. A returned summary is also published create-only and\nfsynced; an intent without a matching auth-mode-pinned summary showing a\nreturned control plane, auth service and (for auth mode) bound delivery\nresponder stays unsettled and\nblocks a later fresh boot. `team show <id>` and the team inventory expose the original intent without\nclaiming whether its broker started. `team show` displays up to 100 declared names from a\nsummary and retains the complete list in JSON; none are claimed to have joined. A missing\nor refused summary after boot is not a reason to boot again. A summary records the absolute folder\n`up -f` ran in. After that folder is moved or copied, the summary still reads but names the old\npath. It settles nothing in the new folder and blocks no `up` or `down` there, and `team show <id>`\nrefuses it with CONFLICT, naming the folder it was recorded at.\nThe inventory's `completeness` stays `partial` because older fresh boots may have no summary. The\nterminal lists up to 100 records; JSON retains all readable records. Its Next column\nsays inspect the stack for a fresh record or review the native scoped teardown plan for an\nadditive ledger; neither row grants permission to stop anything. A fresh summary is\nnot an additive ownership ledger or evidence that an agent joined, and it does not establish\ncurrent process health. For a fresh summary, `team show <id>` names the recorded root and offers\n`cotal down --space <space> --dry-run`, the preview of the guarded whole-stack stop, with the\nrecorded space filled in. `team remove <id>` refuses a fresh boot ID with `CONFLICT` and offers the\nsame stop. The summary is not authority to run it.\n\nFor an additive record, `team show` prints up to 100 full recorded agent identities\nand lifecycle UIDs where the ledger has them, with the full validated ledger in JSON,\nwithout claiming those agents are still live. Its follow-up uses\n`down -f <file> --run <id> --dry-run` from the recorded root. The file is a required argument,\nbut the full run ID selects the ledger; an edited manifest is not new teardown authority. Review\nthe native plan before a separate apply. Listing ownership does not confer permission to stop\nanything. One guarded `team deploy <file>` route accepts a\nchannel-only manifest whose declared cards are all absent and explicitly set\n`deliveryClass: live`. It refuses agents, runtime requests, defaults,\nbroker bind changes, access edges, existing cards,\na changed re-apply, registered broker lists and unresolved prior attempts before an effect.\nA card that does not set `deliveryClass: live` is refused with `UNSUPPORTED`. The refusal names\nthe cards, and its Next says to set `deliveryClass: live` on each card or to create a durable\nchannel with `cotal channel set <name> --delivery durable`. `team deploy --dry-run` warns\n`LIVE_DELIVERY_REQUIRED` for the same cards, so the preview says the apply would refuse.\nIt selects the exact registered root, single broker address, auth mode and TLS\nrequirement, then holds the local maintenance lock. The original user bearer and its deployer and writer\nviews must name the same owner and actor. A refused view stops before the\nintent; no static or broader caller is substituted. A scoped writer uses one\nnon-reconnecting broker connection and KV create-only on the existing bucket,\nnever the legacy merge-and-put path. The private intent is published and synced\nbefore any create. Every channel has a synced submitted step before the native\nrequest and a synced accepted revision after its returned acknowledgement.\nThe returned receipt matches every declared channel and both steps. A missing\nacknowledgement or receipt leaves the attempt unsettled; no create is retried.\nIf closing the writer connection fails after the receipt is confirmed, the result\nkeeps the confirmed revisions and warns about the close. A returned receipt\nproves only historical created revisions, not the current\ncards, readers or removal authority. `team show <id>` presents one row per\nreturned channel; incomplete attempts show their submitted/accepted steps.\nThe JSON result retains both forms of evidence. Team inventory and status\nshow returned versus unsettled state. Additive broker addresses in reports\nomit URL user info, queries and fragments; the protected intent keeps the\nexact connection target. Invalid or orphan steps stay visible and\nblocking. Guarded channel-only card removal uses the returned create revisions as described below.\nCompletion reads those receipts locally; it does not query the broker or guess ownership from an\nedited manifest. Channel apply emits one finite rich, JSON or plain result.\n\nThe agent route accepts one to 100 all-new agents on a registered static-auth\nor open mesh, optionally with up to 100 new channel cards that explicitly use\nlive delivery. Defaults, bind changes and stale waivers refuse. User-auth agent\napply refuses before inline persistence. The route selects a manager through an attributed status read,\nthen pins credential issuance and every subsequent request to that full instance.\nIt requires native execution and a matching runtime; it never starts a replacement\nmanager. `--runtime` overrides the manifest's requested runtime but does not\ninstall or switch a provider.\n\nA mixed deployment also requires every requested application to appear available\nin that manager's connector inventory. This is a startup observation, not proof\nthat a later launch will succeed. A separate deployer reader checks the requested\nchannel keys, and a separately scoped writer uses one non-reconnecting connection\nfor native KV create-only. Manager credentials are not used as channel authority.\nExisting cards refuse; no patch, adoption or legacy merge-and-put occurs.\n\nOne private intent records all channels, ordered resolved agent hashes and the\nmanager before effects. Channels are created first, with submitted and accepted\nrevision steps synced before continuing. Each agent's original request is synced\nbefore submission, its native allocation before goal following, and its matched\njoined result before the next agent. A joined result must match the accepted\nname, principal, lifecycle, harness and runtime. Unreadable or unknown phases\nremain unsettled and are not replayed. The full receipt is saved only when both\nchannel and agent sequences match the original intent.\n\nA later failure retains earlier channel creates, allocations and joins without\nrollback. When it created anything, its Next adds `cotal team remove <id> --dry-run`.\nWriter-close uncertainty is reported separately from returned native\nreceipts. The finite JSON/plain/rich report distinguishes those receipts from\ncurrent health and task completion. `team show` exposes both native journals,\nincluding partial channel creates when a later launch did not complete. Inventory,\nstatus, down and reset distinguish returned mixed deployments from unsettled\nattempts. Retained verification and guarded removal use the paths below.\n\n`team boot <file> --dry-run` shows the resolved fresh topology, its target root,\nrequested applications, roles, models and channels with declared delivery,\nreplay and window settings. It shows the manifest's `defaults:` too, and a channel field that\ninherits one is marked `(default)`, because boot seeds them as the space defaults. A setting\nneither declares is not inferred from a saved registry that this preview does not read. An open manifest with an\nexplicit durable default or channel declaration is warned as unsupported:\nits native fresh apply refuses before effects because open mode starts no\ndelivery daemon. Direct `up -f --dry-run` refuses the same contradiction.\nA space with no prepared or registered root previews the current folder. Its root source says\nwhether that folder belongs to other spaces, and a hint names the `cotal space create` the apply\nneeds: `--root .` for an unprepared folder, or a folder of its own when this one belongs to\nanother space.\nIt reads the local control\ndirectory, recorded spaces, maintenance journal, the selected manager PID record,\npossibly live process records, the root's additive deployment ledgers and\nattempts, and fresh boot attempts/summaries, plus selected-space service\ndefinitions, instances, supervisor receipts and retained native attempts. An\nunreadable additive or fresh record is listed rather than treated as absent.\nThe team inventory separately lists unsettled fresh and additive attempts\nand returned agent/channel receipts. `team show <id>` distinguishes those\nhistorical records without inferring current card or broker state. Bare\n`down --dry-run`, `team deploy --dry-run` and both reset previews expose the\nrelevant retained additive IDs and unreadable paths. The legacy bare `down`,\n`down --preserve-state`, fresh `team boot` and `reset store` refuse unsettled\nintents and unremoved agent/channel receipts before effects. Bare `down` and a later\n`team deploy` name `cotal team remove <id> --dry-run`, then `--yes`, for each deployment that\nblocks them. A complete matched\nstatic agent-retirement receipt releases only its original deployment's blocker.\nA bare `up` of a stopped controller stack starts it again over a returned channel-only deployment\nmade on that stack, and keeps the deployment's records and cards as they are. Any other retained\ndeployment, such as an unsettled attempt or an agent deployment, still refuses the start with\n`LIFECYCLE_BLOCKED`, and its Next names `cotal team show <id>` for each one.\nThis is a known-blocker guard,\nnot complete broker ownership. The service records belong to the workstation's selected-space store,\nnot necessarily this boot root; their recorded\nstate does not establish live ownership. A dead manager record remains visible,\nnot proof that a later boot may replace it. For a concrete candidate broker\naddress, the preview also lists other local registrations with that exact\naddress. A match blocks fresh apply; no match does not prove exclusivity against\naliases or remote tenants. A host-derived candidate leaves this comparison not\nchecked.\nWhere the manifest supplies a broker URL or uses the default without a host override,\nit makes a credless reachability probe. A failed probe is not proof of absence on a\nTLS-first listener. Its `completeness` is `partial`: port binding, exclusive local ownership,\ninstalled providers, credentials, grants and startup readiness have not been checked.\nIt writes no record and starts nothing.\n\n`team boot <file>` requires a prepared local root and starts its environment through the service\ncontroller. A root without a stack journal takes the fresh static-auth or open route. An eligible\nstopped stack can resume in static-auth, open or user-auth mode. The manifest's channels seed the broker. The stack's own manager\nstarts its agents from a launch spec pinned by digest. One rich, JSON or plain receipt includes\nthe controller members and requested topology. Agent joins are not checked, so the receipt is\npartial. A manifest that requests no agents leaves no join unchecked, so its receipt is `ok`. The fresh attempt and summary are launch records, not stop authority. No additive\nownership ledger is created; `cotal down` uses the stack journal and each member's native lifecycle.\n\nBefore writes it checks the selected root, auth mode, broker address, runtime, connectors and\nretained ownership records. A journal, legacy PID record, possibly live component, service instance,\nsupervisor receipt, native attempt, additive ledger or unsettled fresh attempt blocks a fresh boot.\nUnreadable records are never treated as absent. The lock protects local preparation, and ownership\nand bind checks run again before startup. A bind probe releases its socket and does not reserve\nthe address. A later failure keeps its attempt and any started members, reports the known partial\nor unknown outcome and never repeats effects. A failed summary write also keeps the running stack.\n\n`team boot --foreground` holds the stack's lifetime in the terminal. Ctrl-C or SIGTERM invokes the\nsame guarded whole-stack stop as bare `cotal down`, without force killing members. Terminal loss\nor an external kill is not a confirmed stop. When a `cotal down` from another terminal completes,\nit prints `Foreground stack released` and exits 0, as a foreground `up` does, although its start\nreceipt was partial. Foreground apply takes neither JSON nor plain output;\na dry-run remains a finite preview. It does not establish native apply readiness or platform support.\n`up -f --foreground` still refuses on its retained launcher path.\n\nA `team boot` in a root whose stack is up, with no `cotal down` since, refuses with\n`LIFECYCLE_BLOCKED`, says the space is already up and starts nothing. Its Next offers\n`cotal team deploy <file>`, which adds the manifest's channels and agents to the running stack, or\n`cotal down` before the boot.\nA recorded stack resumes only after its last whole-stack stop completed and its manager was never\nattempted. Its broker inputs must be recorded, every applicable member must have its original\nsettled stopped record, and no supervisor, retained custody or earlier team boot may remain. A\nstack that an earlier team boot started refuses with `LIFECYCLE_BLOCKED`, and its Next names\n`cotal up` in the root, which starts that stack again, and `cotal team show <id>` with each earlier\nboot's ID. The broker requires its kernel-exit receipt. Auth and delivery use their settled process\nstop and released custody; that is not proof of credential, lease or authority retirement. A generic\nprocess stop now records a `process-exit` receipt on the stopped instance when that stop signalled\nthe pinned process and saw it exit. `service stop --json` and `service status --json` expose it as\n`processStop`, including the stop request, original process identity, signal and observation time.\nHuman stop and status reports show the recorded signal, request and exit observation time, or\n`Process stop evidence: not recorded`. This is retained evidence, not a new signal from a repeated\nstop. An instance that was already gone gets `processStop: null`. Resume does not yet require this receipt. The resume receipt\nidentifies the prior stop and each member's evidence. Changed records refuse under the lock.\nA journal without recorded broker inputs cannot take this route. Restart it once with its original\nexisting `--store-dir` and its original `--max-file-store` and `--host`, if set, then run `cotal down`\nbefore the team boot. Omitting those values is safe only when the original start set neither.\nPreparation rechecks that explicit old store's directory identity instead of recreating it if it\ndisappears. These checks do not exclude an unrelated local process changing the path. Open broker\nconfiguration stays inside the selected root's `.cotal` directory, even when its store is elsewhere.\n\nFor a first user-auth team, prepare with `cotal up --user-auth --idp <url>`, then `cotal login`,\nexplicitly grant the invoking user with `cotal auth grant cli --sub <subject>`, and run `cotal down`\nbefore `cotal team boot <file>`. Do not start the manager between preparation and that stop. A\nstatic-auth or open preparation uses `cotal up --no-manager` before `cotal down`. No implicit owner\ngrant or borrowed caller is substituted. A previously attempted manager is never adopted.\n\n`--space` must agree with the manifest. Hand-registered and discovered spaces are not local boot\ntargets. Public broker addresses and fresh-attempt views omit URL user info, queries and fragments;\nthe private intent keeps the target. Native warnings and failures remain on stderr. Installed\nextensions may produce their own output outside the finite result.\n\n`team deploy <file> --dry-run` resolves the manifest and personas and requires a\nregistration for the declared space in this root. It shows the declared\nchannel delivery/replay/window values separately from its non-atomic live\nregistry classification. If the selected running mesh is open, an explicit\ndurable channel declaration is warned as unsupported by its missing delivery\ndaemon; the read-only preview remains available. Additive deploy does not\nseed manifest-wide defaults. For an existing\nunmanaged card, it names only explicitly declared fields that differ from the live value, never the live\ninstructions or description body. No declaration authorizes overwriting a\nsaved card.\nIt makes scoped, TLS-bound\nnative reads of the presence and channel-registry buckets. A complete presence\nscan can prove an empty roster; a failed or malformed scan refuses rather than\nclassifying absence. It classifies channel cards against the registry and agents\nagainst a unique prior ledger for the same manifest content. The derived membership\nfeed, when readable, shows a lower bound of other readers, not authorization.\nThese observations are non-atomic and do not reserve names or prove that a manager,\nruntime, connector, channel writer or launch grant will be ready. An unregistered or\nonly prepared space refuses. `--runtime` changes the requested runtime; `--allow-stale`\nis apply-only and refuses in preview. Apply accepts all-new live channel-only\nmanifests, or bounded all-new agent and mixed manifests on static/open meshes\nthrough one native manager. An identical settled channel-only deployment returns\na no-op only after its recorded target and every original create revision match\nleader-served native reads. It reuses that full deployment ID without issuing a\nchannel writer, creating an intent or changing cards. Missing or changed cards,\nambiguous or unsettled attempts, and removed ownership cannot become this no-op.\nNothing is repaired or adopted. The result names its observation time; it does\nnot establish current readers or health.\nFor an identical settled agent or mixed deployment, reapply verifies the original manager and every\noriginal incarnation. Static-auth uses active live slots; open meshes use the pinned manager's exact\nidentity and lifecycle rows. Mixed cards must retain their original create revisions and content.\nMissing, retired or replaced agents refuse; nothing is relaunched or adopted. Persona drift refuses\nunless `--allow-stale <names>` names the stale agents. Those agents keep their old policy and receive\na warning; the waiver never restarts or updates them. The no-op records an observation time and\nlabels its ordered reads non-atomic. Unsettled, ambiguous or removed ownership cannot become that\nno-op. User-auth agent launch and retained verification remain unsupported.\nRuntime requests must match the original incarnations; no runtime or manager replacement is inferred.\n\n`team remove <full-deployment-id> [--dry-run] [--yes]` can retire a returned\nstatic-auth agent-only deployment with at most 100 original incarnations. It\nrequires the original registered root, broker, TLS mode and exact manager.\nThe revision-32 native `slots` query checks each recorded name, principal and\nlifecycle UID. A successor, omitted slot or unconfirmed transition refuses\nbefore a stop. A dry-run only reads this impact; apply requires a default-cancel\nterminal confirmation or `--yes` in scripts. Every `team remove --dry-run` exits 0 with\n`completeness` `partial`: it reads the manager's slots and the card revisions in order, not atomically, and the membership feed\nis display-only, so the preview does not establish what the apply will find. The apply reads the\nslots and revisions again before it acts.\n\nUnder the root maintenance lock, apply records one private synced removal intent\nbound to the original creation receipt. Each necessary stop has a synced original\nrequest marker and an expected principal/UID guard. An accepted stop is not\ncompletion. The CLI observes that same incarnation under a 30-second loop budget, in addition\nto each native read's request bound, checking\nits native retired slot, retired lifecycle head and cleanup-complete fact. It\nretains both revisions and the answering manager epoch before continuing. An\nalready retired incarnation needs no repeated stop. A missing row, successor,\ninterruption or observation failure stops the sequence without retrying effects.\n\nOnly a complete matching retirement sequence yields the create-only full removal\nreceipt. Earlier confirmed retirements and stop requests remain in rich/plain/JSON\noutput on a partial or unknown result. An unsettled removal intent stays blocking\nand is never resumed by repeating its effects. `team show` keeps its original creation and removal\njournals, and inventory/status/down/reset distinguish historical retired receipts. It lists a card\nremoval's steps in the order they were written: the cards in manifest order, and each card's delete\nsubmission before its outcome.\nThe full receipt releases only this deployment's known additive blocker, not any\nother service, broker, shared-root or process guard. It never authorizes stopping\ncurrent alias holders. No channel, broker, service or remote account is removed.\n\n`team remove <full-deployment-id>` also removes the registry cards of a returned channel-only\ndeployment, with `--dry-run` for a read-only preview and `--yes` for scripted apply. It pins the\noriginal receipt, root, broker, auth mode and TLS requirement, and refuses changed card revisions,\nconflicting retained ownership or unreadable records. A changed card refuses `CONFLICT` naming\neach changed card. With no registration for the space, as after the stack's `cotal down`, the\nrefusal names `cotal up` in the deployment's root, which registers the stack again, and then the\nsame `team remove`. The preview discloses delivery and replay\nfallback to space defaults and removal of description/instructions. A derived membership feed is\ndisplay-only and never authorizes or blocks this metadata change.\n\nApply records a private intent before any delete. Each card delete is conditioned on its original\ncreate revision, then read back as a later removal marker. Already absent cards name the remover\nunknown. Missing acknowledgements remain unknown with a blocking intent and no retry. A complete\nreceipt releases only that deployment's retained blocker. History, grants and readers are unchanged;\nthe channel stays addressable and no reader exclusivity or complete cleanup is claimed. Repeating a\ncompleted removal returns its historical receipt without deleting a later same-name card. A new\ncard intent records the registry stream's name and creation time.\n\n`team remove <full-deployment-id>` also removes what an unsettled deployment attempt created,\nsuch as one whose agent exited on launch, when the attempt's own journal records every effect. In\nmanifest order, each channel and agent reached its accepted step or was never submitted, and no\nmember has a step after one that was not submitted. An accepted agent counts by its native\nallocation, whether or not it joined. Removal then takes the route of a returned deployment of the\nsame shape, with the same preview, confirmation and receipts. Each allocated incarnation is retired\nthrough the original manager by its identity and lifecycle UID, and one the manager already retired\nand cleaned needs no stop. Each card is then deleted at its accepted create revision. Agent\nretirement still needs static auth. The removal is bound to a digest of that journal, which is\nderived on each read and never written, so a journal that changes later no longer matches it. A\nchannel or agent submitted with no recorded acceptance, an unreadable record of the attempt or a\njournal with no step leaves the effects unknown. `team remove` then refuses with\n`LIFECYCLE_BLOCKED` and names that step.\n\n`team remove <full-deployment-id>` on an unsettled removal intent reconciles it from native\nobservation instead of refusing. `--dry-run` shows each original member's classification, and\n`--yes` records only what the observation proves. It never sends a stop or delete, never repeats\nan unknown action and never continues a member that was not submitted; that member is reported\nunattempted and keeps the deployment blocking.\n\n- A card whose delete was submitted without a confirmed outcome is read with the deployer view\n from the registry stream the intent names, between two stream identity reads. A later removal\n marker, or no message at all, records `observed-removed` with the remover unknown, and it counts\n toward the receipt. A later value records `observed-superseded`; that card is not owned and the\n deployment stays blocking. A card still at its create revision stays unknown, because the\n original conditioned delete could still be applied. An older revision, a revision past the\n stream's last sequence or a recreated stream refuses. A read failure is never treated as absence.\n An intent written before stream binding keeps its submitted cards unknown.\n- An incarnation whose stop was submitted without a confirmed outcome is read with the privileged\n control instrument, which holds no stop or attach rows, from the original manager's exact-name slots. Only the original lifecycle\n UID's retired slot and head with cleanup complete record a retirement, under the original stop\n request. A terminal in progress stays open. A live original, an active slot with no live process,\n a retired slot without cleanup proof and an alias that now names a successor stay unknown. No\n process or presence absence settles anything. Open and user-auth deployments are unsupported.\n- A mixed deployment reconciles retirement first. Its card intent is observed only after a\n complete retirement receipt, and a retirement settled by reconciliation does not start a card\n phase.\n\nA receipt is written only when every original member settles as removed or retired. The\nreceipt then releases that deployment's blocker as an ordinary removal receipt does.\n\n`team remove <full-deployment-id> --dry-run` can also select a validated legacy ledger. It refuses a\nfresh boot ID or a changed root, broker, auth mode or TLS requirement. The old ledger removal path\ncannot adopt a new receipt. For a legacy ledger, a scoped deployer read asks the serving manager for a bounded native PS\nsnapshot and compares recorded IDs, names and lifecycle UIDs. It reads current\nKV revisions for ledger-named channels without exposing their descriptions or\ninstructions; an unavailable registry read refuses rather than treating cards\nas absent. The old ledger has no creation revisions, so a current revision and a\nmatching name do not authorize deletion. It also reads the derived membership\nfeed when available and shows other observed members on owned channels; that feed\nis display-only and cannot authorize channel removal. A missing or unusable manager\nPS refuses rather than calling owned agents absent. On a user-auth mesh a deployer view the\nidentity provider refuses is `PERMISSION_DENIED` (exit 2), with the provider's sentence as the\nremedy. An unavailable membership feed\nstays explicitly unknown. Other retained or unreadable additive attempts under\nthe root are shown separately; their effects are not attributed to this ledger.\nThese native reads are not atomic. The preview changes nothing and cannot\nauthorize a stop. Legacy removal still refuses effects, including with `--yes`. Agent retirement\nremains static-auth only. Channel-only metadata removal uses the existing scoped writer on static\nor user-auth meshes, or the open mesh's bare connection; it grants no new access.\n\nA returned static mixed deployment uses one preview, confirmation and maintenance lock. It first\nretires the original agent incarnations through their native manager. Only a complete retirement\nreceipt permits fresh card checks and deletes conditioned on the original creation revisions.\nBoth receipts are required to release the deployment's blocker. A partial or unknown phase stops\nthere without replaying its effects. An interrupted transition can continue only after complete\nretirement and before any card-removal intent exists. Open and user-auth mixed removal refuse.\n\n`team check <file>` resolves the native manifest and referenced personas without a broker.\nIt lists the manifest's `defaults:` and each channel's delivery class, replay and replay window.\nA field the channel leaves out shows the manifest default it inherits at boot, marked `(default)`.\nA field neither declares does not claim a live registry value; the native seed may merge over\nsaved metadata. The JSON result carries `defaults` beside the channels.\n`--graph` shows effective channel access edges from merged agent policies, marking persona-inherited\nchannels that have no manifest card. It also shows each agent's role, capabilities and whether\ncapabilities came from the manifest, a persona or neither. Terminal tables show up to 100\nrows of each kind. The JSON result includes all resolved sources, merged policies,\ninherited scopes and, when requested, the channel graph, without persona bodies,\nkickoff prompts or opaque launch-option values.\nA manifest it cannot resolve, such as one whose `space:` is not a name `space create` takes, is\nrefused with `SCHEMA_INVALID` (exit 1). The refusal names each\nproblem by file, line and column where the parser has them, the key path, and the reason, and\n`--json` lists every problem under `target.issues`. A missing required key, an empty file, a\nvalue of the wrong shape and one outside a key's fixed values each get a plain reason. An unknown key\nthat misspells a required key left out beside it, such as `chanels`, is one problem that names the\nkey meant. A problem in a referenced persona\nnames that file and the parser's reason without quoting its contents. A persona file that does not\nexist is named at its entry's line along with the manifest's other problems, so fixing those does\nnot uncover it later. A manifest file that does not exist is refused with `TARGET_NOT_FOUND`\n(exit 2), naming its path. A folder given as the manifest, such as `team check .`, is refused with\n`USAGE_INVALID` (exit 1), naming the path and that it is a directory, as `topology view` refuses it,\nand `--json` gives the path as `target.file`. `team boot` and\n`team deploy` refuse a manifest they cannot resolve the same way, before they contact a broker.\nThis file check does not prove live authorization, installed applications or launch readiness.\nThese read views do not apply a boot, an additive deployment or a removal.\n\n\n## Terminal input receipts\n\n`input <agent> <text>` writes through the selected manager's native input command.\nUse `--text` when the value begins with a dash; do not combine duplicate positional and flag\nsources. `--no-enter` omits the trailing Enter request. JSON and plain receipts identify the\nanswering manager and its reported byte count without repeating the input body. An acknowledgment\nis not proof of application consumption or task completion. Unknown results are not retried.\nManaged-launch failures retain native request and accepted-goal identifiers when a reply supplied\nthem, including when following that accepted goal failed. No identifier is invented for a silent\nsubmission, and a missing effect outcome is not a proved pre-execution refusal.\n\nGoal observation keeps the caller's authorized route. A class caller reads its original goal on\nthat class route; an explicit `--on` or credential-bound instance stays pinned. Learning the\naccepting instance ID does not confer an instance route or cause another launch. The native\nfollower verifies the returned fact against the original caller and goal. A launch the manager\nrefuses before its first effect, such as a missing persona file or connector, carries\n`not-executed`. The CLI reports it `refused` with exit 2 and prints the manager's reason. A missing\npersona names the manager, its host and the file it looked for, which is created on that host. A\nmissing connector is `DEPENDENCY_UNAVAILABLE`, as it is for a spawn in this terminal, and the\nrefusal names the manager's instance and the host its status reports. An\nunpinned launch that no manager received is refused with `TARGET_UNAVAILABLE` and exit 2. With no\nmanager running, the CLI says so before it sends and names bare `cotal up`. When registered managers do not answer the launch describe, the launch\nwas never sent and is refused the same way. The reason follows `ps` when this folder records\nits manager stopped, starting or running but silent. When every live manager\nreports `classSpawn: false`, the refusal is `DEPENDENCY_UNAVAILABLE`: it names each manager and its\nhost, says none has an agent application installed, and names the `cotal ext add` that adds one.\nA registered manager whose rail the broker reports empty is skipped without waiting and named\nin a `MANAGER_SILENT` warning; the reason comes from the managers that answered.\nBoth refusals say to add the application where the manager runs, not on your machine. A manager\nreads its applications when it starts, so restart it after adding one. A failure after the manager reserved\nthe name or wrote a launch file keeps its native outcome, and the CLI never reclassifies it from\nits message.\n\n\n## Stopping an agent\n\n`stop <agent>` requests graceful native despawn. `--force` requests the existing\n`graceful:false` contract; it does not grant authority or bypass incarnation checks.\n`stop --self` uses only a verified managed seat caller and its attributed manager. It accepts no\nother target or credential. Missing, mismatched and unsupported user-auth seat context refuses\nwithout switching to the operator. JSON and plain modes show the acknowledged native result,\nnot a separate measurement of process exit. Stop and input do not seed connector installations.\n\nFailed name resolution refuses before a stop, terminal input or first attach is submitted. The\nreceipt retains the inspect error and responding manager. It records local pre-submission proof\nwithout inventing a native effect outcome.\n\nWhen the manager refuses a stop, terminal input or first attach before anything ran, for example\nbecause the agent is not running or the caller may not control it, its reply carries\n`not-executed`. The CLI reports `refused` with exit 2, names the agent and prints the manager's\nreason on one line. A failure after an effect may have started, such as a terminal write the\nruntime rejected, stays `unknown` with exit 3, still shows the manager's reason, and is not retried.\n\nManaged imperative spawn accepts `--restarts <n> --restart-window <ms>` as a pair, using the\nexisting native `supervise` object. Both values must be positive safe integers. Direct foreground\nlaunch and the legacy manifest form reject these flags instead of ignoring them.\n\n\n## Native endpoint reports\n\n`describe <class>` shows the authorized command table, capabilities, target modes and answering\ninstance/epoch. JSON includes the native compiled schemas and verified closure digests. This is\nnative discovery, not a saved-reference inventory or a grant of authority.\n\n`invoke <class> <command> --args '<json>'` validates public inputs before target preparation,\nthen uses the native fetched contract before publication. `--on` pins one full instance ID; a\ncredential-bound pin must agree. Request IDs and actual replying instance/epoch remain in the\nresult. Refusals with a proved `not-executed` outcome stay distinct from unknown mutations.\nThere is no blind retry of an unknown operation.\n\nPlain and rich reports show at most 60 whole result lines; `--json` preserves the full returned\nvalue. Endpoint-provided result content is not claimed to be secret-redacted. A successful action\nacceptance is not whole-task completion. This route does not use saved references or cover the\ncomplete endpoint namespace.\n\n\n## Watch views\n\n`watch --view` accepts `feed`, `events`, `messages`, `roster`, `channels`, `waiting`, `dms` and\n`topology`; `activity` remains the feed spelling. The terminal cycles available views with `f`.\nRoster and waiting rows show reported state without inventing blocked duration. Channel counts\nkeep retained messages separate from observed arrivals. Topology displays broker-sourced live\npatterns and durable channels, not task dependencies or permission grants. Missing or unreadable\nmembership is explicit, never an empty complete graph. Long identities wrap without clipping.\n\nThe authenticated observer remains narrowed to channel traffic. Its DM view refuses instead of\nbroadening credentials or inventing a private conversation identity, and with `--json` its `error`\nrecord names the space and the reason. Open-space DM observations\nretain native sender and target IDs. Use `--json` for versioned view records or `--plain` for line-oriented reports outside a\nterminal. Both use the same bounded observer lifecycle and shared view model. A line-oriented\nroster, channels, waiting or topology view prints its report again each time that report changes.\n\n\n## Authority\n\nBare `auth` reports only safe cached provider metadata. It does not verify a session online,\nturn a sign-in into a grant, or print tokens. Its next steps name the `--space` you gave or the\nspace a bare command resolves; with no space they point to `cotal space`. `auth grants` reads the\nselected root's authoritative user-auth actor ledger; a root without its owner secret and pinned IdP\nrefuses rather than reporting an empty authoritative list. A static or open space has no such ledger\non any machine, so there `auth grants`, `auth grant` and `auth revoke` refuse with `UNSUPPORTED` and\nsay that actor grants apply only to a space started with `cotal up --user-auth`. `auth grant` and\n`auth revoke` refuse there before they ask for a subject or the ACL flags. A space that cannot be\nresolved refuses with its next step, such as `cotal up` or `cotal space`.\n\n`auth grant <actor>` and `auth revoke <actor>` use the existing local operator authority path.\nChoose `--sub` or `--owner`, never both. Grant requires explicit scope/read/post lists unless\n`--full` deliberately selects the native wide defaults. These usage errors refuse with exit 1 before\nthe space's authority is read, and one refusal names every missing input. Re-grant/revoke\nretain predecessor lifecycle retirement and concurrent-row checks before replacing or deleting\nauthority. A retirement that did not run, because the auth service is not running, refused the\nconnection or answered that the retirement did not run, is refused (exit 2) with the actor row\nunchanged and the recovery named, usually `cotal up`. Any other failed retirement request may have\nstarted the retirement, so it is unknown (exit 3) and is not retried. Grant\nrows in reports contain scope and ACL metadata, not token hashes or secret material.\n\nRevocation receipts separate committed deny-new from the delivery daemon's live-eviction notice.\nThey do not claim all related processes stopped. An interrupted mutation is unknown and is not\nretried. Neither grant management nor actor reads install connector packages. Grant management\ndoes not issue credentials, export signers, retire lifecycles or act on capability handles.\n\n\n## Saved endpoint references\n\n`endpoint` (`ep`) reads local references only and labels live state Not checked. `show <name>`\nshows the saved space, native class, full instance pin and storage revision. These commands do\nnot connect to a broker or infer registrations from participant presence.\n\n`endpoint add <name> --file <reference.json>` imports a strict version-1 reference object.\nGuided add is available in an interactive terminal. Names use case-safe hexadecimal\nstorage keys under the workstation endpoint directory. Imports are capped at 256 KiB. References\nreject unknown fields, raw inline credentials, target-space mismatches and malformed native pins.\nA credential reference is an owned private regular file; its contents are not read when saving.\nExisting names are not silently overwritten.\n\n`endpoint remove <name>` defaults to cancel, or takes `--yes` for scripted confirmation. It\ncompares the held revision and content under the private writer lock before forgetting metadata.\nIt never deregisters a native endpoint, stops a process, removes credential files or revokes grants.\n\nProtected no-follow/nonblocking file access supports POSIX hosts. Windows uses its access-list\nmodel for local records instead: a credential reference there must be a regular file, and its\nowner, mode and links are not checked. Saved references do not provide native live\ninventory, invocation resolution or the remaining endpoint verbs.\n\n\nSaved-reference reads validate record structure without opening or revalidating its credential\nfile. A moved credential does not prevent inspecting or forgetting the local mapping. Protection\nis checked when importing/saving a credential reference and must be checked again before any\nfuture invocation uses it. Read receipts label credential validation Not checked.\n\n## Local service definitions\n\n`service` (`svc`, `services`) reads the selected space's local definitions and existing component\nPID observations. Saved definitions, process presence, ownership and serving readiness are separate\nfacts. The definition table counts its already-read instance records and labels any sole instance's\nrecorded supervisor and historical readiness. The separate instance table gives full IDs,\nprocess observations and each recorded supervisor; a record count is not a count of live services.\nRetained native install, uninstall and unit-action attempts appear with their full instance ID,\nstep, position and whether an instance record still exists. They mark the inventory partial:\na submitted or unknown step may have changed the original unit even if its instance record\nis absent. A legacy PID record is not adopted into the new instance controller. Unreadable\ncomponent records make the inventory partial. Reads do not start, repair or install a service.\n\n`service add [name] --file <definition.json>` imports the version-1 service object described under\n[Adding a service](#adding-a-service). A declared preset uses\n`service add <name> --preset <preset> <declared-options>`. Guided input offers installed presets\nand file import. Input is capped at 256 KiB, checked against the selected native space and its\nschema before a private record is saved. Whether a root or credential suits the preset is checked\nat start. A process definition needs an existing executable and\nabsolute working directory. Credential file protection is checked without reading its contents.\nAdapter definitions use an installed local-process provider's versioned, closed option schema.\nAdding or previewing a definition validates cached metadata without importing provider code. The\ncache records SHA-256 fingerprints of the declaration and installed package metadata. Missing,\nchanged or version-skewed metadata refuses with an update or re-add remedy. These fingerprints\nare not publisher signatures or a proof of every executable file. No remote schema is fetched.\n\n`service show <name>` reads declared policy, not live state. Public reports omit argument bodies,\nadapter option values and credential contents. Missing launch paths do not prevent reading the\nsaved declaration. Records live under the workstation store's case-safe per-space service path.\n\n`service remove <name> --yes` forgets only the unchanged definition. Interactive removal defaults\nto keeping it. Unknown or foreign instance custody makes removal refuse. The process driver verifies that every\nowned direct incarnation is gone and that no OS supervisor is installed before removing its\nunchanged instance records and definition. Logs are retained. No process or OS unit is removed\nby this operation. The presets are described below. A preset is not installed under an OS\nsupervisor.\nOn Windows, local records use the access-list model described under `up`. There, `cotal ext add`\nand the setup seed record a service receipt for an extension that declares service adapters or\npresets, and its process services run under a custodian as\n[Owned process services](#owned-process-services) describes.\n\n## Service presets\n\nBare `service` lists declarative presets separately from definitions and process observations.\nUse `service add <name> --preset <preset> --help` for that preset's option flags. Parsing, scoped\nhelp and completion derive those flags from the adapter's closed schema. Boolean options have\npositive and negative forms; a pair refuses. A boolean whose schema admits only true, such as an\nexplicit intent, has only the positive form. Numeric and complex options use JSON syntax.\nA preset no registered or installed provider declares refuses by name; for `web` the refusal\nnames `cotal ext add @cotal-ai/web`.\nOptions that collide with service/global flags require the file form. No preset is installed or\nprovider code imported by help, completion, a guided question or definition creation.\n\nA provider registers a `service-preset` alongside its `local-process` adapter. Preset metadata\ncontains its name, adapter, summary, readiness deadline and option defaults. It does not duplicate\noption schemas, contain executable factories or supply credentials. A default is a literal value or\na fact of the selected space: its registered root, its registered broker, or a private file of the\nspace under the root's `.cotal`, such as the delivery credential `cotal up` keeps there. A file\ndefault that does not exist refuses, naming the flag that replaces it; no other file is chosen. Cached preset and adapter declarations share\none package-metadata fingerprint receipt. A changed selected declaration refuses during dispatch.\nPath annotations require absolute physical files/directories, or owned private files, without\nreading their contents during definition validation.\n\nThe CLI's own presets are `auth-service`, `broker`, `delivery`, `exec-endpoint`, `feedback-intake`\nand `manager`. The `linear` and `web` presets come with their extensions. The intake preset is\n`feedback-intake`. Supply its `--root`, protected `--keys` and scoped\nbroker `--creds` paths in the selected registered space. The keys file is JSON with one entry per\nkey, `{\"keys\":[{\"key\":\"<token>\",\"tester\":\"<tester-id>\",\"name\":\"<display-name>\"}]}`, where `name`\nis optional. A tester's `--key-file` holds their `key`. The intake serves `POST /v1/feedback` on its\n`--host` and `--port`. Optional options include `--server`,\n`--host`, `--port`, `--channel`, `--store`, `--max-bytes` and `--rate-limit`. A supplied store must\nalready be private. Without one, the intake appends to a private instance-keyed store beside its\nservice records. Feedback records and logs remain private local data and are retained on removal.\n\nThe `broker` preset uses `nats-broker`. It requires a prepared local `root`, a protected existing\n`config` inside that root's `.cotal`, an absolute `binary` and one explicit `server` address. It\nprepares no trust, generates no configuration and starts no other service. Legacy broker custody,\nknown shared registrations, maintenance and mismatched transport refuse. Its creation-pinned\ndirect child writes a native ports report in a fresh private launch directory, and the readiness\ncheck sets that report to `0600` before it reads it. Its listener must\nmatch the declared IP, port and transport. `add` refuses a `server` that is not one `nats://` or\n`tls://` URL naming an IP literal, and DNS, wildcard and mapped-IPv6 targets, so none is saved. A\ndefinition saved before that check refuses on start, before launch, naming its `server` option and\nhow to add it again. This reports the child's client listener, not JetStream readiness, endpoint serving\nor a fresh trust check.\n\nBroker stop has a separate kernel-exit policy, with no invented endpoint principal or epoch.\nKnown live or unresolved dependants refuse. After confirmation the controller rechecks the scope\nand process, sends one SIGTERM and waits for that incarnation to exit. Its receipt distinguishes\nprocess exit and removal of the ports file from a native shutdown acknowledgement or cleanup.\nAn unconfirmed stop keeps its original request; no force kill, replay or restart follows. A typed\nadapter refusal before child launch keeps its boundary code, safe cause and remedy. Its local\ninstance receipt remains diagnostic history, recorded as failed with no process, so a later start,\nstop, removal or `down` is not blocked by it. Named multi-service stops do not use this policy. Local registry absence does not prove that\nno remote client or tenant uses this broker, and whole-stack sharing is not checked.\n\nCreating the intake definition launches nothing. Starting it does not start a broker, manager, delivery\ndaemon or installer, mint a credential, or substitute an operator caller. The native child\nreuses the existing HTTP/mesh intake implementation and preserves the target's TLS requirement.\nIt publishes a create-only, full-instance/full-request startup receipt only after the HTTP listener\nand native signal handlers exist. Adapter readiness matches that receipt to the creation-pinned\nchild. It reports an owned running intake, not native endpoint serving, developer action or a\npromise that a future request will succeed. An old receipt is never a replacement launch.\nThe top-level `feedback-intake` entry is deprecated: it keeps its flags for existing callers and\nwarns on every run.\n\n`delivery` uses the selected registered broker root and an existing protected delivery credential\nfrom that root's native store. `--root` defaults to the space's root and `--creds` to the delivery\ncredential `cotal up` keeps for the space; `--server` may repeat the recorded broker but cannot\nreplace it. Open targets refuse. The native daemon still checks its store,\naccount scan and singleton shard lease. No manager, broker or installer is started implicitly.\nA live, foreign or unresolved delivery record refuses rather than being adopted. When a member of\nthe space's controller stack runs it, as `stack-delivery` does while the stack runs, the refusal\nnames that member with its pid and instance, and its Next is that member's stop with `down`. The\nadapter publishes its request-specific startup receipt after the native runner reports Ready\nand its shutdown handlers exist. That is not a fresh liveness response or proof of endpoint\nserving. The existing `deliver` machine-facing entry stays for the stack callers that still use it.\n\nNative service children retain only the explicit base/path/terminal environment allow-list and\nselected references. `COTAL_HOME` and XDG paths retain the invoking workstation registry. Arbitrary\nambient credentials and agent identity markers are not inherited by generic process services.\nA component needing the invoking caller must supply its supported native handoff, not infer a new\noperator identity. In particular, the web service does not receive the invoking caller's\nauthority.\n\n## Native Linear service\n\nThe `linear` service preset uses an existing native `account`, a reverse-DNS `endpoint` in a\nnamespace you own, and the selected registered static-auth or user-auth `root`. An optional `server` only\nrepeats its broker. Account login/token setup remains a separate explicit operation. A start whose\naccount has no record refuses with `TARGET_NOT_FOUND` and the `linear account add` route before a\nchild is launched. That check reads the Cotal home of the process that runs `service start`. While\n`COTAL_HOME` is unset, a definition whose `envRefs` set `HOME` (on Windows, `LOCALAPPDATA` or\n`USERPROFILE`) can give the child another home, so that start refuses with `UNSUPPORTED` before a\nchild is launched. Set `COTAL_HOME` for the start, or remove that reference.\n`service add` checks only the declared option types and lengths. The account\nand the reverse-DNS endpoint name are checked when the service starts, and an invalid endpoint\nname refuses that start before a child is launched, leaving a failed instance record. The native\nstatic issuer, inventory, registration, credential renewal, request handling and deregistration\nstay in the Linear implementation. No generic caller authority, new account or broker dependency\nis supplied by the preset. User-auth spaces use their remote service authority and retain the\noriginal approved registration on restart. Open spaces refuse instead of using another issuer.\n\nOn static-auth roots, an explicit start can allocate another instance through the native registration path. The preset\ndeclares foreground control through the shared terminal-bound lifetime; OS-supervised service\ncontrol is not supported. The child keeps the selected\nroot and registered TLS requirement and does not print upstream bodies or credentials into its\nstartup report. Its private phase records initialized native serve handlers and installed shutdown\nhandlers. It does not claim a broker-flushed subscription, a fresh authenticated serving response\nor upstream business-work completion. Reports label it process/adapter initialization, not\nendpoint-serving proof.\n\nStop closes the native serving connection and Linear session. Static-auth roots also attempt bounded\nderegistration; user-auth roots retain the original approved registration. Original process exit\nstill does not establish that deregistration, revocation or other cleanup completed. The legacy\n`linear serve` foreground entry remains available for installed callers; the independent\nservice route does not call that parser or its printed launch output.\n\n## Native auth service\n\nThe `auth-service` preset requires a registered hosting `root` with its existing prepared\nuser-auth projection. It loads the native callout account, issuer, owner secret and service key\nprojection. Saving or starting this preset does not generate those keys, create an account or\nstart its broker, delivery daemon or manager.\n\nThe preset options derive from the native daemon flags. Ports are bounded integers; public\nexchange options keep the native validation and reverse-proxy requirement. Advertised URLs\ncannot put credentials, query parameters or fragments on the child argv. No environment-export\nonboarding or generic caller-credential substitution is used.\n\nThe preset supports a prepared local user-auth root and its registered plain loopback NATS\nbroker only. The native authority path does not yet carry a required TLS policy through every\nconnection. TLS-required and remote broker targets refuse before authority startup rather than\nweakening that requirement. The preset declares foreground control through the shared\nterminal-bound lifetime; OS-service installation remains unsupported.\n\nThe real child exclusively claims the existing native per-space PID slot and publishes its\ncreation pin privately before opening authority planes. Any existing slot, including a dead or\nmalformed legacy record, refuses without adoption or reclaim. The startup phase is written only\nafter native discovery, flushed callout/listener initialization and shutdown handlers. Discovery's\nloopback capability stays in its native private store; no capability or bootstrap URL appears in\nthe startup phase. This is process/startup evidence, not a fresh authenticated endpoint response.\nA confirmed native close releases only this child's matching PID custody. An unconfirmed close\nkeeps that evidence. Service reports still do not claim lease, registration or family cleanup.\n\n## Manager stop impact\n\nThe native manager status contract includes its selected `lifecycleUid` and a caller-scoped `stopImpact` observation. It lists\nvisible managed principals and lifecycle IDs, their runtime modes and the seat actions a default\nstop or `--with-agents` would request. In-process PTYs without a release hook stop by default;\nreleasable seats lose manager custody. Preservation or pending resume uses the native retained-state\npolicy instead. None of these labels confirms seat exit, authority retirement or cleanup. From\nmanager cluster revision 33, a manager that is not `active` and holds a preservation inventory also\nreports `preservation`: the attempt and the inventory digest a preserved-exit stop names. A\npreserved-exit stop request carries that binding with `withAgents: false`; the manager accepts it\nonly when it is fully preserved, whole-manager and quiescent, and its result is\n`preserved-exit-returned`. The open-space local receipt keeps its revision-32 shape.\n\nThe manager resolves visibility through the same fresh owner/admin policy as `ps`. An\n`owner-filtered` observation is not the full manager impact, even if its rows happen to match a\ncount. A refused stop reports the selected manager, visibility, maintenance state and pending-work\ncounts that blocked its preflight, without exposing other owners' agent rows. Owner-filtered\nvisibility requires the stack operator or an authorized admin-scoped caller; the command never\nsubstitutes one. On a user-auth space, the refusal's next step is the whole-row\n`cotal actor grant` that adds `admin` to your current grant, as the auth plane prints it from the\nledger row. A stop in the folder that hosts the space reads the manager's own local receipt\ninstead, so its owner stops the whole manager without that scope or a sign-in. No stop policy is\naccepted while any blocker remains. This is\nan observation at the reported time, not a reservation, permission to stop or a guarantee that the\nset cannot change. Older managers can omit it; the native service adapter refuses when the\nrequired process/lifecycle correlation is absent. Generic process launch/stop still refuses managers.\n\nThe `manager` service preset uses the `manager-service` adapter. It requires the selected registered\n`root` and an explicit installed `runtime`; an optional `server` can repeat the registered broker.\nThe optional `console-port`, `console-host`, `ws-port` and `max-sessions` pass explicit native\nsettings, and an optional `name` names the manager as `supervise --name` does. Omitting the host keeps the native loopback binding; port zero requests an allocated\nconsole port. A declared `launch` also requires `launch-sha256`. It names an existing private\nresolved launch spec, pins its full SHA-256 and must name this space. The child parses those\nverified bytes before authority preparation and keeps that snapshot through startup. An optional\n`roster` names an existing [roster file](define-a-team.md#roster-files) by absolute path. Only its\nowner or root may write it or its directory. The child parses it before startup, and each launch\nboots the agents it lists the way `supervise --roster` does. A resumed manager takes no roster.\nSaving these options does not prepare a manifest, provision an agent or prove that requested agents\njoined. Saving it starts nothing and grants no authority. Its native child starts no broker or\ndelivery daemon, does not adopt an existing legacy manager, and never prints its private console URL.\nIt declares one instance per definition and runs in the background or with `start --foreground`.\nIt is not OS-supervised installation or platform parity.\nA root that `space add --root` registered has no `.cotal` until a command writes there. A start\nmakes it, as a directory private to you, after its other checks pass and once the root itself is\nprotected against other local writers. It does not replace a `.cotal` that is already there, such\nas a link, which the child refuses.\n\nThe real child exclusively claims its creation-pinned native PID custody before hosted authority\npreparation or local endpoint initialization. That starting record is not readiness; another existing slot is not adopted or reclaimed.\nAfter a crash, the record the killed child left names a dead pid. The manager adapter removes that\nrecord before the next launch, so the next `up` relaunches the manager; a live, foreign or\nunreadable record still refuses.\nIt binds its actual native identity before endpoint construction. A startup phase appears only\nafter native start, settled custody and signal handlers. On authenticated meshes the adapter also\nrequires a current status response pinned to that native instance and epoch, with the same process\nlifecycle UID. It preserves the invoking managed-seat caller, registered root/broker and required\nTLS. Missing caller support refuses without an operator substitution. The completed status read's\nown admission is excluded from stop impact; other admitted work remains counted.\n\nAn open mesh uses the pinned child's private request-bound local observation, and so does a stop\nin the folder that hosts a user-auth space. Its service, launch,\nrequest and both process creation identities must match, and its registration must match the\nlaunch identity. Output labels this a child-local receipt, not a broker-attributed endpoint\nresponse. A retained stop handoff blocks readiness adoption on either route instead of treating\na live PID as a new serving launch.\n\nA real `service stop` or `restart` reads complete caller-visible impact before confirmation.\n`--with-agents` requests native agent stops, not generic descendant signalling. An interactive\nconfirmation defaults to cancel and shows every affected identity, lifecycle and requested seat\naction. Scripts require `--yes`. The offline `--dry-run` still imports no provider and performs no\nnative impact query; it is not stop authorization.\n\nThe native stop reserves its original PID custody. It reclaims an existing reservation only when\nthe pid it names has exited and no request was published for this launch, and otherwise refuses and\nnames that pid.\nIt rechecks the confirmed scope, binds the full service/launch/stop request and both process creation\nidentities, and uses the existing native shutdown intent for agent reap. The child rechecks the\nsame complete scope before accepting. Changed or incomplete scope refuses without a broader policy.\nThe controller requires immutable accepted/returned evidence and the original process exit before\nrecording a native stopped state. An unconfirmed request retains its reservation and never escalates\nto force kill, restarts, or treats PID disappearance as completion. A later bare `up` or `down`\nsettles it only from the same evidence, and only then releases the reservation. Reported cleanup remains not\nchecked. Retained native-policy markers prevent a metadata change from selecting generic signals,\nand an unconfirmed native stop cannot be forgotten just because its process disappeared. Legacy shutdown refuses the native child rather than bypassing that boundary.\n\n## Resolving endpoint targets\n\n`endpoint describe <target>` and `endpoint invoke <target> <command> [--args '<json>']` use the\nexisting native caller and digest-verified contracts. `ref:<name>` requires a saved reference.\n`ep:<class>` bypasses local references. An unprefixed name checks the selected space's saved\nreferences first, then uses the native class only when no reference exists. An unreadable or\ninvalid reference refuses instead of selecting another target.\n\nA selected reference is disclosed on stderr and in `result.resolution`, with its revision and\nresolved native class, instance and epoch. The CLI holds its cooperative writer lock and checks\nthe same bytes before invocation. `--on` must agree with a saved instance pin. A missing pinned\ninstance does not fall back to the class queue. Unknown native effects are not blindly retried.\n\nBoth read the endpoint's contract first, and so does `auth mint --endpoint`. When no instance\nanswers that read within 10 seconds, the command refuses with `TARGET_UNAVAILABLE`: nothing is\nserving the endpoint, or nothing could reach this caller. Its Next says to start the service that\nserves the endpoint. Nothing was invoked or issued.\n\nA reference's file credential uses the [paired native caller adapter](#endpoint-caller-credentials).\nIt must match the selected registered static root, space, endpoint and signed native scope. A\nconflicting explicit `--creds` refuses. Arbitrary raw files do not supply a caller triple, and a\nreference's instance pin cannot be dropped to fit a class-only file. Neither case substitutes an\noperator identity. References without a file use the normal registered-space caller and its\nexisting capability checks. No local reference grants remote authority.\n\n## Declared capabilities that remain gated\n\nThe CLI grammar also names `endpoint watch <target> <record-key-or-topic> [--since <native-sequence>]`,\n`endpoint session <target> <command>`, `endpoint work lease <target> <pool>` and\n`endpoint work commit <target> <pool> --lease <private-file> --result <JSON>`. They return a typed\n`UNSUPPORTED` result before any broker connection, subscription, cursor, session, lease or commit.\nThe current resolver supplies command contracts, not a verified mapping of these application\ncapabilities. This is a CLI integration limit, not a claim that the selected endpoint lacks them.\nNo arbitrary subject, pool implementation or remote execution is guessed.\n\n`endpoint invoke --handle <private-file>` is also an explicit unsupported boundary, because no\nverified native auth-slot adapter is connected. It opens no file, redeems no handle and publishes no call.\nAn opaque low-level auth string alone does not establish receiver-side attenuation. Offline\n`auth handle inspect` remains explicitly unverified metadata without `--space`; its online\nstatic-registry signature inspection also does not establish presenter binding or use authority,\nand is not a substitute for that gate.\n\n`endpoint describe --refresh` refuses as unsupported and sends nothing. This CLI keeps no resolved\ncontract between calls: each describe makes a fresh describe request and fetches the contracts\nagain, so there is no cache for the flag to bypass.\n\nA saved-reference or paired-file invocation uses the described instance and epoch without bind-repair\nto a different handle. A pre-effect bind refusal remains a refusal, not permission to choose another\ninstance. Invocation receipts attribute resolution to the native responder that answered.\n\n`endpoint --live` adds authorized native service-registration metadata to the local reference\ninventory. It uses the existing scoped service-record read rows, without adding grants or reading\nmessage bodies. The SDK paginates the filtered metadata list; per-key spec/status reads come from\nthe leader. The list can lag concurrent changes and is not an atomic whole-space snapshot.\n\nThe snapshot reads at most 1000 registration keys. Omitted keys, unreadable records or unavailable\nregistry access make the requested live portion partial and return exit 4, while preserving the\nlocal references. Status projection, reported state, process ownership and a serving response are\nseparate. No active probe or process control is implied by a registration row.\n\n## Clearing retained history\n\n`history clear` purges all retained channel history in the space it targets: `--space`, else this\nfolder's space, else the selected one. It takes no channel argument. `--dms` also requests DM\nhistory under the existing purger authority. In a terminal, confirmation names the space, broker\nand space-wide scope and defaults to cancellation. Scripts\nand `--json` require `--yes`. The old `--force` spelling refuses with the replacement.\n\n`history clear --dry-run` resolves the target and names the affected streams offline. It issues no\ncaller credential, connects nowhere and does not check authorization or retained message counts.\nA space that is prepared but not started is refused with `TARGET_UNAVAILABLE` (exit 2), and the\nrefusal says how to start it.\nThe purge uses the existing purger instrument or one-action user purger view, not a broader caller.\nOn a user-auth mesh whose grant lacks scope `admin`, the purger view is not issued and the refusal\nis `PERMISSION_DENIED` (exit 2). Its remedy is the identity provider's own sentence, which names\nthe missing scope and the whole-row re-grant that adds it.\n\nJSON and plain receipts retain each native stream acknowledgement and message count. A later\nrefusal after an acknowledged purge is partial. An unconfirmed submitted purge is unknown and is\nnot retried. A confirmed clear interrupted by SIGINT, SIGTERM or SIGHUP exits 130, 143 or 129 and\nsays which purges were acknowledged or submitted, or that none was submitted and no history was\ncleared. Clearing history does not stop agents, remove stream definitions or reset identity;\nnew messages may arrive after the acknowledged purge.\n\n## Following an endpoint action\n\n`endpoint invoke <target> <command> --wait` requires the command's digest-verified action marker\nand the native goal-result read surface. The CLI submits once, retains the accepted goal ID and\nfingerprint and follows through the native helper. Reconciliation reads the accepting instance,\nnot an unrelated class winner. A reference keeps its described binding for submission.\n\n`--wait --json` emits goal NDJSON. It exports the original request, acceptance and observed terminal\nresult. Intermediate native progress is not exported; a gap record states that limitation. No\nglobal sequence or task progress estimate is invented. An unknown observation keeps its accepted\nidentifiers and is not retried. Ctrl-C or termination ends observation, not the underlying action.\n\nGoal-stream terminal values are capped at 16384 UTF-8 bytes. A larger value is omitted with an\nexplicit gap and retained goal identity, not split into invalid JSON. Read that original goal's\ncanonical result if needed; do not resubmit an action to recover its output.\n\n## Unconfirmed requests\n\nClassified failures use the same report layout as successful commands. The report names the\noutcome and stable code, then shows available request, goal and native responder attribution.\nUnknown and partial results use waiting treatment, not a healthy completion marker. Plain mode\nkeeps the same state words without ANSI. The JSON envelope stays on stdout; diagnostics stay on\nstderr. Raw argument objects and arbitrary native detail payloads are not copied into the human\nreport. Confirmed history purge acknowledgements remain visible when a later stream fails.\n\n## Persona previews\n\n`spawn --file/-f <persona-file>` selects a persona, not a team manifest. It cannot be combined\nwith a positional persona or the persisted internal `--config` spelling. A bare filename is\nstill a file, including when it has no extension. Managed paths remain relative to the manager's\nworkspace and are read on that execution host. No file upload is implied. Direct foreground\nexecution reads its local file. A supported Mesh-manifest shape returns a schema refusal with\nthe literal `cotal team deploy <file>` remedy, before agent allocation.\n\n`spawn --dry-run` never refreshes the catalog, repairs stale registrations, imports connector code,\nsigns in to the broker, redeems enrollment, provisions or allocates an agent. Where the launch reads\nwhether the recorded brokers answer to choose its space, as in a folder whose registered space may\nbe stopped, the preview reads the same without signing in, so it names the space the launch uses. The managed preview\nshows the request and exits 0 with `completeness` `not-checked`, because the manager resolves the\npersona, harness and working directory on its own host at launch. It does not substitute the\ncaller's files. A named `--runtime` is checked on this machine only, through the lookup the launch\nuses: `result.requested.runtime` names it, and `result.runtimeCheck` gives the `host` it was checked\non, whether it is `available` there, and the `error` when it is not. A runtime this machine cannot\nrun is a `RUNTIME_UNAVAILABLE` warning, because a manager on another host checks its own.\n`--runtime auto` is refused as the launch refuses it. With `--foreground`, the preview resolves the\nlocal persona, harness precedence and physical working directory. Binary, login, model-policy and live authority checks are labeled not\nperformed. Both previews say where the persona came from: `--file`, the command line, a preference\nor the default. A harness that names no connector this workstation knows is a `HARNESS_UNKNOWN`\nwarning, or the `PREFERENCE_INVALID` warning `config` gives when only `COTAL_DEFAULT_AGENT` chose\nit. In a folder whose `.cotal/` names no space, as after `space forget`, the preview keeps `Mesh:\nmain`, says under it that the held default was assumed, and its JSON `target` names the folder as\n`root` and the space as `assumed`.\n\n`spawn --foreground --enrollment-file <private-file> --space <name>` uses the existing one-seat\nforeground enrollment path. The file is opened with protected ownership/mode and bounded input\nchecks; its URL is not printed, copied into the child environment or persisted by this input\nadapter. It does not supply a managed enrollment authority path. Dry-run does not read or redeem\nthe enrollment file. On Windows the file must be a regular file; its owner, mode and links are not\nchecked.\n\n## Owned process services\n\nPreset inventory checks the selected installed declaration without importing provider code. It\nshows declared foreground/multi-instance capabilities, process versus endpoint readiness, and\nnative-request-bound versus direct-process stop policy. Missing, skewed or unreadable metadata\nleaves the inventory partial, with unknown capability fields. Passing metadata checks is not\nproof that an application, dependency or service is ready. Linux direct-process\ninstances can use the owned systemd user-unit route described below. An adapter\ndoes not acquire that mechanism from its name or preset.\n\nThe direct-process driver pins the machine, the boot, the OS user and each process's start. On\nLinux these are the machine ID, the kernel boot ID and the process start ticks. On macOS they are\nthe hardware UUID, the boot session UUID and the process start in microseconds, read through the\nbundled binding described under the identity pins above. On Windows they are the system ID that\nWindows gives this publisher's apps on the machine (`GetSystemIdForPublisher`), the account's SID and\neach process's creation time, read through the same binding. The record also keeps where Windows\nderived that ID: a TPM, UEFI or the registry. An ID from the registry changes when Windows is\ninstalled again. When Windows has none of these, the driver refuses. Windows records no boot pin.\nThe custodian described below holds each instance's job instead, and a restart ends it.\n\nInstance reports separate recorded supervisor policy from supervisor discovery, which remains not\nchecked by this direct-process driver. A recorded `none` means no supervisor assigned by this\ncontroller. It is not proof that an external unit cannot supervise or restart the process.\nA retained supervisor receipt binds its definition revision, full instance, machine and OS user.\nIts file view hashes only the recorded physical unit, without printing its body or environment.\nMatching bytes are not loaded/active state or enabled-autostart proof. Missing, modified or\nunreadable unit files do not turn a receipt into an uninstalled service. Direct start/stop/remove\nrefuse while such a receipt is retained; they do not launch beside a unit or implicitly uninstall\nit. `service status` can read an unchanged recorded systemd user unit on the same host through\none bounded exact-unit query. It checks the returned physical fragment and reports native state,\nmain-PID attribution, invocation, reload need and presence of unverified drop-ins. Missing or\nambiguous observations stay partial. It neither prints unit/environment contents nor discovers\nownership from names or cwd. This observation proves neither endpoint readiness nor effective\nlogin/reboot autostart. Inventory and direct-process dry-runs perform no OS query;\nLinux native-unit removal dry-runs make one bounded exact-unit status read.\nOn Linux, `service install <name> --instance <full-id> --dry-run` asks the\nsystemd user manager for its config directory, then renders a\nbounded direct-process unit and private environment candidates and reports\ntheir paths and digests without printing their bytes or the inherited `PATH`.\nWith no existing instance, the inspection checks the declared process,\ndurable CLI entry and user manager, then reports that apply would allocate a\nstopped full ID; it\ninvents neither an ID nor a unit candidate. On macOS the same preview renders\na bounded LaunchAgent plist candidate for a settled stopped direct-process\ninstance, with its label, path and digest but not its body or inherited `PATH`.\nIt checks the `Library/LaunchAgents` segments without following symlinks;\nit does not query a launchd domain, load a job or enable autostart. An existing\nplist remains unattributed. With no instance it reports a possible stopped-ID\nallocation, not a plist. macOS install apply refuses, because it has no native\ncontroller or custody records.\nLinux bare install allocates that stopped full ID only after its source validation;\nit does not start a process. If later installation fails, the allocation is\nreported as a changed resource rather than hidden or retried.\nThe unit environment pins an absolute-entry `PATH` from this invocation;\nrelative or empty entries refuse rather than acquiring another working\ndirectory at boot. It requires a settled stopped instance, its current\ndefinition revision and no recorded supervisor, host environment reference or\ncredential reference. An existing unit at that path remains unattributed, not\nowned. The directory's parent ownership, private seed, receipt intent, loaded\nunit and autostart are not established; the preview is partial and authorizes\nno install. Linux apply retains a per-step intent before exclusive private file\nwrites, isolated connector seeding, a bounded private-tree fingerprint, user\nmanager reload, enable without `--now`, and the exact supervisor receipt. It\nreturns the full instance and observed unit-file state, not a process-readiness\nor effective reboot-autostart claim.\nA retained installation or removal intent blocks a new install and is reported\nas partial by `service status` with its exact unit path, private-store path,\ncurrent step and last acknowledged step. A bounded native read for each retained install, removal or start/stop/reseed\naction distinguishes an unchanged physical unit fragment from a name-only\nstatus when the file is missing or modified. It also classifies the private\ndirectory without inspecting its children. None of these reads permits an\neffect to resume. It cannot\nauthorize a blind retry. A changed\npre-effect target leaves a planned step and a partial receipt; an unconfirmed\nsubmitted effect is unknown and is never automatically repeated.\n`service remove` refuses while any native attempt names the definition. There is no\nexplicit reconciliation for an uncertain attempt.\n`service uninstall <name>\n--instance <full-id> --dry-run` reads only a matching recorded systemd unit,\nits private-store directory and one native status snapshot. A retained native\naction, or missing, modified or foreign receipt files, refuses. An owned private\ndirectory does not prove its descendants unchanged. A separate unexposed\nremoval candidate requires a bounded no-follow fingerprint of the complete\nprivate tree to match the install receipt. Changed content is preserved and\nremoval refuses. The preview does not reserve a stopped unit, enable state or\nremoval authority; its report remains partial. Linux apply requires the exact\nunchanged loaded inactive unit and private tree, with `--yes` in scripts or a\nscoped terminal confirmation. It disables autostart, removes only the pinned\nunit and private tree, reloads the user manager, then removes the supervisor\nreceipt. The definition and stopped instance record remain.\n\nA retained native start, stop or reseed action is reported as partial by\n`service status`; it does not authorize another request. Native start and stop\nrequire the exact owned receipt and current native unit observation. An older\nprivate store is reseeded while the unit is stopped, with a separate retained\naction and refreshed generation/tree digest before start. An unconfirmed seed\nis never retried. Restart uses the same exact stop and subsequent start; if\nstart does not confirm, the stopped original remains in its result.\nUninstall retains a step before disable, exact unit unlink, reload, private\ntree removal and receipt deletion. A non-confirming step remains visible in\n`service status`; a missing unit alone never clears its attempt.\n\nThe private unit entry resolves only its built-in command and validates the explicit controller\nhome, space, definition and full instance before any extension overlay, repair or package load.\nIt refuses an inherited managed-spawn handoff without reading or deleting that file.\nBefore native replacement, it requires the recorded private seed generation,\nexecutable provenance and absence of unfinished seed markers; it never reseeds\nthat store at boot. For a\nrecorded direct-process unit it also requires no retained installation or\nremoval intent, the current native unit invocation and matching physical\nfragment before replacing itself with the saved executable. It does not fork a detached\nwrapper or retry execution. A starting record does not establish successful replacement or\nreadiness; an interrupted replacement remains unknown. A later unit invocation refuses an\nunsettled starting, stopping or unknown attempt instead of replacing its request.\nInstall dry-run makes only the bounded native directory read above; uninstall\ndry-run reads the exact recorded unit and private-store directory. Neither\nallocates an instance, enables autostart nor removes a receipt or unit.\n\nProcess definitions support `service start/stop/restart/status/logs <name>`. The driver requires a\nstable machine identity and readable process-creation tokens. Linux and macOS add the boot identity.\nWindows adds the account's SID and a custodian that holds the service's job, described below. It\nrefuses another platform instead of using PID-only ownership. It launches executable and argv\nseparately without a shell. No broker, manager or other dependency is started implicitly.\n\nOn Windows each background instance runs under its own custodian. The custodian is a detached\nCotal process that creates the program inside a new job object. The job ends every member when its\nlast handle closes and allows no breakaway, and only the custodian holds that handle. So a\ncustodian that exits for any reason, a restart of the computer included, ends the service and\neverything it started. Commands reach the custodian over a named pipe and prove that they hold the\ninstance's 32-byte capability, and the custodian proves the same back before anything is done. The\ncapability lives in a protected file beside the instance records, which name only that file. A stop\nterminates the whole job and waits until it is empty, and its receipt records the signal\n`job-terminate`. When the program has exited but processes it started still run, `service status`\nreports the process as `members-running` rather than gone, and a stop ends them. `cotal down`\nrefuses a stack with such a member and names that stop. Windows has no foreground start. A\nmanager's agents run in its job, so a manager stop without `--with-agents` is refused while the job\nstill holds them.\n\nInstance IDs are assigned once and printed in full. With several records, scripts need\n`--instance <full-id>`; a terminal can choose from those IDs. `start --new-instance` explicitly\nrequests another process-driver instance. Otherwise one serving instance is a no-op and an\nunresolved prior launch blocks another allocation. A launch that failed before its child existed\nis recorded as failed, not unresolved. A stopped instance retains its logical ID\nand receives a new launch request. Unknown startup retains its receipt and is not resubmitted.\n\nOnly safe base environment names and the definition's selected `envRefs` are passed. A declared\nprotected credential file is forwarded to that program as the `COTAL_CREDS` path, not credential\nbytes. Control credentials are not inherited wholesale. The program still runs as the current\nOS user; this is not a process sandbox. Native managers require their service adapter and\nagent-impact guards and are refused by generic process launch/stop.\n\nAdapter definitions use the same owned direct-child controller. The local-process provider\nadds a service ABI declaration and launch/readiness hooks. Its closed option schema supports\nbounded objects, arrays and scalar fields, with no schema references, regular expressions,\ncoercion, defaults or unknown-option removal. Every object closes its declared fields. An\nunsupported schema refuses rather than being widened. A declared path option and its ancestors\nfollow the [`space create`](#selecting-a-space) rule for other local writers; a private file stays\nowner-only. One refusal names the option's entry and every unsafe directory above it, with one\n`chmod` that fixes them all. `npm i -g` under umask 0002, an ordinary Ubuntu user's default, leaves the bundled\n`nats-server` and its directories group-writable, so where the private-group proof is unavailable,\n`up` refuses the broker's `binary` path and names that `chmod g-w`. When every unsafe folder is\ninside Cotal's own install root, as it is for the runtime Node an earlier installer left\ngroup-writable, the refusal says the folders belong to Cotal's own install and names `cotal update`\nas the fix.\n\nThe provider declares foreground, multi-instance and log capabilities. An explicit start records\nits definition revision, full instance, launch request and adapter package/version/declaration\nfingerprint before loading provider code. The launch hook receives the definition's `envRefs`\nnames, never their values, and returns executable, argv and working directory separately. It must not launch children, mint credentials or start dependencies while\nplanning. A changed adapter is not adopted by a retained instance. Native stop declarations add\nclosed impact and terminal hooks; a missing hook refuses instead of using generic signals. The\nmanager's adapter supplies its own native policy and receipts. A preset is not installed under an\nOS supervisor.\n\nReadiness hooks receive the pinned process and a bounded cancellation signal. Ready observations\nmust match that process and carry a current observation time. An endpoint-serving declaration\nalso returns its checked native class, instance, principal, lifecycle and epoch. The adapter must\ncorrelate those native coordinates with its owned launch using its supported authority; a matching\nname or successful HTTP request is insufficient. Reports preserve those coordinates and their\nobservation time. Local status does not refresh them or promote them to current live authorization.\nA failed or timed-out hook retains an unknown receipt and is not retried. A cancellation signal\ndoes not prove third-party code stopped working.\n\nExtension removal checks retained service records in every local service space, including spaces\nabsent from the mesh-selection registry. Live, foreign, unreadable or unresolved instances block\nremoval. A starting intent and the extension writer lock prevent an uninstall from crossing the\ncontroller's final provider check. Providers still run as the current OS user, not in a sandbox.\n\nProcess readiness waits up to one second, or its deadline when that is shorter, for the process to\nexit. One that exits by then fails the start with `OPERATION_FAILED`, exit 5, and its exit code is\nrecorded; a restart whose replacement exits so is partial, exit 4, and names that exit code. A\nbackground start also quotes the last line the process logged during that launch, and says its log\nrecords the cause when it logged nothing. A live\ncreation-pinned direct process is reported Running. HTTP readiness uses\na bounded loopback request with credentials omitted and redirects disabled. Its success is\nReachable, not proof that the child owns that listener or serves a native endpoint. Status labels\nthe stored readiness observation and its time separately from the current process observation.\nBefore it launches a process with HTTP readiness, `service start` sends one such request. When\nsomething already answers there, such as an instance of the same definition on its port, any answer\ncould come from it rather than the new process, so the start refuses `CONFLICT`, exit 2, names the\naddress and that instance, and launches nothing. When no instance answers there, the refusal names\neach process of this account that listens there by PID and program name, as `SERVICE_ADDRESS_HELD`\ndoes, and its Next says to stop that process. The start does not signal it. When the HTTP deadline passes first, `service start`\nnames the loopback address without the URL's path, the expected status and the deadline. When the\nservice kept answering another status, such as 503, it exits 5 with `OPERATION_FAILED` and names that\nstatus. When the last check got no answer, such as a refused connection, it exits 3, names what it\nsaw, and its Next also names `cotal service show <name>`, which shows the readiness deadline. Either\nway the process is left running, and the Next names the `service logs` and `service stop` for that\nexact instance.\n\nA Ctrl-C or `SIGTERM` while a background `service start` waits does not reach the service, which\nkeeps running. The start exits 130 or 143 and names the instance it left. Its Next is\n`cotal service start <name>` to wait for that instance's readiness again, or the `service stop` for\nit. Until a later command settles that start, `service status` and `cotal status` say the start was\ninterrupted.\n\nStop and restart default to cancellation in a terminal and need `--yes` in scripts. The selected\nrecord revision and creation identity are checked before signals, including escalation. A reused\nPID is never signalled as the old process. Restart holds the definition operation across the\nconfirmed old-process exit and one replacement launch. An unconfirmed stop cannot cause a\nreplacement. `--dry-run` creates no instance and sends no process signal.\n\n`start --foreground` is an owned foreground process, not an attachable view. It rejects JSON and\ncannot adopt an already running background instance into the terminal. Before allocation it prints\nthe terminal lifetime and output policy. Native auth and Linear presets use their installed signal\nhandlers through this same flow; neither endpoint readiness nor native cleanup is inferred from\nprocess exit. A foreground manager stops through the same request-bound native stop as\n`service stop`, keeping its agents: the SIGINT or SIGTERM the CLI receives is the confirmation.\nCtrl-C also reaches the native child, which ignores a `SIGINT` that carries no stop request and\nprints one warning line for it. A plain `SIGTERM` stops it with the default agent policy.\nNative service child boot never repairs or seeds connector packages. Background stdout/stderr\ngo to owner-private logs. A start or restart moves a log of 16 MiB or more to `<log>.1`, replacing\nan earlier one, and the run writes a fresh log, so the log grows until the next start.\n`logs --tail <1..1000>` reads back from the end of the log until it has that many lines, reading at\nmost 16 MiB. The result is partial only when those 16 MiB hold fewer lines than the tail asked for.\n`service remove` deletes `<log>` and `<log>.1` for each instance it removes. Foreground output is\nnot captured there. Log text is not promised to be secret-redacted. Live following uses the bounded\nread-only stream described below.\n\nFor a recorded systemd user unit, logs use its saved original invocation, boot and OS user,\nnot the native manager's current invocation. Finite tails read at most 1 MiB and 1000 records.\nIndividual native JSON records are capped at 256 KiB. Native journal cursors and microsecond\nwall/monotonic timestamps are kept; binary or repeated message values stay native JSON. Missing\ninvocation evidence refuses without substituting an old direct-child file. Native diagnostics are\nwithheld, not suppressed by the query; their presence makes a finite result partial.\n\nJournal following freezes those same selectors. It reports oversized, incomplete or unattributed\nrecords as gaps and emits native cursors, rather than inventing a sequence. Ctrl-C closes only the\nowned journal reader. It never signals the service or adopts a later invocation. This scope includes\nreadable process-origin messages, not every user-manager lifecycle diagnostic. Journal access and\nretention may omit messages, and an empty result does not prove there was no output or work.\nJournald must retain an accessible per-user journal; no alternate file, privilege escalation or\nremote log service is used. These are authorized local logs and can contain secrets.\n\nA background start runs its process as the leader of its own process group. `service stop`, and\nthe stop half of a restart, signal that group and wait until the process and every member of the\ngroup are gone, so a descendant left in the group, such as a server a shell backgrounded, ends with\nit. A foreground start shares the command's group, so only its own process is signalled.\n`service stop --dry-run` names which it would signal, the process group with its member count or\nthe process alone, and the grace period before `SIGKILL`; `--json` carries it as `stopSignal`. A\ndescendant that leaves the group, such as one started with `setsid`, is not followed, and a\nstopped/direct-process-gone observation is not proof that all external work ended. When the HTTP\nreadiness address of a saved service still answers while none of its instances runs, `service\nstop`, `service status`, `cotal service`, `cotal status` and `cotal down` print a\n`SERVICE_ADDRESS_HELD` warning. It names the address and any process of this account that listens\nthere, by PID and program name. The process driver itself provides no service presets, agent-aware\nmanager control, owned OS units, non-Linux lifecycle parity or full preset and OS-supervisor\nintegration.\n\n`service logs <name> --instance <id> --follow` follows that private file without controlling the\nservice. JSON emits log snapshot/event/connection/gap/error/end records. Events carry native file\nidentity and byte offsets, not a manufactured global sequence. Initial tails and reader backlog\nare bounded at 1 MiB and chunks at 16384 bytes, with explicit gaps for omissions, replacement or\ntruncation. Instance-generation changes are attributed. UTF-8 decoding can replace invalid bytes\nand is labeled accordingly. Output backpressure pauses the reader instead of growing a buffer\nwithout bounds. Ctrl-C, termination and closed output end the view, not the service.\n\n`auth retire <full-lifecycle-uid> [--space <space>] [--yes]` validates the native UID and resolves\nthe selected auth target. A lifecycle UID is 26 to 32 lowercase letters and digits, and\n`cotal ps --wide` shows each seat's. On a user-auth mesh that runs elsewhere and advertises an\nagent-provisioning endpoint, it releases that one lifecycle there with your login\n([Held agent names](identity-and-auth.md#held-agent-names)) and reports the mesh's answer: revoked,\nalready revoked (`no-op`), `TARGET_NOT_FOUND` (exit 2) for a lifecycle the mesh does not list, or\n`OPERATION_UNKNOWN` (exit 3) for a release the mesh could not confirm, which is not repeated. A\nrevoked lifecycle may still be retiring. On any other mesh the CLI refuses `UNSUPPORTED` (exit 2): the registered auth\nendpoint accepts retirement only from the requester's own current serve identity and gate, which this\nCLI does not have. It neither borrows a manager identity nor substitutes the interactive or managed\nloopback host doors. `--yes` cannot create authority, and no retirement request is sent. The refusal\nnames the levers that work: [auth revoke-credential](#auth-revoke-credential) for one minted\ncredential, which keeps the store, and rotating the space identity, which stops every credential the\nspace issued and removes the space's store with its history (see [auth mint](#auth-mint)). The rotation also leaves\nearlier full backups of the space restoring only their registry.\n\n`auth redeem <private-handle-file> [--space <space>]` selects the existing local auth target and\nrefuses with `UNSUPPORTED` because its conferring-handle redemption path is not exposed. It does\nnot read the file, consume a handle or issue credentials. Session grants, agent enrollment and\nparsing an unverified handle are not substituted for that authority.\n\nNested help follows declared command words, for example `cotal help auth handle inspect`.\nAliases resolve within their declared parent. Help reads cached metadata and imports no provider,\nstarts no service and opens no credential file. A command's `-h` selects the deepest declared\nmode before its data arguments; it does not execute that mode. Scoped options refer to the\ncommand's shared flag definitions. Completion uses the same declared subgroups and option names,\nwithout treating an option's value as another command word.\n\n\n## Endpoint class scatters\n\n`endpoint invoke <target> <command> --all` submits one native ephemeral class scatter.\nIt freezes the expected live instance set, gathers attributed replies and reconciles registration\ncurrency through the native SDK. It does not probe additional instance rails or retry a mutation.\nInstance pins, targeted commands, `--cast` and `--wait` cannot combine with a class scatter.\nThe installed helper does not submit journaled commands through this route.\n\nFinite JSON retains the original request, each valid member result and the native missing, churn,\nunexpected, duplicate, late and invalid buckets. Invalid-reply diagnostic bodies are omitted.\nCoverage and command success are separate: complete coverage can still contain refusals or failures.\nAll confirmed pre-execution refusals exit 2 (invalid input exits 1), all executed failures exit 5,\nunconfirmed execution exits 3 and mixed or partial outcomes exit 4. Reports keep full IDs, show\nbounded member/observation rows and state their omissions. A missing reply is not proof of death.\nResult values are endpoint-provided and are not guaranteed to be secret-redacted.\n\n\n## Personas on an exact manager\n\n`persona --on <full-manager-instance>` lists that manager's authorized catalog.\n`persona show <name> --on <id>` reads a card only when its native ownership policy allows it.\nA name-only row does not grant access to its instructions or permission to redefine it.\nThese operations never substitute the local catalog or another manager.\n\n`persona create <name> --on <id>` accepts one of `--file`, `--prompt` or `--from`, with\n`--from -` as explicit piped input. Leading frontmatter follows the native persona merge:\nthe supplied name wins, explicit model/role/application and channel fields override the file,\nand the manager preserves existing ownership and redefinition policy. Inputs are bounded to\n1 MiB and must be valid UTF-8. A malformed leading block refuses before the remote request.\n\nThe remote wire accepts name, body, model, role, application, initial read channels and read/post\nACLs. `--harness`, `--subscribe`, `--allow-subscribe` and `--allow-publish` name those fields.\nOther frontmatter, including kind, tags, description, attention settings, variant, launch options,\ncapabilities and owner, refuses as unsupported instead of being silently discarded. Remote edit\nand removal are not native wire operations and refuse; no local file is changed instead.\n\nCreation is silent unless `--announce <concrete-channel>` is supplied. Announcement preparation\nmust resolve to the same space, root and account context before the write. A confirmed save and\na later denied or unconfirmed announcement produce a partial receipt retaining the save. Do not\nrepeat the definition to recover an announcement; read the channel first. A stored announcement\nis not delivery, a spawned agent or task completion. Finite JSON/plain receipts keep the requested\nand answering manager, epoch and original request, without printing the creation input body. Rich results offer\nfollow-up show or spawn commands with the original exact manager pin; a suggested launch still\nrequires the caller's native spawn authority.\nWhen the manager writes the file into this root's catalog, the receipt's `Local files` line names\nit and JSON `localFilesChanged` is true. On a static space the manager records an owner only for\none of its own seats. The CLI mints a new caller for each call, so its card is operator-owned, as a\nlocal `persona create` writes it, and the manager does not show it over `--on`. The receipt then\noffers `cotal p show <name>` for a file in this root, as the `--on` listing's Next does, and a\n`show --on` that the manager answers not-found names that command when this root's catalog holds\nthe file. A peer joined with its own\ncredentials is not one of the manager's seats either, so on a static space it cannot redefine or\nread over `--on` what it defines.\n"
27570
27570
  },
27571
27571
  {
27572
27572
  "slug": "config",