Cotal
DocsAPI Reference
Services

Local service reference

service saves local definitions and controls their recorded instances. Compatibility daemon entries are listed below.

service

cotal service [--space <s>] [--json|--plain]
cotal service show <name>
cotal service add <name> --exec "<command>" [--cwd <dir>]
cotal service add <name> --preset <preset> <preset-options>
cotal service add <name> --file <definition.json>
cotal service start <name> [<preset-options>] [--instance <id>|--new-instance] [--foreground|--dry-run]
cotal service stop <name> [--instance <id>] [--with-agents] [--yes|--dry-run]
cotal service restart <name> [--instance <id>] [--with-agents] [--yes|--dry-run]
cotal service status <name> [--instance <id>]
cotal service logs <name> [--instance <id>] [--tail <n>] [--follow]
cotal service install <name> [--instance <id>] [--dry-run]
cotal service uninstall <name> [--instance <id>] [--yes|--dry-run]
cotal service remove <name> [--instance <id>] [--yes]

svc and services are aliases. Every form takes --space, --json and --plain.

FlagMeaning
-s, --space <s>The space whose services to use, resolved as in Selecting a space. A space that is prepared but not started can be named
--exec <command>add: your own program and its arguments, quoted as in a shell but run without one
-C, --cwd <dir>add --exec: the folder it runs in (default: this folder)
--preset <name>add: save a declared preset. Its options become flags of add
-f, --file <definition.json>add: import a version-1 definition file
--instance <full-id>Select one instance by its full ID, not a prefix
--new-instancestart: allocate another instance of a process definition
--foregroundstart: keep the process in this terminal
--with-agentsstop, restart: request the manager's native agent-stop policy
--dry-runstart, stop, restart: preview the effect. install, uninstall: inspect the unit
-y, --yesstop, restart, remove, uninstall: confirm in a script
--tail <1..1000>logs: how many records to read (default 100)
--followlogs: keep reading

A service is a saved definition in one space plus the instances started from it. Bare service lists every service you can name here with its state and the one command that moves it on: the built-in services (the presets), whether started or not, and the ones you added. Nothing starts a dependency for you: starting the manager does not start its broker.

A built-in service needs no add. cotal service start <preset> saves a definition of that name from the preset's defaults and starts it; stderr says what it saved. A preset that needs something only you can give, such as the web dashboard's credential file or the intake's keys, refuses with those flags named, and takes them on the start line: cotal service start web --creds <file>. A start refused before anything launched forgets the definition it saved. A preset name acts on the space's one service built on that preset, so after cotal up the name delivery reaches stack-delivery. The manager has its own manager commands. --json writes one envelope for every form, and logs --follow --json writes one record per line. Without --json, a follow's last line says in a sentence why it ended. logs refuses a log file this account cannot read with PERMISSION_DENIED (exit 2), naming the file and chmod u+rw, quoted when a shell would split the path. The failed read changes no permissions or service state. With --follow the refusal is the stream's error record, which carries that step as remedy, and the stream ends.

Adding a service

Your own program is one line:

cotal service add reviews --exec "node /srv/reviews/server.mjs --port 8080"
cotal service start reviews

--exec splits its text into the program and its arguments as a shell quotes words, but runs no shell. Single and double quotes and backslashes work as in a terminal. A pipe, a redirection, a ; or &, a variable, a glob or a ~ outside quotes refuses USAGE_INVALID (exit 1), because it would not mean here what it means in a terminal; for those, run a shell yourself with --exec "sh -c '<command line>'". A bare program name is looked up on PATH as add saves it, and a relative path is saved as the absolute path it names in --cwd, which defaults to the folder you run add in. The service is ready once its process runs. --file keeps the full definition shape below, for readiness over HTTP, environment references or a credential file.

The presets built into the CLI are auth-service, broker, delivery, exec-endpoint, feedback-intake and manager. The linear and web presets come with the @cotal-ai/linear and @cotal-ai/web extensions, which cotal ext add <package> installs. Bare service lists the presets this machine has, and a name it does not have is refused as DEPENDENCY_UNAVAILABLE.

