@cotal-ai/connector-core 0.109.16 → 0.109.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DocsBundle } from "./docs.js";
|
|
2
2
|
/** The installed Cotal version, for stamping the orientation card and other surfaces. */
|
|
3
|
-
export declare const DOCS_VERSION = "0.109.
|
|
3
|
+
export declare const DOCS_VERSION = "0.109.17";
|
|
4
4
|
export declare function loadDocsBundle(): DocsBundle;
|
|
5
5
|
//# sourceMappingURL=docs-bundle.generated.d.ts.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** The installed Cotal version, for stamping the orientation card and other surfaces. */
|
|
2
|
-
export const DOCS_VERSION = "0.109.
|
|
2
|
+
export const DOCS_VERSION = "0.109.17";
|
|
3
3
|
export function loadDocsBundle() {
|
|
4
4
|
return {
|
|
5
|
-
"version": "0.109.
|
|
5
|
+
"version": "0.109.17",
|
|
6
6
|
"generatedFrom": "docs/*.md + SPEC.md + spec/cotal-lang.md + spec/cotal.schema.json",
|
|
7
7
|
"pages": [
|
|
8
8
|
{
|
|
@@ -259,7 +259,7 @@ export function loadDocsBundle() {
|
|
|
259
259
|
"kind": "Concept (informative)",
|
|
260
260
|
"category": "Concept",
|
|
261
261
|
"summary": "How peers see each other and how messages reach them: the presence directory, the three delivery modes, and the two delivery guarantees.",
|
|
262
|
-
"body": "# Message flow\n\n> **Concept** (informative) · **For:** everyone · **Normative:** [SPEC §4](../SPEC.md#4-delivery-modes), [§6](../SPEC.md#6-presence-and-discovery), [§6.1](../SPEC.md#61-plane-liveness), [§7](../SPEC.md#7-channels), [§8](../SPEC.md#8-nats--jetstream-binding)\n\nHow peers see each other and how messages reach them: the presence directory, the three\ndelivery modes, and the two delivery guarantees. This page explains; the linked spec\nsections define.\n\n## Presence\n\nPresence is a per-space directory keyed by instance id: each peer's identity card\n(`AgentCard`: name, role, kind, tags, what it can do) plus its live state:\n\n- `idle`: free\n- `waiting`: blocked on input, approval, or a peer\n- `working`: the seat (or its surviving connector) claims it is busy. That is a\n process-alive claim, not a progress claim. A frozen seat can stay `working` while\n its heartbeat stays fresh. Surfaces that have no outside observation of last\n assistant-message age render `progress unknown` rather than treating heartbeat\n age as work age. A stale observation overlays `stalled Xm` on the still-fresh\n presence. The observation classifier is workstation/operator data, not part of\n the wire protocol; human wording stays in each CLI, connector, or web renderer.\n Compact maps and DM pickers use the presence glyph only and make no progress\n claim. See issue #876.\n- `offline`: gone (gracefully, or its heartbeat lapsed)\n\nA peer refreshes its own entry on a heartbeat; observers also derive `offline` from stale\ntimestamps, so a crashed agent cannot linger as \"working\". That derivation is gated on the\nobserver actually hearing the bucket: if the whole watch has been silent past the liveness\nwindow, the honest output is that the *view* is stale, not that every peer died in one TTL\n(real rosters do not do that). Offline peers stay in the roster for observability. An\n`activity` string rides along (\"what I'm doing right now\"), and a peer's **attention**\npreference is mirrored here too (below). A connector writes a new status and its activity in one\nrecord, so a peer never reads the new activity beside the old status. Each instance writes *only its own* key; presence\nis where discovery lives (our equivalent of `.well-known`), not a place to describe others.\nThe optional `condition` beside status relays a harness-reported cause such as `rate_limit`,\n`approval`, or `input`; missing means the harness reported none. A condition is cleared when a\nnormal next turn starts. The optional `activeAt` is the epoch ms of the last work event the harness\nreported, such as a token or a tool call; missing means the connector reports none. `ts` is only the\nheartbeat, so a seat whose turn stopped advancing keeps a fresh `ts` and an old `activeAt`. The\nconnector records it as events arrive and the next heartbeat carries it. `cotal ps`,\n`cotal endpoints`, `cotal console`, `cotal join` and `cotal_roster` print a condition with its age\nand the age of `activeAt`, such as `waiting (rate_limit for 40m) · active 40m ago`. The optional\n`statusSince` is the epoch ms when the instance entered its current status and activity. A change to\neither moves it, while a heartbeat or a repeated report does not, so an activity that outlived what\nit described reads as old. `cotal endpoints`, `cotal console`, `cotal join` and `cotal_roster` print\nits age, such as `idle · unchanged for 40m`. An offline record carries none,\nbecause an observer that derives `offline` from a stale heartbeat does not know when the peer left.\nThe optional `activitySince` is when the current activity was set. A status change does not move it,\nso an activity left behind while hooks flip the status every turn still shows its age after the\nactivity on the same surfaces, such as `(set 9h ago)`. A stamp that is not a finite number shows no\nage. The optional `runtime` describes the OS process holding the mesh connection. Its opaque `id`\nis minted once per process and lifecycle scope and is never reused across scopes. Its `startedAt`\nis the process's own `performance.timeOrigin`, shared by all endpoints in that process, and its\nclosed `source` is `\"endpoint-process\"`. Status, activity, heartbeats and in-process reconnects keep\nthat observation unchanged within the scope. A lifecycle registered later reports the process's\nage, not the lifecycle's age. It does not date a separately launched parent or model harness.\nAn offline record retains the observation without claiming a running duration. Older publishers\nomit it, which means unknown. A runtime-only change notifies presence observers.\nA peer writes its own condition code and activity, so every surface above prints each on one\nline, shows a control character or line separator in it as a `\\uXXXX` escape, and cuts it at 200\ncharacters.\nThe optional `environment` is an opaque provider reference. Core publishes\nit and never interprets it. Readers reject a row whose `card.id` does not match its KV key, whose\n`card.name` or `status` is missing or has the wrong type, whose `ts` is not a finite number, whose\n`card.role`, `activity` or `attention` is present and not a string, or whose `condition` is present and\nis not an object with a string `code`, such as `null`, a bare string or an object with no `code`. They\nreport that rejection through the recoverable warning path. A kept row whose `ts` is missing or text\nsuch as `\"nope\"` would never read as stale, so it would stay live after its key expired. A kept object such as\n`{\"toString\":null,\"valueOf\":null}` in a printed field would throw when converted and fail every\nreader's roster.\nDetails: [SPEC §6](../SPEC.md#6-presence-and-discovery). The dashboard surfaces a stale view\non the same header mark it uses for a refused poll ([watch a mesh](watch-a-mesh.md)).\n\nAn agent seat does not watch presence. A watch delivers every member's heartbeat to every watcher,\nso a space of K watching members moves K² deliveries every heartbeat. A seat reads the roster when\na tool needs it instead: `CotalEndpoint.readPresence()` pages batched Direct Gets over the bucket,\nbinds no consumer, applies the reader rules above and reads a record older than the liveness window\nas offline. Calls made while a read is in flight share it. A peer an earlier read saw that has left\nthe bucket stays in the roster as offline. The manager, the delivery daemon, `cotal chat` and the\ndashboard keep their watches, because they act on liveness transitions. The read needs the agent\ngrant `$JS.API.DIRECT.GET.KV_cotal_presence_<space>`, and a credential minted before that grant\nexisted is refused with the grant named.\n\n`CotalEndpoint.presenceView()` reports whether a watching endpoint's roster can support an absence verdict.\n`current` is usable, `unpopulated` means the current watch has not completed its initial snapshot,\nand `stale` means the watch has been silent past its liveness window. Both unsafe states carry\n`fresh: false`, so an older consumer degrades instead of treating a partial reconnect refill as a\ncomplete roster. `waitForPresenceSnapshot()` returns `snapshot` or `timeout`; a timeout is a bounded\ngive-up, not proof that the snapshot completed. An observer that does not register completes its\nsnapshot at once on a bucket with no keys, and its view is `current` with nobody present.\n\nA stale view under a live connection is not left to stand. The endpoint rebinds its presence watch\nfrom the bucket's current state once per liveness window and reports the rebind as a `warning`\nnaming the silent interval; a held link's rebind fails or stays silent and the view stays stale. A\nrebind that is still awaiting the broker when the endpoint stops or rebuilds its connection installs\nnothing. A rebind that lands on a bucket with no keys is current knowledge for an observer that\ndoes not register (nobody is present), and a wipe for one that does (its own key is missing too):\nthe latter re-publishes itself and lets the delivery of that record make the view current.\n`cotal ps` prints `mesh unknown` with the reason, never a liveness word, for a row whose\nmanager reports a view that is not `current` ([cli.md](cli.md)).\n\nPresence publishing is also monitored separately from the watch. One refused heartbeat remains a\nrecoverable warning. If consecutive writes keep failing for a full presence TTL, the endpoint raises\n`PresenceWriteStuckError` with code `presence-write-stuck` and marks the failure record as stuck.\n`cotal_orientation` and `cotal_roster` then say the view is not live and call the roster\nlast-known until a write succeeds. A successful write resets the consecutive count and clears the\ncondition. The condition is local diagnosis, not a new wire field.\n\nThe same two tools also render a failed roster read: they say the presence read failed and why,\ncall the roster last-known, and say a missing name is not an absence verdict. A DM to a name, or a\nsend that @mentions a name, that a failed read cannot verify is refused with that reason rather\nthan sent, instead of being reported as an unknown peer; a name an earlier read saw is still\naccepted. Both tools and both refusals print the reason in the same words.\n\n## Plane liveness\n\nPresence tells you which peers are around. It does not tell you whether the manager or the\ndelivery daemon is up, and the lease buckets that do know are not readable by agents. So when a\njoin or a send fails, a peer used to have no way to separate a credential problem from a dead\nmanager or an unbound delivery daemon.\n\nAny credentialed peer can now ask. It sends an empty request on\n`cotal.<space>.live.<plane>.<owner>.<actor>`, where `<plane>` is `manager` or `delivery` and\n`<owner>.<actor>` is its own principal, and names its reply subject under that request as\n`<request>.reply.<nonce>`. In code this is `CotalEndpoint.probeLiveness(plane)`. Any other plane\nname is refused before anything is sent.\n\nThe reply is a `LivenessAnswer`:\n\n```json\n{ \"plane\": \"delivery\", \"responder\": \"bound\", \"instance\": \"3f1c0b52-...\" }\n```\n\n`responder` is one of `bound`, `unbound`, `stale` or `unknown`. `instance` is an opaque token\nthe responder mints each time it binds. Two answers with different tokens came from two\nresponders, which is how a caller probing more than once can spot two manager instances that\ndisagree. The token identifies nothing else: it is not the instance id, the principal, a pid, a\nhost or a path. The reply carries nothing beyond these three fields, so the lease row's holder\nand workspace path never leave the responder.\n\nHow the caller reads the outcome:\n\n| What happened | `responder` |\n| --- | --- |\n| a well-formed reply | whatever the reply says |\n| the broker answered \"no responders\" | `unbound` |\n| timeout, permission refusal, transport failure | `unknown` |\n| a reply that does not parse | `unknown` |\n\n`unknown` means the probe did not find out. It is never a health report. Each responder grades\nonly itself. The manager says `bound` while its service endpoint is serving, and `unbound` while\nthat connection is down, both while the client reconnects it and while the manager re-dials it\nafter a close. The delivery daemon\nreads its own shard lease: no ready row is `unbound`, a ready row held by another instance is\n`stale`, its own ready row is `bound`. A responder that cannot read its own state answers\n`unknown`.\n\nResponders serve `live.<plane>.*.*` in the queue group `live.<plane>`, so one probe gets one\nanswer from one instance. Only the plane's own credential holds that filter, and its reply grant\nstops at the `.reply.` leaf, so a responder cannot forge a probe and an agent cannot answer one.\nA request whose reply target is outside the caller's own `.reply.` subtree is dropped with a\nwarning, and the responder keeps serving. When an endpoint replaces its broker connection it\nbinds its responders again on the new one, under a new token, so a reconnect does not leave the\nplane looking unbound. The normative rules are in\n[SPEC §6.1](../SPEC.md#61-plane-liveness).\n\n## Three delivery modes\n\nEvery delivery message is addressed one of three ways\n([SPEC §4](../SPEC.md#4-delivery-modes)):\n\n| Mode | Addressed by | Reaches |\n|---|---|---|\n| **multicast** | `channel` | every subscriber of the channel |\n| **unicast** | `to` (instance id) | one specific peer's inbox |\n| **anycast** | `toService` (role) | *any one* holder of the role: \"whoever is a reviewer\" |\n\n\n\n\n\n\n\nChannels are dotted and hierarchical (`team.backend`); publishing is always concrete,\nsubscriptions may wildcard a subtree (`team.>`). Anycast is queued work: a task with no\nworker online *waits*; multiple online instances of a role load-balance; the task is\nremoved once acked.\n\n**Mentions.** A multicast message may carry `mentions: [name…]`, a *priority hint*, not\na routing target. The message still reaches the whole channel, but a mentioned peer is\nwoken immediately while everyone else picks it up when next idle. Names (not instance\nids) ride the wire, so the match survives reconnects.\nOn a `live` channel a mention also writes a durable copy for each mentioned peer whose read\nACL covers the channel, so an authorized peer that is offline or not joined still receives it.\nA mention never carries content to a peer outside its read ACL\n([SPEC §4](../SPEC.md#4-delivery-modes)).\n\n**Deriving the mode.** A receiver derives how a message was addressed (channel / dm /\nanycast) from the *delivering subject*, never from payload fields: the payload is\nadvisory and forgeable, while the subject is broker-policed\n([SPEC §4](../SPEC.md#4-delivery-modes), [identity & auth](identity-and-auth.md)).\n\n**How the block is framed.** Delivered messages arrive as one block: a header, the items,\nand a tail. The tail gives the order of operations: do what was asked with your own tools,\nverify the result, then reply. It says not to report an action that was not performed, and it\nstill names the reply verbs. A peer message is often a work order, and the tail sits where the\nmodel decides its next action. A tail that listed only reply tools read as a chat turn to\nanswer. In one observed case a live seat told to write a file and confirm sent the confirmation\nseconds later, twice, with no file tool called and no file on disk. The tail narrows that\nfailure but cannot prevent it, since no footer makes a model honest. It does keep the connector\nfrom steering the model toward a reply-only answer.\n\n## Durable transport\n\nPlain pub/sub is at-most-once: a message reaches only whoever is subscribed *at that\ninstant*. Agents are constantly `working` or `offline`; a DM sent mid-turn would simply\nvanish. So delivery rides **JetStream streams**: the broker stores each message and every\ndurable reader keeps its own bookmark, catching up at its own pace. One publish reaches the live\nsubscribers and is retained for each reader's inbound buffer and for late-join history. DMs and\nanycast are always at-least-once this way ([SPEC §8](../SPEC.md#8-nats--jetstream-binding)).\n\nA send result proves only that the broker accepted and stored the message at a sequence\n(`stored seq N`), not that any recipient read it, and no send claims `delivered`. The `cotal_dm`\ntool reports that sequence together with the recipient's roster status at the moment of send\n(`idle`, `working`, or `offline`). When that status is `offline`, `cotal_dm`, `cotal send -t`,\n`cotal chat`, `cotal join` and the console also say the agent is offline and that the message is\ndelivered when it reconnects. The DM durable belongs to the recipient's lifecycle, so the same\nsession receives the message when it comes back, and a new agent spawned under that name does not.\nA `cotal_dm` reply to a\nsender that has no roster row, such as a one-shot `cotal send`, reads\n`recipient had no roster row at send` instead: the DM stream keeps it under the sender's id, and\nit may never reach an inbox. The stored sequence\nis a fact about the stream; the status-at-send words are a fact about the roster a moment\nbefore publish; retention (how long the durable holds it, whether a same-name respawn\ninherits it) is a third, separate fact, covered below and inspectable with\n[`cotal pending`](cli.md#pending).\n\nA recipient's connector acknowledges a DM only once it has handed it to the session. When the\nsession is busy and its bounded local inbox fills with directed mail, the oldest DM is evicted\nfrom that buffer but left unacknowledged: it stays pending on the recipient's durable and the\nbroker redelivers it after the durable's ack wait, so it lands once the session drains\n([Connect Claude](connect-claude.md#how-messages-reach-the-session)).\n\nA connector that acks only when a turn ends keeps the messages that turn carries from being\nredelivered while it runs, by restarting their ack wait with JetStream's in-progress ack\n([Connect OpenCode](connect-opencode.md#how-it-binds)). A redelivered copy says so: the broker\ncounts each delivery, and a connector marks a message the broker delivered more than once as\nredelivered in the frame it injects. The count comes from the broker, so a sender cannot forge\nor hide it.\n\nDelivery stays at-least-once, so a role request can still run twice: when its holder crashes or\nfails a turn after acting, or when renewal lapsed and the broker handed the request to another\nholder of the role. A stored sequence proves the broker accepted the request, not that one worker\ncompleted it alone. Work with side effects that must not repeat needs its own ownership or an\nidempotency key.\n\n### Reading only your own DMs\n\nA reader whose credential grants nothing but its own DM durable can still receive DMs. Construct\n`CotalEndpoint` with `dmOnly: { handler }`. The endpoint binds the existing\n`dm_<owner>-<actor>-<uid>` durable and reads nothing else: no presence, channel registry, channel\nsubscription, delivery durable, history or JetStream account lookup. On the wire it sends only\nCONSUMER.INFO and MSG.NEXT for that durable and acks under its `$JS.ACK` subtree.\n\nIt never creates the durable. A missing durable fails start with `dm-durable-missing`, and a\nrefused bind fails with `dm-bind-refused`. No other credential is tried. Each DM passes the same\nsender check as every other durable read before the handler sees it, and the handler runs one DM at\na time. A handler acks explicitly; a rejection leaves the copy for redelivery. `stop()` resolves\nafter the running handler settles and the connection closes. When either cannot be proven, it\nthrows and the caller keeps custody. After `stop()` begins, or after a reconnect replaces the\nconnection, a copy the client already received never reaches the handler; it stays unacked and the\nbroker redelivers it after the durable's ack wait. An ack, nak or in-progress signal from a handler\nwhose connection has closed throws instead of being lost. The mode registers no presence, so the\nreader is not on the roster. Credential renewal and reconnects use the endpoint's normal paths.\nOn a user-auth mesh, the `dm-reader` exchange view mints this credential\n([identity-and-auth.md](identity-and-auth.md)).\n\n## Channel delivery\n\nChannel delivery has two wire-observable classes, fixed per channel\n([SPEC §4](../SPEC.md#4-delivery-modes), [§7](../SPEC.md#7-channels)):\n\n- **`live`**: native broker subscription, at-most-once. You receive what is published\n while you are subscribed; a busy or offline moment is a gap. Join = subscribe, leave =\n unsubscribe: self-serve, bounded by your read ACL, no privileged mediation.\n- **`durable`**. `live` plus a per-member **durable backstop**: the message is also\n retained for each member and delivered on its next connection or turn, pending until\n acked. At-least-once for current members, within the channel's retention window. The\n machinery behind the backstop is the [delivery daemon](delivery-daemon.md).\n\nA message delivered both ways is one logical delivery; receivers dedupe by `id`, and\nreceiver deduplication MUST NOT use the empty string as a key: distinct messages that carry\n`id: \"\"` are not coalesced by the receiver. Duplicate surfacing is disclosed only where the\npath is already at-least-once (live is at-most-once). The publisher obligation to supply a\nunique string id (SPEC §5) is unchanged; an absent or non-string id is a malformed envelope. The\nspace default class is set at creation from the deployment profile (local/self-hosted ⇒\n`durable`); a channel can override it.\n\n**Replay on join.** A channel's registry config (`replay`, `replayWindow`) says whether a\nfresh joiner gets recent history backfilled, marked as historical so an agent doesn't\nmistake a resolved old thread for live traffic. All historical channel messages, including\n@mentions, are pull-only: they never drive automatic turns or wake the session, in any attention\nmode. Read them on demand through `cotal_inbox`. Catch-up is framed separately from new messages,\nwithout an instruction to act on old requests. New mentions and direct messages keep their normal\nwake behavior. Redelivery of an unhandled live message is not join replay and remains eligible for\ndelivery. Replay off is **noise control, not confidentiality**: history stays readable within the read ACL\n([channels & permissions](channels-and-permissions.md)).\n\n## Attention\n\nOrthogonal to all of the above, each agent chooses how much traffic *wakes* it: a global\nmode (`open` / `dnd` / `focus`) plus per-channel overrides (`quiet` / `muted`). This is\n**connector UX**: the broker still authorizes and delivers; attention\nonly shapes when the receiving agent's session is interrupted. It is mirrored into\npresence as advisory observability (\"locally muted #deploys; DM to reach\"), never read\nback into delivery. Semantics and tables:\n[Connect Claude](connect-claude.md#attention); the concrete\nknobs: [`cotal_status` / `cotal_channel_mode`](mcp-tools.md).\n\n## Related\n\n- [Spaces & channels](spaces.md): the isolation boundary vs the topic axis.\n- [Delivery daemon](delivery-daemon.md): the durable backstop's three pieces.\n- [Identity & auth](identity-and-auth.md): who may publish and read where.\n- [Watch a mesh](watch-a-mesh.md): seeing presence and traffic live.\n"
|
|
262
|
+
"body": "# Message flow\n\n> **Concept** (informative) · **For:** everyone · **Normative:** [SPEC §4](../SPEC.md#4-delivery-modes), [§6](../SPEC.md#6-presence-and-discovery), [§6.1](../SPEC.md#61-plane-liveness), [§7](../SPEC.md#7-channels), [§8](../SPEC.md#8-nats--jetstream-binding)\n\nHow peers see each other and how messages reach them: the presence directory, the three\ndelivery modes, and the two delivery guarantees. This page explains; the linked spec\nsections define.\n\n## Presence\n\nPresence is a per-space directory keyed by instance id: each peer's identity card\n(`AgentCard`: name, role, kind, tags, what it can do) plus its live state:\n\n- `idle`: free\n- `waiting`: blocked on input, approval, or a peer\n- `working`: the seat (or its surviving connector) claims it is busy. That is a\n process-alive claim, not a progress claim. A frozen seat can stay `working` while\n its heartbeat stays fresh. Surfaces that have no outside observation of last\n assistant-message age render `progress unknown` rather than treating heartbeat\n age as work age. A stale observation overlays `stalled Xm` on the still-fresh\n presence. The observation classifier is workstation/operator data, not part of\n the wire protocol; human wording stays in each CLI, connector, or web renderer.\n Compact maps and DM pickers use the presence glyph only and make no progress\n claim. See issue #876.\n- `offline`: gone (gracefully, or its heartbeat lapsed)\n\nA peer refreshes its own entry on a heartbeat; observers also derive `offline` from stale\ntimestamps, so a crashed agent cannot linger as \"working\". That derivation is gated on the\nobserver actually hearing the bucket: if the whole watch has been silent past the liveness\nwindow, the honest output is that the *view* is stale, not that every peer died in one TTL\n(real rosters do not do that). Offline peers stay in the roster for observability. An\n`activity` string rides along (\"what I'm doing right now\"), and a peer's **attention**\npreference is mirrored here too (below). A connector writes a new status and its activity in one\nrecord, so a peer never reads the new activity beside the old status. Each instance writes *only its own* key; presence\nis where discovery lives (our equivalent of `.well-known`), not a place to describe others.\nThe optional `condition` beside status relays a harness-reported cause such as `rate_limit`,\n`approval`, or `input`; missing means the harness reported none. A condition is cleared when a\nnormal next turn starts. The optional `activeAt` is the epoch ms of the last work event the harness\nreported, such as a token or a tool call; missing means the connector reports none. `ts` is only the\nheartbeat, so a seat whose turn stopped advancing keeps a fresh `ts` and an old `activeAt`. The\nconnector records it as events arrive and the next heartbeat carries it. `cotal ps`,\n`cotal endpoints`, `cotal console`, `cotal join` and `cotal_roster` print a condition with its age\nand the age of `activeAt`, such as `waiting (rate_limit for 40m) · active 40m ago`. The optional\n`statusSince` is the epoch ms when the instance entered its current status and activity. A change to\neither moves it, while a heartbeat or a repeated report does not, so an activity that outlived what\nit described reads as old. `cotal endpoints`, `cotal console`, `cotal join` and `cotal_roster` print\nits age, such as `idle · unchanged for 40m`. An offline record carries none,\nbecause an observer that derives `offline` from a stale heartbeat does not know when the peer left.\nThe optional `activitySince` is when the current activity was set. A status change does not move it,\nso an activity left behind while hooks flip the status every turn still shows its age after the\nactivity on the same surfaces, such as `(set 9h ago)`. A stamp that is not a finite number shows no\nage. The optional `runtime` describes the OS process holding the mesh connection. Its opaque `id`\nis minted once per process and lifecycle scope and is never reused across scopes. Its `startedAt`\nis the process's own `performance.timeOrigin`, shared by all endpoints in that process, and its\nclosed `source` is `\"endpoint-process\"`. Status, activity, heartbeats and in-process reconnects keep\nthat observation unchanged within the scope. A lifecycle registered later reports the process's\nage, not the lifecycle's age. It does not date a separately launched parent or model harness.\nAn offline record retains the observation without claiming a running duration. Older publishers\nomit it, which means unknown. A runtime-only change notifies presence observers.\nA peer writes its own condition code and activity, so every surface above prints each on one\nline, shows a control character or line separator in it as a `\\uXXXX` escape, and cuts it at 200\ncharacters.\nThe optional `environment` is an opaque provider reference. Core publishes\nit and never interprets it. Readers reject a row whose `card.id` does not match its KV key, whose\n`card.name` or `status` is missing or has the wrong type, whose `ts` is not a finite number, whose\n`card.role`, `activity` or `attention` is present and not a string, or whose `condition` is present and\nis not an object with a string `code`, such as `null`, a bare string or an object with no `code`. They\nreport that rejection through the recoverable warning path. A kept row whose `ts` is missing or text\nsuch as `\"nope\"` would never read as stale, so it would stay live after its key expired. A kept object such as\n`{\"toString\":null,\"valueOf\":null}` in a printed field would throw when converted and fail every\nreader's roster.\nDetails: [SPEC §6](../SPEC.md#6-presence-and-discovery). The dashboard surfaces a stale view\non the same header mark it uses for a refused poll ([watch a mesh](watch-a-mesh.md)).\n\nAn agent seat does not watch presence. A watch delivers every member's heartbeat to every watcher,\nso a space of K watching members moves K² deliveries every heartbeat. A seat reads the roster when\na tool needs it instead: `CotalEndpoint.readPresence()` pages batched Direct Gets over the bucket,\nbinds no consumer, applies the reader rules above and reads a record older than the liveness window\nas offline. Calls made while a read is in flight share it. A peer an earlier read saw that has left\nthe bucket stays in the roster as offline. The manager, the delivery daemon, `cotal chat` and the\ndashboard keep their watches, because they act on liveness transitions. The read needs the agent\ngrant `$JS.API.DIRECT.GET.KV_cotal_presence_<space>`. An older manager or auth service can mint\ncredentials without it, including newly minted ones. Upgrade the issuer first, then mint new\ncredentials and use them for the connection. Reconnecting with the same credential does not add a\ngrant. A refused read remains an authorization failure, never an empty roster.\n\n`CotalEndpoint.presenceView()` reports whether a watching endpoint's roster can support an absence verdict.\n`current` is usable, `unpopulated` means the current watch has not completed its initial snapshot,\nand `stale` means the watch has been silent past its liveness window. Both unsafe states carry\n`fresh: false`, so an older consumer degrades instead of treating a partial reconnect refill as a\ncomplete roster. `waitForPresenceSnapshot()` returns `snapshot` or `timeout`; a timeout is a bounded\ngive-up, not proof that the snapshot completed. An observer that does not register completes its\nsnapshot at once on a bucket with no keys, and its view is `current` with nobody present.\n\nA stale view under a live connection is not left to stand. The endpoint rebinds its presence watch\nfrom the bucket's current state once per liveness window and reports the rebind as a `warning`\nnaming the silent interval; a held link's rebind fails or stays silent and the view stays stale. A\nrebind that is still awaiting the broker when the endpoint stops or rebuilds its connection installs\nnothing. A rebind that lands on a bucket with no keys is current knowledge for an observer that\ndoes not register (nobody is present), and a wipe for one that does (its own key is missing too):\nthe latter re-publishes itself and lets the delivery of that record make the view current.\n`cotal ps` prints `mesh unknown` with the reason, never a liveness word, for a row whose\nmanager reports a view that is not `current` ([cli.md](cli.md)).\n\nPresence publishing is also monitored separately from the watch. One refused heartbeat remains a\nrecoverable warning. If consecutive writes keep failing for a full presence TTL, the endpoint raises\n`PresenceWriteStuckError` with code `presence-write-stuck` and marks the failure record as stuck.\n`cotal_orientation` and `cotal_roster` then say the view is not live and call the roster\nlast-known until a write succeeds. A successful write resets the consecutive count and clears the\ncondition. The condition is local diagnosis, not a new wire field.\n\nThe same two tools also render a failed roster read: they say the presence read failed and why,\ncall the roster last-known, and say a missing name is not an absence verdict. A DM to a name, or a\nsend that @mentions a name, that a failed read cannot verify is refused with that reason rather\nthan sent, instead of being reported as an unknown peer; a name an earlier read saw is still\naccepted. Both tools and both refusals print the reason in the same words.\n\n## Plane liveness\n\nPresence tells you which peers are around. It does not tell you whether the manager or the\ndelivery daemon is up, and the lease buckets that do know are not readable by agents. So when a\njoin or a send fails, a peer used to have no way to separate a credential problem from a dead\nmanager or an unbound delivery daemon.\n\nAny credentialed peer can now ask. It sends an empty request on\n`cotal.<space>.live.<plane>.<owner>.<actor>`, where `<plane>` is `manager` or `delivery` and\n`<owner>.<actor>` is its own principal, and names its reply subject under that request as\n`<request>.reply.<nonce>`. In code this is `CotalEndpoint.probeLiveness(plane)`. Any other plane\nname is refused before anything is sent.\n\nThe reply is a `LivenessAnswer`:\n\n```json\n{ \"plane\": \"delivery\", \"responder\": \"bound\", \"instance\": \"3f1c0b52-...\" }\n```\n\n`responder` is one of `bound`, `unbound`, `stale` or `unknown`. `instance` is an opaque token\nthe responder mints each time it binds. Two answers with different tokens came from two\nresponders, which is how a caller probing more than once can spot two manager instances that\ndisagree. The token identifies nothing else: it is not the instance id, the principal, a pid, a\nhost or a path. The reply carries nothing beyond these three fields, so the lease row's holder\nand workspace path never leave the responder.\n\nHow the caller reads the outcome:\n\n| What happened | `responder` |\n| --- | --- |\n| a well-formed reply | whatever the reply says |\n| the broker answered \"no responders\" | `unbound` |\n| timeout, permission refusal, transport failure | `unknown` |\n| a reply that does not parse | `unknown` |\n\n`unknown` means the probe did not find out. It is never a health report. Each responder grades\nonly itself. The manager says `bound` while its service endpoint is serving, and `unbound` while\nthat connection is down, both while the client reconnects it and while the manager re-dials it\nafter a close. The delivery daemon\nreads its own shard lease: no ready row is `unbound`, a ready row held by another instance is\n`stale`, its own ready row is `bound`. A responder that cannot read its own state answers\n`unknown`.\n\nResponders serve `live.<plane>.*.*` in the queue group `live.<plane>`, so one probe gets one\nanswer from one instance. Only the plane's own credential holds that filter, and its reply grant\nstops at the `.reply.` leaf, so a responder cannot forge a probe and an agent cannot answer one.\nA request whose reply target is outside the caller's own `.reply.` subtree is dropped with a\nwarning, and the responder keeps serving. When an endpoint replaces its broker connection it\nbinds its responders again on the new one, under a new token, so a reconnect does not leave the\nplane looking unbound. The normative rules are in\n[SPEC §6.1](../SPEC.md#61-plane-liveness).\n\n## Three delivery modes\n\nEvery delivery message is addressed one of three ways\n([SPEC §4](../SPEC.md#4-delivery-modes)):\n\n| Mode | Addressed by | Reaches |\n|---|---|---|\n| **multicast** | `channel` | every subscriber of the channel |\n| **unicast** | `to` (instance id) | one specific peer's inbox |\n| **anycast** | `toService` (role) | *any one* holder of the role: \"whoever is a reviewer\" |\n\n\n\n\n\n\n\nChannels are dotted and hierarchical (`team.backend`); publishing is always concrete,\nsubscriptions may wildcard a subtree (`team.>`). Anycast is queued work: a task with no\nworker online *waits*; multiple online instances of a role load-balance; the task is\nremoved once acked.\n\n**Mentions.** A multicast message may carry `mentions: [name…]`, a *priority hint*, not\na routing target. The message still reaches the whole channel, but a mentioned peer is\nwoken immediately while everyone else picks it up when next idle. Names (not instance\nids) ride the wire, so the match survives reconnects.\nOn a `live` channel a mention also writes a durable copy for each mentioned peer whose read\nACL covers the channel, so an authorized peer that is offline or not joined still receives it.\nA mention never carries content to a peer outside its read ACL\n([SPEC §4](../SPEC.md#4-delivery-modes)).\n\n**Deriving the mode.** A receiver derives how a message was addressed (channel / dm /\nanycast) from the *delivering subject*, never from payload fields: the payload is\nadvisory and forgeable, while the subject is broker-policed\n([SPEC §4](../SPEC.md#4-delivery-modes), [identity & auth](identity-and-auth.md)).\n\n**How the block is framed.** Delivered messages arrive as one block: a header, the items,\nand a tail. The tail gives the order of operations: do what was asked with your own tools,\nverify the result, then reply. It says not to report an action that was not performed, and it\nstill names the reply verbs. A peer message is often a work order, and the tail sits where the\nmodel decides its next action. A tail that listed only reply tools read as a chat turn to\nanswer. In one observed case a live seat told to write a file and confirm sent the confirmation\nseconds later, twice, with no file tool called and no file on disk. The tail narrows that\nfailure but cannot prevent it, since no footer makes a model honest. It does keep the connector\nfrom steering the model toward a reply-only answer.\n\n## Durable transport\n\nPlain pub/sub is at-most-once: a message reaches only whoever is subscribed *at that\ninstant*. Agents are constantly `working` or `offline`; a DM sent mid-turn would simply\nvanish. So delivery rides **JetStream streams**: the broker stores each message and every\ndurable reader keeps its own bookmark, catching up at its own pace. One publish reaches the live\nsubscribers and is retained for each reader's inbound buffer and for late-join history. DMs and\nanycast are always at-least-once this way ([SPEC §8](../SPEC.md#8-nats--jetstream-binding)).\n\nA send result proves only that the broker accepted and stored the message at a sequence\n(`stored seq N`), not that any recipient read it, and no send claims `delivered`. The `cotal_dm`\ntool reports that sequence together with the recipient's roster status at the moment of send\n(`idle`, `working`, or `offline`). When that status is `offline`, `cotal_dm`, `cotal send -t`,\n`cotal chat`, `cotal join` and the console also say the agent is offline and that the message is\ndelivered when it reconnects. The DM durable belongs to the recipient's lifecycle, so the same\nsession receives the message when it comes back, and a new agent spawned under that name does not.\nA `cotal_dm` reply to a\nsender that has no roster row, such as a one-shot `cotal send`, reads\n`recipient had no roster row at send` instead: the DM stream keeps it under the sender's id, and\nit may never reach an inbox. The stored sequence\nis a fact about the stream; the status-at-send words are a fact about the roster a moment\nbefore publish; retention (how long the durable holds it, whether a same-name respawn\ninherits it) is a third, separate fact, covered below and inspectable with\n[`cotal pending`](cli.md#pending).\n\nA recipient's connector acknowledges a DM only once it has handed it to the session. When the\nsession is busy and its bounded local inbox fills with directed mail, the oldest DM is evicted\nfrom that buffer but left unacknowledged: it stays pending on the recipient's durable and the\nbroker redelivers it after the durable's ack wait, so it lands once the session drains\n([Connect Claude](connect-claude.md#how-messages-reach-the-session)).\n\nA connector that acks only when a turn ends keeps the messages that turn carries from being\nredelivered while it runs, by restarting their ack wait with JetStream's in-progress ack\n([Connect OpenCode](connect-opencode.md#how-it-binds)). A redelivered copy says so: the broker\ncounts each delivery, and a connector marks a message the broker delivered more than once as\nredelivered in the frame it injects. The count comes from the broker, so a sender cannot forge\nor hide it.\n\nDelivery stays at-least-once, so a role request can still run twice: when its holder crashes or\nfails a turn after acting, or when renewal lapsed and the broker handed the request to another\nholder of the role. A stored sequence proves the broker accepted the request, not that one worker\ncompleted it alone. Work with side effects that must not repeat needs its own ownership or an\nidempotency key.\n\n### Reading only your own DMs\n\nA reader whose credential grants nothing but its own DM durable can still receive DMs. Construct\n`CotalEndpoint` with `dmOnly: { handler }`. The endpoint binds the existing\n`dm_<owner>-<actor>-<uid>` durable and reads nothing else: no presence, channel registry, channel\nsubscription, delivery durable, history or JetStream account lookup. On the wire it sends only\nCONSUMER.INFO and MSG.NEXT for that durable and acks under its `$JS.ACK` subtree.\n\nIt never creates the durable. A missing durable fails start with `dm-durable-missing`, and a\nrefused bind fails with `dm-bind-refused`. No other credential is tried. Each DM passes the same\nsender check as every other durable read before the handler sees it, and the handler runs one DM at\na time. A handler acks explicitly; a rejection leaves the copy for redelivery. `stop()` resolves\nafter the running handler settles and the connection closes. When either cannot be proven, it\nthrows and the caller keeps custody. After `stop()` begins, or after a reconnect replaces the\nconnection, a copy the client already received never reaches the handler; it stays unacked and the\nbroker redelivers it after the durable's ack wait. An ack, nak or in-progress signal from a handler\nwhose connection has closed throws instead of being lost. The mode registers no presence, so the\nreader is not on the roster. Credential renewal and reconnects use the endpoint's normal paths.\nOn a user-auth mesh, the `dm-reader` exchange view mints this credential\n([identity-and-auth.md](identity-and-auth.md)).\n\n## Channel delivery\n\nChannel delivery has two wire-observable classes, fixed per channel\n([SPEC §4](../SPEC.md#4-delivery-modes), [§7](../SPEC.md#7-channels)):\n\n- **`live`**: native broker subscription, at-most-once. You receive what is published\n while you are subscribed; a busy or offline moment is a gap. Join = subscribe, leave =\n unsubscribe: self-serve, bounded by your read ACL, no privileged mediation.\n- **`durable`**. `live` plus a per-member **durable backstop**: the message is also\n retained for each member and delivered on its next connection or turn, pending until\n acked. At-least-once for current members, within the channel's retention window. The\n machinery behind the backstop is the [delivery daemon](delivery-daemon.md).\n\nA message delivered both ways is one logical delivery; receivers dedupe by `id`, and\nreceiver deduplication MUST NOT use the empty string as a key: distinct messages that carry\n`id: \"\"` are not coalesced by the receiver. Duplicate surfacing is disclosed only where the\npath is already at-least-once (live is at-most-once). The publisher obligation to supply a\nunique string id (SPEC §5) is unchanged; an absent or non-string id is a malformed envelope. The\nspace default class is set at creation from the deployment profile (local/self-hosted ⇒\n`durable`); a channel can override it.\n\n**Replay on join.** A channel's registry config (`replay`, `replayWindow`) says whether a\nfresh joiner gets recent history backfilled, marked as historical so an agent doesn't\nmistake a resolved old thread for live traffic. All historical channel messages, including\n@mentions, are pull-only: they never drive automatic turns or wake the session, in any attention\nmode. Read them on demand through `cotal_inbox`. Catch-up is framed separately from new messages,\nwithout an instruction to act on old requests. New mentions and direct messages keep their normal\nwake behavior. Redelivery of an unhandled live message is not join replay and remains eligible for\ndelivery. Replay off is **noise control, not confidentiality**: history stays readable within the read ACL\n([channels & permissions](channels-and-permissions.md)).\n\n## Attention\n\nOrthogonal to all of the above, each agent chooses how much traffic *wakes* it: a global\nmode (`open` / `dnd` / `focus`) plus per-channel overrides (`quiet` / `muted`). This is\n**connector UX**: the broker still authorizes and delivers; attention\nonly shapes when the receiving agent's session is interrupted. It is mirrored into\npresence as advisory observability (\"locally muted #deploys; DM to reach\"), never read\nback into delivery. Semantics and tables:\n[Connect Claude](connect-claude.md#attention); the concrete\nknobs: [`cotal_status` / `cotal_channel_mode`](mcp-tools.md).\n\n## Related\n\n- [Spaces & channels](spaces.md): the isolation boundary vs the topic axis.\n- [Delivery daemon](delivery-daemon.md): the durable backstop's three pieces.\n- [Identity & auth](identity-and-auth.md): who may publish and read where.\n- [Watch a mesh](watch-a-mesh.md): seeing presence and traffic live.\n"
|
|
263
263
|
},
|
|
264
264
|
{
|
|
265
265
|
"slug": "release",
|
|
@@ -363,7 +363,7 @@ export function loadDocsBundle() {
|
|
|
363
363
|
"kind": "Concept (informative)",
|
|
364
364
|
"category": "Concept",
|
|
365
365
|
"summary": "A workflow run is a program that coordinates agents over hours or days and survives the process that started it.",
|
|
366
|
-
"body": "# Workflow runs\n\n> **Concept** (informative) · **For:** people writing a durable multi-agent workflow, and implementers hosting one · **Normative:** [SPEC §14](../SPEC.md#14-workflow-runs-v05) and the language reference [`spec/cotal-lang.md`](../spec/cotal-lang.md)\n\nA **workflow run** is a program that coordinates agents over hours or days and survives the\nprocess that started it. The program is written in **Cotal Lang**, a small subset of JavaScript in\nwhich every interaction with the world is one of a dozen **effects** (`spawn`, `turn`, `ask`,\n`checkpoint`, `sleep`, `wait`, `notify`, `monitor`, and the four concurrency scopes) and everything\nelse is ordinary, pure JavaScript. Every effect is written into the run's **step journal** before\nit is performed and settled after, keyed by where in the program it happened rather than by when,\nso a run that dies is resumed on any host by **re-running the program from the top** with recorded\neffects returning their recorded results. Nothing about the interpreter is ever serialized: the\njournal and the program are the whole state.\n\n## A first program\n\n```js\nconst planner = await spawn(\"planner\")\nconst builder = await spawn(\"builder\", { worktree: \"wt-1\" })\n\nconst plan = await ask(planner, { name: \"plan\", schema: { steps: \"array\" } })\nconst ok = await checkpoint(\"approve-plan\", \"Approve the plan?\", { timeout: \"4h\", onExpiry: \"proceed\" })\nif (ok.status !== \"resolved\") {\n await notify([planner], { decision: \"approve-plan\", outcome: \"expired\" })\n}\n\nconst r = await turn(builder, { name: \"build\", deadline: \"30m\" })\nif (r.status === \"blocked\") {\n await turn(planner, { name: \"unblock\" })\n}\n\nconst outcome = await race({\n reply: () => wait(replied(builder), { timeout: \"20m\" }),\n giveUp: () => sleep(\"1h\"),\n}, { name: \"await-or-move-on\" })\nlog(\"outcome\", outcome.index)\n```\n\nRead it as the flowchart it is. `spawn` brings agents in; `ask` is the narrow case where the\nprogram itself needs a value (`schema` is a record the program hands the handler unchanged; the\nlanguage hashes it and gives it no meaning, and the handlers in this repository enforce it as the\nshorthand of the language reference §6.5);\n`checkpoint` is a durable pause a human resolves from anywhere, raced against a durable timer; `turn`\nwakes an agent for one turn and returns how it yielded; `race` runs two branches and keeps the one\nwhose recorded clock is earliest. Agents talk to each other in channels as they always do; the\nprogram never speaks in a channel, and the one thing it can put in front of an agent (`notify`) is a\nbounded decision record, not prose.\n\n## The mental model\n\n- **Pure code is JavaScript.** Loops, records, arrays, closures, template literals, destructuring,\n `try`/`catch`, arithmetic, `switch`, compound assignment, optional chaining, spread and rest: what\n you would write anyway, with the parts that hide effects or make meaning depend on the host removed\n (`class`, `this`, `new`, `for...in`, `==`, labels, regex literals, `Math`/`Date`/`JSON`, promises,\n generators). Every refusal names its code and the edit that fixes it. The builtins are a short list\n (`keys`, `map`, `sort`, `json.stringify`, `now()`, `random()`), and arrays, strings and numbers\n answer their usual methods (`xs.map`, `s.trim()`, `n.toFixed()`) and nothing outside that table.\n Records and arrays you build are yours to change until they cross an effect boundary; a member you\n do not own, a host prototype, or a value another branch built is refused with a code, never a\n surprise.\n- **Every effect is journalled and hashed.** A step is keyed `(scope path, kind, name, occurrence)`\n and its inputs are hashed. Reorder your program, add a step, rename a variable: recorded steps\n still match. Change what a step asks (a checkpoint's prompt, a sleep's duration, a turn's\n deadline) and the resume stops with a **divergence** naming the step, rather than replaying an\n answer to a question the program no longer asks.\n- **Concurrency is visible.** `parallel`, `race`, `fanOut` and `conclave` are the only ways to do\n two things at once, each branch gets its own journal namespace, and the scope writes its own\n entry saying how it settled: which arm won a race is a recorded fact, decided by the arms'\n recorded clocks and declaration order, never by a scheduler. A failure the program itself caused\n inside the scope settles the entry under its own catalog code (`fanOut` without a stable key is\n `L3021`, kind `runtime`), so a resume reads the same code the live run threw; a plain failure\n from the handler records the generic `L4000` `scope-fault`. A value the program throws inside a\n branch is recorded with that fault, so `catch` receives the same value on resume, and in a\n migration's dry walk, as it did live.\n A branch may not write to anything declared outside it; return the value and read it out of the\n scope's result.\n- **Time and randomness are tamed.** `now()` is the branch's run clock, the end of the last effect\n it awaited; `random()` is a seeded stream derived per scope, each draw a number from 0 up to but\n not including 1. `randomInt(n)` is `floor(random() * n)`, an integer from 0 to `n - 1` for a\n positive integer `n`, and `pick(xs)` draws one element. They replay identically. A builtin ignores\n arguments past the ones it takes, as JavaScript does, so there is no range form:\n `randomInt(1, 1000)` is `randomInt(1)`, which is always 0, and `1 + randomInt(1000)` draws from 1\n to 1000.\n- **Values freeze at the boundary.** What crossed into or out of an effect is what the journal\n recorded, and it cannot change afterwards; build a new value.\n- **The journal is the debugger.** Every entry carries its key, its inputs' hash, its outcome and\n its timing, and every error is in the program's own coordinates. A run can be **simulated** with a\n scripted handler and **dry-run** to a plan before it touches an agent. The simulator is\n discrete-event: timed effects park at their wake times and are delivered in wake order on one\n virtual clock, so concurrent branches accumulate the durations they wrote and a simulated `race`\n is decided by the same rule a live handler produces (least recorded clock, ties by declaration\n order). A `sleep(\"1m\")` arm beats a `sleep(\"1h\")` arm whatever their declaration order. Behind the\n worker bridge the simulator delivers its next wake only after the thread reports it has reacted\n to the last one, so a bridged simulation settles a race the same way, also when the dry run's\n recorder wraps the simulator.\n\n`waitUntil` observations and ordinary effects use the same dispatch-failure recorder. A handler\nrefusal records `refused` and holds the run for a capable host. Cancellation records `cancelled`.\nOther handler failures record their language code, or `L4000` when none is supplied, with their\nmessage, recordable detail and stack when available. A journal append rejection during dispatch\nleaves the entry pending and exits without recording a handler failure.\n\nFull rules, with every code: [`spec/cotal-lang.md`](../spec/cotal-lang.md).\n\n## Continuing a run\n\n**Resume** is re-execution: the driver replays the journal, the program runs from the top, recorded\nsteps return instantly, and the first unrecorded step is performed live. It refuses a journal that\nbelongs to another run, a pin that differs from the recorded ones, and a different language version.\n\nA failed journal entry replays its error, while a pending entry lets the handler recover the\nexternal work it bound before the interruption.\n\nA deadline that passes during authorized arming, before its pause is minted, ends the step as its\nexpiry would. A relative `sleep` completes, and a `checkpoint` expires for its `onExpiry` to act on.\nA `wait` on a channel resolves `null` at its timeout and returns the event at the end of an `idle`\nwindow, after it opens the channel under the run's read admission. A `waitUntil` observes again\nwhile its `deadline` allows. None of them creates a past-due checkpoint. An existing pause keeps its\nrecorded deadline and settlement, and cancellation still rejects the step.\n\nA step that writes to a far side that honors no idempotency key (posting a comment, sending a mail)\nbelongs in `once`. A resume that finds a step inside `once` begun and never settled does not\ndispatch it again: it opens a hold, a checkpoint under a token derived from the step's recorded\nrequest id, whose prompt names that id. Answer it with\n`cotal run answer <run> <step-key> --value <json>`, and the value becomes the step's result. An\nexpired hold fails the step with the catchable L4027. Only `ask` runs inside `once`, so wrap just\nthe step that writes: a host restart while it is in flight costs a settle. `run validate` refuses\nanother effect in the body, or in a function the body calls by name, with L2033, and the run\nrefuses one it reaches any other way with L4028.\n\n```js\nconst publisher = await spawn(\"publisher\")\nconst res = await once(async () => {\n return await ask(publisher, { name: \"publish\", schema: { commentId: \"number\" } })\n}, { name: \"publish-360\" })\n```\n\n**Migrate** moves a run onto edited source. A dry walk of the new program over the recorded journal\nfinds every recorded step the edit changed (a divergence) and every one it no longer reaches (an\norphan), and the orphan table says what each means: a removed `sleep` is nothing, a removed `turn`\nalready happened, a removed `spawn` is a live agent you must adopt or release, a removed resolved\n`checkpoint` is a human decision you must explicitly discard. The decision is filed as a\n`migration` record with the actor's name on it. An adopted seat (`--adopt <name>#<uid>`) goes to\nthe edited program's next `spawn` of that persona, which returns the recorded handle and mints\nnothing, so the agent keeps its identity, its worktree and its turn history across the edit. A\nreleased seat (`--release <name>#<uid>`) is despawned when the migration commits, through the same\ndischarge a cancelled branch's seat leaves by, so the record never claims a release nothing did.\nThe spawn that adopts a seat binds the orphaned spawn's goal as its own, so a resume of that step\nreads the same seat back and a cancellation of it despawns the seat it holds.\n\n**Fork** starts a new run from a named step of an old one, copying the prefix under the parent's\npins (seed included, so the copied history's pure draws are the same draws). The child is a new run\nunder a new id whose record names the parent and the cut step (`forkedFrom`); the parent is\nuntouched. A spawn inside the copied prefix is honoured by its `onFork`: `\"adopt\"` copies it, and\nthe child shares the parent's agent (the manager shows that seat one turn at a time across both\nruns); `\"respawn\"`, the default, would mint a fresh identity the copied turns do not address, so\nthis host refuses that cut (L5019) rather than rewriting the parent's history.\n\nFork planning and migration inspection use the recorded language version. Version-1 history uses\nthe interpreter. Version-2 history is inspected inside a locked-down worker with a read-only\njournal, without a live effect handler or durable store. Inspection stops before the fork's cut\nstep or any effect that needs new work. Program catch and finally blocks cannot extend the cut.\nThe recorded pins are preserved.\n\n**Restore** brings a run back from a full backup (`cotal backup create`) of a static-auth or open\nmesh, which holds each run's journal, records and admission. A run that waited at a checkpoint or a\ntimer when the backup was made is back after a full restore, and the manager takes it over as after\na restart. What the run did after the backup is not in its record. A user-mode backup, and one made\nbefore full backups held workflow runs, holds none ([CLI reference](cli.md#backups)). After a\nrestore from one of them, or a registry-only restore, the store the restore set aside holds the only\ncopy of the runs, and `cotal backup cleanup-fallback` deletes it only with `--discard-history`.\nAfter a full restore of a backup made before the space last ran, that store holds the only copy of\nwhat the runs did after the backup, under the same rule. The restore names each run it rolled back\nand each checkpoint answered after the backup that is open again, and that checkpoint accepts an\nanswer again.\n\n## From an agent session\n\nFresh `cotal setup` defaults declare `capabilities: [spawn, run]`. On an authenticated mesh,\nthat exposes `cotal_run` alongside the teammate tools. The selected manager must support execution.\nRead `cotal_docs` pages `lang-card` and `workflows`, then try:\n\n```json\n{\n \"verb\": \"start\",\n \"source\": \"await sleep(\\\"1s\\\", { name: \\\"first-run\\\" });\",\n \"file\": \"first-run.cotal.js\"\n}\n```\n\nPass this object to `cotal_run`. `source` contains the program; `file` only labels diagnostics\nand reads nothing from disk. The response returns a run ID before execution finishes. Call\n`cotal_run` with `verb: \"status\"` and that `runId` to inspect the state and step journal.\nA completed timer records its sleep step as `ok`.\n\n### If `cotal_run` is missing\n\n1. Call `cotal_orientation` and check the connector version, capabilities and tool list.\n Upgrade an older installation using the [upgrade guide](https://github.com/Cotal-AI/Cotal/blob/main/docs/UPGRADING.md).\n2. Have the operator add `run` to the persona's existing `capabilities` list, for example\n `capabilities: [spawn, run]`. `spawn` alone does not expose `cotal_run`. Setup leaves existing\n personas unchanged except for its [byte-exact legacy migration](getting-started.md).\n Peer persona-definition tools cannot grant capabilities.\n3. Relaunch the agent through the manager from the updated persona so it receives newly issued\n credentials and a fresh connector configuration. Editing the file or reconnecting with the\n old credential does not grant new broker permissions. If the launch sets `COTAL_CAPABILITIES`,\n update that override too; it takes precedence over the file.\n4. Check `cotal_orientation` again, then call `cotal_run` with `verb: \"ps\"` before starting work.\n\nTool visibility alone does not establish execution support. Hosted runs require issued caller\nauthority. Static authentication issues it to its credentials. On a user-auth mesh, a registered\nparticipant manager hosts authorized runs. A managed agent invokes `cotal_run` through its own\nlauncher-provided bearer command and immutable manager instance selection. Its current managed\ngrant and its direct parent's current grant must both carry `run`. A removed grant, stale lifecycle\nor wrong manager selection refuses rather than borrowing a signed-in user's authority.\n\nOpen meshes may expose the tool but refuse hosted runs. A local user-mode host without remote\nrun authority and an `execution: \"none\"` control manager also refuse the family. A legacy\ncredential without issued authority must be replaced through the current issuance path.\n\n## Operating a run\n\nThe manager hosts runs. `cotal run start` hands the program to the manager of the resolved mesh\n(the usual `--space` / `--server` / `--creds` flags), which validates it, mints the run id, drives\nit in its own process, and answers with the id once the run is recorded. The terminal is free the\nmoment the id prints; the run continues on the manager through every pause, and a manager restart\ntakes back every run it had recorded running, from the journal, under the next epoch. `resume`\nnames a run the manager recorded and is refused while the manager is already driving it, and for\na run recorded `completed` or `failed`, whose journal already holds its outcome. Bare\n`run` and `show` read; `answer` resolves an open checkpoint, or an open `ask` attempt, from any terminal\nor agent that holds the `run` capability.\n\n`show` prints an open pause's question under its step. Once a checkpoint or `ask` settles with an\naccepted answer, it instead prints the answer value as JSON, who answered, the artifact when one was\ncited, the recorded time, and the accepted answer id. Expired pauses and ordinary steps print no\nanswer line. A settled `race` names on its line the branch it took, and a step the race cancelled\nin a losing branch names the branch that won and the race. `answer` at such a step is refused with\nthe same words. An answer the program never read\nis marked `not acted on`: its step was in a branch that lost a race, or its pause accepted it and\nwas then cancelled, as a losing branch's pause is. `--json` carries the race's `winner` and, on each\nstep of a losing branch, the `lostRace` it lost.\n\nA settled step is never answered twice, so a participant who changes their mind uses `amend`. It\nfiles a new answer beside the accepted one, naming the answer it supersedes, and `show` prints\neach amendment under the step as an `amended` line, in the order the store committed them. The last\nline is the current position, whatever clock each amender's `at` came from. The pause stays settled\nand the run keeps the answer it acted on. A step that is still open, or that settled with no answer,\nrefuses an amend. So does a step whose answer was just accepted and is not in the run's journal\nyet, and that refusal says so. The manager records the amender from the credential, as for an answer, and a\nspawned seat may amend only an answer recorded under its own name.\n\nA program's `log(...)` lines are not journalled, so `show` does not print them. A run\nthe manager hosts writes each line to the manager's log as `run <run-id>: log <text>`, which\n`cotal service logs stack-manager --space <space>` reads. A run driven with `--local` prints them in\nthat terminal, and `run simulate` lists them under `Log`. A resumed run replays its program, so it\nwrites the lines before the resume point again.\n\n```bash\ncotal run start --file build.cotal.js # the manager starts it; the minted id is printed\ncotal run # list run records and their recorded state\ncotal run show run-3f2a90c41b7e0d5a6c884e19b02df4a1 # print the record and its last 20 journal rows; --limit <n> prints up to 1000, --json all\ncotal run resume run-3f2a90c41b7e0d5a6c884e19b02df4a1 # the manager takes the run back\ncotal run answer run-3f2a90c41b7e0d5a6c884e19b02df4a1 \"/checkpoint:approve#0\" --value '\"yes\"'\ncotal run amend run-3f2a90c41b7e0d5a6c884e19b02df4a1 \"/checkpoint:approve#0\" --value '\"no\"' # record a changed position\ncotal run check run-3f2a90c41b7e0d5a6c884e19b02df4a1 build-v2.cotal.js # check an edited program against the journal\n```\n\nA program that does not validate is refused before anything is recorded, with every problem in the\nanswer as the validator would print it. The driver records the program beside the run, so `resume`\ntakes the run id alone and the manager reads the source back; an edited program is a migration or a\n`fork`, never a resume. `cotal run check <runId> <program>` is that check: it\nreplays the run's journal and walks the edited program over it, prints whether the migration is\nadmissible, how many journal rows the walk accounted for, every orphaned step with its verdict and\ncode, and exits 0 on admissible and non-zero on not. It reads only, under the same credential\n`show` reads on, and the commit side is not reachable yet: the report itself says what a commit\nwould file and that this invocation filed nothing. An answer is recorded under the answerer the\nmanager knows from the caller's credential: a managed agent by its name, anyone else by their\nprincipal. The request\ncarries no name. An agent with `capabilities: [run]` has the same five verbs as the `cotal_run`\ntool ([MCP tools](mcp-tools.md)), so a program can be written and started from inside a session.\nA `start` or `resume` answers once the run's record is written, within a bounded wait; a manager\nthat is still taking back a predecessor's runs at boot refuses both with `unavailable`, and a\nretry a moment later is the whole remedy.\n\n`--local` drives the run in this process instead: `start`, `resume` and `answer` exit when the\ndrive settles, `--by <who>` names the answerer, and `cotal run resume <runId> --local --file\n<program>` is how a run with no recorded program is continued. On a static mesh the local drive\nmints the run's own credential from the folder's trust material, so it runs from the mesh's\nproject folder. A local start also names the run's channel ceiling itself:\n`--admit-read <channels> --admit-publish <channels>`, comma-separated patterns or `none`, both\nrequired. The record it writes says an operator admitted the run and why, and the host checks\nit the same way it checks a hosted admission. A registered remote user-auth manager can host a\nrun through its issuing host. The host resolves a versioned caller against live issuance,\nadmits the run, and signs only the run's fixed driver, mediator and one-shot operator credentials\nfor manager-held nkeys. Renewal checks the activated attempt; the manager holds no signer.\nLocal user-auth runs remain unavailable because a user bearer holds no run rows. An open mesh\nhosts none either, since it issues no caller authority to admit a run under.\n\nA logged-in user starts runs on that remote manager with `cotal run start`. The auth callout issues\nthe user's manager connection against the user's actor-ledger row, the CLI reads the generation\nback from the connection's accepted row, and every `run` verb rides the versioned rail. The issuing\nhost admits a run only for the owner who registered the manager, and on every resume and answer it\nchecks that owner and that the caller's issuance is still live. It also watches the start, resume\nand answer requests on the broker itself, and admits or issues for one the manager forwards only if\nit saw that request, and only once. A resume or answer gets issuance only for the run, step, endpoint\nand amendment that request named. An answer's\ncredential reaches one pause, and the host reads which one off the run's own journal, for a run\nadmitted on that manager's instance. A request bound to another manager instance or epoch gets nothing, and so does one whose class or pinned contract is not the one the manager registered. Another user's start, answer or resume is refused, and so is a revoked actor's.\nSuch a run spawns, turns and despawns agents owned by that user. A spawn has the reach of the user\nwho started the run, as their actor-ledger row reads at that moment, and the host enrolls each agent\nthrough its managed-agent enrollment. A spawn may be placed on the manager that hosts the run and on\nno other instance. [User-auth run start](design/user-auth-run-start.md)\nrecords the path.\n\nRun admission accepts canonical space names such as `cotal_cloud` and `u_example-name`\nwithout rewriting them. Endpoint names keep their separate DNS-label grammar.\n\nA hosted run is **admitted** under the caller that started it. The caller's credential is an\nissuance ([identity and auth](identity-and-auth.md#issued-authority)): its requests ride a\nversioned rail that carries the credential's generation, and the manager resolves that\ngeneration's recorded permission ceiling and writes it beside the run before the driver starts.\nThat ceiling, the caller's own channel scope as it was issued, is what the run may read and post\nin channels; the manager's own reach never stands in for it. A request from a credential minted\nwithout an issuance is refused with `permission-denied` and a detail naming the caller. A run\nwhose caller had no channels can still sleep, checkpoint and turn agents; its `wait` on a channel\nis refused at the effect.\n\nThe manager endpoint serves `run-cancel` with `{ runId, reason }`. Call it through an issued\ncredential on a handle pinned to the manager instance, retaining the Describe-derived epoch bind.\nThe reason must encode to 1 through 512 UTF-8 bytes, and the request deadline is at most 30 seconds.\nNo CLI or MCP verb wraps it.\n\nA signer-holding manager accepts the original caller's owner, actor and lifecycle under a live\nissued generation. On a signerless manager, the issuing host also accepts an interactive actor of\nthat owner with `run`, or an interactive admin. It must observe the caller's request itself before\na manager can forward it. Managed-agent names and platform assignments do not grant this authority.\n\nThe reply separates the durable marker from the local observation: `stopped` means a local drive\noutcome was observed; `stop-requested` means it was not observed within the request budget.\n`launch-abandoned`, `terminal` and `not-driven-here` describe their respective local states.\n`duplicate` is false for a confirmed new marker, true for a confirmed existing one, and null after\nan unknown write followed by marker readback. Preserve unknown outcomes. Neither a marker nor a\nreply proves process cleanup or that another host stopped. Revoked runs remain readable through\n`run show`, but cannot receive new drive or answering authority. The endpoint's `run-status`\nreply carries `revoked: { by, reason }` independently of the recorded run state. A running record\nwith that marker is not proof of a stopped holder. `revocationUnreadable` means the marker could\nnot be checked; do not read that as unrevoked or resumable. The same reply carries the run's\n`programHash`, the value its `run-ps` row carries, read from that run's records alone, so checking one\nrun's program identity needs no full run list. It is absent when the run has no recorded program,\nand `programUnreadable` replaces it when the program record cannot be read. A released record without a marker can\nbe resumed under its original admission; a released record with a marker cannot. The human\n`run show` view displays admission revocation separately from recorded state. An unreadable marker\nproduces a partial result with exit 4, in human and JSON output, as it does in the run list.\n\nA cancellation can retain a local pending intent even after a definite no-dispatch refusal if its\nbudget ends before the manager can confirm marker absence. That intent blocks a new local launch\nor resume; it does not stop an existing drive. A later cancellation that observes a marker reconciles\nthe retained intent. Replaying the same request without a marker reports that it remains pending and\ndoes not dispatch again. A manager process restart loses the local intent, but does not settle the\nunknown write or prove cessation. An absent marker after an unknown write does not clear the intent\nin the running process. A `not-executed` write outcome therefore does not promise that a local launch\nis available.\n\nIssuer observation custody has finite lifetime budgets per caller, owner and issuer. Exhaustion\nlogs the budget class once per issuer process, without caller identities or request contents.\nRepeated refusals do not repeat that diagnostic. Custody entries are not evicted to make room.\n\nEmbedding hosts can call a drive's `cancel(reason)` handle to signal cooperative root cancellation.\nThe language's `RootCancellation` source carries the first reason through the walker and compiled\nworker to in-flight effect contexts. It is process-local and carries no authorization. The host must\nestablish permission and observe the durable revocation marker before calling it. A root cancellation\nends the drive as `released`; the status projection can lag if its write fails. Read the recorded\nstate separately. A completion or failure that wins the race keeps its actual outcome. It does not stop an agent process or preempt already-pulled tool work.\n`release(reason)` remains a separate next-effect-boundary request, used for host shutdown.\n\nThe host also supplies `cancellationObserved` for channel-effect classification. A fresh revocation\nread together with an observed local cancellation intent ends that effect as cancelled, even while\nthe manager is still reading the current holder before signalling the root. Pending intents alone\nnever do this. An ordinary local revoke without a cancellation intent keeps its existing failure\nbehavior. The shared observation neither signals the root nor replaces the holder check.\n\nIf the post-marker holder read fails or exceeds the original budget, the command does not signal\nthe root without that check and returns no stopped receipt. A parked checkpoint or sleep can\nremain running beside the marker. Preserve the unknown outcome. A later explicit cancellation\nrequest can read the same marker and a current holder, then report its own observed result.\n\n`cotal run revoke <runId> --local --by <who> --reason <text>` writes the run's revocation marker\nfrom the project folder. An empty `--by` or `--reason` is refused `USAGE_INVALID` before anything is written, naming the empty flag. A run ID with no admission on the endpoint is refused `TARGET_NOT_FOUND`, exit 2, before anything is written. The\nadmission itself is never rewritten. Every host reads the marker\nbefore its next channel effect, so an open `wait` refuses at its next poll, and no resume,\ntakeover or manager restart continues the run. An open checkpoint or `ask` stays listed under\n`Needs an answer`, but `run answer`, hosted or `--local`, refuses it with `LIFECYCLE_BLOCKED`\nnaming the revoker and the reason, and files nothing, so the run stays parked at that pause. A\nmarker that cannot be read refuses the answer the same way. Revoking twice is not an error, and\nthe first reason stands. A run whose admission is missing or revoked is left parked by the manager's boot\nreconcile, named in its log.\n\nA revoke does not cancel the run. Work that touches no channel, such as a loop of `sleep` and\n`log`, keeps running until the host driving the run stops. A `--local` drive ends with the process\ndriving it, which reaches no other run. A hosted run stops with its manager, and so does every\nother run that manager drives: `cotal service restart stack-manager --yes` stops them all, and its\nstop impact names the managed agents in scope. A run that reaches its next step within two seconds\nis recorded `released` and waits for `cotal run resume <runId>`; one still waiting keeps its\n`running` record, and the restarted manager takes it back unless it is revoked. `cotal down` stops\nthe whole stack. The `run-cancel` endpoint above cancels one hosted run, and the CLI has no verb\nfor it.\n\nBare `cotal run`, hosted or `--local`, reads the marker beside each run record and prints\n`admission revoked` in the `Recorded state` column for a run that carries one, whatever state the\nrecord itself holds, with the revoker and the reason under the table. The record is display only\nhere: a revoke writes no terminal state, because no host drove the run to one and the journal owns\nthe facts. A marker the listing cannot read, whether the store is unreachable or the marker has a\nversion or shape it does not know, prints `revocation not checked` in that column and a warning on\nstderr. The listing is then a partial result and exits 4 once every row is printed. The hosted\n`run-ps` rows carry the marker as `revoked` (`by` and `reason`) or a failed read as\n`revocationUnreadable`, beside the record's own `state`.\n\nA run whose step was refused (L5016) stays held; a\nresume on a host that can perform the step performs it live and continues from there.\n`show` prints what an open pause asks beneath its step key, which is the address `answer` takes\nback. Checkpoint expiry rides the mediated timer writer, which the delivery daemon pumps on a live\nmesh; on a bare broker a pause still resolves, it just cannot expire.\n\nA live pause that nobody answers, such as a `sleep` or a checkpoint timeout, is observed by\npolling: every 2 seconds the driver looks for the broker's timer fire, and every 2 seconds for the\nsettlement that fire records. A step therefore ends after its deadline, usually by 2 to 4 seconds\nand later on a loaded host: a live `sleep(\"1s\")` or `sleep(\"2s\")` usually takes about 4 seconds,\nand `sleep(\"5s\")` about 8. Timers whose deadlines fall within one poll can end on the same poll, so\na live `race` between them is not decided by their written durations: a `sleep(\"2s\")` branch\ndeclared before a `sleep(\"1s\")` branch can win. The simulator uses virtual time and reports the\ndurations as written.\n\n## Diagnostic inventory\n\nA large inventory can exceed the broker's reply limit. A host that supports page mode can return\none bounded JSON page. Page mode is not in release 0.88.0 or earlier: the calling CLI or connector and\nthe manager both need a later version.\n\n```bash\ncotal run --page --on <instance> --limit 8\ncotal run --page --on <instance> --cursor <cursor>\n```\n\nFor the MCP surface, use `cotal_run` with `verb: \"ps\"`, `instance` and `page: { limit: 8 }`.\nPass a returned `nextCursor` as `page.cursor` to continue. A managed session can use its existing\nmanager binding. Keep the same space, endpoint filter, instance and process epoch throughout a scan.\nThe CLI requires `--on`; page mode is not available with `--local` or other verbs. An older host\nrefuses page mode rather than returning a truncated legacy list.\n\nEach page has at most 16 rows (default 8), and the complete reply fits 65536 bytes and the broker's\npayload limit. The reader issues at most 64 storage calls and stops issuing them after 2 seconds or\nthe smaller request duration. These are application limits, not bounds on broker CPU, disk work,\nclient-library retries or total elapsed time. One row that cannot fit is refused, never split.\n\nThe response carries `semantics: \"diagnostic\"`, the observed sequence cut, responder, storage-read\ncount and `stopReason`. Rows contain spec/status revisions and whether a revocation marker was\nobserved. Programs, journal contents and revocation reasons are omitted. Legacy requests without\npage mode retain their existing array output.\n\n`endObserved` means the walk reached its cut or received a no-match response. It does not prove an\natomic snapshot, absence, ownership, terminal work or worker inactivity. Cursors are unsigned\ncontinuation data. Deleted history and restores can be invisible. Keep partial results incomplete,\nand retain uncertain-start reservations and ownership holds. A continuation that makes no progress\nis reported as incomplete; do not retry it in a loop or use it to release work.\n\n## Keyed submission\n\nThe manager's `run-start-keyed` command binds an envelope submission ID to one host-minted run.\n`run-submission-status` reads that binding for the authenticated caller incarnation. Both need\nfresh issued reach and a digest-verified description pinned to one manager instance and epoch.\nLegacy `run-start` is unchanged. An unsupported keyed command never falls back to it.\n\nThe scheduler must persist the submission ID, semantic fingerprint, caller incarnation,\ncontract digests and instance/epoch before dispatch. The client helpers prepare that snapshot\nand send one attempt at a time. They pass on only `signal` and `deadlineMs`, which a keyed\nrequest keeps at or below 30000. They refuse a reply that names another submission, instance,\nepoch or fingerprint, and that outcome stays unknown. They do not schedule retries, replace\nunknown work or re-pin it to another manager. Their prepared handle is process-local and WeakMap-bound: serialized JSON\ncannot be rehydrated through these helpers. Persisted scheduler recovery needs separate reviewed\nintegration, not a cast or an automatic fresh Describe. Keep the original work reservation\nwhile reconciling any unknown outcome.\n\nThe binding winner may make at most three admission-create attempts in its original callback\ndeadline, with the same admission bytes and authority. Initial authorization precedes binding,\nbut fresh authority rechecks before each admission attempt can fail after binding and retain\nbinding-only debt. The manager refuses a start while it is stopping or already at its launch\nlimit before it reads or writes a binding, so that refusal writes nothing. It says nothing about\nan earlier attempt under the same key, which may already be bound or running.\n`run-submission-status` takes no launch slot, so read the outcome there. A launch that fails after\nadmission leaves `admitted` with no run, and no later call launches it. Later duplicates only\nread. A `binding-only` result is a visible reconciliation hold, not a queue or permission to repair.\nAn absent admission is an observation at read time, not proof that the original callback cannot\nstill write. A changed caller generation or manager epoch refuses rather than adopting the run.\n\nThe receiving manager or issuing host measures the request budget on its own monotonic clock,\nand checks the caller's authority expiry on the wall clock. Wall-clock changes do not extend or\nspend the monotonic request budget. Authority expiry remains a wall-clock check. The callback\ndeadline a status read returns is a wall-clock record of that window, not something a caller or\nhost enforces.\n\nThe original callback timer bounds waiting and starting more attempts. It does not cancel an\nalready-issued broker publish, which may land later with the same frozen admission bytes.\nNo new admission attempt starts after the request budget or authority window ends. Once an\nadmission exists, the activation check uses only the manager's remaining monotonic request budget;\nit does not recheck the caller's wall-clock expiry. Keep the reservation even if an earlier read\nobserved no admission.\n\nSubmission state `activated` reports evidence of activation, including completed and failed\nruns. It is not a running-state or active-lane signal. Use native `run-status` for lifecycle\nstate. Terminal submission bindings remain permanent, and duplicate starts never relaunch them.\n\nThis implementation is under validation. Static native and signerless issuer paths require\nseparate acceptance evidence before release or integration.\n\n## What is on the wire\n\nThe run's wire footprint is [SPEC §14](../SPEC.md#14-workflow-runs-v05):\n\n| Thing | Where | What it is |\n| --- | --- | --- |\n| the run | `run.<endpoint>.<runId>` record | the resolved **pins** (seed, logical epoch, budgets, language version) on the immutable half; holder, lease and `journalHigh` on the status half |\n| the program | `program.<endpoint>.<runId>` record | the source the run was started from, verbatim, written once by the driver that pinned the run; what a resume reads and what a migration is measured against |\n| the step journal | `WFJ_<space>` stream, one subject per run | append-only, no age eviction, no Direct Get; every append fenced by the run subject's own sequence; takeover is replay-then-activate |\n| a checkpoint answer | `answer.<endpoint>.<token>.<answerId>` | the payload beside the one-use settle fact; the settle names the answer it accepted |\n| a notice | `notice.<endpoint>.<runId>.<addresseeId>.<noticeId>` | one bounded decision told to one agent, rendered ahead of its next turn |\n| a migration | `migration.<endpoint>.<runId>.<migrationId>` | the report and who applied it, keyed by the report's own digest |\n| the admission | `admission.v1.<endpoint>.<runId>` in `cotal_admission_<space>` | the caller the run was admitted for, its channel ceiling and its provenance; written once before the driver starts, and the store refuses a second write on the key |\n| a revocation | `revoked.v1.<endpoint>.<runId>` in the same store | who revoked the run and why; create-only, idempotent, permanent at the broker, read by every host before its next channel effect |\n\nThe implementation writes the `program` record, and SPEC §14 refers to it, but the\nrecord-kind table in SPEC §13.7 does not list it. That table omission remains a\nspecification gap; the table above describes the implemented storage layout.\n\nThe driver writes `journalHigh` at activation and again after each journal append, before the\nprogram acts on the entry. A successor whose replay ends below it refuses the run with\n`RunJournalTailTruncated`. That holds for records appended since the last activation too.\n\nA run's **driver** connects on a `run-driver` credential minted for one run and takeover\nattempt. It can append to its journal, use its replay durable, and write its own `run`, `program`,\n`notice` and `migration` records. It has no store point reads, checkpoint writes, chat consumers,\nor channel and membership registry grants.\n\nThe hosting process keeps a separate `run-mediator` connection for effects and reads. The driver\nreceives methods and data from that host; it never receives the mediator credential or connection.\nThe host checks the journal's current activation and step identity before dispatch, and checks\npause and wait authority again at each operation. Cancellation cleanup also admits losing steps\nnamed by a settled parent whose `cancel.issued` is still false. That permission allows cleanup;\nit cannot mint or rearm a cancelled pause. Wait acknowledgements consume host-held delivery\nreceipts. A recorded match can be reread only at its bound sequence and channel. A conclave's\nrecorded ownership flag must match its step-derived channel before registry writes or cleanup.\n\nThe mediator retains endpoint-wide checkpoint rights and stream-wide leader reads as trusted\nhost authority. Record reads exposed to the driver are restricted to its own run's keys. Reads\nthat decide writes remain leader-served. A read of the journal uses the run's filtered replay\ndurable, including the diagnostic for a journal with no run record. That durable is named after\nthe takeover, and an attempt reads it many times, so reads under one takeover run one at a time in\nthe hosting process and a replay removes a durable of its own name that an interrupted earlier read\nleft behind. A create failure permits this recovery only for a structured JetStream API error\nwith code `10148`. Other errors propagate without deleting the durable, even if they carry that\nnumeric or string code. A durable that survives a replay's own delete belongs to a reader the\nprocess cannot account for, and reading its tail is refused. A drive handles that refusal as a\ntakeover does: the reads behind its steps, and the diagnostic for a journal with no run record,\nreplay up to three times before the refusal is raised. An operator read runs under a takeover\nminted for that read and reports the refusal on its first read.\n\nA served read uses a one-shot `run-operator` credential. An answer uses a read to find the open\npause, then a second credential pinned to that token for the answer and settlement.\n`cotal run --local` uses the same driver/mediator split. On an authenticated mesh it needs the\nlocally recorded space signer to mint both credentials; a single `--creds` file is refused.\nDirect library users supplying broker clients to `MeshHandler` are constructing a trusted effect\nhost. A hosted driver receives its closed effect interface instead.\n\nThis split confines broker credentials; it is not process isolation for injected host code.\nThe runtime and its effect host share the manager process. A run's channel reach is the admitted\nceiling ([SPEC §14.8](../SPEC.md#148-run-admission)): the starting caller's issued channel scope,\nrecorded once in `cotal_admission_<space>` under a per-run `run-admitter` credential the driver\nnever holds, and re-read by the host before every channel effect. Spawn and turn keep their own\ndelegated checks; `notify` writes agent-addressed notices and is not channel publication. Treat\n`run` as program-execution authority bounded by that ceiling, not as sandboxing of the program.\n\nA version-1 fork can replay its settled parent history through the host. Inherited checkpoint\nidentifiers carry no authority to read, rearm or claim the parent's pauses. New child effects use\nchild-derived identifiers. A fork is a new run and takes a new admission under the caller who\nforks it; the parent's ceiling is not inherited.\n\n## What ships today\n\nThe language, its validator, interpreter, simulator and dry run are `@cotal-ai/lang`\n(`packages/lang`), usable in-process with your own effect handler and with no broker: `validate(src)`,\nthen `run(src, { runId, handler })`, and `resume(src, journal, { runId, pins, handler })` to pick a\nrun up from its journal (the package README has the snippet, with `SimHandler` as the handler). That\nis the in-process route, yours to drive with your own handler; a run the driver starts executes on\nthe compiled engine, as the engine paragraph below says. The wire\nsubstrate of §14 (the `WFJ_<space>` stream, the five record kinds, the activation barrier, the\nper-run grants) is in `@cotal-ai/core`, and the run driver, journal store, migrate and fork are\n`@cotal-ai/runtime` (`implementations/runtime`). The migrate check is reachable as\n`cotal run check <runId> <program>`; committing a migration it judged admissible\nis not reachable from any surface yet, and neither is a fork: `planFork` and `commitFork` are\nlibrary exports of `@cotal-ai/runtime` with no CLI or MCP verb. On the mesh handler, `sleep`,\n`checkpoint`, `wait(message(...))`, `wait(idle(...))`, `wait(down(...))`, `wait(replied(...))`,\n`waitUntil`, `notify`, `spawn`, `conclave`, `ask`, `monitor` and `turn` are durable.\n`spawn` is\nthe manager's spawn action submitted under the step's own identity: the goal binds under the step's\nrequest id, so a resumed run re-attaches to the same seat instead of allocating a second one, a\nspawn whose seat fails to come up is catchable as L4002 with the manager's recorded reason (one the\nendpoint refuses at accept is L4000, described below), and a spawn on a\nrace branch that loses is despawned by the run's own cancellation sweep. A seat belongs to the run\nthat spawned it: when the run completes, it despawns every seat it spawned, including a race\nwinner's and one whose spawn failed while its process stayed up. A spawn marked `onFork: \"adopt\"`\nis the exception: a fork can share that seat and no run can see whether another still uses it, so\nthe seat stays up until you stop it with `cotal stop` once every run sharing it is done. A seat a\nmigration handed to a later spawn follows that spawn's policy, and it stays up if any spawn that held\nit was marked `onFork: \"adopt\"`, because a fork taken before the migration may still share it. In a\nspace with several managers, a despawn counts a seat as already gone only when the manager that\nallocated it says so. A run that\nfails or is released keeps its seats until a resume completes it or you stop them with\n`cotal stop`. Start a seat with `cotal spawn` when it should outlive any run. `permits` are the budgets\nthis host meters: `turns`, how many turns the run may dispatch to the agent, and `wallClock`, a\nduration from the spawn after which no turn is admitted. The turn that would exceed one is the\ncatchable L4001 (kind `permit-turns` or `permit-wall-clock`; a deadline the remaining wall clock\ncannot hold counts as exceeding it), an adopted run counts the turns its journal recorded, and a\nbudget the host has no meter for, such as `tokens` or `spend`, is refused at the spawn rather than\naccepted and ignored. `supervise` is the restart policy this host asks the manager to enforce:\n`restarts`, how many in-window process deaths may come back under the same handle, and `window`,\nthe duration those deaths are counted in (default `10m`). The manager restarts the process in\nplace under the same name, lifecycle uid, persona, worktree and permits; `monitor` does not fire\nfor a restart, and `wait(down)` fires only when the seat is gone for good. Spending the budget\nretires the seat, and the next `turn` is the catchable L4002. The policy also covers the manager's\nown death. On a static mesh, when the manager ends without stopping the seat (a crash, or a pty seat\nthat ended with its manager), the next manager of the same instance restarts the seat the same way\nand counts that as one restart. A turn the dead manager had accepted for the seat is served to the\nrestarted seat, so the interrupted turn runs again and finished turns do not. A turn an older\nmanager accepted is not served again, because its record does not name the seat's owner, and it\nends at its deadline as L4003. A seat whose budget\nis spent, or whose persona file or credential changed or expired while no manager ran, is retired instead. A policy this host cannot enforce\n(an unknown key, a user-mode seat, or a runtime that cannot respawn a name in place) is refused\nat the spawn rather than accepted and ignored. `events: false` is the workflow form of\n`cotal spawn --no-events`: the seat starts without its AG-UI event plane. A connector that\npublishes none, such as Hermes, needs it, because an omitted `events` arms the plane and the\nmanager refuses that connector at the spawn. A value that is not a boolean is refused at the\nspawn. `runtime` names the terminal runtime of this one seat (`pty`, or an installed extension such\nas `tmux`). Omitted, the seat uses the serving manager's own runtime. A name that manager cannot\nresolve refuses the spawn and starts nothing, and the other seats keep the runtime they already have.\n`conclave` joins its\nmembers to a real channel as durable membership rows: the channel derives from the step's own\nrequest id when the program names none (a program-named channel is borrowed, never torn down, and\na membership that predates the conclave survives its close), each member handle resolves to its\nprincipal through the seat's own presence row (an absent member is catchable as L4002), and a\nconclave cancelled on a losing branch is released by the same cancellation sweep. `ask` parks one\ncheckpoint-plane pause per attempt, answered through `cotal run answer` as a checkpoint is, and\ntells the agent through the same relay `turn` uses: one relay per attempt under the attempt's own\ntoken, carrying the schema, the attempt count, the deadline and the previous refusal, which the\nseat's connector renders as the record wanted and the hosted command that answers it. The relay\nnames the ask's absolute deadline, so the manager stops serving an attempt at the instant its\npause expires and refuses a relay already past it. When that\nliteral command is run inside the managed seat, the CLI reuses the seat's lifecycle credential and\nissued caller identity rather than minting an operator instrument. The command\ndoes not take `--by`: the manager records the authenticated caller as the answerer. A spawned seat's\nbaseline credential carries only the self-targeted `run-answer` row, and the manager accepts it only\nfor the open ask or escalation relayed to that exact incarnation. It cannot answer another seat's\nask, an unrelayed checkpoint, another run, or start and resume commands. An ask addresses\nan agent the run spawned (anything else refuses before an attempt opens), a resumed attempt tells\nthe seat nothing twice, and a seat gone at the relay is L4002. On the pause itself:\nthe shorthand of the language reference §6.5 is enforced (an unreadable schema is L4022), a\nnon-conforming answer costs one attempt and its refusal reason is recorded on the entry for the\nanswerer to read, exhausted attempts (default one) are the catchable L4006, and so is the one\nabsolute deadline for the whole ask passing with no conforming record (its kind is `ask-deadline`),\nincluding one that passes before an attempt's pause is armed.\n`checkpoint` binds what it asks on its own entry, so `cotal run show` prints the question under\nthe step key an answer is addressed by while the pause is open: the address alone left whoever was\nasked reading the source to find out what \"approve\" meant. The entry also records the open\nattempt's deadline and the `onExpiry` it was armed with, which `run show --json` reports; what\nan expiry does is still decided from the program's source. An escalation's attempt records its own\ndeadline, one `timeout` after it opens, and a resumed attempt keeps the deadline it recorded. After a checkpoint or `ask` accepts an\nanswer, the journal prints that accepted answer's recorded value and attribution under the settled\nstep. A checkpoint's comes from its frozen result, and the journal never substitutes another filed\nanswer or invents fields that result does not hold. An `ask`'s result is the value alone, so its line\nis read from the answer record its last attempt's settle named, even when that value is a record with\nfields named like a checkpoint's. Amendments print on their own lines after it.\nAn `escalate` addressed to an agent this\nrun spawned is relayed to that seat through the same turn relay an `ask` uses, carrying the prompt\nand the token to answer under, held to the pause's own deadline; a `to` naming anyone else is a\nperson, and their pause stays the one anybody can answer, with the addressee recorded and rendered\nbeside the question.\n`monitor` registers interest in an agent, and the\nregistration is the journal entry itself, carrying the handle it registered: monitoring an agent\nthat is already dead succeeds, and the death is the wait's to observe. `wait(down(...))` observes\na monitored agent, and refuses one the run never performed `monitor` on. It reads the death off presence liveness, the\nsame witness a conclave join resolves members through: the value carries the handle, the reason\n(`lapsed` when nothing live holds the name any more, `superseded` when a live row holds it under\na different incarnation) and the time of observation. Presence liveness skips any value without a\nstring `card.id` and `card.name`, so a participant that publishes a malformed row under its own key\ncannot fail another run's wait, turn or conclave join. A superseded incarnation is down at once. A\nlapsed one is down only after its presence row has stayed gone for 30 seconds, because a seat whose\nconnector stalls past the row's 6-second TTL, under host load or across a reconnect, renews it under\nthe same incarnation and is still working. The 30 seconds count only across presence reads that\neach end within 6 seconds of the previous one starting, so a slow read or a run of failed reads, which\ncould hide a renewal, starts the count over. Even then the run asks the manager first: a seat whose\nprocess the manager still runs is alive however long its row has been gone, so the count starts over.\nOnly the manager that spawned the seat can report it gone, because another manager in the space knows\nonly its own seats, so the run asks again until that one answers. When it does not answer, the presence\nrow is the only witness and the lapse stands. The asking stops at the wait's timeout or cancellation,\nand at a turn's deadline, yield or cancellation, which then decide. A wait that begins\nafter the death resolves once that holds, and a timeout resolves null on one absolute deadline a\nresumed run re-attaches to.\n`turn` wakes one seat for one host turn through the manager: the run\nsubmits the turn under the step's own identity, the manager holds it as a goal pinned to the\nseat's incarnation, and the seat pulls it under its own reach ahead of its next host turn, so\nnothing is pushed into a session mid-thought. The manager sends the seat a turn notice whenever\nits pending turns change, and repeats it every 5 seconds until the seat pulls a new turn. The seat\npulls when a notice arrives, when it first connects, and after a reconnect while it holds a turn,\nso an idle seat sends no requests ([SPEC §14.3](../SPEC.md#143-the-run-record)). The payload the seat reads names the run and the\nstep and carries the rendered run context, plus any pending notices addressed to it, which the\nturn consumes. The seat yields through `cotal_yield` (`done`, `blocked`, or `handoff` with an\naddressee), and ending its host turn yields `done` for every turn it was shown. A `handoff` names\nanother seat the same run spawned: the next `turn` in the same scope to that seat records the\nlink, a handoff to a name the run never spawned is the catchable L4005, and one to a seat bound\nto a different worktree is L4004. The deadline elapsing before any yield is the catchable L4003:\nthe acceptance names the instant, the manager's goal-bound hold denies at it, and the run arms its\nown pause on that same instant, so either side outliving the other still converges on the same\nanswer. A seat that pulled the turn earlier stops showing it to its session at that same\ninstant. A seat that dies mid-turn is read off its own presence row by the run itself, with the\nsame 30-second confirmation for a lapsed row, and is the catchable L4002, and a death the manager\nmarked on the deadline terminal reads the same way. Two\nturns on one seat, from two branches or from two runs, reach it one at a time: the language\ndispatches the second when the first settles, and the manager shows a seat the oldest unsettled\nturn alone. A turn's goal id belongs to the caller that submitted it, so two callers may each submit\na turn under the same id. A seat yields by goal id alone, so the manager refuses to hand one seat\na second caller's turn under an id it already holds. On an auth mesh the relay needs no extra grant: every spawned seat's baseline\ncredential carries its own pull, yield, and caller-bound answer rows, the run driver's operator instrument carries the\nturn request and the `inspect` read that confirms a lapse, and the manager arms the deadline hold over its own serve grant and expires it\nitself once due. An accept the manager cannot finish is unwound to a failed terminal on the goal\nit bound, and a retry of that submission is refused naming the terminal rather than accepted a\nsecond time.\n`wait(replied(...))` observes those turns from another branch: a completed turn is a reply, and\nthe wait resolves with the observation record (the handle, the yield's status and note, the\nyield's own stamp). It reads as a level, the way `wait(down)` does: a reply that already exists\nresolves the wait at once, and two replies resolve to the latest by the yield's stamp. A denied\nor cancelled turn is never a reply, so an unanswered wait rides its own mediated timeout to\n`null`, and a handle the run never spawned or turned refuses loudly, since only this run's turns\nare observable. A turn the run itself ended without an accepted yield (its deadline, a\ncancellation, a refused handoff) is never a reply, whatever the seat yields to the relay later.\nA cancelled branch also withdraws what it relayed to a seat before its cancellation completes: its\nturn, its ask attempt or its escalation ends `cancelled` through the manager's reserved `cancel`\n(SPEC §13.6), so the seat is not shown it after the branch's scope settles and the next turn to\nthat seat does not wait behind it. Only the manager that accepted the relay holds it, so in a\nspace with more than one manager the run sends the cancel again while another manager refuses it,\nuntil the accepting one answers. A cancel refused as `unavailable`, such as by a manager still\nreconciling at boot, or one whose reply is lost (`deadline-exceeded`) is sent again until it\nlands or the relay's deadline passes, so the branch's cancellation does not complete while the\nseat can still be shown the relay. Any other refusal fails the branch's step at once under its\ncode, since sending it again changes nothing: a manager that serves no `cancel` is refused\n`not-found` before anything is sent, and an accepting manager with no such relay pending refuses\n`failed-precondition`. The run reads the relay's deadline from its goal and retries a failed\nread, since the relay may still be served until the deadline is known. A goal record\nthat can never yield a deadline, such as one that is not JSON, fails the branch's step at once,\nand so does a refusal that lasts past the deadline.\nA `spawn` may bind its agent to a **logical worktree** (`spawn(\"builder\", { worktree: \"wt-1\" })`):\nthe handle carries the id, and the run enforces the one rule the language states about it: two\nagents never share a worktree concurrently. The validator rejects the literal case up front\n(L3022: two branches of one concurrent scope spawning into one literal worktree, named branch\nfunctions included), and the runtime guards the rest, computed ids included: a spawn claims its\ntree before it submits, so a second spawn into a tree held by a live seat or by a spawn still\nbringing one up is the catchable L4008, a spawn that ends without a handle gives the tree back,\nand the tree is reusable the moment a holder's presence row is gone, so a discharged race loser\nor a crashed seat releases its tree with no bookkeeping. A spawn the endpoint refuses at accept\nis the catchable L4000 (L4001 when the refusal is the endpoint's seat capacity), and one whose\nseat never came up is L4002. A refusal that states the command did not run is answered\nbefore it gets that far. In a space served by more than one manager the resolve and the invoke\nare separate trips through the same anycast queue, so a run's call can reach an instance it did\nnot resolve against, and that instance refuses ahead of any effect. The run drops its resolved\nhandle, re-describes and re-issues, for a bounded number of attempts; after them the refusal\nsurfaces as the effect's own failure and still states that nothing ran. A spawn that names a\n`placement` addresses one instance by name, so a refusal from it is that incarnation answering\nabout itself and is never re-issued. When the spawn also names `cwd`, that manager resolves the\nexisting absolute directory to its canonical host path before accepting the spawn. A missing,\nrelative or non-directory path refuses with no seat and never falls back to the manager workspace.\nOne hosted run may name one placement instance; a program naming several is refused instead of\nwidening one run credential across hosts. Placement is accepted only from an object-literal spawn\noption bag whose `endpoint` and `instanceId` are string literals. A computed option bag, a computed\nplacement field, or a spread in either object refuses at `run-start`. The option argument may be\nabsent, and an object-literal bag without placement is accepted.\nA turn handoff across worktrees is the L4004 described above. Recovery keeps these honest: a resumed run\nreseeds its roster, holders and handoff memos from its own journal, and the driver re-issues any\nrecorded-but-undischarged cancellation at adoption, before the engine performs a new step, so a\nloser a crash left alive does not keep its seat or its tree while the resumed run works on. The\nsame sweep withdraws a cancelled branch's relays and undelivered notices: a notice waits on the run for its\naddressee's next turn, so a decision the run cancelled would otherwise arrive at an agent with\nnothing to distinguish it from one that stood.\n\nEvery effect the language defines performs on the mesh handler; nothing is refused as\nnot-yet-durable any more. The operator surface over the driver is `cotal run`; the section above has the verbs.\n\n**Two engines, and which one runs your program.** The tree-walker is language version `1` and the\ncompiled engine is version `2`, two languages rather than two speeds of one (`spec/cotal-lang.md`\n§8.4 lists what differs). The driver hosts both: **every run a driver starts is stamped `2` and\nexecuted by the compiled engine**. The program runs in its own locked-down worker thread with\nnothing in its global scope, while the effects and the durable journal stay in the driver's process,\nbridged over a message port. No socket or credential enters the isolate holding the program,\nand **every version-`1` record keeps replaying on the walker**, which is the walker's job. On either\nengine the driver bounds an effect's `ok` result at the broker's `max_payload` less 4096 bytes: a\nlarger result is refused ahead of the settling append (**L5006**), the step stays pending, and the\nrun is released. The driver serves a declared set of versions, and a record whose version it does\nnot serve is refused by name (**L5023**) with the run left untouched, instead of being replayed by\nwhichever engine happens to be present. Records do not cross between versions in either\ndirection; the repair is to resume on the recorded version, or to fork.\n\n**The engine needs node 22 or newer** and refuses below it as `EngineUnavailable`, which is an\nimplementation limit and not a language error: it carries no `L` code, so there is nothing to look\nup in the catalog. It is a floor rather than a warning because the engine's frame plumbing rests on\n`AsyncLocalStorage`, and 22 is the lowest node it has been measured on. The walker has no such floor.\n"
|
|
366
|
+
"body": "# Workflow runs\n\n> **Concept** (informative) · **For:** people writing a durable multi-agent workflow, and implementers hosting one · **Normative:** [SPEC §14](../SPEC.md#14-workflow-runs-v05) and the language reference [`spec/cotal-lang.md`](../spec/cotal-lang.md)\n\nA **workflow run** is a program that coordinates agents over hours or days and survives the\nprocess that started it. The program is written in **Cotal Lang**, a small subset of JavaScript in\nwhich every interaction with the world is one of a dozen **effects** (`spawn`, `turn`, `ask`,\n`checkpoint`, `sleep`, `wait`, `notify`, `monitor`, and the four concurrency scopes) and everything\nelse is ordinary, pure JavaScript. Every effect is written into the run's **step journal** before\nit is performed and settled after, keyed by where in the program it happened rather than by when,\nso a run that dies is resumed on any host by **re-running the program from the top** with recorded\neffects returning their recorded results. Nothing about the interpreter is ever serialized: the\njournal and the program are the whole state.\n\n## A first program\n\n```js\nconst planner = await spawn(\"planner\")\nconst builder = await spawn(\"builder\", { worktree: \"wt-1\" })\n\nconst plan = await ask(planner, { name: \"plan\", schema: { steps: \"array\" } })\nconst ok = await checkpoint(\"approve-plan\", \"Approve the plan?\", { timeout: \"4h\", onExpiry: \"proceed\" })\nif (ok.status !== \"resolved\") {\n await notify([planner], { decision: \"approve-plan\", outcome: \"expired\" })\n}\n\nconst r = await turn(builder, { name: \"build\", deadline: \"30m\" })\nif (r.status === \"blocked\") {\n await turn(planner, { name: \"unblock\" })\n}\n\nconst outcome = await race({\n reply: () => wait(replied(builder), { timeout: \"20m\" }),\n giveUp: () => sleep(\"1h\"),\n}, { name: \"await-or-move-on\" })\nlog(\"outcome\", outcome.index)\n```\n\nRead it as the flowchart it is. `spawn` brings agents in; `ask` is the narrow case where the\nprogram itself needs a value (`schema` is a record the program hands the handler unchanged; the\nlanguage hashes it and gives it no meaning, and the handlers in this repository enforce it as the\nshorthand of the language reference §6.5);\n`checkpoint` is a durable pause a human resolves from anywhere, raced against a durable timer; `turn`\nwakes an agent for one turn and returns how it yielded; `race` runs two branches and keeps the one\nwhose recorded clock is earliest. Agents talk to each other in channels as they always do; the\nprogram never speaks in a channel, and the one thing it can put in front of an agent (`notify`) is a\nbounded decision record, not prose.\n\n## The mental model\n\n- **Pure code is JavaScript.** Loops, records, arrays, closures, template literals, destructuring,\n `try`/`catch`, arithmetic, `switch`, compound assignment, optional chaining, spread and rest: what\n you would write anyway, with the parts that hide effects or make meaning depend on the host removed\n (`class`, `this`, `new`, `for...in`, `==`, labels, regex literals, `Math`/`Date`/`JSON`, promises,\n generators). Every refusal names its code and the edit that fixes it. The builtins are a short list\n (`keys`, `map`, `sort`, `json.stringify`, `now()`, `random()`), and arrays, strings and numbers\n answer their usual methods (`xs.map`, `s.trim()`, `n.toFixed()`) and nothing outside that table.\n Records and arrays you build are yours to change until they cross an effect boundary; a member you\n do not own, a host prototype, or a value another branch built is refused with a code, never a\n surprise.\n- **Every effect is journalled and hashed.** A step is keyed `(scope path, kind, name, occurrence)`\n and its inputs are hashed. Reorder your program, add a step, rename a variable: recorded steps\n still match. Change what a step asks (a checkpoint's prompt, a sleep's duration, a turn's\n deadline) and the resume stops with a **divergence** naming the step, rather than replaying an\n answer to a question the program no longer asks.\n- **Concurrency is visible.** `parallel`, `race`, `fanOut` and `conclave` are the only ways to do\n two things at once, each branch gets its own journal namespace, and the scope writes its own\n entry saying how it settled: which arm won a race is a recorded fact, decided by the arms'\n recorded clocks and declaration order, never by a scheduler. A failure the program itself caused\n inside the scope settles the entry under its own catalog code (`fanOut` without a stable key is\n `L3021`, kind `runtime`), so a resume reads the same code the live run threw; a plain failure\n from the handler records the generic `L4000` `scope-fault`. A value the program throws inside a\n branch is recorded with that fault, so `catch` receives the same value on resume, and in a\n migration's dry walk, as it did live.\n A branch may not write to anything declared outside it; return the value and read it out of the\n scope's result.\n- **Time and randomness are tamed.** `now()` is the branch's run clock, the end of the last effect\n it awaited; `random()` is a seeded stream derived per scope, each draw a number from 0 up to but\n not including 1. `randomInt(n)` is `floor(random() * n)`, an integer from 0 to `n - 1` for a\n positive integer `n`, and `pick(xs)` draws one element. They replay identically. A builtin ignores\n arguments past the ones it takes, as JavaScript does, so there is no range form:\n `randomInt(1, 1000)` is `randomInt(1)`, which is always 0, and `1 + randomInt(1000)` draws from 1\n to 1000.\n- **Values freeze at the boundary.** What crossed into or out of an effect is what the journal\n recorded, and it cannot change afterwards; build a new value.\n- **The journal is the debugger.** Every entry carries its key, its inputs' hash, its outcome and\n its timing, and every error is in the program's own coordinates. A run can be **simulated** with a\n scripted handler and **dry-run** to a plan before it touches an agent. The simulator is\n discrete-event: timed effects park at their wake times and are delivered in wake order on one\n virtual clock, so concurrent branches accumulate the durations they wrote and a simulated `race`\n is decided by the same rule a live handler produces (least recorded clock, ties by declaration\n order). A `sleep(\"1m\")` arm beats a `sleep(\"1h\")` arm whatever their declaration order. Behind the\n worker bridge the simulator delivers its next wake only after the thread reports it has reacted\n to the last one, so a bridged simulation settles a race the same way, also when the dry run's\n recorder wraps the simulator.\n\n`waitUntil` observations and ordinary effects use the same dispatch-failure recorder. A handler\nrefusal records `refused` and holds the run for a capable host. Cancellation records `cancelled`.\nOther handler failures record their language code, or `L4000` when none is supplied, with their\nmessage, recordable detail and stack when available. A journal append rejection during dispatch\nleaves the entry pending and exits without recording a handler failure.\n\nFull rules, with every code: [`spec/cotal-lang.md`](../spec/cotal-lang.md).\n\n## Continuing a run\n\n**Resume** is re-execution: the driver replays the journal, the program runs from the top, recorded\nsteps return instantly, and the first unrecorded step is performed live. It refuses a journal that\nbelongs to another run, a pin that differs from the recorded ones, and a different language version.\n\nA failed journal entry replays its error, while a pending entry lets the handler recover the\nexternal work it bound before the interruption.\n\nA deadline that passes during authorized arming, before its pause is minted, ends the step as its\nexpiry would. A relative `sleep` completes, and a `checkpoint` expires for its `onExpiry` to act on.\nA `wait` on a channel resolves `null` at its timeout and returns the event at the end of an `idle`\nwindow, after it opens the channel under the run's read admission. A `waitUntil` observes again\nwhile its `deadline` allows. None of them creates a past-due checkpoint. An existing pause keeps its\nrecorded deadline and settlement, and cancellation still rejects the step.\n\nA step that writes to a far side that honors no idempotency key (posting a comment, sending a mail)\nbelongs in `once`. A resume that finds a step inside `once` begun and never settled does not\ndispatch it again: it opens a hold, a checkpoint under a token derived from the step's recorded\nrequest id, whose prompt names that id. Answer it with\n`cotal run answer <run> <step-key> --value <json>`, and the value becomes the step's result. An\nexpired hold fails the step with the catchable L4027. Only `ask` runs inside `once`, so wrap just\nthe step that writes: a host restart while it is in flight costs a settle. `run validate` refuses\nanother effect in the body, or in a function the body calls by name, with L2033, and the run\nrefuses one it reaches any other way with L4028.\n\n```js\nconst publisher = await spawn(\"publisher\")\nconst res = await once(async () => {\n return await ask(publisher, { name: \"publish\", schema: { commentId: \"number\" } })\n}, { name: \"publish-360\" })\n```\n\n**Migrate** moves a run onto edited source. A dry walk of the new program over the recorded journal\nfinds every recorded step the edit changed (a divergence) and every one it no longer reaches (an\norphan), and the orphan table says what each means: a removed `sleep` is nothing, a removed `turn`\nalready happened, a removed `spawn` is a live agent you must adopt or release, a removed resolved\n`checkpoint` is a human decision you must explicitly discard. The decision is filed as a\n`migration` record with the actor's name on it. An adopted seat (`--adopt <name>#<uid>`) goes to\nthe edited program's next `spawn` of that persona, which returns the recorded handle and mints\nnothing, so the agent keeps its identity, its worktree and its turn history across the edit. A\nreleased seat (`--release <name>#<uid>`) is despawned when the migration commits, through the same\ndischarge a cancelled branch's seat leaves by, so the record never claims a release nothing did.\nThe spawn that adopts a seat binds the orphaned spawn's goal as its own, so a resume of that step\nreads the same seat back and a cancellation of it despawns the seat it holds.\n\n**Fork** starts a new run from a named step of an old one, copying the prefix under the parent's\npins (seed included, so the copied history's pure draws are the same draws). The child is a new run\nunder a new id whose record names the parent and the cut step (`forkedFrom`); the parent is\nuntouched. A spawn inside the copied prefix is honoured by its `onFork`: `\"adopt\"` copies it, and\nthe child shares the parent's agent (the manager shows that seat one turn at a time across both\nruns); `\"respawn\"`, the default, would mint a fresh identity the copied turns do not address, so\nthis host refuses that cut (L5019) rather than rewriting the parent's history.\n\nFork planning and migration inspection use the recorded language version. Version-1 history uses\nthe interpreter. Version-2 history is inspected inside a locked-down worker with a read-only\njournal, without a live effect handler or durable store. Inspection stops before the fork's cut\nstep or any effect that needs new work. Program catch and finally blocks cannot extend the cut.\nThe recorded pins are preserved.\n\n**Restore** brings a run back from a full backup (`cotal backup create`) of a static-auth or open\nmesh, which holds each run's journal, records and admission. A run that waited at a checkpoint or a\ntimer when the backup was made is back after a full restore, and the manager takes it over as after\na restart. What the run did after the backup is not in its record. A user-mode backup, and one made\nbefore full backups held workflow runs, holds none ([CLI reference](cli.md#backups)). After a\nrestore from one of them, or a registry-only restore, the store the restore set aside holds the only\ncopy of the runs, and `cotal backup cleanup-fallback` deletes it only with `--discard-history`.\nAfter a full restore of a backup made before the space last ran, that store holds the only copy of\nwhat the runs did after the backup, under the same rule. The restore names each run it rolled back\nand each checkpoint answered after the backup that is open again, and that checkpoint accepts an\nanswer again.\n\n## From an agent session\n\nFresh `cotal setup` defaults declare `capabilities: [spawn, run]`. On an authenticated mesh,\nthat exposes `cotal_run` alongside the teammate tools. The selected manager must support execution.\nRead `cotal_docs` pages `lang-card` and `workflows`, then try:\n\n```json\n{\n \"verb\": \"start\",\n \"source\": \"await sleep(\\\"1s\\\", { name: \\\"first-run\\\" });\",\n \"file\": \"first-run.cotal.js\"\n}\n```\n\nPass this object to `cotal_run`. `source` contains the program; `file` only labels diagnostics\nand reads nothing from disk. The response returns a run ID before execution finishes. Call\n`cotal_run` with `verb: \"status\"` and that `runId` to inspect the state and step journal.\nA completed timer records its sleep step as `ok`.\n\n### If `cotal_run` is missing\n\n1. Call `cotal_orientation` and check the connector version, capabilities and tool list.\n Upgrade an older installation using the [upgrade guide](https://github.com/Cotal-AI/Cotal/blob/main/docs/UPGRADING.md).\n2. Have the operator add `run` to the persona's existing `capabilities` list, for example\n `capabilities: [spawn, run]`. `spawn` alone does not expose `cotal_run`. Setup leaves existing\n personas unchanged except for its [byte-exact legacy migration](getting-started.md).\n Peer persona-definition tools cannot grant capabilities.\n3. Relaunch the agent through the manager from the updated persona so it receives newly issued\n credentials and a fresh connector configuration. Editing the file or reconnecting with the\n old credential does not grant new broker permissions. If the launch sets `COTAL_CAPABILITIES`,\n update that override too; it takes precedence over the file.\n4. Check `cotal_orientation` again, then call `cotal_run` with `verb: \"ps\"` before starting work.\n\nTool visibility alone does not establish execution support. Hosted runs require issued caller\nauthority. Static authentication issues it to its credentials. On a user-auth mesh, a registered\nparticipant manager hosts authorized runs. A managed agent invokes `cotal_run` through its own\nlauncher-provided bearer command and immutable manager instance selection. Its current managed\ngrant and its direct parent's current grant must both carry `run`. A removed grant, stale lifecycle\nor wrong manager selection refuses rather than borrowing a signed-in user's authority.\n\nOpen meshes may expose the tool but refuse hosted runs. A local user-mode host without remote\nrun authority and an `execution: \"none\"` control manager also refuse the family. A legacy\ncredential without issued authority must be replaced through the current issuance path.\n\n## Operating a run\n\nThe manager hosts runs. `cotal run start` hands the program to the manager of the resolved mesh\n(the usual `--space` / `--server` / `--creds` flags), which validates it, mints the run id, drives\nit in its own process, and answers with the id once the run is recorded. The terminal is free the\nmoment the id prints; the run continues on the manager through every pause, and a manager restart\ntakes back every run it had recorded running, from the journal, under the next epoch. `resume`\nnames a run the manager recorded and is refused while the manager is already driving it, and for\na run recorded `completed` or `failed`, whose journal already holds its outcome. Bare\n`run` and `show` read; `answer` resolves an open checkpoint, or an open `ask` attempt, from any terminal\nor agent that holds the `run` capability.\n\n`show` prints an open pause's question under its step. Once a checkpoint or `ask` settles with an\naccepted answer, it instead prints the answer value as JSON, who answered, the artifact when one was\ncited, the recorded time, and the accepted answer id. Expired pauses and ordinary steps print no\nanswer line. A settled `race` names on its line the branch it took, and a step the race cancelled\nin a losing branch names the branch that won and the race. `answer` at such a step is refused with\nthe same words. An answer the program never read\nis marked `not acted on`: its step was in a branch that lost a race, or its pause accepted it and\nwas then cancelled, as a losing branch's pause is. `--json` carries the race's `winner` and, on each\nstep of a losing branch, the `lostRace` it lost.\n\nA settled step is never answered twice, so a participant who changes their mind uses `amend`. It\nfiles a new answer beside the accepted one, naming the answer it supersedes, and `show` prints\neach amendment under the step as an `amended` line, in the order the store committed them. The last\nline is the current position, whatever clock each amender's `at` came from. The pause stays settled\nand the run keeps the answer it acted on. A step that is still open, or that settled with no answer,\nrefuses an amend. So does a step whose answer was just accepted and is not in the run's journal\nyet, and that refusal says so. The manager records the amender from the credential, as for an answer, and a\nspawned seat may amend only an answer recorded under its own name.\n\nA program's `log(...)` lines are not journalled, so `show` does not print them. A run\nthe manager hosts writes each line to the manager's log as `run <run-id>: log <text>`, which\n`cotal service logs stack-manager --space <space>` reads. A run driven with `--local` prints them in\nthat terminal, and `run simulate` lists them under `Log`. A resumed run replays its program, so it\nwrites the lines before the resume point again.\n\n```bash\ncotal run start --file build.cotal.js # the manager starts it; the minted id is printed\ncotal run # list run records and their recorded state\ncotal run show run-3f2a90c41b7e0d5a6c884e19b02df4a1 # print the record and its last 20 journal rows; --limit <n> prints up to 1000, --json all\ncotal run resume run-3f2a90c41b7e0d5a6c884e19b02df4a1 # the manager takes the run back\ncotal run answer run-3f2a90c41b7e0d5a6c884e19b02df4a1 \"/checkpoint:approve#0\" --value '\"yes\"'\ncotal run amend run-3f2a90c41b7e0d5a6c884e19b02df4a1 \"/checkpoint:approve#0\" --value '\"no\"' # record a changed position\ncotal run check run-3f2a90c41b7e0d5a6c884e19b02df4a1 build-v2.cotal.js # check an edited program against the journal\n```\n\nA program that does not validate is refused before anything is recorded, with every problem in the\nanswer as the validator would print it. The driver records the program beside the run, so `resume`\ntakes the run id alone and the manager reads the source back; an edited program is a migration or a\n`fork`, never a resume. `cotal run check <runId> <program>` is that check: it\nreplays the run's journal and walks the edited program over it, prints whether the migration is\nadmissible, how many journal rows the walk accounted for, every orphaned step with its verdict and\ncode, and exits 0 on admissible and non-zero on not. It reads only, under the same credential\n`show` reads on, and the commit side is not reachable yet: the report itself says what a commit\nwould file and that this invocation filed nothing. An answer is recorded under the answerer the\nmanager knows from the caller's credential: a managed agent by its name, anyone else by their\nprincipal. The request\ncarries no name. An agent with `capabilities: [run]` has the same five verbs as the `cotal_run`\ntool ([MCP tools](mcp-tools.md)), so a program can be written and started from inside a session.\nA `start` or `resume` answers once the run's record is written, within a bounded wait; a manager\nthat is still taking back a predecessor's runs at boot refuses both with `unavailable`, and a\nretry a moment later is the whole remedy.\n\n`--local` drives the run in this process instead: `start`, `resume` and `answer` exit when the\ndrive settles, `--by <who>` names the answerer, and `cotal run resume <runId> --local --file\n<program>` is how a run with no recorded program is continued. On a static mesh the local drive\nmints the run's own credential from the folder's trust material, so it runs from the mesh's\nproject folder. A local start also names the run's channel ceiling itself:\n`--admit-read <channels> --admit-publish <channels>`, comma-separated patterns or `none`, both\nrequired. The record it writes says an operator admitted the run and why, and the host checks\nit the same way it checks a hosted admission. A registered remote user-auth manager can host a\nrun through its issuing host. The host resolves a versioned caller against live issuance,\nadmits the run, and signs only the run's fixed driver, mediator and one-shot operator credentials\nfor manager-held nkeys. Renewal checks the activated attempt; the manager holds no signer.\nLocal user-auth runs remain unavailable because a user bearer holds no run rows. An open mesh\nhosts none either, since it issues no caller authority to admit a run under.\n\nA logged-in user starts runs on that remote manager with `cotal run start`. The auth callout issues\nthe user's manager connection against the user's actor-ledger row, the CLI reads the generation\nback from the connection's accepted row, and every `run` verb rides the versioned rail. The issuing\nhost admits a run only for the owner who registered the manager, and on every resume and answer it\nchecks that owner and that the caller's issuance is still live. It also watches the start, resume\nand answer requests on the broker itself, and admits or issues for one the manager forwards only if\nit saw that request, and only once. A resume or answer gets issuance only for the run, step, endpoint\nand amendment that request named. An answer's\ncredential reaches one pause, and the host reads which one off the run's own journal, for a run\nadmitted on that manager's instance. A request bound to another manager instance or epoch gets nothing, and so does one whose class or pinned contract is not the one the manager registered. Another user's start, answer or resume is refused, and so is a revoked actor's.\nSuch a run spawns, turns and despawns agents owned by that user. A spawn has the reach of the user\nwho started the run, as their actor-ledger row reads at that moment, and the host enrolls each agent\nthrough its managed-agent enrollment. A spawn may be placed on the manager that hosts the run and on\nno other instance. [User-auth run start](design/user-auth-run-start.md)\nrecords the path.\n\nRun admission accepts canonical space names such as `cotal_cloud` and `u_example-name`\nwithout rewriting them. Endpoint names keep their separate DNS-label grammar.\n\nA hosted run is **admitted** under the caller that started it. The caller's credential is an\nissuance ([identity and auth](identity-and-auth.md#issued-authority)): its requests ride a\nversioned rail that carries the credential's generation, and the manager resolves that\ngeneration's recorded permission ceiling and writes it beside the run before the driver starts.\nThat ceiling, the caller's own channel scope as it was issued, is what the run may read and post\nin channels; the manager's own reach never stands in for it. A request from a credential minted\nwithout an issuance is refused with `permission-denied` and a detail naming the caller. A run\nwhose caller had no channels can still sleep, checkpoint and turn agents; its `wait` on a channel\nis refused at the effect.\n\nThe manager endpoint serves `run-cancel` with `{ runId, reason }`. Call it through an issued\ncredential on a handle pinned to the manager instance, retaining the Describe-derived epoch bind.\nThe reason must encode to 1 through 512 UTF-8 bytes, and the request deadline is at most 30 seconds.\nNo CLI or MCP verb wraps it.\n\nA signer-holding manager accepts the original caller's owner, actor and lifecycle under a live\nissued generation. On a signerless manager, the issuing host also accepts an interactive actor of\nthat owner with `run`, or an interactive admin. It must observe the caller's request itself before\na manager can forward it. Managed-agent names and platform assignments do not grant this authority.\n\nThe reply separates the durable marker from the local observation: `stopped` means a local drive\noutcome was observed; `stop-requested` means it was not observed within the request budget.\n`launch-abandoned`, `terminal` and `not-driven-here` describe their respective local states.\n`duplicate` is false for a confirmed new marker, true for a confirmed existing one, and null after\nan unknown write followed by marker readback. Preserve unknown outcomes. Neither a marker nor a\nreply proves process cleanup or that another host stopped. Revoked runs remain readable through\n`run show`, but cannot receive new drive or answering authority. The endpoint's `run-status`\nreply carries `revoked: { by, reason }` independently of the recorded run state. A running record\nwith that marker is not proof of a stopped holder. `revocationUnreadable` means the marker could\nnot be checked; do not read that as unrevoked or resumable. The same reply carries the run's\n`programHash`, the value its `run-ps` row carries, read from that run's records alone, so checking one\nrun's program identity needs no full run list. It is absent when the run has no recorded program,\nand `programUnreadable` replaces it when the program record cannot be read. A released record without a marker can\nbe resumed under its original admission; a released record with a marker cannot. The human\n`run show` view displays admission revocation separately from recorded state. An unreadable marker\nproduces a partial result with exit 4, in human and JSON output, as it does in the run list.\n\nA cancellation can retain a local pending intent even after a definite no-dispatch refusal if its\nbudget ends before the manager can confirm marker absence. That intent blocks a new local launch\nor resume; it does not stop an existing drive. A later cancellation that observes a marker reconciles\nthe retained intent. Replaying the same request without a marker reports that it remains pending and\ndoes not dispatch again. A manager process restart loses the local intent, but does not settle the\nunknown write or prove cessation. An absent marker after an unknown write does not clear the intent\nin the running process. A `not-executed` write outcome therefore does not promise that a local launch\nis available.\n\nIssuer observation custody has finite lifetime budgets per caller, owner and issuer. Exhaustion\nlogs the budget class once per issuer process, without caller identities or request contents.\nRepeated refusals do not repeat that diagnostic. Custody entries are not evicted to make room.\n\nEmbedding hosts can call a drive's `cancel(reason)` handle to signal cooperative root cancellation.\nThe language's `RootCancellation` source carries the first reason through the walker and compiled\nworker to in-flight effect contexts. It is process-local and carries no authorization. The host must\nestablish permission and observe the durable revocation marker before calling it. A root cancellation\nends the drive as `released`; the status projection can lag if its write fails. Read the recorded\nstate separately. A completion or failure that wins the race keeps its actual outcome. It does not stop an agent process or preempt already-pulled tool work.\n`release(reason)` remains a separate next-effect-boundary request, used for host shutdown.\n\nThe host also supplies `cancellationObserved` for channel-effect classification. A fresh revocation\nread together with an observed local cancellation intent ends that effect as cancelled, even while\nthe manager is still reading the current holder before signalling the root. Pending intents alone\nnever do this. An ordinary local revoke without a cancellation intent keeps its existing failure\nbehavior. The shared observation neither signals the root nor replaces the holder check.\n\nIf the post-marker holder read fails or exceeds the original budget, the command does not signal\nthe root without that check and returns no stopped receipt. A parked checkpoint or sleep can\nremain running beside the marker. Preserve the unknown outcome. A later explicit cancellation\nrequest can read the same marker and a current holder, then report its own observed result.\n\n`cotal run revoke <runId> --local --by <who> --reason <text>` writes the run's revocation marker\nfrom the project folder. An empty `--by` or `--reason` is refused `USAGE_INVALID` before anything is written, naming the empty flag. A run ID with no admission on the endpoint is refused `TARGET_NOT_FOUND`, exit 2, before anything is written. The\nadmission itself is never rewritten. Every host reads the marker\nbefore its next channel effect, so an open `wait` refuses at its next poll, and no resume,\ntakeover or manager restart continues the run. An open checkpoint or `ask` stays listed under\n`Needs an answer`, but `run answer`, hosted or `--local`, refuses it with `LIFECYCLE_BLOCKED`\nnaming the revoker and the reason, and files nothing, so the run stays parked at that pause. A\nmarker that cannot be read refuses the answer the same way. Revoking twice is not an error, and\nthe first reason stands. A run whose admission is missing or revoked is left parked by the manager's boot\nreconcile, named in its log.\n\nA revoke does not cancel the run. Work that touches no channel, such as a loop of `sleep` and\n`log`, keeps running until the host driving the run stops. A `--local` drive ends with the process\ndriving it, which reaches no other run. A hosted run stops with its manager, and so does every\nother run that manager drives: `cotal service restart stack-manager --yes` stops them all, and its\nstop impact names the managed agents in scope. A run that reaches its next step within two seconds\nis recorded `released` and waits for `cotal run resume <runId>`; one still waiting keeps its\n`running` record, and the restarted manager takes it back unless it is revoked. `cotal down` stops\nthe whole stack. The `run-cancel` endpoint above cancels one hosted run, and the CLI has no verb\nfor it.\n\nBare `cotal run`, hosted or `--local`, reads the marker beside each run record and prints\n`admission revoked` in the `Recorded state` column for a run that carries one, whatever state the\nrecord itself holds, with the revoker and the reason under the table. The record is display only\nhere: a revoke writes no terminal state, because no host drove the run to one and the journal owns\nthe facts. A marker the listing cannot read, whether the store is unreachable or the marker has a\nversion or shape it does not know, prints `revocation not checked` in that column and a warning on\nstderr. The listing is then a partial result and exits 4 once every row is printed. The hosted\n`run-ps` rows carry the marker as `revoked` (`by` and `reason`) or a failed read as\n`revocationUnreadable`, beside the record's own `state`.\n\nA run whose step was refused (L5016) stays held; a\nresume on a host that can perform the step performs it live and continues from there.\n`show` prints what an open pause asks beneath its step key, which is the address `answer` takes\nback. Checkpoint expiry rides the mediated timer writer, which the delivery daemon pumps on a live\nmesh; on a bare broker a pause still resolves, it just cannot expire.\n\nA live pause that nobody answers, such as a `sleep` or a checkpoint timeout, is observed by\npolling: every 2 seconds the driver looks for the broker's timer fire, and every 2 seconds for the\nsettlement that fire records. A step therefore ends after its deadline, usually by 2 to 4 seconds\nand later on a loaded host: a live `sleep(\"1s\")` or `sleep(\"2s\")` usually takes about 4 seconds,\nand `sleep(\"5s\")` about 8. Timers whose deadlines fall within one poll can end on the same poll, so\na live `race` between them is not decided by their written durations: a `sleep(\"2s\")` branch\ndeclared before a `sleep(\"1s\")` branch can win. The simulator uses virtual time and reports the\ndurations as written.\n\n## Diagnostic inventory\n\nA large inventory can exceed the broker's reply limit. A host that supports page mode can return\none bounded JSON page. Page mode is not in release 0.88.0 or earlier: the calling CLI or connector and\nthe manager both need a later version.\n\n```bash\ncotal run --page --on <instance> --limit 8\ncotal run --page --on <instance> --cursor <cursor>\n```\n\nFor the MCP surface, use `cotal_run` with `verb: \"ps\"`, `instance` and `page: { limit: 8 }`.\nPass a returned `nextCursor` as `page.cursor` to continue. A managed session can use its existing\nmanager binding. Keep the same space, endpoint filter, instance and process epoch throughout a scan.\nThe CLI requires `--on`; page mode is not available with `--local` or other verbs. An older host\nrefuses page mode rather than returning a truncated legacy list.\n\nEach page has at most 16 rows (default 8), and the complete reply fits 65536 bytes and the broker's\npayload limit. The reader issues at most 64 storage calls and stops issuing them after 2 seconds or\nthe smaller request duration. These are application limits, not bounds on broker CPU, disk work,\nclient-library retries or total elapsed time. One row that cannot fit is refused, never split.\n\nThe response carries `semantics: \"diagnostic\"`, the observed sequence cut, responder, storage-read\ncount and `stopReason`. Rows contain spec/status revisions and whether a revocation marker was\nobserved. Programs, journal contents and revocation reasons are omitted. Legacy requests without\npage mode retain their existing array output.\n\n`endObserved` means the walk reached its cut or received a no-match response. It does not prove an\natomic snapshot, absence, ownership, terminal work or worker inactivity. Cursors are unsigned\ncontinuation data. Deleted history and restores can be invisible. Keep partial results incomplete,\nand retain uncertain-start reservations and ownership holds. A continuation that makes no progress\nis reported as incomplete; do not retry it in a loop or use it to release work.\n\n## Keyed submission\n\nThe manager's `run-start-keyed` command binds an envelope submission ID to one host-minted run.\n`run-submission-status` reads that binding for the authenticated caller incarnation. Both need\nfresh issued reach and a digest-verified description pinned to one manager instance and epoch.\nLegacy `run-start` is unchanged. An unsupported keyed command never falls back to it.\n\nThe scheduler must persist the submission ID, semantic fingerprint, caller incarnation,\ncontract digests and instance/epoch before dispatch. The client helpers prepare that snapshot\nand send one attempt at a time. They pass on only `signal` and `deadlineMs`, which a keyed\nrequest keeps at or below 30000. They refuse a reply that names another submission, instance,\nepoch or fingerprint, and that outcome stays unknown. They do not schedule retries, replace\nunknown work or re-pin it to another manager. Their prepared handle is process-local and WeakMap-bound: serialized JSON\ncannot be rehydrated through these helpers. Persisted scheduler recovery needs separate reviewed\nintegration, not a cast or an automatic fresh Describe. Keep the original work reservation\nwhile reconciling any unknown outcome.\n\nThe binding winner may make at most three admission-create attempts in its original callback\ndeadline, with the same admission bytes and authority. Initial authorization precedes binding,\nbut fresh authority rechecks before each admission attempt can fail after binding and retain\nbinding-only debt. The manager refuses a start while it is stopping or already at its launch\nlimit before it reads or writes a binding, so that refusal writes nothing. It says nothing about\nan earlier attempt under the same key, which may already be bound or running.\n`run-submission-status` takes no launch slot, so read the outcome there. A launch that fails after\nadmission leaves `admitted` with no run, and no later call launches it. Later duplicates only\nread. A `binding-only` result is a visible reconciliation hold, not a queue or permission to repair.\nAn absent admission is an observation at read time, not proof that the original callback cannot\nstill write. A changed caller generation or manager epoch refuses rather than adopting the run.\n\nThe receiving manager or issuing host measures the request budget on its own monotonic clock,\nand checks the caller's authority expiry on the wall clock. Wall-clock changes do not extend or\nspend the monotonic request budget. Authority expiry remains a wall-clock check. The callback\ndeadline a status read returns is a wall-clock record of that window, not something a caller or\nhost enforces.\n\nThe original callback timer bounds waiting and starting more attempts. It does not cancel an\nalready-issued broker publish, which may land later with the same frozen admission bytes.\nNo new admission attempt starts after the request budget or authority window ends. Once an\nadmission exists, the activation check uses only the manager's remaining monotonic request budget;\nit does not recheck the caller's wall-clock expiry. Keep the reservation even if an earlier read\nobserved no admission.\n\nSubmission state `activated` reports evidence of activation, including completed and failed\nruns. It is not a running-state or active-lane signal. Use native `run-status` for lifecycle\nstate. Terminal submission bindings remain permanent, and duplicate starts never relaunch them.\n\nThis implementation is under validation. Static native and signerless issuer paths require\nseparate acceptance evidence before release or integration.\n\n## What is on the wire\n\nThe run's wire footprint is [SPEC §14](../SPEC.md#14-workflow-runs-v05):\n\n| Thing | Where | What it is |\n| --- | --- | --- |\n| the run | `run.<endpoint>.<runId>` record | the resolved **pins** (seed, logical epoch, budgets, language version) on the immutable half; holder, lease and `journalHigh` on the status half |\n| the program | `program.<endpoint>.<runId>` record | the source the run was started from, verbatim, written once by the driver that pinned the run; what a resume reads and what a migration is measured against |\n| the step journal | `WFJ_<space>` stream, one subject per run | append-only, no age eviction, no Direct Get; every append fenced by the run subject's own sequence; takeover is replay-then-activate |\n| a checkpoint answer | `answer.<endpoint>.<token>.<answerId>` | the payload beside the one-use settle fact; the settle names the answer it accepted |\n| a notice | `notice.<endpoint>.<runId>.<addresseeId>.<noticeId>` | one bounded decision told to one agent, rendered ahead of its next turn |\n| a migration | `migration.<endpoint>.<runId>.<migrationId>` | the report and who applied it, keyed by the report's own digest |\n| the admission | `admission.v1.<endpoint>.<runId>` in `cotal_admission_<space>` | the caller the run was admitted for, its channel ceiling and its provenance; written once before the driver starts, and the store refuses a second write on the key |\n| a revocation | `revoked.v1.<endpoint>.<runId>` in the same store | who revoked the run and why; create-only, idempotent, permanent at the broker, read by every host before its next channel effect |\n\nThe implementation writes the `program` record, and SPEC §14 refers to it, but the\nrecord-kind table in SPEC §13.7 does not list it. That table omission remains a\nspecification gap; the table above describes the implemented storage layout.\n\nThe driver writes `journalHigh` at activation and again after each journal append, before the\nprogram acts on the entry. A successor whose replay ends below it refuses the run with\n`RunJournalTailTruncated`. That holds for records appended since the last activation too.\n\nA run's **driver** connects on a `run-driver` credential minted for one run and takeover\nattempt. It can append to its journal, use its replay durable, and write its own `run`, `program`,\n`notice` and `migration` records. It has no store point reads, checkpoint writes, chat consumers,\nor channel and membership registry grants.\n\nThe hosting process keeps a separate `run-mediator` connection for effects and reads. The driver\nreceives methods and data from that host; it never receives the mediator credential or connection.\nThe host checks the journal's current activation and step identity before dispatch, and checks\npause and wait authority again at each operation. Cancellation cleanup also admits losing steps\nnamed by a settled parent whose `cancel.issued` is still false. That permission allows cleanup;\nit cannot mint or rearm a cancelled pause. Wait acknowledgements consume host-held delivery\nreceipts. A recorded match can be reread only at its bound sequence and channel. A conclave's\nrecorded ownership flag must match its step-derived channel before registry writes or cleanup.\n\nThe mediator retains endpoint-wide checkpoint rights and stream-wide leader reads as trusted\nhost authority. Record reads exposed to the driver are restricted to its own run's keys. Reads\nthat decide writes remain leader-served. A read of the journal uses the run's filtered replay\ndurable, including the diagnostic for a journal with no run record. That durable is named after\nthe takeover, and an attempt reads it many times, so reads under one takeover run one at a time in\nthe hosting process and a replay removes a durable of its own name that an interrupted earlier read\nleft behind. A create failure permits this recovery only for a structured JetStream API error\nwith code `10148`. Other errors propagate without deleting the durable, even if they carry that\nnumeric or string code. A durable that survives a replay's own delete belongs to a reader the\nprocess cannot account for, and reading its tail is refused. A drive handles that refusal as a\ntakeover does: the reads behind its steps, and the diagnostic for a journal with no run record,\nreplay up to three times before the refusal is raised. An operator read runs under a takeover\nminted for that read and reports the refusal on its first read.\n\nA served read uses a one-shot `run-operator` credential. An answer uses a read to find the open\npause, then a second credential pinned to that token for the answer and settlement.\n`cotal run --local` uses the same driver/mediator split. On an authenticated mesh it needs the\nlocally recorded space signer to mint both credentials; a single `--creds` file is refused.\nDirect library users supplying broker clients to `MeshHandler` are constructing a trusted effect\nhost. A hosted driver receives its closed effect interface instead.\n\nThis split confines broker credentials; it is not process isolation for injected host code.\nThe runtime and its effect host share the manager process. A run's channel reach is the admitted\nceiling ([SPEC §14.8](../SPEC.md#148-run-admission)): the starting caller's issued channel scope,\nrecorded once in `cotal_admission_<space>` under a per-run `run-admitter` credential the driver\nnever holds, and re-read by the host before every channel effect. Spawn and turn keep their own\ndelegated checks; `notify` writes agent-addressed notices and is not channel publication. Treat\n`run` as program-execution authority bounded by that ceiling, not as sandboxing of the program.\n\nA version-1 fork can replay its settled parent history through the host. Inherited checkpoint\nidentifiers carry no authority to read, rearm or claim the parent's pauses. New child effects use\nchild-derived identifiers. A fork is a new run and takes a new admission under the caller who\nforks it; the parent's ceiling is not inherited.\n\n## What ships today\n\nThe language, its validator, interpreter, simulator and dry run are `@cotal-ai/lang`\n(`packages/lang`), usable in-process with your own effect handler and with no broker: `validate(src)`,\nthen `run(src, { runId, handler })`, and `resume(src, journal, { runId, pins, handler })` to pick a\nrun up from its journal (the package README has the snippet, with `SimHandler` as the handler). That\nis the in-process route, yours to drive with your own handler; a run the driver starts executes on\nthe compiled engine, as the engine paragraph below says. The wire\nsubstrate of §14 (the `WFJ_<space>` stream, the five record kinds, the activation barrier, the\nper-run grants) is in `@cotal-ai/core`, and the run driver, journal store, migrate and fork are\n`@cotal-ai/runtime` (`implementations/runtime`). The migrate check is reachable as\n`cotal run check <runId> <program>`; committing a migration it judged admissible\nis not reachable from any surface yet, and neither is a fork: `planFork` and `commitFork` are\nlibrary exports of `@cotal-ai/runtime` with no CLI or MCP verb. On the mesh handler, `sleep`,\n`checkpoint`, `wait(message(...))`, `wait(idle(...))`, `wait(down(...))`, `wait(replied(...))`,\n`waitUntil`, `notify`, `spawn`, `conclave`, `ask`, `monitor` and `turn` are durable.\n`spawn` is\nthe manager's spawn action submitted under the step's own identity: the goal binds under the step's\nrequest id, so a resumed run re-attaches to the same seat instead of allocating a second one, a\nspawn whose seat fails to come up is catchable as L4002 with the manager's recorded reason (one the\nendpoint refuses at accept is L4000, described below), and a spawn on a\nrace branch that loses is despawned by the run's own cancellation sweep. A seat belongs to the run\nthat spawned it: when the run completes, it despawns every seat it spawned, including a race\nwinner's and one whose spawn failed while its process stayed up. A spawn marked `onFork: \"adopt\"`\nis the exception: a fork can share that seat and no run can see whether another still uses it, so\nthe seat stays up until you stop it with `cotal stop` once every run sharing it is done. A seat a\nmigration handed to a later spawn follows that spawn's policy, and it stays up if any spawn that held\nit was marked `onFork: \"adopt\"`, because a fork taken before the migration may still share it. In a\nspace with several managers, a despawn counts a seat as already gone only when the manager that\nallocated it says so. A run that\nfails or is released keeps its seats until a resume completes it or you stop them with\n`cotal stop`. Start a seat with `cotal spawn` when it should outlive any run. `permits` are the budgets\nthis host meters: `turns`, how many turns the run may dispatch to the agent, and `wallClock`, a\nduration from the spawn after which no turn is admitted. The turn that would exceed one is the\ncatchable L4001 (kind `permit-turns` or `permit-wall-clock`; a deadline the remaining wall clock\ncannot hold counts as exceeding it), an adopted run counts the turns its journal recorded, and a\nbudget the host has no meter for, such as `tokens` or `spend`, is refused at the spawn rather than\naccepted and ignored. `supervise` is the restart policy this host asks the manager to enforce:\n`restarts`, how many in-window process deaths may come back under the same handle, and `window`,\nthe duration those deaths are counted in (default `10m`). The manager restarts the process in\nplace under the same name, lifecycle uid, persona, worktree and permits; `monitor` does not fire\nfor a restart, and `wait(down)` fires only when the seat is gone for good. Spending the budget\nretires the seat, and the next `turn` is the catchable L4002. The policy also covers the manager's\nown death. On a static mesh, when the manager ends without stopping the seat (a crash, or a pty seat\nthat ended with its manager), the next manager of the same instance restarts the seat the same way\nand counts that as one restart. A turn the dead manager had accepted for the seat is served to the\nrestarted seat, so the interrupted turn runs again and finished turns do not. A turn an older\nmanager accepted is not served again, because its record does not name the seat's owner, and it\nends at its deadline as L4003. A seat whose budget\nis spent, or whose persona file or credential changed or expired while no manager ran, is retired instead. A policy this host cannot enforce\n(an unknown key, a user-mode seat, or a runtime that cannot respawn a name in place) is refused\nat the spawn rather than accepted and ignored. `events: false` is the workflow form of\n`cotal spawn --no-events`: the seat starts without its AG-UI event plane. A connector that\npublishes none, such as Hermes, needs it, because an omitted `events` arms the plane and the\nmanager refuses that connector at the spawn. A value that is not a boolean is refused at the\nspawn. `runtime` names the terminal runtime of this one seat (`pty`, or an installed extension such\nas `tmux`). Omitted, the seat uses the serving manager's own runtime. A name that manager cannot\nresolve refuses the spawn and starts nothing, and the other seats keep the runtime they already have.\n`conclave` joins its\nmembers to a real channel as durable membership rows: the channel derives from the step's own\nrequest id when the program names none (a program-named channel is borrowed, never torn down, and\na membership that predates the conclave survives its close), each member handle resolves to its\nprincipal through the seat's own presence row (an absent member is catchable as L4002), and a\nconclave cancelled on a losing branch is released by the same cancellation sweep. `ask` parks one\ncheckpoint-plane pause per attempt, answered through `cotal run answer` as a checkpoint is, and\ntells the agent through the same relay `turn` uses: one relay per attempt under the attempt's own\ntoken, carrying the schema, the attempt count, the deadline and the previous refusal, which the\nseat's connector renders as the record wanted and the hosted command that answers it. The relay\nnames the ask's absolute deadline, so the manager stops serving an attempt at the instant its\npause expires and refuses a relay already past it. When that\nliteral command is run inside the managed seat, the CLI reuses the seat's lifecycle credential and\nissued caller identity rather than minting an operator instrument. The command\ndoes not take `--by`: the manager records the authenticated caller as the answerer. A spawned seat's\nbaseline credential carries only the self-targeted `run-answer` row, and the manager accepts it only\nfor the open ask or escalation relayed to that exact incarnation. It cannot answer another seat's\nask, an unrelayed checkpoint, another run, or start and resume commands. An ask addresses\nan agent the run spawned (anything else refuses before an attempt opens), a resumed attempt tells\nthe seat nothing twice, and a seat gone at the relay is L4002. On the pause itself:\nthe shorthand of the language reference §6.5 is enforced (an unreadable schema is L4022), a\nnon-conforming answer costs one attempt and its refusal reason is recorded on the entry for the\nanswerer to read, exhausted attempts (default one) are the catchable L4006, and so is the one\nabsolute deadline for the whole ask passing with no conforming record (its kind is `ask-deadline`),\nincluding one that passes before an attempt's pause is armed.\n`checkpoint` binds what it asks on its own entry, so `cotal run show` prints the question under\nthe step key an answer is addressed by while the pause is open: the address alone left whoever was\nasked reading the source to find out what \"approve\" meant. The entry also records the open\nattempt's deadline and the `onExpiry` it was armed with, which `run show --json` reports; what\nan expiry does is still decided from the program's source. An escalation's attempt records its own\ndeadline, one `timeout` after it opens, and a resumed attempt keeps the deadline it recorded. After a checkpoint or `ask` accepts an\nanswer, the journal prints that accepted answer's recorded value and attribution under the settled\nstep. A checkpoint's comes from its frozen result, and the journal never substitutes another filed\nanswer or invents fields that result does not hold. An `ask`'s result is the value alone, so its line\nis read from the answer record its last attempt's settle named, even when that value is a record with\nfields named like a checkpoint's. Amendments print on their own lines after it.\nAn `escalate` addressed to an agent this\nrun spawned is relayed to that seat through the same turn relay an `ask` uses, carrying the prompt\nand the token to answer under, held to the pause's own deadline; a `to` naming anyone else is a\nperson, and their pause stays the one anybody can answer, with the addressee recorded and rendered\nbeside the question.\n`monitor` registers interest in an agent, and the\nregistration is the journal entry itself, carrying the handle it registered: monitoring an agent\nthat is already dead succeeds, and the death is the wait's to observe. `wait(down(...))` observes\na monitored agent, and refuses one the run never performed `monitor` on. It reads the death off presence liveness, the\nsame witness a conclave join resolves members through: the value carries the handle, the reason\n(`lapsed` when nothing live holds the name any more, `superseded` when a live row holds it under\na different incarnation) and the time of observation. Presence liveness skips any value without a\nstring `card.id` and `card.name`, so a participant that publishes a malformed row under its own key\ncannot fail another run's wait, turn or conclave join. A superseded incarnation is down at once. A\nlapsed one is down only after its presence row has stayed gone for 30 seconds, because a seat whose\nconnector stalls past the row's 6-second TTL, under host load or across a reconnect, renews it under\nthe same incarnation and is still working. The 30 seconds count only across presence reads that\neach end within 6 seconds of the previous one starting, so a slow read or a run of failed reads, which\ncould hide a renewal, starts the count over. Even then the run asks the manager first: a seat whose\nprocess the manager still runs is alive however long its row has been gone, so the count starts over.\nOnly the manager that spawned the seat can report it gone, because another manager in the space knows\nonly its own seats, so the run asks again until that one answers. When it does not answer, the presence\nrow is the only witness and the lapse stands. The asking stops at the wait's timeout or cancellation,\nand at a turn's deadline, yield or cancellation, which then decide. A wait that begins\nafter the death resolves once that holds, and a timeout resolves null on one absolute deadline a\nresumed run re-attaches to.\n`turn` wakes one seat for one host turn through the manager: the run\nsubmits the turn under the step's own identity, the manager holds it as a goal pinned to the\nseat's incarnation, and the seat pulls it under its own reach ahead of its next host turn, so\nnothing is pushed into a session mid-thought. The manager sends the seat a turn notice whenever\nits pending turns change, and repeats it every 5 seconds until the seat pulls a new turn. The seat\npulls when a notice arrives, when it first connects, and after a reconnect while it holds a turn,\nso an idle seat sends no requests ([SPEC §14.3](../SPEC.md#143-the-run-record)). The payload the seat reads names the run and the\nstep and carries the rendered run context, plus any pending notices addressed to it, which the\nturn consumes. The seat yields through `cotal_yield` (`done`, `blocked`, or `handoff` with an\naddressee), and ending its host turn yields `done` for every turn it was shown. A `handoff` names\nanother seat the same run spawned: the next `turn` in the same scope to that seat records the\nlink, a handoff to a name the run never spawned is the catchable L4005, and one to a seat bound\nto a different worktree is L4004. The deadline elapsing before any yield is the catchable L4003:\nthe acceptance names the instant, the manager's goal-bound hold denies at it, and the run arms its\nown pause on that same instant, so either side outliving the other still converges on the same\nanswer. A seat that pulled the turn earlier stops showing it to its session at that same\ninstant. A seat that dies mid-turn is read off its own presence row by the run itself, with the\nsame 30-second confirmation for a lapsed row, and is the catchable L4002, and a death the manager\nmarked on the deadline terminal reads the same way. Two\nturns on one seat, from two branches or from two runs, reach it one at a time: the language\ndispatches the second when the first settles, and the manager shows a seat the oldest unsettled\nturn alone. A turn's goal id belongs to the caller that submitted it, so two callers may each submit\na turn under the same id. A seat yields by goal id alone, so the manager refuses to hand one seat\na second caller's turn under an id it already holds. On an auth mesh the relay needs no extra grant: every spawned seat's baseline\ncredential carries its own pull, yield, and caller-bound answer rows, the run driver's operator instrument carries the\nturn request and the `inspect` read that confirms a lapse, and the manager arms the deadline hold over its own serve grant and expires it\nitself once due. An accept the manager cannot finish is unwound to a failed terminal on the goal\nit bound, and a retry of that submission is refused naming the terminal rather than accepted a\nsecond time.\n`wait(replied(...))` observes those turns from another branch: a completed turn is a reply, and\nthe wait resolves with the observation record (the handle, the yield's status and note, the\nyield's own stamp). It reads as a level, the way `wait(down)` does: a reply that already exists\nresolves the wait at once, and two replies resolve to the latest by the yield's stamp. A denied\nor cancelled turn is never a reply, so an unanswered wait rides its own mediated timeout to\n`null`, and a handle the run never spawned or turned refuses loudly, since only this run's turns\nare observable. A turn the run itself ended without an accepted yield (its deadline, a\ncancellation, a refused handoff) is never a reply, whatever the seat yields to the relay later.\nA cancelled branch also withdraws what it relayed to a seat before its cancellation completes: its\nturn, its ask attempt or its escalation ends `cancelled` through the manager's reserved `cancel`\n(SPEC §13.6), so the seat is not shown it after the branch's scope settles and the next turn to\nthat seat does not wait behind it. Only the manager that accepted the relay holds it, so in a\nspace with more than one manager the run sends the cancel again while another manager refuses it,\nuntil the accepting one answers. A cancel refused as `unavailable`, such as by a manager still\nreconciling at boot, or one whose reply is lost (`deadline-exceeded`) is sent again until it\nlands or the relay's deadline passes, so the branch's cancellation does not complete while the\nseat can still be shown the relay. Any other refusal fails the branch's step at once under its\ncode, since sending it again changes nothing: a manager that serves no `cancel` is refused\n`not-found` before anything is sent, and an accepting manager with no such relay pending refuses\n`failed-precondition`. The run reads the relay's deadline from its goal and retries a failed\nread, since the relay may still be served until the deadline is known. A goal record\nthat can never yield a deadline, such as one that is not JSON, fails the branch's step at once,\nand so does a refusal that lasts past the deadline.\nA `spawn` may bind its agent to a **logical worktree** (`spawn(\"builder\", { worktree: \"wt-1\" })`):\nthe handle carries the id, and the run enforces the one rule the language states about it: two\nagents never share a worktree concurrently. The validator rejects the literal case up front\n(L3022: two branches of one concurrent scope spawning into one literal worktree, named branch\nfunctions included), and the runtime guards the rest, computed ids included: a spawn claims its\ntree before it submits, so a second spawn into a tree held by a live seat or by a spawn still\nbringing one up is the catchable L4008, a spawn that ends without a handle gives the tree back,\nand the tree is reusable the moment a holder's presence row is gone, so a discharged race loser\nor a crashed seat releases its tree with no bookkeeping. A spawn the endpoint refuses at accept\nis the catchable L4000 (L4001 when the refusal is the endpoint's seat capacity), and one whose\nseat never came up is L4002. A refusal that states the command did not run is answered\nbefore it gets that far. In a space served by more than one manager the resolve and the invoke\nare separate trips through the same anycast queue, so a run's call can reach an instance it did\nnot resolve against, and that instance refuses ahead of any effect. The run drops its resolved\nhandle, re-describes and re-issues, for a bounded number of attempts; after them the refusal\nsurfaces as the effect's own failure and still states that nothing ran. A spawn that names a\n`placement` addresses one instance by name, so a refusal from it is that incarnation answering\nabout itself and is never re-issued. When the spawn also names `cwd`, that manager resolves the\nexisting absolute directory to its canonical host path before accepting the spawn. A missing,\nrelative or non-directory path refuses with no seat and never falls back to the manager workspace.\nOne hosted run may name one placement instance; a program naming several is refused instead of\nwidening one run credential across hosts. Placement is accepted only from an object-literal spawn\noption bag whose `endpoint` and `instanceId` are string literals. A computed option bag, a computed\nplacement field, or a spread in either object refuses at `run-start`. The option argument may be\nabsent, and an object-literal bag without placement is accepted when it names no `cwd`.\nA spawn that names `cwd` must also name `placement`, because that directory belongs to one host.\nWithout placement it refuses with L4000 before submitting a spawn. Read the manager instance with\n`cotal ps`, then put its full instance ID into the program as a literal:\n\n```js\nconst worker = await spawn(\"worker\", {\n placement: { endpoint: \"manager\", instanceId: \"abcdefghijklmnopqrstuvwxyz\" },\n cwd: \"/home/operator/project\"\n})\n```\n\nReplace both example values with the manager instance and an existing absolute directory on that\nmanager's host. No manager is chosen by class anycast for this spawn.\nA turn handoff across worktrees is the L4004 described above. Recovery keeps these honest: a resumed run\nreseeds its roster, holders and handoff memos from its own journal, and the driver re-issues any\nrecorded-but-undischarged cancellation at adoption, before the engine performs a new step, so a\nloser a crash left alive does not keep its seat or its tree while the resumed run works on. The\nsame sweep withdraws a cancelled branch's relays and undelivered notices: a notice waits on the run for its\naddressee's next turn, so a decision the run cancelled would otherwise arrive at an agent with\nnothing to distinguish it from one that stood.\n\nEvery effect the language defines performs on the mesh handler; nothing is refused as\nnot-yet-durable any more. The operator surface over the driver is `cotal run`; the section above has the verbs.\n\n**Two engines, and which one runs your program.** The tree-walker is language version `1` and the\ncompiled engine is version `2`, two languages rather than two speeds of one (`spec/cotal-lang.md`\n§8.4 lists what differs). The driver hosts both: **every run a driver starts is stamped `2` and\nexecuted by the compiled engine**. The program runs in its own locked-down worker thread with\nnothing in its global scope, while the effects and the durable journal stay in the driver's process,\nbridged over a message port. No socket or credential enters the isolate holding the program,\nand **every version-`1` record keeps replaying on the walker**, which is the walker's job. On either\nengine the driver bounds an effect's `ok` result at the broker's `max_payload` less 4096 bytes: a\nlarger result is refused ahead of the settling append (**L5006**), the step stays pending, and the\nrun is released. The driver serves a declared set of versions, and a record whose version it does\nnot serve is refused by name (**L5023**) with the run left untouched, instead of being replayed by\nwhichever engine happens to be present. Records do not cross between versions in either\ndirection; the repair is to resume on the recorded version, or to fork.\n\n**The engine needs node 22 or newer** and refuses below it as `EngineUnavailable`, which is an\nimplementation limit and not a language error: it carries no `L` code, so there is nothing to look\nup in the catalog. It is a floor rather than a warning because the engine's frame plumbing rests on\n`AsyncLocalStorage`, and 22 is the lowest node it has been measured on. The walker has no such floor.\n"
|
|
367
367
|
}
|
|
368
368
|
],
|
|
369
369
|
"spec": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs-bundle.generated.js","sourceRoot":"","sources":["../src/docs-bundle.generated.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"docs-bundle.generated.js","sourceRoot":"","sources":["../src/docs-bundle.generated.ts"],"names":[],"mappings":"AAKA,yFAAyF;AACzF,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AAEvC,MAAM,UAAU,cAAc;IAC5B,OAAO;QACP,SAAS,EAAE,UAAU;QACrB,eAAe,EAAE,mEAAmE;QACpF,OAAO,EAAE;YACP;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,0BAA0B;gBAClC,UAAU,EAAE,YAAY;gBACxB,SAAS,EAAE,yFAAyF;gBACpG,MAAM,EAAE,6rKAA6rK;aACtsK;YACD;gBACE,MAAM,EAAE,iBAAiB;gBACzB,OAAO,EAAE,YAAY;gBACrB,MAAM,EAAE,0BAA0B;gBAClC,UAAU,EAAE,YAAY;gBACxB,SAAS,EAAE,gHAAgH;gBAC3H,MAAM,EAAE,ypfAAypf;aAClqf;YACD;gBACE,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,cAAc;gBACvB,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,wMAAwM;gBACnN,MAAM,EAAE,0ktBAA0ktB;aACnltB;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,kBAAkB;gBAC3B,MAAM,EAAE,mEAAmE;gBAC3E,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,wMAAwM;gBACnN,MAAM,EAAE,ol5CAAol5C;aAC7l5C;YACD;gBACE,MAAM,EAAE,0BAA0B;gBAClC,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,mCAAmC;gBAC3C,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,gGAAgG;gBAC3G,MAAM,EAAE,4sNAA4sN;aACrtN;YACD;gBACE,MAAM,EAAE,mBAAmB;gBAC3B,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,oDAAoD;gBAC/D,MAAM,EAAE,i6sEAAi6sE;aAC16sE;YACD;gBACE,MAAM,EAAE,aAAa;gBACrB,OAAO,EAAE,aAAa;gBACtB,MAAM,EAAE,yFAAyF;gBACjG,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,gJAAgJ;gBAC3J,MAAM,EAAE,2+YAA2+Y;aACp/Y;YACD;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,oFAAoF;gBAC/F,MAAM,EAAE,iqLAAiqL;aAC1qL;YACD;gBACE,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,+EAA+E;gBAC1F,MAAM,EAAE,qkgFAAqkgF;aAC9kgF;YACD;gBACE,MAAM,EAAE,uBAAuB;gBAC/B,OAAO,EAAE,uBAAuB;gBAChC,MAAM,EAAE,sFAAsF;gBAC9F,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,6GAA6G;gBACxH,MAAM,EAAE,ukUAAukU;aAChlU;YACD;gBACE,MAAM,EAAE,gBAAgB;gBACxB,OAAO,EAAE,sBAAsB;gBAC/B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,sMAAsM;gBACjN,MAAM,EAAE,koTAAkoT;aAC3oT;YACD;gBACE,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,uBAAuB;gBAChC,MAAM,EAAE,wGAAwG;gBAChH,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,iKAAiK;gBAC5K,MAAM,EAAE,61ruBAA61ruB;aACt2ruB;YACD;gBACE,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,uHAAuH;gBAC/H,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,wMAAwM;gBACnN,MAAM,EAAE,8o6CAA8o6C;aACvp6C;YACD;gBACE,MAAM,EAAE,gBAAgB;gBACxB,OAAO,EAAE,gBAAgB;gBACzB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,+EAA+E;gBAC1F,MAAM,EAAE,mzgFAAmzgF;aAC5zgF;YACD;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,sBAAsB;gBAC/B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,wMAAwM;gBACnN,MAAM,EAAE,kttBAAkttB;aAC3ttB;YACD;gBACE,MAAM,EAAE,gBAAgB;gBACxB,OAAO,EAAE,wBAAwB;gBACjC,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,kJAAkJ;gBAC7J,MAAM,EAAE,q+eAAq+e;aAC9+e;YACD;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,sBAAsB;gBAC/B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,6CAA6C;gBACxD,MAAM,EAAE,0/3BAA0/3B;aACng4B;YACD;gBACE,MAAM,EAAE,kBAAkB;gBAC1B,OAAO,EAAE,yBAAyB;gBAClC,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,wJAAwJ;gBACnK,MAAM,EAAE,wmdAAwmd;aACjnd;YACD;gBACE,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,oBAAoB;gBAC7B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,8DAA8D;gBACzE,MAAM,EAAE,qqRAAqqR;aAC9qR;YACD;gBACE,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,YAAY;gBACrB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,4HAA4H;gBACvI,MAAM,EAAE,yiOAAyiO;aACljO;YACD;gBACE,MAAM,EAAE,iBAAiB;gBACzB,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,oIAAoI;gBAC/I,MAAM,EAAE,y47CAAy47C;aACl57C;YACD;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,oBAAoB;gBAC7B,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,+DAA+D;gBAC1E,MAAM,EAAE,wj1BAAwj1B;aACjk1B;YACD;gBACE,MAAM,EAAE,eAAe;gBACvB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,uMAAuM;gBAClN,MAAM,EAAE,m7eAAm7e;aAC57e;YACD;gBACE,MAAM,EAAE,iBAAiB;gBACzB,OAAO,EAAE,+BAA+B;gBACxC,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,6HAA6H;gBACxI,MAAM,EAAE,k3iBAAk3iB;aAC33iB;YACD;gBACE,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,uBAAuB;gBAChC,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,6GAA6G;gBACxH,MAAM,EAAE,0tOAA0tO;aACnuO;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,iBAAiB;gBAC1B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,yEAAyE;gBACpF,MAAM,EAAE,o/pIAAo/pI;aAC7/pI;YACD;gBACE,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,6DAA6D;gBACxE,MAAM,EAAE,gtHAAgtH;aACztH;YACD;gBACE,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,wEAAwE;gBACnF,MAAM,EAAE,0zQAA0zQ;aACn0Q;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,+CAA+C;gBAC1D,MAAM,EAAE,2jQAA2jQ;aACpkQ;YACD;gBACE,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,8BAA8B;gBACvC,MAAM,EAAE,8CAA8C;gBACtD,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,qIAAqI;gBAChJ,MAAM,EAAE,mhWAAmhW;aAC5hW;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,UAAU;gBACnB,MAAM,EAAE,sDAAsD;gBAC9D,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,mFAAmF;gBAC9F,MAAM,EAAE,i6ZAAi6Z;aAC16Z;YACD;gBACE,MAAM,EAAE,uBAAuB;gBAC/B,OAAO,EAAE,cAAc;gBACvB,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,0IAA0I;gBACrJ,MAAM,EAAE,69sBAA69sB;aACt+sB;YACD;gBACE,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,sBAAsB;gBAC/B,MAAM,EAAE,0CAA0C;gBAClD,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,8DAA8D;gBACzE,MAAM,EAAE,k+HAAk+H;aAC3+H;YACD;gBACE,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,SAAS;gBAClB,MAAM,EAAE,yBAAyB;gBACjC,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,mLAAmL;gBAC9L,MAAM,EAAE,68MAA68M;aACt9M;YACD;gBACE,MAAM,EAAE,YAAY;gBACpB,OAAO,EAAE,YAAY;gBACrB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,wMAAwM;gBACnN,MAAM,EAAE,ymtDAAymtD;aAClntD;YACD;gBACE,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,gBAAgB;gBACzB,MAAM,EAAE,oCAAoC;gBAC5C,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,kGAAkG;gBAC7G,MAAM,EAAE,62bAA62b;aACt3b;YACD;gBACE,MAAM,EAAE,iBAAiB;gBACzB,OAAO,EAAE,oCAAoC;gBAC7C,MAAM,EAAE,0CAA0C;gBAClD,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,wLAAwL;gBACnM,MAAM,EAAE,ou6BAAou6B;aAC7u6B;YACD;gBACE,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,QAAQ;gBACjB,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,2DAA2D;gBACtE,MAAM,EAAE,+9KAA+9K;aACx+K;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,qBAAqB;gBAC9B,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,uDAAuD;gBAClE,MAAM,EAAE,w1KAAw1K;aACj2K;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,WAAW;gBACpB,MAAM,EAAE,WAAW;gBACnB,UAAU,EAAE,WAAW;gBACvB,SAAS,EAAE,wEAAwE;gBACnF,MAAM,EAAE,qtTAAqtT;aAC9tT;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,uBAAuB;gBAChC,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,kGAAkG;gBAC7G,MAAM,EAAE,8vQAA8vQ;aACvwQ;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,gCAAgC;gBACzC,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,iEAAiE;gBAC5E,MAAM,EAAE,yrsJAAyrsJ;aAClssJ;YACD;gBACE,MAAM,EAAE,aAAa;gBACrB,OAAO,EAAE,mBAAmB;gBAC5B,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,iGAAiG;gBAC5G,MAAM,EAAE,s8NAAs8N;aAC/8N;YACD;gBACE,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,cAAc;gBACvB,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,OAAO;gBACnB,SAAS,EAAE,2HAA2H;gBACtI,MAAM,EAAE,6wrBAA6wrB;aACtxrB;YACD;gBACE,MAAM,EAAE,WAAW;gBACnB,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,uBAAuB;gBAC/B,UAAU,EAAE,SAAS;gBACrB,SAAS,EAAE,kHAAkH;gBAC7H,MAAM,EAAE,y4gEAAy4gE;aACl5gE;SACF;QACD,MAAM,EAAE;YACN,OAAO,EAAE,0BAA0B;YACnC,MAAM,EAAE,0lxpBAA0lxpB;SACnmxpB;QACD,MAAM,EAAE;YACN,OAAO,EAAE,mCAAmC;YAC5C,MAAM,EAAE,g9tGAAg9tG;SACz9tG;QACD,QAAQ,EAAE;YACR,OAAO,EAAE,oCAAoC;YAC7C,MAAM,EAAE,s8tBAAs8tB;SAC/8tB;KACF,CAAC;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/connector-core",
|
|
3
3
|
"description": "Shared MCP-bridge runtime for Cotal connectors: the mesh agent, cotal_* tools, and hook relay.",
|
|
4
|
-
"version": "0.109.
|
|
4
|
+
"version": "0.109.17",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"@cotal-ai/core": ">=0.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@cotal-ai/auth": "0.109.
|
|
40
|
+
"@cotal-ai/auth": "0.109.17",
|
|
41
41
|
"jose": "^6.2.3",
|
|
42
42
|
"@cotal-ai/smoke-kit": "0.0.0",
|
|
43
43
|
"@ag-ui/core": "0.0.57",
|
|
44
|
-
"@cotal-ai/core": "0.109.
|
|
44
|
+
"@cotal-ai/core": "0.109.17",
|
|
45
45
|
"@nats-io/jetstream": "^3.4.0",
|
|
46
46
|
"@nats-io/transport-node": "^3.4.0"
|
|
47
47
|
},
|