Cotal
DocsAPI Reference

CLI workflow runs

Workflow commands operate recorded runs and durable step journals. A run receipt records acceptance; it does not establish live agent progress or task completion.

run

Operate durable workflow runs (cotal-lang programs) from the terminal.

cotal run start --file <program> [--timeout <dur>] [--local --admit-read <channels> --admit-publish <channels>] [--on <instance>]
cotal run resume <runId> [--local --file <program>] [--on <instance>]
cotal run [--endpoint <ep>] [--json] [--on <instance>]
cotal run --page --on <instance> [--limit <n>] [--cursor <cursor>] [--endpoint <ep>]
cotal run show <runId> [--endpoint <ep>] [--limit <n>] [--json] [--on <instance>]
cotal run answer <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]
cotal run amend <runId> <stepKey> [--value <json>] [--artifact <ref>] [--endpoint <ep>] [--local --by <who>] [--on <instance>]
cotal run revoke <runId> --local --by <who> --reason <text> [--endpoint <ep>]
cotal run check <runId> <program> [--endpoint <ep>]

ps (or list), journal and migrate still work as compatibility aliases of bare run, show and check.

Every verb also takes --space <s>, --server <url> and --creds <path> to pick the mesh and the credential. A hosted verb refuses a raw --creds file UNSUPPORTED, exit 2, because the file carries no endpoint-caller triple for the manager call and that route cannot mint one. Its next step is to run the verb again without --creds, in the mesh's project folder or with --space, where the CLI mints the one-shot caller itself.

Manager selection

Hosted run verbs accept --on <manager-instance> to choose one full manager id as cotal ps prints it. A managed seat defaults to the manager that launched it; an unbound user-auth caller with several managers must choose one. Without --on its call is refused with exit 2 before any request, and the refusal names each live manager. --on does not apply to local, offline or seat-only run commands, and a failed pin never falls back to another manager. Rich run reports label an explicit pin as the requested manager, and the JSON target carries requestedManagerInstance. Follow-up commands printed by the CLI retain the same explicit pin and fill in the run ID the command named or the manager returned, only when it matches the native run-token shape. Keep the pin on later status, answer and resume calls.

A run ID that is not 1 to 64 letters, digits, _ or - is refused as USAGE_INVALID, exit 1, on every verb that takes one, before anything is contacted. A hosted verb the manager refuses reports the outcome and code as other commands do, and with --json as one refusal envelope on stdout. A refusal the manager makes before acting is refused: a denied credential or a step in the wrong state exits 2, and a program that does not validate is SCHEMA_INVALID, exit 1, like other invalid input. A run or step it holds no record of is TARGET_NOT_FOUND, with cotal run or cotal run show <runId> as the next step. A manager activates at most 8 runs at once, counting each start or resume until its run is recorded. One more is refused LIFECYCLE_BLOCKED, exit 2, before anything is written; the refusal names the runs still activating, and the next step is to wait until one has activated and run it again. Any other refusal of start, resume, answer or amend may follow a recorded change, so it is unknown, exit 3, and is not retried.

An unpinned hosted verb run by the operator reads the space's service registry first. When it lists no live manager, as after cotal down stack-manager, the verb refuses TARGET_UNAVAILABLE (exit 2) at once, as ps does, and names bare cotal up as the next step, instead of waiting out the manager's answer deadline.

A mesh a run verb cannot resolve or connect to is refused before any request under the refusal's own code, hosted or --local, as ps refuses it. With no mesh running that is TARGET_NOT_FOUND, exit 2, and with --json one refusal envelope on stdout.