cotal service add <name> --preset <preset> --help lists a preset's options. An option the preset fills itself shows (default: ...), such as the space's root for --root and pty for the manager's --runtime. A required option with no default is marked (required), and an option that takes a path shows <dir>, <file> or <path>, and the broker's --server shows <url>. A <dir> or <file> option takes the absolute path of one that exists, and a relative one is refused. An option the service names in camelCase takes a dashed flag, so the broker's logFile is --log-file <path>, as cotal up spells it, and add takes it as cotal up --log-file does: a relative path is made absolute against the folder add runs in, and the file need not exist yet. A missing required option is refused with each one named, and the refusal prints the command line with them. That line also names each option whose default the space cannot fill yet, such as the broker's --server <url> in a space with no registered broker, so it works as printed.

A preset's options describe the service and never choose its space. add saves into the space --space names, or else the selected space or the folder's, as other commands resolve it.

add checks the options against the preset's schema, and checks that each declared path exists with its required protection. It does not check that a root or credential suits the preset, such as a root's auth mode or which credential a file holds. Those are checked when the service starts. A refusal names the option or field at fault, by its flag on a preset add, such as --log-file. A value the preset's schema refuses gives the preset's help as its Next: cotal service add <name> --preset <preset> --help. Option values are not printed, except the broker preset's server: add checks it against the address rules a start applies, and its refusal quotes the address unless it holds an @, which may be a credential.

--file imports a version-1 definition. This one runs a program and treats it as ready once its loopback health URL answers 200:

{
  "schemaVersion": 1,
  "space": "main",
  "launch": { "type": "process", "executable": "/usr/bin/node", "args": ["/srv/reviews/server.mjs"], "cwd": "/srv/reviews" },
  "readiness": { "type": "http", "url": "http://127.0.0.1:8080/health", "expectedStatus": 200, "timeoutMs": 10000 },
  "envRefs": { "REVIEWS_TOKEN": "REVIEWS_TOKEN" },
  "credentialRef": { "kind": "file", "path": "/srv/reviews/reviews.creds" }
}
FieldMeaning
schemaVersion1
spaceThe selected space's name. Another name is refused
launch{type: "process", executable, args, cwd}: an executable by absolute path, by a path relative to cwd, or by a bare name that add finds on PATH, at most 256 arguments of at most 8192 characters each, and an absolute working directory. Or {type: "adapter", adapter, options} for an installed adapter, which a preset writes for you
readiness{type: "process", timeoutMs}: ready once the process runs. {type: "http", url, expectedStatus, timeoutMs}: ready when a loopback http://127.0.0.1 or http://[::1] URL answers that status. An adapter definition uses {type: "adapter", timeoutMs}. timeoutMs is 1 to 120000
envRefsOptional. Child variable name to the name of a variable in the environment start runs in. CLAUDECODE and names starting COTAL_ or CLAUDE_CODE_ are refused
credentialRefOptional. {kind: "file", path}: an absolute, protected credential file. The child receives its path as COTAL_CREDS, never its contents

A space that names another space is refused SCHEMA_INVALID, exit 1, before add checks whether the selected space already has a service of that name. No other field is accepted. add saves the definition with the space's root, which show lists as Added in; a file that sets root itself is refused SCHEMA_INVALID, exit 1, and nothing is saved. The file is at most 256 KiB. A readiness value that breaks these rules refuses SCHEMA_INVALID, exit 1, naming the field and its rule, such as a timeoutMs outside 1 to 120000, a field the chosen type does not take, or a url that is not on loopback. A launch argument over 8192 characters refuses SCHEMA_INVALID, exit 1, naming its position and that limit.

A process launch runs its executable as you, so the executable follows the space create rule for other local writers. The file it names, where a link points, every folder above that, and the folder a link is in must be writable only by you or root. add checks this before it saves, and start, restart and an installed unit check it again before anything launches or stops. An unsafe path refuses PERMISSION_DENIED (exit 2), and the refusal names each unsafe path and one remedy such as chmod g-w <paths>.

add looks a bare executable name up on its own PATH once and saves the absolute path it finds, which its receipt shows as Executable and --json as result.executable. Every later start runs that file, whatever its own PATH is. A PATH with a relative or empty entry refuses that lookup UNSUPPORTED (exit 2), as a unit install does, because the entry would resolve against whichever folder add runs in. A name not on PATH refuses DEPENDENCY_UNAVAILABLE (exit 2). A definition an earlier release saved with a bare name refuses to start SCHEMA_INVALID (exit 1); remove it and add it again.

Running a service

