# Workflow runs

A workflow run coordinates agents through a program and a durable step journal.
The journal records each effect before dispatch and records its result afterwards.
If the host stops, a successor runs the program from the beginning and reuses recorded results until it reaches unfinished work.
The source and journal hold the recovery state. The runtime does not serialize a running interpreter.

Cotal Lang uses pure JavaScript for calculations and named effects for work outside the program.
Agents still communicate through their channels. The workflow controls when work proceeds, when a decision is required, and how concurrent branches settle.
[SPEC §14](/docs/spec/#14-workflow-runs-v05) defines run records and authority. The [language reference](https://github.com/Cotal-AI/Cotal/blob/main/spec/cotal-lang.md) defines program behavior.

[Workflow operations reference](/docs/workflow-operations/)

Agent tools, hosted and local commands, admission, revocation, inventory, and keyed submission.


[Workflow runtime reference](/docs/workflow-runtime/)

Replay, migration, forks, wire records, authority, and engine limits.


[Workflow effect reference](/docs/workflow-effects/)

Seat ownership, budgets, decisions, presence, turns, placement, and cancellation.



## Keyed submission

Keyed submission binds one submission ID to one host-minted run.
It lets a scheduler reconcile an unanswered start without launching replacement work.
The client must retain the original caller, fingerprint, contracts, manager instance, and epoch.
An `activated` submission can already have completed or failed, so it is not evidence of a running worker.

> **Warning: Under validation**
>
> Static native and signerless issuer paths need separate acceptance evidence before release or integration.
> The process-local client helper does not provide persisted scheduler recovery.



The [keyed submission reference](/docs/workflow-operations/#keyed-submission) retains the binding states, timing rules, retry limits, and uncertainty obligations.

## An example program

This program requests a plan, pauses for approval, gives a builder a turn, and waits for a reply.
It illustrates the language. The [agent-session request](/docs/workflow-operations/#from-an-agent-session) shows a single timer and its recorded result.

```js
const planner = await spawn("planner")
const builder = await spawn("builder", { worktree: "wt-1" })

const plan = await ask(planner, { name: "plan", schema: { steps: "array" } })
const ok = await checkpoint("approve-plan", "Approve the plan?", { timeout: "4h", onExpiry: "proceed" })
if (ok.status !== "resolved") {
  await notify([planner], { decision: "approve-plan", outcome: "expired" })
}

const r = await turn(builder, { name: "build", deadline: "30m" })
if (r.status === "blocked") {
  await turn(planner, { name: "unblock" })
}

const outcome = await race({
  reply: () => wait(replied(builder), { timeout: "20m" }),
  giveUp: () => sleep("1h"),
}, { name: "await-or-move-on" })
log("outcome", outcome.index)
```

`ask` obtains a structured value from a spawned agent. The language hashes the schema without interpreting it. This repository's handlers enforce the shorthand in language reference §6.5.
`checkpoint` records a pause that a person can answer, with a durable expiry timer.
`turn` gives an agent one turn and returns its yield. `race` keeps the branch with the earliest recorded completion clock.
`notify` carries a bounded decision record for an agent's next turn. It does not send prose into a channel.

### Personal native conversation

An approved personal native conversation can join its own run with `participant({ name? })`. The immutable admission pins its exact owner, actor, and lifecycle. `cotal_turn_participate` opts that conversation into a short self-only turn lease, renewed after reconnect or manager restart. The handle grants no stop, attach, or terminal custody. See [personal participants](/docs/workflow-effects/#participant).

## The mental model

Pure code calculates values. Effects interact with agents, time, decisions, and messages.
The language removes JavaScript constructs that hide effects or depend on the host.
Values remain mutable while your branch owns them, then freeze when they cross an effect boundary.

A step's identity combines its scope path, kind, name, and occurrence.
Changing a variable name or moving an unrelated step need not invalidate the recorded result.
Changing an effect's inputs produces a divergence, because the old result answered a different request.

Concurrency has explicit scopes and separate branch journals.
Race results use recorded clocks and declaration order, so replay does not depend on host scheduling.
`now()` uses the branch's run clock. `random()` uses a seeded stream for the scope.
The journal records enough information to explain both results and failures in the program's own coordinates.

A scripted simulator and dry run exercise a program before it reaches agents.
The [language behavior reference](/docs/workflow-runtime/#language-and-simulation) retains the syntax limits, scope error rules, and virtual-clock behavior.

## Reusable helpers

Helper calls share named-step occurrence counters. Give each helper instance a distinctly named single-branch `parallel` scope to preserve its namespace. See [reusable helpers](/docs/workflow-runtime/#reusable-helpers).

## Continuing a run

Resume replays the same program under the same pins and language version.
Settled effects return their results. Failed effects replay their errors. Pending effects recover the external work they already bound.

`once` protects an `ask` whose external write cannot honor an idempotency key.
If the host stops before the result is recorded, recovery asks a person to settle the unknown result instead of dispatching the write again.
This bounds dispatches. It does not guarantee a single external write.

Migration compares edited source with the journal and reports changed or orphaned steps.
The CLI exposes this read-only check. It does not commit the proposed migration.
Fork creates a new run from a recorded prefix, retaining the parent's pins while leaving the parent unchanged.

See [resume, migration, and fork contracts](/docs/workflow-runtime/#continuing-a-run), including seat adoption and the `once` example.

## Runs from an agent session

A persona with `run` capability can expose `cotal_run` on an authenticated mesh.
Fresh setup defaults include `spawn` and `run`, but existing personas and credentials can differ.
The manager must also support hosted execution and accept the caller's issued authority.

The [agent-session request](/docs/workflow-operations/#from-an-agent-session) supplies a small start request and the expected journal result.

### Missing `cotal_run`

Tool visibility depends on connector configuration and capability issuance.
Adding `run` to a file does not update an already issued credential.
The [tool availability reference](/docs/workflow-operations/#if-cotal_run-is-missing) describes orientation, upgrades, overrides, relaunch, and manager support.

## One task without a run

For a single task handed to a single helper, a run is more than you need. `cotal_spawn` with
`once: true` and a `prompt` starts the helper and relays the prompt to it as its only task. The
manager stops the helper when the task ends. It needs no `run` capability, so it works on an open
mesh too. The language's `once` scope above is a different thing.

The task ends in one of these ways:

- The helper ends its turn or calls `cotal_yield`. The task succeeds and the helper is stopped.
- The deadline passes. It is one hour unless the spawn passes `deadline`, and `-1` sets none. The
  task fails with `turn-deadline` and the helper is stopped.
- The helper exits first, never joins, or cannot confirm that its harness took the task. The task
  is cancelled or fails, with that reason.

After a deadline or an early exit, the task may have run in part or in full. Nothing runs it again;
spawning it again is the caller's choice. A spawn that succeeds names the task's goal in its
result, and `goal-result` reads its outcome. A helper with no deadline keeps its name and its
seat slot until its task ends or it is despawned.

Jcode, OpenCode and pi helpers can take a once task. A Claude Code helper takes one when its
startup shows channels on: Claude's development-channels prompt, then its notice that `server:cotal`
messages reach the session. Without both, the spawn fails at startup and names the reason, and the
task is cancelled without running. When a Claude Code helper has to stop itself, it ends the
`claude` process it was launched as. If it cannot prove that process is still the one launched, it
leaves it running until a person or its runtime stops it.

A manager that restarts can lose hold of a helper. If it restarted before the spawn's readiness
verdict, the task is cancelled as never served. If it cannot take over a running helper, as on an
open mesh, it never serves the task again: the task ends at its deadline, or as `uncertain` after
one readiness window when it has none, and the reason says the helper may still be running. Once
the task has ended, the helper is told to stop itself the next time it asks for its task.

A Jcode helper runs some processes outside its own. Before its task is recorded as ended, the
manager stops and rules out each one it can tie to the helper's launch: those that carry the
launch's identity, their descendants, and the members of a process group one of them leads. A process
that hides its environment and has left both its parent and its group cannot be tied to the launch,
so it can outlive the task.

Use a run when the work has several steps, or when a program needs the helper's answer.

## Operating a run

The manager records and drives a hosted run. Your terminal is free after start returns the run ID.
`ps` lists runs, `journal` shows steps and questions, `answer` settles a pause, and `amend` records a later change of position without changing the answer the run used.

A hosted run inherits the starting caller's issued channel ceiling, recorded at admission.
The manager's broader permissions do not replace that ceiling.
A revocation marker prevents the next channel effect and prevents a later resume or takeover.

The [operations reference](/docs/workflow-operations/#operating-a-run) covers commands, local execution, user-auth hosting, admission, revocation, and refused steps.

## Diagnostic inventory

Page mode bounds large inventory replies by row count, bytes, storage calls, and read duration.
The CLI or connector and manager must both support it. Release 0.88.0 and earlier do not.

A diagnostic scan can be incomplete. Its cursor and `endObserved` field cannot prove absence, retirement, ownership release, or worker inactivity.
See [inventory limits and continuation rules](/docs/workflow-operations/#diagnostic-inventory).

## Wire authority

The wire records separate immutable run pins, status, source, the append-only journal, answers, notices, migrations, admission, and revocation.
Takeover replays the journal before activation. `journalHigh` detects a truncated tail.

Driver, mediator, and one-shot operator credentials divide broker access.
The effect host still holds trusted authority, and injected host code shares its process.
See the [wire record and credential reference](/docs/workflow-runtime/#what-is-on-the-wire) for the limits of that separation.

## Available implementations

`@cotal-ai/lang` provides validation, execution, replay, simulation, and dry run without a broker when you supply an effect handler.
`@cotal-ai/core` implements the wire records and grants. `@cotal-ai/runtime` hosts those effects on the mesh.
The mesh handler implements the language's effects, including durable agent work and cancellation cleanup.

Driver-started runs use compiled language version `2` in a locked-down worker.
Recorded version-`1` runs continue on the tree-walker. Records do not switch engines during resume.
The compiled engine needs Node 22 or newer.

The [effect reference](/docs/workflow-effects/) describes ownership, supervision, budgets, asks, presence, turns, placement, and cancellation.
The [runtime reference](/docs/workflow-runtime/#engine-versions-and-payload-limits) covers payload limits and engine refusals.
