Running and observing
Fill the run form, dry-run a plan, watch generations stream live, handle approval gates, and track every run across the workspace.
Running a Loop is the arrive-and-use moment: supply its declared inputs, optionally dry-run the plan, then watch the run stream live until it reaches one named outcome.
The run form
The run form is generated from the Loop's declared inputs
— a typed field per input, each with a type badge, a required marker, and inline validation. An
agent input renders a picker; a boolean renders a switch. A sticky preview beside the form shows
"what will run" — the contract, the verification rows, the seven terminal chips, and a lifecycle line
— and recomputes on every change. After a successful dry-run, the preview switches to the returned
materialized_contract, so input-backed goal text is exactly what the runtime will use.
Two actions submit the form:
- Start run — start the run. Disabled until required inputs are filled.
- Dry run — validate the inputs and render the first generation's plan without starting a run. It spends no budget and creates no run row. See authoring.
Close lives in the top bar and leaves the form without submitting it. A folded Limits · this run only section exposes six per-run limit overrides — iteration cap, token budget, wall-clock budget, no-progress window, fan-out ceiling, and gate max revisions — plus the budget-exceeded policy. Each numeric field shows the per-Loop default and the hard daemon ceiling, and clamps at the ceiling. The summary changes from "loop defaults" to "overrides set" only when a value actually differs.
The live run page
The run page is the truthful, real-time view of one execution. It streams over SSE and never renders a control the runtime does not support.
A sticky contract header carries the live status pill (pulsing only while running or
watching), the generation and attempt count, the run's start metadata, the goal, and the valid
Pause, Resume, or Cancel run action. Kill run is the destructive escape in the
overflow menu and appears only while the run is live. The Progress story reads the run's
materialized_contract; Inspect keeps the raw executed_definition available for authoring
and audit questions.
Five meters track spend against limits — attempts, tokens, wall clock, cost, and breadth (fan-out). Bars warn-tint only near a ceiling. The cost meter is derived (tokens × price) and never a cap.
A generation timeline shows each generation as a collapsible card on a flat node spine. A
fan-out renders as batch branches; a gate renders as a flat verdict card (pass tinted success,
fail tinted danger) with its reason and route (revise or next_generation). When a Loop drives
a channel conversation, the converse exchange is embedded
inline. A right rail streams the live event feed and the run facts.
Each generation also shows its durable origin and parent_generation. Most generations point to
N-1; a ratchet_restore points to the older best generation it was seeded from. Scored verdicts
show score, and the accepted baseline carries the Best marker. The run header keeps
best_generation and best_score separate from the current generation, so a later regression or
an exhausted run does not make the last attempt look like the winner.
Approval gates
A human gate parks the run in needs-approval — a live pause, never a terminal outcome. The
page shows the decision context (for a merge gate: the branch, diff, test result, and verifier) and
three actions:
- Approve → the run resumes.
- Request changes → the run revises and continues into the next generation.
- Reject → the run halts on a
blockedterminal outcome.
Pause, resume, cancel, and kill
- Pause is a generation-boundary operation, not a mid-node kill. The daemon records a pause
intent; the in-flight generation's already-claimed nodes finish; the status flips
running → pausedonly at the boundary. Pause is hidden or disabled outsiderunning. - Resume continues a paused (or pause-requested) run.
- Cancel run cooperatively moves through request, delivery, and drain before ending
canceledwith causeoperator_cancel. The request is durable: boot recovery and scheduler sweeps retry interrupted delivery until the active worker acknowledges it or its session is already absent, then the run drains. If a canceled coordinator task belongs to a nonterminal Loop, recovery reopens that task and reserves one replacement coordinator run. - Kill run immediately fences stale work and ends
canceledwith causeoperator_kill. It is destructive and never happens as a hidden escalation from Cancel.
There is no stop alias. Cancel and Kill are idempotent: retrying the same accepted request returns
the committed winner instead of starting a second cancellation path.
Node controls and inventories
The run page offers only the verbs valid for a node's current state: Pause, Resume,
Cancel, Kill, and Requeue. Pause supports drain (the default) or cancel; Resume
supports plain, reset_attempts, or immediate, and can carry a JSON payload for a manual wait.
Requeue is available only for quarantined work and creates a bounded successor generation.
Use the workspace inventories to find durable work before acting:
| State | What it finds |
|---|---|
waiting | Durable waits and approvals that are parked. |
quarantined | Attempts removed from dispatch until an explicit requeue. |
attention | Silence, exhausted resume, or quarantined-dependency attention. |
retrying | Attempts with a durable next_attempt_at. |
compozy loop nodes --state <state> is workspace-scoped, cursor-paginated, defaults to 50 rows,
and caps at 200. Filter with --loop or --run-id; structured rows carry the exact run,
generation, node, and item identity needed by the node verbs. See
Failure handling for the command forms.
Runs across the workspace
The Runs view is a first-class, workspace-wide list of every execution across every Loop. A KPI
strip surfaces "Active now," "Awaiting you" (the needs-approval queue), "Done today," and "Needs a
look." An outcome filter spans the full status spectrum, rendering a segment for each status present
in the window, plus Loop and date filters. Active and Past runs list in tables with an outcome pill,
the Loop and run id, the goal, the generation count, timing, and a budget mini-bar.
Status detail versus run summaries
Use the detail surface when you need provenance or gate decisions. List surfaces stay deliberately small.
| Surface | Shape |
|---|---|
compozy loop status / compozy__loop_status | The detail includes raw executed_definition plus input-resolved materialized_contract; run includes optional best_generation/best_score; generations[] includes parent_generation, origin, verdicts[], and outputs. |
compozy loop runs / compozy__loop_runs | Each run summary includes optional best_generation/best_score; it never embeds generation history. |
| Loop catalog list | last_run includes optional best_generation/best_score; it never embeds generation history. |
The best fields are omitted until an approved finite score establishes a baseline. A detailed JSON response can therefore look like this:
{
"run": {
"generation": 3,
"best_generation": 1,
"best_score": 0.82
},
"generations": [
{
"generation": 3,
"parent_generation": 1,
"origin": "ratchet_restore",
"verdicts": [{ "gate_id": "quality", "outcome": "rejected", "score": 0.61 }]
}
]
}Origins use the closed runtime vocabulary: initial, stop_when, reattempt, gate_revise,
gate_next_generation, dod_retry, ratchet_restore, and requeue. See
Ratchets for how a scored best changes succession.
Every surface, not just the UI
| Action | CLI | HTTP | Native tool |
|---|---|---|---|
| Start / dry-run | compozy loop run [--dry-run] | POST /loops/:name/run[?dry=true] | compozy__loop_run |
| Run status | compozy loop status | GET /loop-runs/:id | compozy__loop_status |
| List runs | compozy loop runs | GET /loop-runs | compozy__loop_runs |
| Pause / resume | compozy loop pause · compozy loop resume | POST /loop-runs/:id/pause · /resume | compozy__loop_pause · compozy__loop_resume |
| Approve | compozy loop approve --gate-id … --decision … | POST /loop-runs/:id/approve | compozy__loop_approve |
| Cancel / kill | compozy loop cancel · compozy loop kill | POST /loop-runs/:id/cancel · /kill | compozy__loop_cancel · compozy__loop_kill |
| List node state | compozy loop nodes --state … | GET /loop-nodes?state=… | compozy__loop_nodes |
| Pause node | compozy loop node pause | POST /loop-runs/:id/nodes/:node/pause | compozy__loop_node_pause |
| Resume node | compozy loop node resume | POST /loop-runs/:id/nodes/:node/resume | compozy__loop_node_resume |
| Cancel node | compozy loop node cancel | POST /loop-runs/:id/nodes/:node/cancel | compozy__loop_node_cancel |
| Kill node | compozy loop node kill | POST /loop-runs/:id/nodes/:node/kill | compozy__loop_node_kill |
| Requeue node | compozy loop node requeue | POST /loop-runs/:id/nodes/:node/requeue | compozy__loop_node_requeue |
Live events stream from GET /loop-runs/:id/events (SSE). HTTP routes are workspace-scoped under
/api/workspaces/{workspace_id}/…; the Loops API reference lists
the exact paths and payloads. See also the compozy loop CLI.