start records the instance before it launches anything and prints its full ID. A start that fails before its process exists records the instance as failed. Its refusal names the cause, such as a space that is prepared but not started. start can then try again on the same instance, and stop, remove and cotal down find nothing to stop.

A definition starts only in the root it was added in. One a forgotten space of the same name left in another folder refuses start, restart and install CONFLICT (exit 2) before anything launches. The refusal names the root it was added in and cotal service remove <name> --space <space> --yes, which forgets it so you can add it again for this project. Bare service marks it inherited from <root> in its State column and names that remove as its Next, and --json gives each definition its root and a rootMatch of this-root, inherited or not-recorded. A definition an earlier release saved has no recorded root: it starts as before and is listed as root not recorded, as are a controller stack's own member definitions.

A start or restart whose environment does not set a variable that envRefs reads refuses DEPENDENCY_UNAVAILABLE (exit 2) before anything launches or stops. It names each missing variable and the child variable it feeds, never a value. show lists the references by name.

A start, restart or install, or an installed unit, whose executable or working directory was moved, removed or changed mode since add refuses DEPENDENCY_UNAVAILABLE (exit 2) before anything launches or stops. The refusal names the path and what is wrong with it, with its mode when the mode is the cause. The Next names the fix, such as chmod u+x <path>, quoting paths that need it for executable and working-directory permission repairs. No permissions change automatically. For a path that is gone, it also names cotal service remove <name> --space <space> --yes, after which you can add the service again with another path. add refuses a path that does not exist the same way and names the field to correct.

stop and restart ask before they act in a terminal and need --yes in a script. Enter, Ctrl-C, Ctrl-D or closed input at that question leaves the service unchanged and exits 0. Without an installed unit, a restart whose process is already gone stops nothing, so it does not ask. A start or stop that finds nothing to do says so and reports no-op.

status names the file a running instance's process executes, as the kernel reports it, so the stack-broker row shows which nats-server serves the stack. --json carries it as executable. A stopped instance shows no running process, and a host with no /proc shows it as not readable. For a background instance with no recorded supervisor, when its recorded state is running but its process is gone, status keeps its historical readiness and records no exit code or signal. Its Next names cotal service logs <name> --space <space> --instance <id>, where the process's own output is kept.

remove forgets a definition once every instance is stopped and no unit is installed. It deletes those instances' logs and lists each file it deleted. With --instance <id> it forgets only that stopped instance record, and keeps the definition, its other instances and its logs. A command that names no instance while several records exist refuses TARGET_AMBIGUOUS; its Next lists each instance with its state and the remove --instance command for each stopped one, so a finished record stops blocking the bare form. A start that selects a record of an earlier revision of its definition, as a definition moved aside and added again leaves, refuses CONFLICT (exit 2) and launches nothing. Its Next names cotal service remove <name> --instance <id> --space <space> --yes and then start again, after a cotal service stop of that instance when it still runs. A controller stack's member definitions and records are not removed this way, since its up and down read them: remove refuses UNSUPPORTED, exit 2, and names cotal down --yes, then cotal space forget <space>, which removes them with the space.

install writes an owned systemd user unit for a saved direct-process definition with no envRefs or credentialRef, and enables it without starting it. It is Linux only. uninstall removes that unit. A preset is not installed as a unit in this release, the manager included: install of one refuses UNSUPPORTED, allocates nothing, and for a preset that runs in the foreground names the ExecStart= line, cotal service start <name> --foreground, for a unit of your own.

The sections Local service definitions, Service presets and Owned process services describe each preset and the process driver in detail.

Local service definitions

service (svc, services) reads the selected space's local definitions and existing component PID observations. Saved definitions, process presence, ownership and serving readiness are separate facts. The definition table counts its already-read instance records and labels any sole instance's recorded supervisor and historical readiness. The separate instance table gives full IDs, process observations and each recorded supervisor; a record count is not a count of live services. Retained native install, uninstall and unit-action attempts appear with their full instance ID, step, position and whether an instance record still exists. They mark the inventory partial: a submitted or unknown step may have changed the original unit even if its instance record is absent. A legacy PID record is not adopted into the new instance controller. Unreadable component records make the inventory partial. Reads do not start, repair or install a service.

