--- title: "Process supervision" description: "Manager compatibility entry, owned direct processes, systemd user units, and service logs." kind: Reference audience: operators --- import { Callout } from "fumadocs-ui/components/callout"; The process controller records custody, readiness, and stop evidence for each service instance. ## supervise [#supervise] ```bash cotal supervise [--execution runtime|none] [--runtime ] [--space ] [--server ] [--name ] [--spawn ] ``` `supervise` runs a manager in this terminal until Ctrl-C. It is deprecated, hidden from help and completion, and removed in `1.0.0`, with the other [deprecated daemon entries](UPGRADING.md#deprecated-daemon-entries): every run prints one stderr warning naming `cotal manager start`, and `cotal manager start --foreground` for a terminal or a unit. On a terminal it wraps like any other warning. A run given `--execution`, `--spawn`, `--seat-cap` or a resume flag, which `manager start` does not take yet, says to keep `supervise` for that run instead. It keeps its flags and its run for existing units, scripts and containers. The manager that `cotal up` starts runs the same code through a hidden entry. Without `--space` it supervises the space other commands target here: `COTAL_SPACE`, else the running space this folder hosts or is registered for, else the one `cotal use` selected, else the space this folder holds or hosts. A space the folder only holds, such as a prepared or stopped one or one a `cotal up` in a folder above it left, does not take the run from the selection. With nothing selected, a prepared or stopped space is refused as not started, and the refusal names the `cotal up` that starts it. A folder that holds no space, with nothing selected, is refused `TARGET_NOT_FOUND` in the words `cotal manager start` uses there. Next leads with `cotal manager start --space ` or `cotal use `, which start the manager of a space a folder on this machine hosts from any folder. It keeps `cotal supervise --space ` for a space no folder here hosts, and names `cotal up` to host one here. A registered or selected space runs its manager in the folder that space is registered at, with that folder's records and signer, so a start from any folder is the same manager, with the same instance and name. With `--space` or `COTAL_SPACE` the manager runs in this folder. For a static or user-auth space that another folder hosts, a folder that holds no signer for it is refused `PERMISSION_DENIED` before anything is written, and Next names the folder that hosts the space. | Flag | Default | Meaning | |---|---|---| | `--space ` | `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 | | `--server ` | 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. | | `--runtime ` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) | | `--execution ` | `runtime` | `none` starts a control instance for a hosting user-auth root, with no runtime, connector inspection, workload or terminal execution | | `--console-port ` | none | Protocol-console port | | `--console-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 | | `--max-sessions ` | 64 | Live-session ceiling. Each console pane and each `cotal attach` is one session, so size for agents × 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 | | `--seat-cap ` | `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. A recorded cap that cannot be read fails the start, naming the record's path, and the next step is to remove or repair that record. With no record, the start uses `unlimited` | | `--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 `/` | | `--roster ` | none | Declarative roster to boot at startup. See [Roster files](define-a-team.mdx#roster-files) | | `--launch ` | none | Resolved manifest launch spec (from `up -f` / `team boot`) | | `--spawn ` | none | Comma-separated personas to pre-spawn at startup | `--ws-port`, `--resume-attempt`, `--resume-commit-file` and `--resume-commit-sha256` are passed by `cotal up` when it starts the manager, for the console session client and for resuming preserved seats. They are not for direct use. `--resume-commit-token` is the deprecated argv form of the resume commit, still accepted for older callers (see [Backups](cli-backups.mdx#backups)). The manager is the agent supervisor and control plane: it answers `spawn --detach`, `stop`, `ps`, `attach`, and the `cotal_*` manager tools. `cotal up --detach` starts one for you; run `supervise` directly to recover a dead manager or drive a custom runtime. Default runtime is `pty`; install an optional provider first (`cotal ext add @cotal-ai/orca`, `@cotal-ai/tmux`, `@cotal-ai/cmux`, or `@cotal-ai/herdr`) and select it explicitly. A missing provider or app fails loudly; there is no fallback. See [Deploy](deploy.mdx). Boot inventory decides whether this process takes unpinned `spawn`/`launch` on the class rail: if every declared connector is unavailable, those commands stay on this instance rail only (`status` reports `classSpawn: false`). `describe` still answers on the class rail, so an unpinned spawn can bind-fence against a skip member; re-issue, or pin `--on`. With no member on the class rail, an unpinned spawn is refused and names the agent application to add. A partial inventory keeps the class rail and names `--on` on a harness refusal, because sibling inventories are not readable from the serve credential. See [control surface](control-surface.mdx#instance-routing). The manager prints what its boot inventory found once it is up, so a start that is refused prints no boot line. A remote manager's boot goal-index scan skips entries deleted after settlement or withdrawal and returns the remaining live entries for its owner. Purged entries and malformed live rows still refuse the scan; an ordinary terminal deletion does not prevent predecessor reconciliation. ### Control-only execution Use `--execution none` only for a fresh control instance in an existing hosting user-auth root. It requires that space's signing trust, provider pin and user-mode mesh record, without remote manager authority. It rejects `--runtime`, startup workload flags and participant registrations before resolving a Runtime or connector. The mode needs no connector seeding. An existing unclassified identity or retained workload refuses admission; a recorded NONE instance cannot restart as runtime. Do not delete identity or capability files to change modes. NONE retains native registration, status and the selected Manager's empty managed-seat ps. It does not adopt foreground agents, roster peers or HTTP-hosted sessions. Workload operations still need an explicitly selected execution-owning participant Manager and its existing authority. Starting a control instance does not enable an auth service's interactive local-primary policy. NONE also refuses model inspection and caller-driven persona, purge, transcript, preservation, resume, cancellation, turn-yield and self-stop writes. It starts no transcript receiver or sweep. Its native registration, lease, renewal, session-ledger and goal maintenance can still write. Local runtime startup now records identity and execution mode during admission. A later failed authentication or registration can leave those files classified as runtime. Invalid supervise arguments refuse before connector seeding, with one error line rather than seeding first. A refusal names the caught value's `message`, or the value itself when it has none, so an auth provider that rejects with `null` or a string still gets its refusal line and exit status 1. When the broker refuses the credential the manager presents, such as one this folder's signer minted for a space another folder hosts, `supervise` exits 2 with a `PERMISSION_DENIED` refusal under a `Mesh:` heading that names the credential and the broker, and its Next names the folder that hosts the space. Before it connects, `supervise` refuses the same way, with exit 2 under a `Mesh:` heading, and writes nothing in this folder. A space whose own stack in this folder is stopped or not started gets `TARGET_UNAVAILABLE` and `cotal up` there, and with `--server` also the folder whose `cotal up` started that broker. A static or user-auth space that another folder hosts gets `PERMISSION_DENIED` and the folder that hosts it, when this folder holds no signer for it. For a stopped seat with a runtime custody reference, shutdown carries that reference into retirement so the runtime can reap its recorded work before footprint cleanup. Normal `SIGINT`/`SIGTERM` uses the sparing stop policy. The manager stops process-bound seats, releases seats the runtime can leave running, and applies retained-state policy to preserved seats. For a seat selected for stop, the runtime must prove exit before the manager releases its lease or service registration. A stop that cannot prove exit fails loud and keeps manager authority instead of reporting a clean shutdown while an orphan still holds broker rails. When the runtime refuses the stop itself, the shutdown fails at once and its error names the refusal as `stop failed: `. A manager that a controller stack or `service start` runs stops this way on a plain `SIGTERM` that carries no stop request, such as one from `kill`, a systemd stop or a system shutdown, with the default agent policy. Its log shows `received SIGTERM with no stop request`, it writes no stop receipt, and the next `cotal down` records it as already gone. It ignores a `SIGINT` that carries no stop request and logs one warning line, because Ctrl-C reaches it together with the CLI that publishes the request. A manager that does not answer its endpoint, such as a hung or stopped process, cannot report its stop impact, so `cotal down`, `service stop` and `service restart` refuse it `LIFECYCLE_BLOCKED`, exit 2, submit no stop and name its pid. Once that process is ended, as with `kill -KILL `, the same command finds it gone and goes on. After an abrupt manager death, the same logical successor terminalizes only its own durable static slots, verify-evicts the predecessor's broker principal, records that result in the lifecycle's caller-readable audit detail, reaps the predecessor's seat process through any runtime custody reference recorded on the slot, and only then retires the lifecycle and frees the alias. Automatic restart also requires proof that all prior local work ended, including detached tools. A successor needs a durable custody reference and the runtime's work-closure operation; ordinary seat-process reap is insufficient. Missing, failed or timed-out proof refuses recovery and retires the seat without claiming its tools or remote effects were cancelled. Built-in `pty` provides no work-closure guarantee and refuses new `supervise` requests before launch. Its older supervised records cannot authorize recovery without proof. Ordinary launches remain available. A seat on a runtime that can adopt a live seat (tmux) and still running when the successor starts is adopted in place: the successor checks that the recorded tmux server, window and pane are the ones the slot names and that the pane's process still runs, then manages that seat again under its existing credential, without a restart. Both paths read the record the manager writes to `.cotal/seat-records//.json` once a supervised or adoptable seat is ready, rewrites at every restart and credential renewal, and removes when the seat is freed. A spent budget, a missing record, an expired credential, a persona file or credential that no longer matches the record, or an adoptable seat whose pane has exited retires the lifecycle as above. A runtime that custodies its seats reserves that reference before it launches one, and the manager records it on the slot's first durable row, so a manager that dies part-way through a spawn also leaves a seat its successor can address. A same-lifecycle restart or a resume records the new seat's reference on the slot the same way, and when the slot does not take it the restart or resume fails and stops any seat it started, so the slot never names a seat that has already exited while its replacement runs. A resumed seat keeps its retained credentials, so the resume frees it only once its exit is proved; a seat whose stop cannot be proved stays managed, and the resume's error says so, naming a stop the runtime refused as `stop failed: `. A spawn that launched its seat and then failed is rolled back by the manager that launched it, and that rollback reaps the seat through the same reserved reference before the lifecycle retires. Missing or unverified broker evidence keeps the slot terminalizing, and so does a runtime that cannot reap by reference. While the name stays held, a same-name spawn or a resume re-drives that teardown with the same recorded reference, so it reaps the seat again or keeps the name held. ### Participant manager authority A `meshes add --mode user` entry is a **participant** registration, not hosting authority. A participant may run `supervise` only when the host advertises the remote manager authority service and the signed-in actor has the dedicated `supervise` ledger scope. The CLI obtains the closed, loopback-only `manager-service` view; `spawn` and `admin` do not substitute for that scope. The host issues the manager's public-nkey JWT material through its lifecycle-bound prepare → activate → renew protocol, never by handing the participant a signer or static provisioner credential. The host also performs instance-scoped eviction and guarded gate reconciliation. A remote manager refreshes its short-lived registration executor before clean deregistration, so a long-running process removes its service row on `SIGINT` or `SIGTERM`. After an unclean stop, the same instance verify-evicts its superseded family and advances the process epoch. If an abandoned frozen gate holds the manager governance slot, a different supervise-scoped manager asks the host to reconcile that holder after a complete gone verdict, then retries its registration once. A remote supervise never uses local signing trust: with host-issued authority in hand, the manager mints from that authority alone and consults local records only to refuse a conflict, namely the supervised space's own trust records under the cwd root. A root that hosts another static space beside the sign-in is a normal configuration and is never read as this space's trust. The broker URL in the registry entry decides the transport. A remote broker is often published over a `wss://` edge rather than a raw `nats://` port, and `supervise` dials whichever scheme the record holds, starting with the manager-authority registration it runs before the manager exists. The record also decides whether that registration requires TLS, so a participant never downgrades the credential exchange to a plaintext connection the registry did not describe. Without that advertised host service or scope, `supervise` refuses before it starts a manager. Run `cotal spawn` without `--detach` to launch a foreground agent, or ask the space host to enable the authority service and grant `supervise` for detached agents. If a running remote manager loses renewal, it reports degraded state and refuses unsafe new starts and restarts; live agents are not silently replaced. Do not run `cotal down` or `cotal up` on a participant machine to repair this condition. ## Owned process services [#owned-process-services] Preset inventory checks the selected installed declaration without importing provider code. It shows declared foreground/multi-instance capabilities, process versus endpoint readiness, and native-request-bound versus direct-process stop policy. Missing, skewed or unreadable metadata leaves the inventory partial, with unknown capability fields. Passing metadata checks is not proof that an application, dependency or service is ready. Linux direct-process instances can use the owned systemd user-unit route described below. An adapter does not acquire that mechanism from its name or preset. The direct-process driver pins the machine, the boot, the OS user and each process's start. On Linux these are the machine ID, the kernel boot ID and the process start ticks. On macOS they are the hardware UUID, the boot session UUID and the process start in microseconds, read through the bundled binding described under the identity pins above. On Windows they are the system ID that Windows gives this publisher's apps on the machine (`GetSystemIdForPublisher`), the account's SID and each process's creation time, read through the same binding. The record also keeps where Windows derived that ID: a TPM, UEFI or the registry. An ID from the registry changes when Windows is installed again. When Windows has none of these, the driver refuses. Windows records no boot pin. The custodian described below holds each instance's job instead, and a restart ends it. ### Supervisor observations Instance reports separate recorded supervisor policy from supervisor discovery, which remains not checked by this direct-process driver. A recorded `none` means no supervisor assigned by this controller. It is not proof that an external unit cannot supervise or restart the process. A retained supervisor receipt binds its definition revision, full instance, machine and OS user. Its file view hashes only the recorded physical unit, without printing its body or environment. Matching bytes are not loaded/active state or enabled-autostart proof. Missing, modified or unreadable unit files do not turn a receipt into an uninstalled service. Direct start/stop/remove refuse while such a receipt is retained; they do not launch beside a unit or implicitly uninstall it. `service status` can read an unchanged recorded systemd user unit on the same host through one bounded exact-unit query. It checks the returned physical fragment and reports native state, main-PID attribution, invocation, reload need and presence of unverified drop-ins. Missing or ambiguous observations stay partial. It neither prints unit/environment contents nor discovers ownership from names or cwd. This observation proves neither endpoint readiness nor effective login/reboot autostart. Inventory and direct-process dry-runs perform no OS query; Linux native-unit removal dry-runs make one bounded exact-unit status read. On Linux, `service install --instance --dry-run` asks the systemd user manager for its config directory, then renders a bounded direct-process unit and private environment candidates and reports their paths and digests without printing their bytes or the inherited `PATH`. A user manager that does not answer `systemctl --user show-environment` refuses `DEPENDENCY_UNAVAILABLE`, naming that command, with a Next to run from a logged-in session. With no existing instance, the inspection checks the declared process, durable CLI entry and user manager, then reports that apply would allocate a stopped full ID; it invents neither an ID nor a unit candidate. On macOS the same preview renders a bounded LaunchAgent plist candidate for a settled stopped direct-process instance, with its label, path and digest but not its body or inherited `PATH`. It checks the `Library/LaunchAgents` segments without following symlinks; it does not query a launchd domain, load a job or enable autostart. An existing plist remains unattributed. With no instance it reports a possible stopped-ID allocation, not a plist. macOS install apply refuses, because it has no native controller or custody records. Linux bare install allocates that stopped full ID only after its source validation; it does not start a process. If later installation fails, the allocation is reported as a changed resource rather than hidden or retried. The unit environment pins an absolute-entry `PATH` from this invocation; relative or empty entries refuse rather than acquiring another working directory at boot. It requires a settled stopped instance, its current definition revision and no recorded supervisor, host environment reference or credential reference. An existing unit at that path remains unattributed, not owned. The directory's parent ownership, private seed, receipt intent, loaded unit and autostart are not established; the preview is partial and authorizes no install. Linux apply retains a per-step intent before exclusive private file writes, isolated connector seeding, a bounded private-tree fingerprint, user manager reload, enable without `--now`, and the exact supervisor receipt. It returns the full instance and observed unit-file state, not a process-readiness or effective reboot-autostart claim. A retained installation or removal intent blocks a new install and is reported as partial by `service status` with its exact unit path, private-store path, current step and last acknowledged step. A bounded native read for each retained install, removal or start/stop/reseed action distinguishes an unchanged physical unit fragment from a name-only status when the file is missing or modified. It also classifies the private directory without inspecting its children. None of these reads permits an effect to resume. It cannot authorize a blind retry. A changed pre-effect target leaves a planned step and a partial receipt; an unconfirmed submitted effect is unknown and is never automatically repeated. `service remove` refuses while any native attempt names the definition. There is no explicit reconciliation for an uncertain attempt. `service uninstall --instance --dry-run` reads only a matching recorded systemd unit, its private-store directory and one native status snapshot. A retained native action, or missing, modified or foreign receipt files, refuses. An owned private directory does not prove its descendants unchanged. A separate unexposed removal candidate requires a bounded no-follow fingerprint of the complete private tree to match the install receipt. Changed content is preserved and removal refuses. The preview does not reserve a stopped unit, enable state or removal authority; its report remains partial. Linux apply requires the exact unchanged loaded inactive unit and private tree, with `--yes` in scripts or a scoped terminal confirmation. It disables autostart, removes only the pinned unit and private tree, reloads the user manager, then removes the supervisor receipt. The definition and stopped instance record remain. ### Native unit lifecycle A retained native start, stop or reseed action is reported as partial by `service status`; it does not authorize another request. Native start and stop require the exact owned receipt and current native unit observation. An older private store is reseeded while the unit is stopped, with a separate retained action and refreshed generation/tree digest before start. An unconfirmed seed is never retried. Restart uses the same exact stop and subsequent start; if start does not confirm, the stopped original remains in its result. Uninstall retains a step before disable, exact unit unlink, reload, private tree removal and receipt deletion. A non-confirming step remains visible in `service status`; a missing unit alone never clears its attempt. ### Private unit entry The private unit entry resolves only its built-in command and validates the explicit controller home, space, definition and full instance before any extension overlay, repair or package load. It refuses an inherited managed-spawn handoff without reading or deleting that file. Before native replacement, it requires the recorded private seed generation, executable provenance and absence of unfinished seed markers; it never reseeds that store at boot. For a recorded direct-process unit it also requires no retained installation or removal intent, the current native unit invocation and matching physical fragment before replacing itself with the saved executable. It does not fork a detached wrapper or retry execution. A starting record does not establish successful replacement or readiness; an interrupted replacement remains unknown. A later unit invocation refuses an unsettled starting, stopping or unknown attempt instead of replacing its request. Install dry-run makes only the bounded native directory read above; uninstall dry-run reads the exact recorded unit and private-store directory. Neither allocates an instance, enables autostart nor removes a receipt or unit. ### Direct process lifecycle Process definitions support `service start/stop/restart/status/logs `. The driver requires a stable machine identity and readable process-creation tokens. Linux and macOS add the boot identity. Windows adds the account's SID and a custodian that holds the service's job, described below. It refuses another platform instead of using PID-only ownership. It launches executable and argv separately without a shell. No broker, manager or other dependency is started implicitly. On Windows each background instance runs under its own custodian. The custodian is a detached Cotal process that creates the program inside a new job object. The job ends every member when its last handle closes and allows no breakaway, and only the custodian holds that handle. So a custodian that exits for any reason, a restart of the computer included, ends the service and everything it started. Commands reach the custodian over a named pipe and prove that they hold the instance's 32-byte capability, and the custodian proves the same back before anything is done. The capability lives in a protected file beside the instance records, which name only that file. A stop terminates the whole job and waits until it is empty, and its receipt records the signal `job-terminate`. When the program has exited but processes it started still run, `service status` reports the process as `members-running` rather than gone, and a stop ends them. `cotal down` refuses a stack with such a member and names that stop. Windows has no foreground start. A manager's agents run in its job, so a manager stop without `--with-agents` is refused while the job still holds them. Instance IDs are assigned once and printed in full. With several records, scripts need `--instance `; a terminal can choose from those IDs. `start --new-instance` explicitly requests another process-driver instance. Otherwise one serving instance is a no-op and an unresolved prior launch blocks another allocation. A launch that failed before its child existed is recorded as failed, not unresolved. A stopped instance retains its logical ID and receives a new launch request. Unknown startup retains its receipt and is not resubmitted. Only safe base environment names and the definition's selected `envRefs` are passed. A declared protected credential file is forwarded to that program as the `COTAL_CREDS` path, not credential bytes. Control credentials are not inherited wholesale. The program still runs as the current OS user; this is not a process sandbox. Native managers require their service adapter and agent-impact guards and are refused by generic process launch/stop. ### Adapter declarations Adapter definitions use the same owned direct-child controller. The local-process provider adds a service ABI declaration and launch/readiness hooks. Its closed option schema supports bounded objects, arrays and scalar fields, with no schema references, regular expressions, coercion, defaults or unknown-option removal. Every object closes its declared fields. An unsupported schema refuses rather than being widened. A declared path option and its ancestors follow the [`space create`](cli-selection.mdx#selecting-a-space) rule for other local writers; a private file stays owner-only. One refusal names the option's entry and every unsafe directory above it, with one `chmod` that fixes them all. `npm i -g` under umask 0002, an ordinary Ubuntu user's default, leaves the bundled `nats-server` and its directories group-writable, so where the private-group proof is unavailable, `up` refuses the broker's `binary` path and names that `chmod g-w`. When every unsafe folder is inside Cotal's own install root, as it is for the runtime Node an earlier installer left group-writable, the refusal says the folders belong to Cotal's own install and names `cotal update` as the fix. The provider declares foreground, multi-instance and log capabilities. An explicit start records its definition revision, full instance, launch request and adapter package/version/declaration fingerprint before loading provider code. The launch hook receives the definition's `envRefs` names, never their values, and returns executable, argv and working directory separately. It must not launch children, mint credentials or start dependencies while planning. A changed adapter is not adopted by a retained instance. Native stop declarations add closed impact and terminal hooks; a missing hook refuses instead of using generic signals. The manager's adapter supplies its own native policy and receipts. A preset is not installed under an OS supervisor. Readiness hooks receive the pinned process and a bounded cancellation signal. Ready observations must match that process and carry a current observation time. An endpoint-serving declaration also returns its checked native class, instance, principal, lifecycle and epoch. The adapter must correlate those native coordinates with its owned launch using its supported authority; a matching name or successful HTTP request is insufficient. Reports preserve those coordinates and their observation time. Local status does not refresh them or promote them to current live authorization. A failed or timed-out hook retains an unknown receipt and is not retried. A cancellation signal does not prove third-party code stopped working. Extension removal checks retained service records in every local service space, including spaces absent from the mesh-selection registry. Live, foreign, unreadable or unresolved instances block removal. A starting intent and the extension writer lock prevent an uninstall from crossing the controller's final provider check. Providers still run as the current OS user, not in a sandbox. Process readiness waits up to one second, or its deadline when that is shorter, for the process to exit. One that exits by then fails the start with `OPERATION_FAILED`, exit 5, and its exit code is recorded; a restart whose replacement exits so is partial, exit 4, and names that exit code. A background start also quotes the last line the process logged during that launch, and says its log records the cause when it logged nothing. A line over 1000 characters is quoted by its start and its last `; next:` step. A foreground start says its output went to this terminal and was not captured in its log, and names no log to read. A live creation-pinned direct process is reported Running. HTTP readiness uses a bounded loopback request with credentials omitted and redirects disabled. Its success is Reachable, not proof that the child owns that listener or serves a native endpoint. Status labels the stored readiness observation and its time separately from the current process observation. Before it launches a process with HTTP readiness, `service start` sends one such request. When something already answers there, such as an instance of the same definition on its port, any answer could come from it rather than the new process, so the start refuses `CONFLICT`, exit 2, names the address and that instance, and launches nothing. When no instance answers there, the refusal names each process of this account that listens there by PID and program name, as `SERVICE_ADDRESS_HELD` does, and its Next says to stop that process. The start does not signal it. When the HTTP deadline passes first, `service start` names the loopback address without the URL's path, the expected status and the deadline. When the service kept answering another status, such as 503, it exits 5 with `OPERATION_FAILED` and names that status. When the last check got no answer, such as a refused connection, it exits 3, names what it saw, and its Next also names `cotal service show `, which shows the readiness deadline. Either way the process is left running, and the Next names the `service logs` and `service stop` for that exact instance. A Ctrl-C or `SIGTERM` while a background `service start` waits does not reach the service, which keeps running. The start exits 130 or 143 and names the instance it left. Its Next is `cotal service start ` to wait for that instance's readiness again, or the `service stop` for it. Until a later command settles that start, `service status` and `cotal status` say the start was interrupted. ### Process stop policy Stop and restart default to cancellation in a terminal and need `--yes` in scripts. The selected record revision and creation identity are checked before signals, including escalation. A reused PID is never signalled as the old process. Restart holds the definition operation across the confirmed old-process exit and one replacement launch. An unconfirmed stop cannot cause a replacement. `--dry-run` creates no instance and sends no process signal. ### Foreground process lifetime `start --foreground` is an owned foreground process, not an attachable view. It rejects JSON and cannot adopt an already running background instance into the terminal. Before allocation it prints the terminal lifetime and output policy on stderr. Native auth and Linear presets use their installed signal handlers through this same flow; neither endpoint readiness nor native cleanup is inferred from process exit. A foreground manager stops through the same request-bound native stop as `service stop`, keeping its agents: the SIGINT or SIGTERM the CLI receives is the confirmation. Ctrl-C also reaches the native child, which ignores a `SIGINT` that carries no stop request and prints one warning line for it. A plain `SIGTERM` stops it with the default agent policy. Native service child boot never repairs or seeds connector packages. Background stdout/stderr go to owner-private logs. A start or restart moves a log of 16 MiB or more to `.1`, replacing an earlier one, and the run writes a fresh log, so the log grows until the next start. `logs --tail <1..1000>` reads back from the end of the log until it has that many lines, reading at most 16 MiB. The result is partial only when those 16 MiB hold fewer lines than the tail asked for. `service remove` deletes `` and `.1` for each instance it removes. Foreground output is not captured there. Log text is not promised to be secret-redacted. Live following uses the bounded read-only stream described below. ### Systemd journal logs For a recorded systemd user unit, logs use its saved original invocation, boot and OS user, not the native manager's current invocation. Finite tails read at most 1 MiB and 1000 records. Individual native JSON records are capped at 256 KiB. Native journal cursors and microsecond wall/monotonic timestamps are kept; binary or repeated message values stay native JSON. Missing invocation evidence refuses without substituting an old direct-child file. Native diagnostics are withheld, not suppressed by the query; their presence makes a finite result partial. Journal following freezes those same selectors. It reports oversized, incomplete or unattributed records as gaps and emits native cursors, rather than inventing a sequence. Ctrl-C closes only the owned journal reader. It never signals the service or adopts a later invocation. This scope includes readable process-origin messages, not every user-manager lifecycle diagnostic. Journal access and retention may omit messages, and an empty result does not prove there was no output or work. Journald must retain an accessible per-user journal; no alternate file, privilege escalation or remote log service is used. These are authorized local logs and can contain secrets. A background start runs its process as the leader of its own process group. `service stop`, and the stop half of a restart, signal that group and wait until the process and every member of the group are gone, so a descendant left in the group, such as a server a shell backgrounded, ends with it. A foreground start shares the command's group, so only its own process is signalled. `service stop --dry-run` names which it would signal, the process group with its member count or the process alone, and the grace period before `SIGKILL`; `--json` carries it as `stopSignal`. A descendant that leaves the group, such as one started with `setsid`, is not followed, and a stopped/direct-process-gone observation is not proof that all external work ended. When the HTTP readiness address of a saved service still answers while none of its instances runs, `service stop`, `service status`, `cotal service`, `cotal status` and `cotal down` print a `SERVICE_ADDRESS_HELD` warning. It names the address and any process of this account that listens there, by PID and program name. The process driver itself provides no service presets, agent-aware manager control, owned OS units, non-Linux lifecycle parity or full preset and OS-supervisor integration. ### Direct child file logs `service logs --instance --follow` follows that private file without controlling the service. JSON emits log snapshot/event/connection/gap/error/end records. Events carry native file identity and byte offsets, not a manufactured global sequence. Initial tails and reader backlog are bounded at 1 MiB and chunks at 16384 bytes, with explicit gaps for omissions, replacement or truncation. Instance-generation changes are attributed. UTF-8 decoding can replace invalid bytes and is labeled accordingly. Output backpressure pauses the reader instead of growing a buffer without bounds. Ctrl-C, termination and closed output end the view, not the service. `auth retire [--space ] [--yes]` validates the native UID and resolves the selected auth target. A lifecycle UID is 26 to 32 lowercase letters and digits, and `cotal ps --wide` shows each seat's. On a user-auth mesh that runs elsewhere and advertises an agent-provisioning endpoint, it releases that one lifecycle there with your login ([Held agent names](identity-and-auth.mdx#held-agent-names)) and reports the mesh's answer: revoked, already revoked (`no-op`), `TARGET_NOT_FOUND` (exit 2) for a lifecycle the mesh does not list, or `OPERATION_UNKNOWN` (exit 3) for a release the mesh could not confirm, which is not repeated. A revoked lifecycle may still be retiring. On any other mesh the CLI refuses `UNSUPPORTED` (exit 2): the registered auth endpoint accepts retirement only from the requester's own current serve identity and gate, which this CLI does not have. It neither borrows a manager identity nor substitutes the interactive or managed loopback host doors. `--yes` cannot create authority, and no retirement request is sent. The refusal names the levers that work: [auth revoke-credential](cli-auth.mdx#auth-revoke-credential) for one minted credential, which keeps the store, and rotating the space identity, which stops every credential the space issued and removes the space's store with its history (see [auth mint](cli-auth.mdx#auth-mint)). The rotation also leaves earlier full backups of the space restoring only their registry. `auth redeem [--space ]` selects the existing local auth target and refuses with `UNSUPPORTED` because its conferring-handle redemption path is not exposed. It does not read the file, consume a handle or issue credentials. Session grants, agent enrollment and parsing an unverified handle are not substituted for that authority. ### Scoped help Nested help follows declared command words, for example `cotal help auth handle inspect`. Aliases resolve within their declared parent. Help reads cached metadata and imports no provider, starts no service and opens no credential file. A command's `-h` selects the deepest declared mode before its data arguments; it does not execute that mode. Scoped options refer to the command's shared flag definitions. Completion uses the same declared subgroups and option names, without treating an option's value as another command word. ## daemon [#daemon] A recorded native machine daemon checks for updates daily with up to one hour of jitter. A newly seen release waits one hour before installation. Automatic installation uses `cotal update --self` and waits until every hosted Manager is empty, then holds its admission fence through the update. Running agents are not stopped to make room for an update. An explicit daemon home or a daemon not started by its recorded native service does not install automatic updates. `cotal daemon status` includes an Updates section in human output and an optional `update` object in JSON and desktop status replies. Its fields are `current`, `latest`, `lastCheck`, `lastResult`, `nextAttempt`, `automatic`, `failures` and `channel`. The channel is `npm` for standalone installations and `desktop` for the app's bundled runtime. Desktop runtimes keep registry checks but report automatic installation off and direct updates to the signed app installer. Older daemons may omit the object or channel. Refusals before replacement do not count as failed installations. A busy computer retries after 60 seconds. Registry or extension-closure unavailability retries after one hour. An attempt that begins replacement and does not reach the target version counts as a failure and waits one hour before retry. Two such failures of one version stop installation attempts for that version until a newer release appears or a person repairs it manually. If the updater cannot reconcile or restore a complete generation, automatic updates pause and status names the installed version and `cotal update --self` as the repair command. Use `cotal update --auto off` to disable automatic installation while keeping CLI notices. ```bash cotal daemon setup [--idp ] [--client-id ] [--daemon-home ] [--json|--plain] cotal daemon setup --space [--idp ] [--client-id ] [--no-manager] [--wait ] [--daemon-home ] [--json|--plain] cotal daemon install [--daemon-home ] [--dry-run] [--json|--plain] cotal daemon uninstall [--daemon-home ] [--yes|--dry-run] [--json|--plain] cotal daemon start [--daemon-home ] [--json|--plain] cotal daemon serve [--daemon-home ] [--json|--plain] cotal daemon status [--space ] [--daemon-home ] [--json|--plain] cotal daemon login --idp [--client-id ] [--daemon-home ] [--json|--plain] cotal daemon spaces [--refresh] [--daemon-home ] [--json|--plain] cotal daemon desktop [--daemon-home ] cotal daemon connect-check --space [--daemon-home ] [--json|--plain] cotal daemon register --space [--daemon-home ] [--json|--plain] cotal daemon service-connect --space [--daemon-home ] [--json|--plain] cotal daemon service-disconnect --space [--daemon-home ] [--json|--plain] cotal daemon service-status --space [--daemon-home ] [--json|--plain] ``` Connect this computer once with `cotal daemon setup --idp `. It installs or uses the recorded daemon service, asks for one device approval when needed, and registers the computer with your account without turning on a space. The result names your account and computer. Turn on the spaces that can use it in the provider's dashboard. Run `cotal daemon setup` from a terminal that finds newly installed agent applications to refresh the service's PATH when the daemon is signed in to one provider. The provider comes from the daemon's reply; the CLI does not open the sign-in. With no installed daemon, no sign-in or several signed-in providers, pass `--idp `; the several-provider refusal lists the choices. An explicit `--idp` keeps the installation and sign-in flow. The result lists spaces already turned on for this computer, or asks you to turn them on in the dashboard. Hosted Managers pick up newly installed applications after the guarded service restart. Active seats block that restart until they stop. When a desired space has no available application, `daemon status` shows this recovery under Next. To connect directly to a space, use `cotal daemon setup --space [--idp ]`. `--no-manager` and `--wait` require `--space`; `--wait` also requires `--no-manager`. Space-specific daemon commands require an explicit `--space ` even when `cotal use` has selected a space. `daemon status` can omit it to read the daemon and all hosted Managers. After setup, use `cotal daemon service-status --space ` to read the machine connection and `cotal daemon service-disconnect --space ` to close it. Setup prints these commands with `--space=` so names beginning with a hyphen also work. After sign-in, the daemon registers an account computer with hosts that support it. With several accounts, the status summary prefers a supported provider and breaks ties by provider URL, then subject. The `accounts` list retains each account's state. Catalog refresh replies include an optional `failedProviders` list of provider URLs. The desktop app reports other providers' failures as a notice when the active provider refreshed successfully. Each issuer and subject pair has its own protected, persisted computer UUID. It sends a metadata heartbeat every 60 seconds and polls the account's desired spaces every five seconds. Desired spaces start or inspect the Manager through the same hosting sequence as setup. Each space's connection check decides whether machine-service registration and owner approval are needed. Spaces without service endpoints still run their Manager and report its state. Removing a space stops its Manager and disconnects its machine service when one exists. The Manager stops and retires its running agents before exiting. A clean stop has no last-exit diagnostic. The stopped Manager's next step points to enabling the space for this computer in the dashboard. During teardown it reports `stopping` until its child exits. The graceful allowance is 15 seconds plus five seconds per running seat, capped at 60 seconds. An unknown seat count receives the cap. The daemon then allows two seconds after SIGTERM and two seconds to confirm SIGKILL. Seat-count inspection takes at most ten seconds, so the child stop path is bounded at 74 seconds. Managers stop in parallel. New systemd units and LaunchAgents allow 90 seconds to stop, and systemd sends the initial signal only to the daemon so it can coordinate its children. `daemon status` and its JSON `account` section separate desired spaces from observed connection state, with computer identity, host support, last registration, desired revision and refusals. A host without the route reports account computers unsupported and keeps per-space behavior. Authentication refusal asks you to sign in again. Network failures retain existing connections. On hosts that support account computers, setup with `--space` and local connect or disconnect write the computer's account intent with a fresh revision. A conflicting revision is read again and retried once. The daemon immediately reconciles the result, so dashboard and local changes use the same space list. A local removal caused by a missing registry entry still waits for renewed consent for that binding. Hosts that return 404 retain per-space behavior and report it in status. Existing local connections outside the account list keep running. After a restart, the daemon reads the account's current desired spaces before resuming saved hosting and service connections. A space removed while this computer was off does not start again. If the account request fails, saved connection intents still resume. The local daemon endpoint remains available while this startup check runs. When a space is removed, the daemon also asks its governing authority to remove the recorded Manager registration after its child has exited. The authority checks ownership, requires the broker to affirm that instance gone, and deletes only the inspected registration revision. If removal is unsupported or refused, `cotal daemon status` names the registration left behind and its `cotal endpoint deregister manager --space --yes` recovery command. An unconfirmed removal stays visible across restarts and is not automatically repeated. Inspect that registration before another request. Cleanup never starts a Manager for the removed space. A failed attempt to re-enable hosting keeps that unconfirmed-removal state. It is cleared only when a successor Manager answers or registration removal is confirmed. An explicit `service-disconnect` closes the machine service and forgets its connection intent even when Manager registration cleanup refuses. The cleanup refusal remains visible in status. Daemon commands use the machine home, not the invoking project's `.cotal` records. They run from a removed working directory or a folder with a `.cotal` file, and malformed preferences do not block them. Machine-home validation and the permissions required for each operation still apply. Each mode accepts only its listed flags. A flag from another mode refuses with `USAGE_INVALID` before any daemon or service action, including `--dry-run` on `serve`. Bare `cotal daemon` lists the declared modes and points to `cotal help daemon`, which works without a running daemon. `desktop` serves the desktop app's bounded JSON request on stdin; it has its own protocol output. Its closed `setup` request accepts only a space and provider URL, invokes stock setup, streams `setup-progress` frames, and returns the same launch-readiness or refusal with its next step. The app reads the account's login-shell PATH on Linux and macOS outside the renderer. On Windows it reads current machine and user PATH with the system PowerShell. The bundled Node directory is added after account directories. Absolute-path validation and the hosted-seat restart fence still apply. No renderer-supplied command, argument, path or credential is accepted. Read-only saved-space listing remains available across compatible daemon versions so the app can select a space for setup after replacement. Setup uses the stock owned-service reconciliation and seat fence. Other mismatched-version mutations remain refused. Its `about` request reads host metadata without resolving or contacting a daemon. Other requests resolve the selected daemon only after request validation. A path-resolution refusal returns a versioned `result` frame with `ok: false`, rather than ordinary CLI text. On Linux, a missing or overlong runtime path is reported as `NO_RUNTIME_DIR` with a fixed message naming `XDG_RUNTIME_DIR`; no raw diagnostic is forwarded. Once a daemon mode begins, its JSON failure names that mode in `command`, as success does. It retains the selected space or normalized login provider in `target` once that value is accepted. A failure's more specific target takes precedence; a target not yet established remains `null`. Rejected provider URLs are never copied into the failure target. Outcomes and exit codes are unchanged. This release serves a local endpoint for status, sign-in, a connection check, a registration request and the daemon's own service connection, and installs itself as the user's own service: a systemd user service on Linux, a LaunchAgent on macOS, or a scheduled task on Windows. Setup hosts a stock Manager for each requested user-auth space. Its registered machine service exposes `machine-status`. A space connection requires owner approval, broker acceptance and space readiness. It has local transport implementations for Linux, macOS and Windows. On macOS and Windows the endpoint needs the `@cotal-ai/seat` native helper built for that platform; where none is built or shipped, daemon endpoint operations refuse `UNSUPPORTED` (`UNSUPPORTED_PLATFORM`), and so do those operations on any other platform. The default daemon and CLI share the sign-in in the same Cotal home, so signing in through either one does not require a second approval in the other. An explicit `--daemon-home` keeps its own sign-in, separate from the CLI's Cotal home. `start` starts the recorded, unchanged service without restarting an instance that is already running. `spaces` lists the registry through the daemon; `--refresh` uses the daemon-held sign-in to refresh its catalogs. Catalog rows in the `spaces` reply include optional `kind` and `role` fields from the saved catalog entry. Local entries omit these fields. With no cached sign-in, a refresh reports `not-signed-in`; the CLI refuses with `AUTH_REQUIRED` (exit 2) and directs you to `cotal daemon login --idp `. Saved spaces remain listed, and listing without `--refresh` still works. Name collisions and unreadable registrations are reported rather than replaced. Names outside the daemon's 1-64 letters, digits, underscores or hyphens count as unreadable registrations and are not offered in its CLI list or the desktop space selector. Their records remain unchanged. Listing them reports a partial outcome (exit 4). An unrelated damaged registration does not block catalog refresh: healthy catalog entries still update, and the listing reports the unreadable count with a partial outcome (exit 4). A damaged canonical registration and its usable legacy duplicate stay untouched, including during failure reporting and catalog removal. A selection naming that registration stays selected. The desktop app uses these same controls. Its source and native build instructions are in the [desktop README](../desktop/README.md). Closing its tray UI does not stop the installed daemon. Its first run offers Continue with Cotal Cloud or a custom host, opens browser approval after a click, then offers Connect this computer. Its four steps are Welcome, Sign in, Connect and Ready. A connection link prefills its host and space without starting setup. The app selects the linked space, then the personal space, then an account's only space. Several team spaces without a personal space show a choice on Connect. Nothing connects before the person's click. Ready lists the spaces using this computer, with Remove and Add to another space controls. Adding a space requires its own confirmation. Owner approval appears in that space's row. The app reports This computer is connected only with an answering registered Manager and an available connector. Open dashboard is the primary action on that screen and on later opens. Sign-in and space forms appear only when needed. Five named progress steps use structured setup reports, while original diagnostics and exact refusals remain under Details. Missing applications name the applications checked and offer Check again to rerun setup. Machine-service approval is separate. After installing or starting its recorded background service, the app waits up to 15 seconds for the daemon to answer. A timeout reports `DEPENDENCY_UNAVAILABLE` and directs you to `cotal daemon status` for diagnostics. The service remains installed; the timeout does not ask you to install it again or report it as ready. `setup` validates its arguments, including the space name, before installing or starting the service or changing connector state. Space names use 1 to 64 letters, digits, underscores or hyphens. It makes a computer ready to launch agents from a space with one command. It installs the user service, discovers the space through the daemon-held sign-in, signs in by device code when needed, and asks the daemon to start the space's Manager. Before starting it, the daemon reconciles stock connectors in its own environment and refuses if that preparation fails. The hosted Manager gets the stock default persona in its own catalog, without replacing an existing file, so `cotal spawn --on --space ` can launch it. Setup succeeds when that registered instance answers `status` with an available connector. With no available connector it refuses with `no available connector` and a harness-install next step. Connector availability is read from the daemon-hosted Manager. Install an application named in the remedy and confirm it runs in the terminal that runs setup, then run the same setup command again. On Linux and macOS, when that terminal resolves an application the Manager cannot find, setup refreshes the service's pinned PATH. Terminal directories come first, followed by existing service directories without duplicates. Setup updates the recorded service source and digest, reloads it, and checks availability again. On macOS, reload waits up to ten seconds for the old LaunchAgent to unload before loading the plist. If it does not unload, setup reports an unconfirmed outcome. Run the same setup command again to start the recorded service. It restarts only after every hosted Manager confirms it has no seats and blocks new launches. Setup prints this wait. If a recent observation missed a Manager, restart preparation rechecks its pinned instance for up to 15 seconds before refusing. A refusal names the condition and a next step; the daemon journal records the operation and cause. A recovered observation does not bypass the idle-seat fence. If any seat is active, nothing is changed or restarted. Stop the seats and run the setup command named in the refusal from the same terminal. Windows scheduled tasks do not pin PATH. The native entry reads the current machine and user PATH on each start. If setup finds an application that the running task cannot resolve, it restarts that recorded service under the same hosted-seat fence. Windows restarts during setup and update use the same verified shutdown sequence. Each restart first prepares shutdown on the verified old daemon connection, disables the owned task and confirms its native instances have stopped. It then commits daemon shutdown and waits for the old process, endpoint and lifetime lock to be released before enabling and starting the task. Setup reports a refreshed PATH only after a different verified daemon lifetime answers with the expected version and protocol. An ambiguous stop or start reports an unconfirmed outcome with its phase and recovery guidance, retaining the installation records and restart fence. A validated daemon refusal retains its code and message in the failure detail and JSON native cause. Inspect daemon status and the recorded task before recovery. Setup does not clear a fence through a new connection. After a failed pause, autostart is re-enabled without running a replacement when the pinned old daemon still answers on the same connection, or when no daemon or native task instance remains and the installation receipt is unchanged. An unresolved native End or ownership check leaves the task untouched and reports why. Setup and Check again recover an owned disabled task with zero instances by enabling and starting it, then requiring a fresh kernel lifetime to answer. Restart progress names the old-process wait (up to 130 seconds) and replacement wait (up to 60 seconds), with periodic updates. When no new application is found, setup keeps the service running. A Manager with no available connector still refreshes its inventory by restarting its idle child. The separate `ai.cotal.machine` registration and connection are reported even while owner approval is pending. That approval never holds up agent launches. JSON reports `launchReady: true`, the registered Manager at `manager.instanceId`, its `manager` status and the separate machine `registration`. The top-level `instanceId`, when present, identifies the machine-service registration. The human report prints `Manager instance: ` and the machine-service connection state using the same words as `service-status`. A space without machine-service endpoints reports `unsupported` while the sign-in step retains the session check's result. With `--no-manager`, a failed connection prerequisite stops setup before sign-in or registration. An unregistered space must be added first, and a space without service endpoints refuses as unsupported. Setup reports that check's refusal without repeating it through registration. Progress goes to stderr in every presentation, including `--json`, which writes one document on stdout. With `--no-manager`, the connect stage reports its final state even when it connects on the first attempt or `--wait 0` stops at pending approval. When approval arrives during a wait, the final state follows the waiting line. A refusal before connect emits no connect line. Missing `supervise` scope refuses with the Manager's reason and next step. The daemon supervises each Manager as a separate child, restarts it with bounded backoff after a crash, and resumes recorded hosting intents after its own restart. A child that exits while a generation fence is pending is marked restarting with its exit cause, then rescheduled after rollback. It records intent only after status answers. During startup, status names initialization and registration as the pending work. The Manager logs remote authority, contract publication and activation stages. Contract artifacts publish with at most 32 independent acknowledgements in flight, keeping their create-only and verify-on-read checks. The daemon waits for the child's initialization notice before exchanging a pinned caller credential and probing its endpoint. The notice alone never marks it answering. A permanent refusal, such as a missing sign-in or scope, stops automatic retries. A damaged hosting or refusal record appears as a refusal for that space with the file path. Other spaces still answer status. Run `cotal daemon setup --space ` to repair the record after a successful Manager readiness probe. Manager state stays under the daemon's own per-space directory. Its `manager.log` captures stdout and stderr with mode `0600`, rotates at 1 MiB, and retains one previous file as `manager.log.1`. Status includes `lastExit` after a crash and `lastStop` for graceful, terminated or killed shutdowns. A Manager that exits before readiness reports its exit code or signal and its last stderr line. A stopped row keeps its instance and last observation, clears the PID, and names setup as its next step. Status emits a PID only for a retained child, never from a saved refusal record. An expired or revoked daemon session names `cotal daemon login --idp ` in both the refusal and its next step. Hosted repair commands carry `--daemon-home ` when the daemon has an explicit home. Status also includes `logError` if logging fails. The daemon checks the child's parent IPC channel every five to six seconds without an auth callout. Three consecutive missing answers make recovery eligible. Recovery restarts a live child only after its native preservation fence confirms no seats and blocks new admission. An occupied or unresponsive child stays running without losing custody. An authority or dial failure in the daemon never triggers a restart. Status, per-space Manager status and service status make a fresh instance-pinned probe. Its 15-second bound includes caller credential exchange, broker admission and contract resolution as well as the status request. Local status readers allow that observation to finish. They never reuse an earlier answering result as fresh success. Update checks a retained child's pinned endpoint even when an earlier observation marked it starting. `observedAt` names the latest completed observation. No project folder or selected space is changed. `service-disconnect` stops that space's hosted Manager and removes its hosting intent. `uninstall` stops all hosted Managers and removes their intents from per-space directories, leaving the lifetime lock file and unrelated records alone. A later install has no Manager to resume from those removed intents. Pause and rename are not implemented. Setup reuses completed stages but probes an existing Manager again, pinned to its recorded instance, before reporting launch-readiness. If the running daemon's version or protocol differs from the invoking CLI, setup first compares the recorded service's executable and entry with this CLI. For the same installation it prepares hosted children through the local preservation guard, restarts its recorded native service, and waits for the matching daemon. A different installation is never restarted. Setup uses it when its protocol and hosting operations are compatible. Otherwise the refusal names both installations and asks you to run setup from the daemon's installation, or run `cotal daemon uninstall` and then setup from this installation. An occupied legacy Manager blocks that preparation and remains running. Stop its seats before retrying setup. Aborting preparation restores the Manager's standing-credential and run-hosting renewal schedules. Active seats refuse `SEATS_ACTIVE` with the space and the command to stop them. Admission during an installed generation transition refuses `GENERATION_PENDING` and asks the operator to wait before retrying setup. An occupied Manager is counted without entering preservation. Activation waits with retries doubling from five seconds to five minutes, and status shows the installed and running versions with the seat count and space. Only changed waiting causes are logged. A foreground daemon reports the pending version and stays running. A daemon started by its recorded native service can activate once every Manager is empty. Native service ownership is observed off the serving event loop as owned, not owned or unknown. Status and sign-in requests remain available while native supervisor tools run. Only owned results are cached. Restart and uninstall preparation re-observe unless ownership was verified, replacing an unfinished startup check with one bounded to 65 seconds inside the installer's 70-second wait. A refusal distinguishes a missing native provenance from an observation that remains unknown. The daemon logs safe reason codes without native error text or file paths. Automatic activation requires the initial observation to be owned; a failed or unfinished check never grants native-service authority. Generation activation prepares idle Managers, then restarts the daemon. The new daemon reconciles before admitting a hosted Manager. The old generation never reopens admission after changing the shared connector store. The daemon reconciles stock connectors in its own environment before starting or restarting a hosted Manager. It checks the installed CLI against its loaded version before using the seed stamp. A replaced installation cannot start another child under the old daemon. A verified native service activates it after seats stop. Without a verified native launch marker, status names setup from the installed CLI for a recorded service, or an explicit foreground stop followed by serve. A failed reconcile refuses the space with its cause and repair command and retries from five seconds up to five minutes. The serving Manager never runs first-use seeding. A child rejects a handed generation that differs from the Manager implementation it loaded. Every Manager using installed extensions checks each first-party connector's manifest version against the generation loaded by that Manager before a new launch, including a connector already loaded in that process. This includes Managers started by `up` and `manager start`. A mismatch names both generations and asks you to stop its seats and restart it from the installed CLI. Existing seats stay running. Third-party connectors keep their own versions. This readiness check does not prove login or reboot autostart on any OS. After a successful `cotal update --self`, a running default daemon from the same installation is restarted when its version differs, including older daemons that host no Managers. On Linux without `XDG_RUNTIME_DIR`, update has no default daemon endpoint to check and continues with the ordinary Manager continuity and installation checks. Update reports and skips hosted records with no live child, including stopped or refused Managers. A live child whose custody cannot be confirmed still blocks update and names service-disconnect for that space before retrying. If a native restart stops the daemon but cannot start it, the failure retains the native cause and names `cotal daemon start` after correcting that cause. A failed preservation rollback does not replace the restart failure. `--no-manager` keeps the machine-service-only setup path and waits for owner approval. `--wait` without `--no-manager` refuses `USAGE_INVALID` before setup starts. On the connection-only path `--wait ` bounds the wait, 900 by default; `--wait 0` reports what it reached and stops. A wait that runs out is `partial` (`OPERATION_PARTIAL`) with `APPROVAL_PENDING` as the native code: the service is installed and the registration is sent, and the message says so, so nothing is lost by running it again. An answer that cannot improve is a refusal carrying the space's own reason, such as `PERMISSION_DENIED` (`PROPOSAL_REFUSED`), and the connection stage is never reached. With `--no-manager`, only a connected machine exits 0. `--idp` is needed only when this machine is not signed in. Without it, a machine that needs a sign-in refuses `AUTH_REQUIRED` (`NOT_SIGNED_IN`) and names the provider's login command, before any registration. When a registered space needs sign-in, setup checks `--idp` against its pinned provider before starting device approval. A different provider refuses `USAGE_INVALID` (`IDP_MISMATCH`) and names the setup command with the pinned provider. This applies to both Manager and `--no-manager` setup. An expired or revoked session rejected by the provider needs a new device sign-in, even when a cached session remains on disk. A provider outage or unusable response stops setup with `DEPENDENCY_UNAVAILABLE`, without asking for a new sign-in or reporting the session as valid. A daemon that is already answering while no installed unit is recorded for it refuses `CONFLICT` (`RUNNING_WITHOUT_UNIT`): installing then would start a second daemon that the lifetime lock refuses, and the running one is not a persistent service. Stop it and run `setup` again, or keep it and use `register` and `service-connect` by hand. `install` runs the daemon as the signed-in user's own service, through the native supervisor of each OS. It runs as that user, never as root or LocalSystem. A system-wide headless profile, such as a system unit, a LaunchDaemon or a Windows service account, is not offered. On Linux it is a systemd user service. It writes one unit to the user manager's own configuration directory, enables it, and starts it. The unit starts at login, restarts the daemon when it dies, and keeps running after you log out, which on a headless server is what linger provides. `install` enables linger for the invoking user and says so in its receipt. A linger it cannot enable fails the install, naming `loginctl enable-linger `, because a unit without it stops at logout. `uninstall` stops the service, disables autostart and removes the unit, and leaves linger enabled, because other user services may rely on it. The unit and its receipt are written before enabling linger. If linger cannot be enabled, the unit stays recorded for `uninstall` and is not enabled or started. On macOS it is a LaunchAgent. It writes a plist to `~/Library/LaunchAgents`, then loads it into the user's GUI domain with `launchctl enable` and `launchctl bootstrap`. launchd starts the daemon when the job loads and at each login, and starts it again whenever it exits. Its output goes to `daemon.log` in the daemon's state directory. It needs a logged-in desktop session of that user: a GUI domain that does not answer refuses `TARGET_UNAVAILABLE`, and so does running it as root. `uninstall` boots the job out and removes the plist. On Windows it is a scheduled task. It writes the task definition to `service-task.xml` in the daemon's state directory and registers it with `schtasks /Create /XML`, then runs it. The stock CLI task starts the packaged `daemon-native.js` entry, which marks the native launch before loading the CLI. The task starts at this user's logon and runs with the user's own token at least privilege, so no password is stored and a standard account can install it without elevation. Its logon trigger repeats every minute and skips a start while the daemon runs, so a daemon that exits starts again within a minute. The daemon is a console program, so its console window shows in the user's session. `uninstall` disables, ends and deletes the task. Before removing its definition and receipt, it also confirms that the daemon process and its local endpoint have stopped. An owned launcher exiting does not by itself prove that its Node child stopped. For a running daemon, uninstall holds a connection authenticated against the daemon's kernel process identity and lifetime record. A native-launch-verified daemon acknowledges a stop request on that connection, then closes its Managers, service connections and endpoint only when uninstall commits shutdown after native removal succeeds. A dropped connection or failed native removal does not commit shutdown. A foreground daemon refuses this request. Uninstall never signals a process by name or an unverified PID. If shutdown cannot be confirmed within 130 seconds, it reports an unknown outcome and retains the source and receipt for inspection and retry. A connection reset during shutdown is rechecked within that bound, never counted as endpoint absence. Native stop can also close the pinned connection before its reply. That transport loss still requires confirmation that the original process is gone and the endpoint no longer answers. When the daemon is not running, uninstall takes its lifetime exclusion before native removal and confirms endpoint absence under that exclusion before deleting the source and receipt. A live recorded process without a verified answering endpoint remains an uncertain outcome. The updated Windows native runtime checks its recorded source under the lifetime lock before publishing. Uninstall holds that same lock through its final endpoint check and removal of both the source and receipt, so a delayed updated native child cannot publish after removal. The task's native entry writes its output to `daemon.log` in the daemon's state directory, replacing the previous launch's log and retaining at most 64 KiB of output. When the daemon does not answer on Windows, `status` names that log and Task Scheduler's state and last result. If the entry cannot load at all, Task Scheduler's last result is the diagnostic and no new log exists. Install refuses before preparing a task if its launch entry is missing or is not a file. Task lookup imports the ScheduledTasks PowerShell module directly so a new profile does not need command discovery across installed modules before checking the task. Native service tools have a 30-second budget. A tool that cannot complete refuses `DEPENDENCY_UNAVAILABLE` and names a timeout, output overflow, startup error code or terminating signal with elapsed time. JSON output includes the native code, elapsed milliseconds and budget under `error.cause`. No further native step runs after that refusal. The unit is `cotal-daemon.service`, or `cotal-daemon-<16 hex>.service` for a daemon with its own `--daemon-home`, derived from that directory as its socket is. The LaunchAgent label is `ai.cotal.daemon`, or `ai.cotal.daemon.<16 hex>`. The task is `cotal-daemon-`, or `cotal-daemon-<16 hex>-`, so two users of one machine never share one. `--dry-run` reports the unit name, path and the sha256 of the bytes it would write, and writes, enables and starts nothing. A second `install` for the same daemon refuses `CONFLICT` and names `cotal daemon uninstall`. Outside a terminal `uninstall` needs `--yes`. `uninstall` acts on the unit `install` recorded: a unit file that is gone refuses `TARGET_NOT_FOUND`, and one whose bytes changed refuses `CONFLICT`, with nothing removed either way. On Linux, `start` and both forms of `uninstall` also require the receipt to name the selected daemon home, its derived unit name and that file in the user manager's unit directory. A copied receipt, changed unit name or redirected path refuses `CONFLICT` before service control or file removal, even when the file's bytes match the recorded digest. On Linux, an unrecorded file, directory or symlink at the unit path refuses `install`, including `--dry-run`, with `CONFLICT` before linger or service changes. A path that cannot be inspected refuses `TARGET_UNAVAILABLE`; only a missing path permits planning to continue. Install and setup guidance retains the selected `--daemon-home`. Setup's service commands also retain `--space`, and a service-only setup retry keeps `--no-manager`. On macOS and Windows, a plist, task definition, loaded label or registered task that no install recorded refuses the install `CONFLICT` with nothing written, and a registered task that no longer runs the recorded command refuses `uninstall` `CONFLICT`. On Linux, `install`, `--dry-run` included, needs a logged-in session of the user who will run the daemon. It reads that user from `USER`, or from `LOGNAME` when `USER` is unset, and the user manager's config directory from `systemctl --user show-environment`. When neither variable names a user it refuses `TARGET_UNAVAILABLE` and names both. When the user manager does not answer, as from a shell whose `XDG_RUNTIME_DIR` is not the user manager's runtime directory, it refuses `DEPENDENCY_UNAVAILABLE` and names that command. These refusals' Next says to run it from a logged-in session. Planning also reads linger with `loginctl show-user --property=Linger --no-ask-password --no-pager`. If `loginctl` cannot run or does not answer within its bound, it refuses `DEPENDENCY_UNAVAILABLE`. Next says to make `loginctl` available on `PATH` and use a logged-in session where it can reach systemd-logind, then retry. This read happens during `--dry-run` too; it does not enable linger. Planning checks the unit directory and its existing ancestors for unsafe ownership, permissions and symlinks without creating missing directories. An unsafe path refuses `PERMISSION_DENIED` before linger is enabled. Publication checks the directory again before any linger change. After the linger calls, installation verifies the published unit's digest and directory protection again before submitting reload, enable or start. A changed source or unsafe path stops registration and keeps the source and receipt for inspection. `serve` validates `COTAL_IDP_TIMEOUT_MS` in its own environment before opening the endpoint or resuming connections. The value must be whole milliseconds from `1` to `2147483647`. A malformed value refuses startup with `USAGE_INVALID` and names the setting and supported range. Correct it before starting the daemon again. No sign-in request or session write occurs. `serve` runs in the foreground and prints one ready line, `daemon ready at `, on stdout once the endpoint is bound and checked. Newly installed native jobs carry a launch marker. Automatic generation activation requires that marker and a verified recorded source from the same installation. An unmarked foreground process or older unit remains running until an explicit guarded restart. Its log lines, such as a recovered socket, go to stderr. That success line is plain text; `--json` shapes its refusals. `status` prints the state, version, protocol, process ID, start time and uptime. It also lists each hosted Manager's state, instance, PID, restart count, last refusal and Next. `--space ` selects one space. JSON carries the same facts. `service-status` reports the machine connection and Manager separately. `state: "ready"` means only that the local control listener is bound and verified, not that a mesh or Cloud connection exists. On Linux the endpoint is `$XDG_RUNTIME_DIR/cotal/daemon.sock`. Without `XDG_RUNTIME_DIR` both forms refuse `TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`). On macOS it is `cotal/daemon.sock` in your cache directory under `/var/folders`, the one `confstr(_CS_DARWIN_USER_CACHE_DIR)` names. No environment variable changes it, so the LaunchAgent and your terminal reach the same socket. A socket path longer than the kernel accepts (107 bytes on Linux, 103 on macOS) refuses `TARGET_UNAVAILABLE` (`NO_RUNTIME_DIR`). On both, the forms refuse `PERMISSION_DENIED` for a runtime directory that is a symlink, not a directory, not owned by you, or not mode `0700`. `status` also refuses `PERMISSION_DENIED` for an endpoint entry that is not a socket, not yours, or not mode `0600`. For a socket of another mode (`SOCKET_WRONG_MODE`), Next says to find the process listening there with `ss -xlp` and stop it, then start the daemon with `cotal daemon serve`. On Windows the endpoint is the named pipe `\\.\pipe\cotal-daemon-`, where `` is your account's security identifier. Every instance of it grants access to your account only and refuses network clients. `serve` creates the first instance only when no process serves that name yet, and otherwise refuses `CONFLICT` (`ALREADY_RUNNING`). A pipe that another account serves refuses `PERMISSION_DENIED` (`PEER_UID_MISMATCH`). The name ends with the process that held it, so a killed daemon leaves nothing to recover. The daemon drops connections from other users, and each side reads the other process's user from the system before a request moves. The system separates users, not processes of one user: any process running as you can use the local controls, including sign-in and cancellation. Each local JSON request line is limited to 64 KiB of UTF-8 text, including its newline. An oversized line receives `LINE_TOO_LONG` and the connection closes without processing later requests. This also applies to an unfinished tail after valid requests. Multiple valid lines are checked separately. Already accepted finite replies have up to five seconds to flush before close. Device sign-in streams are cancelled on close. `status` with no daemon running refuses `TARGET_UNAVAILABLE` (`NOT_RUNNING`). SIGTERM and SIGINT shut down cleanly and remove only the daemon's own socket. SIGKILL or SIGHUP, including closing the terminal, leaves a stale socket, and the next `serve` recovers it by itself. That recovery rests on the lifetime lock below. Each `serve` holds a kernel lock on its lock file (`flock` on Linux and macOS, `LockFileEx` on Windows), which the system releases when the holder dies, SIGKILL included, and records the socket it publishes there by device and inode number. A `serve` that takes the lock therefore knows its predecessor is gone, and because publishing the socket needs the lock, nobody else can be putting one at that path while it holds it. It removes the one socket its own record names, once a connect to it is refused, and binds its own. A filesystem can give a new file the inode number of one it freed, so a socket with the recorded numbers that a process answers on belongs to someone else. A `serve` whose state directory is unreadable, or which finds a socket its record does not name or a process answers on, removes nothing and refuses `CONFLICT` (`ALREADY_RUNNING`), naming the path. When a process answers, the refusal says another process is listening at that path and `serve` does not start until it releases it, and Next names `ss -xlp` and `fuser ` to find that process. A clean shutdown or a refused socket check clears the record, so a socket at the path afterwards is refused the same way, and the reason says the record names no socket. A start that fails before checking the socket, such as a runtime-directory permission refusal, preserves its predecessor's record while releasing the lock. Once the directory is corrected, the next start can recover the same dead socket. A non-socket entry there refuses `PERMISSION_DENIED` (`SOCKET_NOT_SOCKET`) and is left alone. `--daemon-home ` runs or reaches a separate daemon whose own state lives in ``: its sign-in session, its per-space identity, register request and service store, all at the paths below with `` in place of ``. Spaces are still read from the shared Cotal home's mesh registry, which this daemon never writes. `spaces --refresh` updates only the separate home's catalog cache; it does not add or remove shared registrations, write shared sentinel files, or clear the default selected space. Its space list still shows the shared registry. Use `cotal sync` from the shared Cotal home to apply that home's signed-in catalogs there. Its socket is `$XDG_RUNTIME_DIR/cotal/home-<16 hex>/daemon.sock` (on macOS the same name under the cache directory, on Windows the pipe `\\.\pipe\cotal-daemon--home-<16 hex>`), derived from the directory, so it runs beside the default daemon. `` must already exist as an absolute directory you own with mode `0700`, and cannot be the Cotal home. On Windows it must be owned by you, SYSTEM or Administrators, and its access list may grant nothing to anyone else. A relative path or the Cotal home refuses `USAGE_INVALID`, a missing directory `TARGET_NOT_FOUND`, and a symlinked, foreign or loose one `PERMISSION_DENIED`. Nothing is created or repaired. Bash and Zsh completion offer file paths for `--daemon-home`, including existing directories; the directory checks still apply when the command runs. `serve` holds the lifetime lock `/daemon.lock` while it runs. The default daemon holds the same lock at `/daemon/daemon.lock`. A second `serve` for the same daemon refuses `CONFLICT` (`LOCK_HELD`), naming the live holder and when it started. A start that fails for any other reason releases the lock again. Every client command sends nothing unless the process at its socket is the live owner (same pid and process start) of its daemon's lock: `/daemon.lock` with `--daemon-home`, and `/daemon/daemon.lock` in the client's own Cotal home without it. Otherwise it refuses `PERMISSION_DENIED` (`PEER_NOT_LOCK_OWNER`). So a client whose `COTAL_HOME` is not the one the default daemon runs with is refused, because that daemon keeps another Cotal home's state. Next asks you to inspect the conflicting listener with your operating system, use that daemon's `COTAL_HOME` for the default daemon, or use the same directory as the intended daemon for an explicit `--daemon-home`. The refusal stops no process and sends no request. This daemon is a different machine identity. Nothing is copied from the default daemon: it needs its own `login`, its own `register`, and the space owner's approval of its own instance. Registration, registration replay and service activation use the sign-in in the selected daemon home, including when `setup` reaches those steps. A missing sign-in there cannot use a sign-in from the default Cotal home. Renewal keeps using its issued service proof while that proof is valid; reactivation uses the selected home's sign-in again. Without `--daemon-home` every command reaches the default daemon. `login` signs the running daemon in with device approval. The daemon runs the same sign-in as `cotal login` and keeps the session in the machine's Cotal home, where `cotal login` keeps it. The command prints only the approval link, code and expiry to stderr, then the signed-in account. No session token crosses the endpoint or reaches the terminal. `--idp` takes an HTTPS auth URL, or an HTTP loopback IP URL; `--client-id` defaults to `cotal-cli`. Signing in as a different account at the same provider removes the previous account's catalog cache from the selected daemon home. The default daemon also removes that account's discovered space registrations, clearing a selection that named a removed space. Manual registrations and credential files stay. This cleanup does not revoke credentials or stop existing connections. A separate `--daemon-home` never changes the shared registry or its credentials. Signing in as the same account or failing before a replacement session is saved does not remove registrations. One sign-in runs at a time: a second `login` refuses `CONFLICT` (`LOGIN_IN_PROGRESS`) without contacting the provider. During sign-in in `login` or either `setup` path, Ctrl-C or `SIGTERM` requests cancellation and waits for the daemon's answer. Closing the command's connection also requests cancellation on this machine. If cancellation takes effect before persistence, nothing is saved, even if device approval already happened. An already-completed attempt remains completed; cancellation revokes nothing at the provider. A cancelled attempt exits 1 with `USAGE_INVALID` (`CANCELLED`) without printing command help. If the command stops before receiving an attempt ID, it closes the connection and reports `USAGE_INVALID` (`ABANDONED`), also without help. A denied or expired approval refuses `PERMISSION_DENIED` (`DENIED` or `EXPIRED`), and an unreachable provider refuses `DEPENDENCY_UNAVAILABLE` (`UNREACHABLE`); none of these saves a session. An HTTP refusal of the initial device-code request refuses `DEPENDENCY_UNAVAILABLE` (`AUTHORIZATION_REFUSED`), exit 2, before approval or persistence. This classification does not depend on the provider's optional error-body shape. Check that `--idp` names the auth base URL (Better Auth: `/api/auth`) and that the provider accepts `--client-id`. This also applies to sign-in during `daemon setup`. Any other provider answer fails `OPERATION_FAILED` (`REFUSED`). Such a failure can come after the session was saved, so run `login` again. A failure is reported with a fixed sentence; the provider's own error text is never relayed. `status` keeps answering while a sign-in waits. An unreadable `idp-sessions.json` or `space-catalogs.json` refuses sign-in with `DEPENDENCY_UNAVAILABLE` (`LOCAL-STATE-ERROR`) before contacting the provider or saving a session. The refusal names the cache and asks you to inspect and restore it in the selected daemon home. The daemon leaves it unchanged. `spaces --refresh` preserves the same cache diagnosis and recovery step instead of reporting a generic spaces failure. Signing in again cannot repair a damaged cache. The sign-in cache check includes provider keys and session records, not only the file's JSON shape. `connect-check` asks the running daemon whether it could register with a space as its own service. The daemon is not a Manager and never borrows a Manager's identity or authority. The check reads the space's registration on this machine and validates the daemon's sign-in through the provider's token exchange, then reads the space's public account identity from its auth service (`GET /service-endpoint-authority`, no redirects). It sends no registration and persists no credential. The exchange reuses the stock in-memory JWT cache for at most 60 seconds, never past the JWT's expiry margin. The provider decides whether a cached session is expired or revoked; the expiry on disk is advisory. A space whose auth service does not host service endpoints refuses `UNSUPPORTED` (`UNSUPPORTED_BY_SPACE`). The daemon does not fall back to anything else. Before declaring readiness, the daemon checks that the returned proof has a signed JWT shape with a decodable signature segment, a subject and a future expiry, and no future not-before time. This does not verify its signature or replace the space's claim checks. An unusable proof refuses `DEPENDENCY_UNAVAILABLE` (`REFUSED`) without asking for another device sign-in. The connection-check caller allows 65 seconds for the token exchange and authority read, whose default request limits are 30 seconds each. Registration allows 95 seconds, and service connection allows 160 seconds for the subsequent stages. The other refusals are `TARGET_NOT_FOUND` (`NOT_REGISTERED`) for a space this machine has not registered, `UNSUPPORTED` (`NOT_USER_AUTH`) for a space without user sign-in, `AUTH_REQUIRED` (`NOT_SIGNED_IN`) when the machine is not signed in to the space's provider, and `DEPENDENCY_UNAVAILABLE` (`UNREACHABLE` or `REFUSED`) when the sign-in provider or auth service cannot be reached or answers with something unusable. Sign-in refusals include a Next command with the space's registered provider URL. A service connection without a registration request names the selected space in its register command. These remedies appear in both terminal output and JSON `error.remedy`, with `--daemon-home` when a separate daemon was selected. POSIX arguments are shell-quoted, and Windows copy targets PowerShell. Option values beginning with a hyphen use the attached `--space=` form so copying a recovery command does not parse the value as another option. Terminal guidance preserves Unicode punctuation in command arguments, including daemon homes. On success it reports `ready-to-register` with the space's account, the daemon's service name `ai.cotal.machine` and the daemon's own instance ID. Nothing has been registered. The space owner must still approve the daemon, and the service name needs the space operator's binding. The first success creates the daemon's identity for that space at `/daemon//service-identity.json` (mode `0600`). It holds the instance ID and the account, and no credential. Later checks reuse it. If the space later answers with a different account, the check refuses `CONFLICT` (`ACCOUNT_MISMATCH`) and changes nothing: confirm the space was not replaced before you remove that file by hand. `register` asks the running daemon to request its own service registration with a space. It first runs `connect-check` and returns any outcome other than `ready-to-register` unchanged, without contacting the space again. Otherwise it sends one registration request for the daemon's instance with this machine's sign-in, through the space's stock service authority. The daemon never sees or relays that sign-in proof. The request ID is saved before the first send, at `/daemon//register-request.json`, and every later `register`, including after a restart, replays the same request unchanged. The stock writer does not guarantee durability across power loss. An identity or registration record that cannot be read or created refuses `CONFLICT` (`LOCAL_STATE_ERROR`), with fixed text naming the local records. Check their file permissions and restore the original record if it is damaged. Existing records are not replaced and their contents are not printed. `service-status` retains this diagnosis after a failed connection attempt, and `setup` stops instead of waiting for owner approval. Recovery must preserve the instance and request IDs; removing a damaged file to mint a new request is not an automatic repair. The fixed service name binds to one owner per space. Different owners in a shared space need distinct operator-approved endpoint names; this command does not yet select one. The operator must verify or delegate the namespace before binding it. The space answers in one of three ways, each reported with exit 0: - `approval-required`: the space owner must approve this daemon and the space operator must bind its service name `ai.cotal.machine`. - `pending`: the space is processing an approved registration. - `registered`: the registration is recorded. It is not activated or serving, so the machine is still not connected. No release ships a command or page that lets an owner approve the daemon or an operator bind its name. A refusal by the space is `PERMISSION_DENIED` with the space's reason as the native code, such as `PROPOSAL_REFUSED`. A reason the daemon does not recognize is reported as `UNRECOGNIZED`, and the space's own text is never shown. A new proposal after a refusal needs a new request, which `register` does not send. A sign-in the provider rejects (expired or revoked) is `AUTH_REQUIRED`: run `cotal daemon login` again. An unreachable auth provider is `DEPENDENCY_UNAVAILABLE`. Only a request whose outcome is unconfirmed is `OPERATION_UNKNOWN`: run the same command again to replay it. A changed registration for the same instance is refused `CONFLICT` and nothing is sent. `service-connect` connects the running daemon to the space as its own registered service. It replays the saved register request unchanged to read its registration. If the live authority answers with a different account than the pinned identity, it refuses `CONFLICT` (`ACCOUNT_MISMATCH`) before sending the registration or requesting credentials. The identity and register request stay unchanged, and `service-status` retains the same diagnosis. Otherwise it gets its service credential from the space's auth provider, verifies the bearer signature and witness against the pinned authority's issuer keys, checks the serving snapshot against its embedded contract, and connects to the broker as that service. It binds the registered responder before reporting ready. It succeeds, exit 0, only after the broker accepts the connection, the responder subscriptions are flushed and the space accepts the daemon as ready. The renewal and ready request IDs are kept in `/daemon//service-store/`, mode `0600`, and reused after a restart. A damaged `daemon-ready/*.json` or `auth//service-renewal/*.json` record in that store reports `CONFLICT` (`LOCAL_STATE_ERROR`) on connection or startup resume. Store read, creation and write failures use the same local diagnosis. The daemon preserves existing records and leaves no connection after a failed connect attempt. Check permissions and restore the original record before retrying; restoring it reuses the saved request IDs. No record is silently replaced. A service bearer issued more than five seconds ahead of this machine's clock reports `PERMISSION_DENIED` (`CREDENTIAL_NOT_YET_VALID`). Nothing connects. Check this machine's and the space authority's clocks, then connect again. The saved request IDs stay unchanged. An access credential that has expired by the daemon's clock reports `LIFECYCLE_BLOCKED` (`CREDENTIAL_EXPIRED`), with state `credential-expired`, rather than a network outage. Check this machine's and the space authority's clocks, then connect again. The credential remains rejected, no new material is saved, and `service-status` retains the diagnosis. Setup stops at this refusal without waiting for owner approval. A retry reuses the saved request IDs. A sign-in proof returned by the provider that has already expired by this machine's clock uses the same `credential-expired` diagnosis. `connect-check`, registration and setup stop before contacting the space authority. Other unusable proofs retain their existing refusal. Every other answer is a refusal and nothing is connected. Not yet approved is `LIFECYCLE_BLOCKED` (`APPROVAL_PENDING`). A refused registration is `PERMISSION_DENIED`, and so is a space that ended the daemon's authority, with its code as the native code, such as `APPROVAL_REVOKED`; connecting again does not reconnect it. A space or broker that cannot be reached is `DEPENDENCY_UNAVAILABLE`, and connecting again retries the same requests. Without a register request the answer is `TARGET_NOT_FOUND`. While the daemon runs, the connection renews itself and reconnects on its own. The access bearer lives at most 60 seconds, so the broker drops the connection when each one expires and the daemon reconnects with the next. While the broker connection is down, for that reason or any other, the answer is `connecting`, not connected; it is connected again only after the broker is back and the space accepts the daemon as ready again. A connection the space accepted is remembered across a restart. `service-connect` records that intent for the space once it is connected. After its initial account check, a starting daemon resumes the records that current account intent has not withdrawn. The records are at `/daemon//service-connect.json`, mode `0600`. A connect that never reached connected records nothing, so a space that refuses is not retried at every start. The resume runs after the local endpoint is published, so a space that is slow or unreachable does not delay `status`, and `service-status` reports what each one actually reached. If a remembered connection is unavailable, the daemon retries every second for the first ten retries, then doubles the delay up to 30 seconds between attempts. Its status stays unavailable during a retry until a new outcome is known. A broker connection lost during startup's ready call is also retried. Recovery needs no manual `service-connect`. Other refusals, including ended authority and damaged local records, stop these retries. Disconnect and shutdown cancel them. An ended connection is remembered only until the daemon stops. `service-status` reads the daemon's current answer without contacting the space. With no current answer, it reports `NOT_REQUESTED` (`TARGET_NOT_FOUND`) when this daemon has no saved register request for the space, including after a restart. A saved request alone reports `disconnected`; it does not prove approval or a connection. A registration awaiting approval reports `approval-pending` for the rest of that daemon process, until another registration or connection attempt updates it or an explicit disconnect clears it. Registration alone never claims a connection and does not replace a live connection's state. `service-disconnect` drains the responder and closes the connection, removes that space's resume record so the disconnect outlives a restart, and stopping the daemon closes all of them without removing a record. Disconnect and graceful shutdown cancel an in-flight service authority or sign-in proof request, including a startup resume, before waiting for the connection attempt to settle. This also applies to a pending `daemon register`. If removing resume intent fails, disconnect reports the failure. Repair the record and retry disconnect or connect without restarting the daemon. The service answers its existing `machine-status` command with `null`. This is a liveness response; it contains no vitals, hostname or machine metadata. Its stock Describe answer is explicitly public: it identifies the endpoint and derived owner token and includes the protocol and command schemas. Callers still need broker-enforced invoke access to the selected instance and command. Connecting the daemon grants no caller access. Owner approval, operator name binding and caller grants remain separate actions. An authority that does not publish its issuer identity cannot support this serving connection; the daemon refuses rather than trusting a decoded token or using Manager credentials. The registration contract is unchanged. ### Managed child environment [#managed-child-environment] See the [current command contract](cli-processes.mdx#supervise) for this behavior. ### Manager successor recovery [#manager-successor-recovery] See the [current command contract](cli-processes.mdx#supervise) for this behavior. ### Platform custody requirements [#platform-custody-requirements] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Unit installation [#unit-installation] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Uncertain native attempts [#uncertain-native-attempts] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Unit removal [#unit-removal] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Local process declarations [#local-process-declarations] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Process readiness [#process-readiness] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Descendant process limits [#descendant-process-limits] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior. ### Unsupported auth lifecycle commands [#unsupported-auth-lifecycle-commands] See the [current command contract](cli-processes.mdx#owned-process-services) for this behavior.