start hands the program to the mesh's manager, which validates it, mints the run id (the record never takes a caller-supplied one), drives it in its own process, and answers with the id once the run is recorded; a program that does not validate is refused with every problem listed, each control character in it shown as its \uXXXX escape. Every verb that takes a program file refuses a missing one with TARGET_NOT_FOUND and one this account cannot read with PERMISSION_DENIED, exit 2, and a folder with USAGE_INVALID, exit 1. A start interrupted with Ctrl-C or SIGTERM after its request went out reports outcome unknown, because the manager may already have recorded the run, and exits 130 or 143. List the runs with cotal run before starting it again. resume asks the manager to take an existing run back and continue it from its step journal; the source is the recorded program, so no --file is taken. A run recorded completed or failed is refused LIFECYCLE_BLOCKED (exit 2) and gets no new activation, because nothing is left to continue; the next step is cotal run show <runId>. Neither takes --endpoint: the manager records its runs under its own endpoint, and naming another is refused. Bare run lists the run records and show renders one run's durable records; both only inspect. show reads the journal from the manager in pages, each one reply, so a journal larger than the broker's payload limit stays readable. A record or a journal row too large for one reply refuses UNSUPPORTED, exit 2, and names cotal run show <run-id> --local, which reads the run's stream from its project folder with no reply. A manager from release 0.99.3 or earlier reads no journal page, so through it both refuse UNSUPPORTED and name the same local read. An open pause prints its question under Needs an answer, and only then does Next offer answer. A checkpoint with a to prints beside its question whom it escalates to, and once it has escalated, until when that attempt is open. A pause counts as open while its last journal row is pending, so a settled or expired one is not listed there. A pause settled with an accepted answer prints its value as JSON plus the recorded answerer, artifact when present, time, and answer id, then one amended line per later amendment, in the order the store committed them, so the last is the current position. A value longer than 120 characters is cut there, with a line that says so; --json carries it whole. Expired pauses and ordinary steps print no answer line. A step whose last journal row recorded an error prints its outcome and the recorded message under Step errors, a caught failure included, with each control character shown as its \uXXXX escape. A run that failed records its cause on its status. Its recorded state reads failed (<code>) in the run list and the run view when the error carries a language code, and the run view prints the message under Failure. When a step's error is the run's failure, Failure names that step and the message stays under Step errors. A run that failed before its status recorded a cause shows only failed. A failure raised by program code outside any step is recorded in no journal row. When the status recorded no cause either, the view says so under Failure. Either way it names cotal service logs stack-manager --space <space>, because a hosted run's cause is in its manager's log. The failure records no line or column in the program: a source position belongs to a problem run validate reports before the program runs.

A run recorded released has no recorded reason. Its Released section points to the executing manager's log for a hosted run or the driving process's output for a locally driven run. A journal read does not establish that execution host or log location, so the view does not guess one. A journal entry larger than the broker's configured max_payload, such as a large checkpoint question, can release a run with L5010 Journal append rejected. Repeating resume while the cause remains can release it again; this view does not change that state or prevent the retry.

