Ctrl K
ComunidadGuías…

docs/cli-processes.mdx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
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 <name>] [--space <s>] [--server <url>] [--name <name>] [--spawn <names>]
```

`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 <name>` or
`cotal use <name>`, which start the manager of a space a folder on this machine hosts from any
folder. It keeps `cotal supervise --space <name>` 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 <s>` | `COTAL_SPACE`, this folder's space, or the `cotal use` selection | Space to supervise. On a root that hosts several spaces, name one of them here |
| `--server <url>` | hosting mesh, or matching registered mesh | Broker URL. A registered mesh supplies it when omitted; a different explicit value is refused before anything is dialed. |
| `--runtime <name>` | `pty` | Agent runtime (`pty` built in; extension runtimes are explicit-only) |
| `--execution <runtime\|none>` | `runtime` | `none` starts a control instance for a hosting user-auth root, with no runtime, connector inspection, workload or terminal execution |
| `--console-port <n>` | none | Protocol-console port |
| `--console-host <host>` | loopback | Bind host for the console endpoint. Loopback keeps it machine-local; `cotal up` passes the address it bound the broker to, which is what lets the browser console reach this manager from another machine. `cotal attach` does not use this face: it redeems a mesh session grant over the broker |
| `--max-sessions <n>` | 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 <n\|unlimited>` | `unlimited` | Live seats this manager admits for one owner. `unlimited` leaves the manager's own 50-slot ceiling as the only bound, so a mesh with one owner keeps every slot. Set a number where several owners share a manager, and one owner cannot take the host. A launch over it is refused before anything is reserved, naming the owner's count, the cap and the remedy. It is recorded in this root, so a later start without the flag keeps it. 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 <name>` | none | The manager's name. Its presence card carries it, `who --all` and the manager choice show it, and `--on` takes it on a user-auth mesh. It is recorded in this root, so a later start without `--name` keeps it. Without a name, the manager shows as `<host>/<runtime>` |
| `--roster <file>` | none | Declarative roster to boot at startup. See [Roster files](define-a-team.mdx#roster-files) |
| `--launch <spec>` | none | Resolved manifest launch spec (from `up -f` / `team boot`) |
| `--spawn <names>` | 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: <message>`. 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 <pid>`, 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/<space>/<name>.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: <message>`. 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 <name> --instance <full-id> --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 <name>
--instance <full-id> --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 <name>`. 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 <full-id>`; 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 <name>`, 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 <name>` 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 `<log>.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 `<log>` and `<log>.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 <name> --instance <id> --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 <full-lifecycle-uid> [--space <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 <private-handle-file> [--space <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 <url>] [--client-id <id>] [--daemon-home <dir>] [--json|--plain]
cotal daemon setup --space <s> [--idp <auth base URL>] [--client-id <id>] [--no-manager] [--wait <seconds>] [--daemon-home <dir>] [--json|--plain]
cotal daemon install [--daemon-home <dir>] [--dry-run] [--json|--plain]
cotal daemon uninstall [--daemon-home <dir>] [--yes|--dry-run] [--json|--plain]
cotal daemon start [--daemon-home <dir>] [--json|--plain]
cotal daemon serve [--daemon-home <dir>] [--json|--plain]
cotal daemon status [--space <s>] [--daemon-home <dir>] [--json|--plain]
cotal daemon login --idp <auth base URL> [--client-id <id>] [--daemon-home <dir>] [--json|--plain]
cotal daemon spaces [--refresh] [--daemon-home <dir>] [--json|--plain]
cotal daemon desktop [--daemon-home <dir>]
cotal daemon connect-check --space <s> [--daemon-home <dir>] [--json|--plain]
cotal daemon register --space <s> [--daemon-home <dir>] [--json|--plain]
cotal daemon service-connect --space <s> [--daemon-home <dir>] [--json|--plain]
cotal daemon service-disconnect --space <s> [--daemon-home <dir>] [--json|--plain]
cotal daemon service-status --space <s> [--daemon-home <dir>] [--json|--plain]
```

Connect this computer once with `cotal daemon setup --idp <url>`. 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 <url>`; 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 <s> [--idp <url>]`.
`--no-manager` and `--wait` require `--space`; `--wait` also requires `--no-manager`.
Space-specific daemon commands require an explicit `--space <s>` 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 <s>` to read the machine connection and
`cotal daemon service-disconnect --space <s>` to close it.
Setup prints these commands with `--space=<s>` 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 <instance-id> --space <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 <url>`. 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 <instance> --space <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: <id>` 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 <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 <url>` in both the refusal
and its next step. Hosted repair commands carry `--daemon-home <dir>` 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 <seconds>` 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 <user>`, 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-<SID>`, or
`cotal-daemon-<16 hex>-<SID>`, 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 <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 <socket>`, 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 <s>` 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-<SID>`, where `<SID>` 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 <path>` 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 <dir>` runs or reaches a separate daemon whose own state lives in `<dir>`: its
sign-in session, its per-space identity, register request and service store, all at the paths
below with `<dir>` in place of `<COTAL_HOME>`. 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-<SID>-home-<16 hex>`), derived from the
directory, so it runs beside the default daemon. `<dir>` 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 `<dir>/daemon.lock` while it runs. The default daemon holds the
same lock at `<COTAL_HOME>/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: `<dir>/daemon.lock`
with `--daemon-home`, and `<COTAL_HOME>/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: `<origin>/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 <exchange>/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=<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
`<COTAL_HOME>/daemon/<space segment>/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
`<COTAL_HOME>/daemon/<space segment>/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
`<COTAL_HOME>/daemon/<space segment>/service-store/`, mode `0600`, and reused after a restart.

A damaged `daemon-ready/*.json` or `auth/<space segment>/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 `<COTAL_HOME>/daemon/<space segment>/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.