service add [name] --file <definition.json> imports the version-1 service object described under Adding a service. A declared preset uses service add <name> --preset <preset> <declared-options>. Guided input offers installed presets and file import. Input is capped at 256 KiB, checked against the selected native space and its schema before a private record is saved. Whether a root or credential suits the preset is checked at start. A process definition needs an existing executable and absolute working directory. Credential file protection is checked without reading its contents. Adapter definitions use an installed local-process provider's versioned, closed option schema. Adding or previewing a definition validates cached metadata without importing provider code. The cache records SHA-256 fingerprints of the declaration and installed package metadata. Missing, changed or version-skewed metadata refuses with an update or re-add remedy. These fingerprints are not publisher signatures or a proof of every executable file. No remote schema is fetched.

service show <name> reads declared policy, not live state. Public reports omit argument bodies, adapter option values and credential contents. Missing launch paths do not prevent reading the saved declaration. Records live under the workstation store's case-safe per-space service path.

service remove <name> --yes forgets only the unchanged definition. Interactive removal defaults to keeping it. Unknown or foreign instance custody makes removal refuse. The process driver verifies that every owned direct incarnation is gone and that no OS supervisor is installed before removing its unchanged instance records and definition. Logs are retained. No process or OS unit is removed by this operation. The presets are described below. A preset is not installed under an OS supervisor. On Windows, local records use the access-list model described under up. There, cotal ext add and the setup seed record a service receipt for an extension that declares service adapters or presets, and its process services run under a custodian as Owned process services describes.

Service presets

Bare service lists declarative presets separately from definitions and process observations. Use service add <name> --preset <preset> --help for that preset's option flags. Parsing, scoped help and completion derive those flags from the adapter's closed schema. Boolean options have positive and negative forms; a pair refuses. A boolean whose schema admits only true, such as an explicit intent, has only the positive form. Numeric and complex options use JSON syntax. A preset no registered or installed provider declares refuses by name; for web the refusal names cotal ext add @cotal-ai/web. Options that collide with service/global flags require the file form. No preset is installed or provider code imported by help, completion, a guided question or definition creation.

A provider registers a service-preset alongside its local-process adapter. Preset metadata contains its name, adapter, summary, readiness deadline and option defaults. It does not duplicate option schemas, contain executable factories or supply credentials. A default is a literal value or a fact of the selected space: its registered root, its registered broker, or a private file of the space under the root's .cotal, such as the delivery credential cotal up keeps there. A file default that does not exist refuses, naming the flag that replaces it; no other file is chosen. Cached preset and adapter declarations share one package-metadata fingerprint receipt. A changed selected declaration refuses during dispatch. Path annotations require absolute physical files/directories, or owned private files, without reading their contents during definition validation.

The CLI's own presets are auth-service, broker, delivery, exec-endpoint, feedback-intake and manager. The linear and web presets come with their extensions. The intake preset is feedback-intake. Supply its --root, protected --keys and scoped broker --creds paths in the selected registered space. The keys file is JSON with one entry per key, {"keys":[{"key":"<token>","tester":"<tester-id>","name":"<display-name>"}]}, where name is optional. A tester's --key-file holds their key. The intake serves POST /v1/feedback on its --host and --port. Optional options include --server, --host, --port, --channel, --store, --max-bytes and --rate-limit. A supplied store must already be private. Without one, the intake appends to a private instance-keyed store beside its service records. Feedback records and logs remain private local data and are retained on removal.

Broker preset

The broker preset uses nats-broker. It requires a prepared local root, a protected existing config inside that root's .cotal, an absolute binary and one explicit server address. An optional logFile names the log file its configuration's log_file writes. add makes a relative one absolute, the file need not exist, and start creates it owner-private. It prepares no trust, generates no configuration and starts no other service. Legacy broker custody, known shared registrations, maintenance and mismatched transport refuse. Its creation-pinned direct child writes a native ports report in a fresh private launch directory, and the readiness check sets that report to 0600 before it reads it. Its listener must match the declared IP, port and transport. add refuses a server that is not one nats:// or tls:// URL naming an IP literal, and DNS, wildcard and mapped-IPv6 targets, so none is saved. A definition saved before that check refuses on start, before launch, naming its server option and how to add it again. This reports the child's client listener, not JetStream readiness, endpoint serving or a fresh trust check.