A program's log(...) lines are not journalled, so show does not print them. A hosted run writes each line to its manager's log as run <run-id>: log <text>, which cotal service logs stack-manager --space <space> reads, and a resumed run writes the lines before its resume point again. --local prints them in the terminal that drives the run. --json on the run list or show writes one envelope on stdout, as other commands do, and nothing on stderr, whether the manager answers or --local reads. The list puts its rows under result.items. show puts the run under result.view: its runId, endpoint, recorded spec and status, its programHash as the list row carries it, and its rows under result.view.journal. A program record that cannot be read gives programUnreadable with its reason, and no programHash. A list row carries runId, endpoint, state, holder, epoch, journalHigh, forkedFrom, startedAt and programHash (the values the program's run() reports; programHash is absent for a run with no recorded program), failure on a failed run with its code and message, and revoked or revocationUnreadable when the marker says so. A journal row is an activation or a step. A step row carries its step key, the effect kind and its name, state, outcome, the recorded status and errorCode once settled, the recorded errorMessage of a failed or refused step, and startedAt and endedAt in epoch milliseconds. An open pause adds its asks, its deadlineAt, and for a checkpoint the onExpiry it was armed with and the addressee its to names. A checkpoint's escalation is armed with proceed, so it never escalates again. A settled pause adds its answer and its amendments, as the text view prints them. A field the journal does not record is absent: a checkpoint opened before onExpiry was recorded carries none. An unreadable revocation marker makes the result partial, exit 4, with a REVOCATION_NOT_CHECKED warning naming the run. A run --endpoint <endpoint> that matches no run lists nothing and says so, naming the endpoints runs are recorded under, with an ENDPOINT_HAS_NO_RUNS warning. The text view is presentation and is not a stable parsing target. With --local, only the list, show, answer and amend take --json or --plain. Any other local form refuses either flag USAGE_INVALID, exit 1: start --local and resume --local name the manager's form, which takes it, and revoke and check, which have no other form, name the same command without it. run --page, which is not in release 0.88.0 or earlier, asks one manager for a single bounded diagnostic JSON page instead of the full list: --limit takes 1 to 16 rows (default 8), and --cursor continues a scan from a returned nextCursor. It requires --on and refuses --local, and a page proves neither absence nor ownership (Diagnostic inventory). revoke writes the run's revocation marker from the project folder; --by and --reason are required (workflows). answer resolves an open checkpoint through the manager, presenting as the holder that armed it; the manager records the answerer from your credential, so no --by is taken there. A settled step refuses a second answer with TARGET_NOT_FOUND, also when it is sent before the run records the first, and a revoked run refuses every answer with LIFECYCLE_BLOCKED and files nothing. A step the run holds no open request at is also TARGET_NOT_FOUND; when an open checkpoint or ask has the name you typed, as frage names /checkpoint:frage#0, the next step gives its full key, and several matches are listed for you to pick from. A run ID that no run is recorded under is TARGET_NOT_FOUND too, with or without --local, and the refusal says no run is recorded under that ID. An answer without --value carries no value: the step settles as answered, and the program's checkpoint result has no value. --value is refused with SCHEMA_INVALID before anything is sent when the record would not hold what you typed: an object that names a key twice, a number outside the range JSON carries (such as 1e999), or a value nested more than 1000 levels deep. An empty or blank --artifact is refused with USAGE_INVALID. The receipt of answer and amend names the step, the value it filed or that none was given, any artifact and the answer id. A value longer than 120 characters is cut there too, with a line that says so. With --json they are in result.answer, whole, where valueGiven says whether a value was sent. amend records a changed position on a settled checkpoint or ask: it files a new answer beside the accepted one, naming it, and the journal lists it under the step. The pause stays settled and the run keeps the answer it acted on. A step that is still open or settled without an answer refuses an amend. So does a run ID that no run is recorded under, and that refusal says no run is recorded under that ID. With --local these refusals are TARGET_NOT_FOUND, exit 2, as the hosted ones are, since nothing was written. The run records an accepted answer a moment after answer returns, and an amend sent in between is refused with a message saying the answer is not recorded yet; amend again once run show lists it. A spawned seat may amend only an answer recorded under its own name. check replays a run's journal against an edited program, from this terminal under a read credential (--local only; the manager serves no run-migrate command). It prints whether the migration is admissible, every orphaned step with its verdict and code, and exits 0 on admissible and non-zero on not. An edited program that does not validate is refused before the replay, with each problem's code, position, cause and fix. It writes nothing: the commit that would file the migration is not reachable yet, and the report says so. A run ID with no record on the endpoint is refused TARGET_NOT_FOUND, exit 2, as run show refuses it. check refuses --adopt, --release and --discard-approvals, since they decide what a commit does and the check files nothing. --timeout sets the default limit of a checkpoint, ask or turn that sets none of its own (default 1h). A checkpoint that expires under onExpiry: "fail" says in its L4007 whether the deadline came from its own timeout or from this default. It takes a duration longer than zero, such as 30m, and refuses anything else with USAGE_INVALID before the run starts. --on <instance> sends a hosted verb to one manager instance. It takes the full instance id ps prints, not a manager name or host. On a mesh whose managers this terminal can list, an --on that names none of the running ones is refused TARGET_NOT_FOUND at once, naming them, and nothing is sent. On a user-auth mesh where you run more than one live manager, an unpinned hosted verb is refused and --on picks one. --local takes no --on. --local drives in this process instead, over one connection per invocation under the run's own credential minted from the project folder's trust material, and is the path on a bare broker with no manager or for a run with no recorded program (cotal run resume <runId> --local --file <program>); answer --local and amend --local take --by <who> and print the same receipt as the hosted path, and start --local requires --admit-read and --admit-publish (comma-separated patterns or none), the run's channel ceiling. An entry that is not a channel or a pattern, such as a b, is refused USAGE_INVALID naming it. When the run's status records that its program failed under this drive, start --local and resume --local fail OPERATION_FAILED, exit 5, naming the run and its language code with that code's title. A failure whose code has no title shows the first line of its recorded message instead, with control characters escaped. Their Next is cotal run show <runId> --local, which prints the whole message. Any other error the drive ends on stays unknown, exit 3. On a user-auth mesh the host's own manager refuses the family by name, and --local has no credential there. A participant's manager started with cotal supervise hosts a logged-in user's runs through its issuing host: the auth callout issues the user's manager connection, and every run verb rides the versioned rail under that issuance. User-auth run start records the path. The guide is workflows.

Workflow command forms

cotal run (or cotal r) lists the authorized workflow records. run show <run-id> reads recorded state, open requests, each step's recorded error and the last 20 journal rows. --limit <n> prints the last n rows instead, from 1 to 1000, and is refused with --json. A settled race row names the branch it took, and a cancelled row in a branch that lost a race names the branch that won and the race. An answer whose step was in a branch that lost a race, or whose pause was cancelled after accepting it, is listed as not acted on. Its JSON result contains the full returned status and journal. Revoked admission and unreadable revocation markers remain distinct from the record's state. Neither a record nor a receipt is a live agent-progress report.

cotal r
cotal r start ./workflow.cotal.js
cotal r show <run-id> --json
cotal r resume <run-id>
cotal r answer <run-id> '<step-key>' --value '<json>'
cotal r amend <run-id> '<step-key>' --value '<json>'
cotal r check <run-id> ./edited.cotal.js
cotal r revoke <run-id> --local --by <actor> --reason '<text>'

Start takes a positional source file. A source file a run verb cannot read is refused naming its path and the cause: TARGET_NOT_FOUND for a missing file or a link whose target does not exist, USAGE_INVALID for a directory and PERMISSION_DENIED for a file it may not read. - is a file name there, since run does not read a program from standard input. A run verb that reads a source file refuses an empty one or one that is not valid UTF-8 with SCHEMA_INVALID, before it checks or sends anything, because a run's source must not be empty and its text is never replaced. A hosted start sends the program to the manager in one message, so a program over the broker's message limit (max_payload) is refused UNSUPPORTED with its size and the limit, and nothing is sent. Resume uses the host's recorded program. Hosted answers and amendments are attributed to the authenticated caller, never a supplied name. Local answers and amendments require --by <who>; a missing or empty value refuses USAGE_INVALID before contacting the broker, with a JSON refusal under --json. check <run-id> <file> replays the run's recorded journal against the edited program and prints whether the migration is admissible, and it files no migration. Revocation keeps the existing local trust and explicit actor/reason requirements and does not cancel a task. Hosted start, resume, answer and amend have JSON and plain receipts. The local drive and local check retain their native output rather than claiming an unsupported structured format.

Offline commands

cotal r validate ./workflow.cotal.js
cotal r plan ./workflow.cotal.js
cotal r simulate ./workflow.cotal.js --handler ./results.json

These three forms are offline and reject mesh or caller selection. Validation checks the language and nothing a broker decides, so a valid program over the broker's message limit is still refused by a hosted start. A program that does not validate is refused by all three with every problem listed: its code, position, cause and fix, as a hosted start lists them. --json carries them under result.problems. A warning leaves the program valid. All three print it on stderr with its code, position, cause and fix, and validate --json lists it under result.warnings. One is L3021, a fanOut over a list written in the call whose items carry no id and that passes no key, which the run refuses when it reaches it. A list held in a variable gets no warning, because it can change before the call; the run refuses it the same way. Plan lists validated static call sites in source order; it does not evaluate branches, loop counts or an execution schedule. Simulation uses the native simulator with a JSON results script. It executes no JavaScript handler and invents no unscripted outcome. A scripted checkpoint result needs a status of resolved or expired and may carry value, by and artifact; any other shape, a scripted at included, is refused SCHEMA_INVALID, because the simulator stamps at from virtual time. A scripted turn result needs a status of done, blocked or handoff and may carry to and note; any other shape, a scripted at included, is refused the same way. When the script has no result for an effect, the simulation does not complete: it exits 5 with OPERATION_FAILED, the message names the step and the entry to add, and target.step carries the step. When the program itself fails under the script, as a checkpoint scripted expired under the default onExpiry: "fail" does with L4007, the simulation completes and exits 0. Its program outcome is failed, with the step and that step's outcome, or the last step the program settled before its own code failed, and the language's error. JSON carries this as result.outcome and result.failure. A simulation does not establish live authority, installed applications or readiness. All three support JSON and plain output and contact no broker.

The --handler file is one JSON object. Each table maps a step's name to its result: one value for every occurrence, or a list used in order. A step with no name is keyed by its effect, such as ask. spawn, sleep, notify, monitor, observe and conclaves need no entry, and a sleep moves the virtual clock forward.

{
  "checkpoints": { "approve-plan": { "status": "resolved", "value": true, "by": "ana" } },
  "asks": { "plan": { "steps": ["design", "build"] } },
  "turns": { "build": [{ "status": "blocked", "note": "needs a key" }, { "status": "done" }] },
  "events": { "await-reply": null }
}
FieldMeaning
turnsturn results: {status, to, note}, where status is done, blocked or handoff
asksask replies, each checked against the step's schema
checkpointscheckpoint results: {status, value, by, artifact}, where status is resolved or expired
eventswait results. null is a timeout
clock{start, turn, ask, checkpoint, wait}: the start time in milliseconds and how long each effect takes, such as "5m". A turn takes 5 minutes and the others 1 minute unless set
faults[{at, kind, code, message}]: the step at at, a step key such as turn:build#1, fails with that error

The simulation's log(...) lines print under Log, and --json carries them in result.log, each with its scope and values. A step with no entry, or a list shorter than the step's occurrences, stops the simulation with L6001 and exit 5. The failure names the table, the step's name and its step key, and the remedy names the entry to add. For a list that is too short, it also says which result the step needs, counted from 1, while the step key counts occurrences from 0. A failure in the program itself names its code and cause.

Seat turns

Inside a manager-launched static or open seat, run turns reads that seat's assigned turns and run yield <turn-id> --status <done|blocked|handoff> reports an outcome. Handoff also requires --to <agent>; --note <text> carries context. The native launcher supplies the caller context. These forms verify the seat's own credential and manager, never substitute an operator caller, and refuse user-auth self impersonation. They skip operator catalog and installation repair. Yielding one turn does not complete the entire workflow.

Run activation

See the current command contract for this behavior.

Recorded state

See the current command contract for this behavior.

Diagnostic pages

See the current command contract for this behavior.

Revocation

See the current command contract for this behavior.

Pause answers

See the current command contract for this behavior.

Migration check

See the current command contract for this behavior.

Local drive

See the current command contract for this behavior.

On this page