Broker stop has a separate kernel-exit policy, with no invented endpoint principal or epoch. Known live or unresolved dependants refuse. After confirmation the controller rechecks the scope and process, sends one SIGTERM and waits for that incarnation to exit. Its receipt distinguishes process exit and removal of the ports file from a native shutdown acknowledgement or cleanup. An unconfirmed stop keeps its original request; no force kill, replay or restart follows. A typed adapter refusal before child launch keeps its boundary code, safe cause and remedy. Its local instance receipt remains diagnostic history, recorded as failed with no process, so a later start, stop, removal or down is not blocked by it. Named multi-service stops do not use this policy. Local registry absence does not prove that no remote client or tenant uses this broker, and whole-stack sharing is not checked.

Feedback intake preset

Creating the intake definition launches nothing. Starting it does not start a broker, manager, delivery daemon or installer, mint a credential, or substitute an operator caller. The native child reuses the existing HTTP/mesh intake implementation and preserves the target's TLS requirement. It publishes a create-only, full-instance/full-request startup receipt only after the HTTP listener and native signal handlers exist. Adapter readiness matches that receipt to the creation-pinned child. It reports an owned running intake, not native endpoint serving, developer action or a promise that a future request will succeed. An old receipt is never a replacement launch. The top-level feedback-intake entry is deprecated: it keeps its flags for existing callers and warns on every run.

Delivery preset

delivery uses the selected registered broker root and an existing protected delivery credential from that root's native store. --root defaults to the space's root and --creds to the delivery credential cotal up keeps for the space; --server may repeat the recorded broker but cannot replace it. Open targets refuse. The native daemon still checks its store, account scan and singleton shard lease. No manager, broker or installer is started implicitly. A live, foreign or unresolved delivery record refuses rather than being adopted. When a member of the space's controller stack runs it, as stack-delivery does while the stack runs, the refusal names that member with its pid and instance, and its Next is that member's stop with down. The adapter publishes its request-specific startup receipt after the native runner reports Ready and its shutdown handlers exist. That is not a fresh liveness response or proof of endpoint serving. The existing deliver machine-facing entry stays for the stack callers that still use it.

Service child environment

Native service children retain only the explicit base/path/terminal environment allow-list and selected references. COTAL_HOME and XDG paths retain the invoking workstation registry. Arbitrary ambient credentials and agent identity markers are not inherited by generic process services. A component needing the invoking caller must supply its supported native handoff, not infer a new operator identity. In particular, the web service does not receive the invoking caller's authority.

Native auth service

The auth-service preset requires a registered hosting root with its existing prepared user-auth projection. It loads the native callout account, issuer, owner secret and service key projection. Saving or starting this preset does not generate those keys, create an account or start its broker, delivery daemon or manager.

The preset options derive from the native daemon flags. Ports are bounded integers; public exchange options keep the native validation and reverse-proxy requirement. Advertised URLs cannot put credentials, query parameters or fragments on the child argv. No environment-export onboarding or generic caller-credential substitution is used.

The preset supports a prepared local user-auth root and its registered plain loopback NATS broker only. The native authority path does not yet carry a required TLS policy through every connection. TLS-required and remote broker targets refuse before authority startup rather than weakening that requirement. The preset declares foreground control through the shared terminal-bound lifetime; OS-service installation remains unsupported.

The real child exclusively claims the existing native per-space PID slot and publishes its creation pin privately before opening authority planes. Any existing slot, including a dead or malformed legacy record, refuses without adoption or reclaim. The startup phase is written only after native discovery, flushed callout/listener initialization and shutdown handlers. Discovery's loopback capability stays in its native private store; no capability or bootstrap URL appears in the startup phase. This is process/startup evidence, not a fresh authenticated endpoint response. A confirmed native close releases only this child's matching PID custody. An unconfirmed close keeps that evidence. Service reports still do not claim lease, registration or family cleanup.

Server daemons

Three long-lived infrastructure roles ship with the CLI: the delivery daemon, the auth service and the feedback intake. They are not part of everyday operation. The delivery daemon comes up automatically with cotal up --detach in auth mode.

deliver, auth-service and feedback-intake are deprecated entries kept for existing units and scripts; help and completion omit them. Each entry's own help marks it deprecated, with its removal release and the preset that replaces it. Each accepts its existing flags and prints a warning on every run naming its service preset and the release that removes it. The delivery daemon and auth service that cotal up starts run the same code through hidden entries and print no warning. New setups use cotal service start <preset>; see deprecated daemon entries. supervise, the manager's entry, is deprecated the same way and names cotal manager start.

cotal deliver --space <s> [--server <url>] [--creds <file>] [--root <dir>]
cotal deliver --spaces <manifest> --server <url> [--root <dir>]
cotal auth-service --space <s> --server <url> [--port <n>] [--exchange-public-port <n>] [--exchange-public-url <https://…>] [--exchange-trusted-proxy] [--advertised-server <url>] [--agent-provisioning-url <https://…>] [--auth-timeout <seconds>]
cotal auth-service --space <s> --server <url> --callout-only [--auth-timeout <seconds>]
cotal auth-service --server <url> --callout-only --spaces <manifest> [--auth-timeout <seconds>]
cotal auth-service --server <url> --spaces <manifest> [--auth-timeout <seconds>]
cotal service add intake --preset feedback-intake --space <s> --root <absolute-root> --keys <private-keys.json> --creds <private-creds>
cotal service start intake --space <s>

auth-service runs a user-auth space's identity plane: the NATS auth callout, the capability-gated local exchange and JWKS, and, when --exchange-public-port is set, the closed public exchange/discovery face forwarded by an HTTPS reverse proxy. --exchange-public-url is the proxy URL advertised to clients; --exchange-trusted-proxy opts into last-hop X-Forwarded-For attribution. --advertised-server and --agent-provisioning-url set the broker address and agent-provisioning endpoint the public bundle advertises, as on up. --auth-timeout is the broker's authorization { timeout } in whole seconds, which the callout answers inside; give it the value the broker config renders, which cotal up passes from --auth-timeout. cotal up --user-auth starts and supervises the service for you, so you run it directly only to recover one by hand. --callout-only runs a callout replica beside the full service: it answers connects alongside it and serves no HTTP, so it refuses --port, the public-face flags and --service-endpoints. It refuses to start in a space that admits service endpoints: one whose full service has ever run with them. It never writes auth-service.json, and it exits non-zero when it loses a store connection or the broker. Give it the same --auth-timeout as the full service, the value the broker config renders. See callout replicas.

--callout-only --spaces serves several spaces from one callout process, one lane each, and stands in for --space. The two are alternatives: naming both refuses. The manifest is a JSON file holding the spaces this process serves:

{ "version": 1, "spaces": ["<s>", "<s>"] }

An empty list, a repeated space, an unknown field, a version other than 1 and an entry that is not a space name all refuse. Every lane dials the one --server, so a deployment whose spaces live on different brokers runs one host process per broker rather than naming a server per space.

Each lane applies the per-space refusals a single-space replica applies, so a space with missing material, a mismatched issuer pin, an unreachable broker or open service-endpoint admission refuses. At startup the host answers for every space it can, names each one it cannot with the reason, and retries those every 5 seconds; its ready line counts the spaces answering against the spaces named and lists the ones it answers and the ones it retries, in manifest order:

✓ auth callout host up (2 of 3 space(s) answering: <s>, <s>; retrying <s>) - answering the callout on <server>; no HTTP listener, no auth-service.json

A refused space gets no lane in this process, so its connects are answered by its other members only. Open service-endpoint admission does not close, so keep such a space off this host's manifest: admitsServiceEndpoints from @cotal-ai/auth answers the check the lane makes (embedding).

The host rereads the manifest when it changes on disk, on SIGHUP, and on a slow backstop interval, and opens or closes the difference. Write a replacement and rename it into place: a manifest that does not read or parse whole is reported and the lanes already open are kept. A lane that loses its store connection or the broker closes on its own and the other lanes keep answering; the host reopens it, retrying every 5 seconds while it fails.

Multi-space hosts admit at most four lane opens at once. The ready line distinguishes queued spaces from failed spaces being retried and opens still running past their deadline. A queued space's 30-second opening deadline starts only when its open begins. An overdue open keeps its slot until it settles; if all four are stuck, queued spaces wait. Each batch rereads the manifest, so a queued removal never opens and a changed entry uses its current settings. Failed attempts return behind spaces still waiting for a first turn, after the existing 5-second retry wait. Authentication and connection budgets are unchanged.

Every multi-space host, this one, the full auth service host below and deliver --spaces, writes <manifest>.applied beside its manifest once a reread has closed the lane of every space the manifest does not name. The file holds {"version": 1, "sha256": "<hex>"}, the SHA-256 of the manifest bytes it applied, and is replaced through a rename. To stop serving one space, remove it from every manifest that names it and wait until each host's <manifest>.applied names the manifest you wrote; from then on no lane for that space is left in those processes. A lane that does not confirm its close holds the acknowledgment back, and the host retries that close every 5 seconds without opening the space again until it does. So does a space the manifest no longer names whose open is still running past its deadline: that open would come up as a lane after you were told there was none, so the host names the space and acknowledges nothing until the open has settled and any lane it brought up is closed. Other spaces keep serving, and queued spaces open as slots become available. A host writes the file before it opens any lane at startup, and refuses to start when it cannot. A host whose last space you remove is stopped instead, since a manifest names at least one space.

--spaces without --callout-only serves several spaces from one process as their full auth service, one lane each: the callout, the authority plane, the loopback exchange and JWKS, and the public face. A lane is the service auth-service --space runs, with the same refusals and its own plane claim, connections, connect limits and capability. Each lane writes its space's own auth-service.json, naming the host's pid, on a loopback port of its own. The manifest is version 2, one object per space, carrying that space's public face and service endpoints under the names of the flags it replaces:

{ "version": 2, "spaces": [
  { "space": "<s>", "exchangePublicPort": 7451, "exchangeTrustedProxy": true },
  { "space": "<s>", "serviceEndpoints": true }
] }

An entry takes exchangePublicPort, exchangePublicUrl, exchangeTrustedProxy, advertisedServer and agentProvisioningUrl, with the rules of the flags: the last four need exchangePublicPort. An entry without exchangePublicPort has no public face. serviceEndpoints, true or false, is the space's --service-endpoints: a lane whose entry sets it opens the service endpoint host and serves /service-endpoint-authority and /service-endpoint-host, and a lane without it opens none of that and answers both routes 404. Two spaces naming one port, a version other than 2 and any refusal of the version 1 grammar refuse the whole manifest. --port, the public-face flags and --service-endpoints refuse with --spaces; --server and --auth-timeout apply to every lane. A changed entry closes that space's lane and opens it again on its new settings, and the other lanes keep serving. An embedder that writes the manifest checks it with parseAuthHostManifest(text) from @cotal-ai/auth before renaming it into place: it applies the rules the host applies and throws the message the host would log.

At startup this host serves every space it can, names each one it cannot with the reason, and retries those every 5 seconds; its ready line counts the spaces serving against the spaces named and lists them in manifest order. A space whose plane another process still holds opens once that process stops, so you can move spaces off one process each by starting the host on all of them and then stopping the old processes. A fence or a lost broker connection closes that space's lane alone, and the host reopens it. The lines of a lane carry a [<space>] prefix. A lane writes no .cotal/auth-service.<key>.pid; the host is stopped by its supervisor, and on SIGTERM or SIGINT it closes every lane and exits 0. An embedder passes its secret store to runAuthService(args, store) with spaces in args as it does for one space. The auth-service service preset runs one space, so auth-service --spaces still prints the deprecation warning saying to keep the entry for --spaces.

deliver runs the server-side Plane-3 delivery daemon: the durable backstop and membership/ACL authority. It is auth-mode-only and single-instance (--shard/--shards accept only N=1); --dev-mint mints a scoped cred for standalone dev, signed by the local account of the space the daemon serves. --creds can start a daemon that already looks healthy, but production renewal is not that file alone: the manager and the daemon must address one credential store. On a stock split host with two project roots, a direct deliver is not an independent repair; keep the daemon under cotal up on the broker host, or inject the same store into both processes (embedding). Typed by hand on the workstation, deliver dials the broker recorded for --space in the mesh registry (a mismatching --server is refused before any dial, and a record for a different workspace root is refused outright); with no record for the space it falls back to the local mesh. The daemon serves the workspace root that --root <dir> names, which must hold .cotal/, or else the nearest .cotal/ above its working directory. With neither, it refuses at start and names the directory it searched from, before it reads a credential or dials a broker. See the delivery daemon.

deliver --spaces serves several spaces from one delivery process, one lane each, and stands in for --space. It reads the same manifest file auth-service --callout-only --spaces reads, with the same refusals. Naming both --space and --spaces refuses, and so does --spaces without --server, because every lane dials that one broker. --creds, --dev-mint, --shard, --shards and deliver pending refuse with --spaces: each lane reads its own space's credential from the root, at the location cotal up writes it, and runs one shard. The root is --root <dir>, or the nearest .cotal/ above the working directory, chosen once at start for every lane.

A lane is the daemon deliver --space runs, with the same refusals, credential renewal, lease, broker watchdog, timer writer and membership feed, on its own connections. Its lines carry a [<space>] prefix. A lane writes no .cotal/delivery.<key>.pid, because that record is what cotal down signals for one space, and signalling the host would stop every space it serves. The host is stopped by its supervisor: on SIGTERM or SIGINT it closes every lane, releasing each lease, and exits 0, or exits after 10 seconds when a lane cannot finish.

At startup the host serves every space it can, names each one it cannot with the reason, and retries those every 5 seconds; its ready line names the spaces it serves and the spaces it retries, in manifest order. A space whose lease a single-space daemon still holds opens once that daemon stops, so you can move spaces from one process each onto a host by starting the host on all of them and then stopping the old daemons. A manifest that does not read or parse whole still refuses startup. The host rereads the manifest when it changes on disk, on SIGHUP, and on a slow backstop interval, and opens or closes the difference. A lane that loses the broker, its credential or its lease closes on its own and the other lanes keep serving; the host reopens it, retrying every 5 seconds while it fails. deliver --spaces still prints the deprecation warning, which says to keep cotal deliver for --spaces because the delivery service preset has no form for it.

feedback-intake runs a self-hosted feedback server at POST /v1/feedback (requires --keys, in the format the feedback-intake service preset describes, and a scoped --creds), announcing submissions into a space channel; flags include --host/--port, --store, --space/--channel, --max-bytes, and --rate-limit. Without --server it dials the broker its --space is registered at, and refuses an unregistered space before dialing. --port takes a decimal port from 1 to 65535 and is checked before the intake dials the broker. --max-bytes and --rate-limit are checked at the same point. Each must be a positive integer no greater than 9007199254740991, and a refusal names the value it rejected.

deliver

cotal deliver [--space <s>] [--server <url>] [--tls] [--creds <file>] [--root <dir>] [--shard <n>] [--shards <n>] [--dev-mint]
cotal deliver --spaces <manifest> --server <url> [--tls] [--root <dir>]
cotal deliver pending <name> [--limit <n>] [--durable <name>] [--json]

With no positional, cotal deliver runs the delivery daemon (see the delivery daemon). With --spaces it runs one delivery lane per space a manifest names, in one process; see server daemons. deliver pending <name> never starts the daemon: it is an operator-only read over one recipient's DM durable, for the moment after a send when the question is "what does the broker actually hold for them." It resolves <name> against a short presence watch (an offline card still counts, since the recipient may be dead, that is what the verb exists to inspect); when neither a card nor the durable can be found, it prints ✗ not-found: no agent "<name>" and no DM durable for it in space <s> and exits non-zero, never pending 0. On a match it prints the durable name and one fact per line: pending, ack-pending, delivered, ack-floor, created, frontier, and the stream's max_age / max_msgs_per_subject / discard limits (--json prints the same facts as one object), followed by a bounded, unacked read of up to --limit (default 20) recent candidate message ids under the heading recent candidate ids (from the ack floor; not proof of a hole), a list of what is there, not proof that nothing was lost. Each plain candidate line prints from= with the sender principal parsed from the stored message subject, which the broker binds to the sender's credential, then ts=, then label= with the name and role the sender wrote in its own message. The label is the sender's claim and can name another agent; control characters and brackets in it print as spaces. --json keeps its earlier candidate lines.

The verb needs the admin credential profile: it runs through the same static-mesh route as cotal auth mint --profile admin, and refuses a user-mode mesh, naming the retired static credential, because there is no user-mode inspection authority yet. Pass --creds <file> for an off-registry admin credential. A same-name respawn never inherits a predecessor's held DMs (the durable is lifecycle-keyed); an old lifecycle's durable is reachable only by the name a live read printed (the <durable> line on the first line of this verb's output). Pass that name with --durable <name> to read it directly once the lifecycle's card is gone from the roster. This skips the presence watch on <name> entirely, so <name> is required but only echoed in error text.

Delivery process records

See the current command contract for this behavior.

On this page