Pause, Resume, Cancel
Anyone who knows a run’s run_id can ask it to pause, resume, or cancel over HTTP — from a
dashboard, a curl command, or a second terminal. See Run Control for
what a safe point is and what a resume replays; this page is the operator’s side: which
endpoint to call, what it hands back, and what to do when a run does not seem to be stopping.
curl -X POST http://localhost:8000/runs/$RUN_ID/pause -d '{"reason": "operator stepped away"}'
curl -X POST http://localhost:8000/runs/$RUN_ID/cancel -d '{"reason": "user closed the tab"}'
curl -X POST http://localhost:8000/runs/$RUN_ID/resumeWhat each call hands back
pause and cancel both answer {"run_id", "verb", "recorded": true} the moment the request
is written down — not when the run stops. resume answers {"run_id", "status", "events"}
with the count of events the continuation produced, or 409 if the run was not paused: still
running, already finished, already cancelled, or picked up by another caller first. None of
those three cases is an error worth retrying differently; they are all “there was nothing here
to resume.”
pause or cancel on a run that already ended is accepted and does nothing, so a double
click is harmless.
Both endpoints also have a 503 — “run control is unavailable: no control backend is
configured” — in their code path, but it does not happen behind a normally-started
agentdeck-serve (or any Deck): resolve_control_port() always wires a real ControlPort,
memory:// by default, and refuses to open at all if AGENTDECK_CONTROL’s scheme names
anything it doesn’t recognize, which surfaces at startup rather than on a pause/cancel
request. That 503 is reachable only from an embedder who builds a Runtime directly with no
ControlPort, bypassing Deck and agentdeck-serve entirely — not a state a deployment
following this page can reach.
Reaching a run in another process
The signal has to land somewhere the run’s own loop is reading from, and the default is
in-process memory: fine for a single worker talking to itself, invisible to anyone else. With
that default, a second web worker and the agentdeck runs signal CLI below cannot reach a run
at all — not because the run rejected the signal, but because they wrote it to a different
process’s memory. This is the shape of bug report that looks like “cancel does nothing” and is
actually “the API server has three workers and you signaled the wrong one.”
Point the control backend at a shared file to fix that:
AGENTDECK_CONTROL=sqlite://./.agentdeck/control.sqlite3Then a second terminal can reach the same run by id alone:
agentdeck runs signal <run_id> cancel --control-db ./.agentdeck/control.sqlite3 --reason "typo"SQLite’s cross-process story rests on shared memory, so this covers one file behind more than one process on the same machine, not one file behind more than one machine.
A paused run’s resume is never a signal, even with the file backend: continuing a run needs
its event log, so lifting a pause belongs to a process holding a Runtime —
POST /runs/{run_id}/resume or deck.resume(run_id) — not to the CLI above.
Watching for the effect
recorded: true is not “stopped.” The question an operator actually has after pausing or
cancelling a run is simple to ask and, today, has no single clean answer: there is no
endpoint that reports a run’s current status by run_id alone. The only ways to learn
whether a pause or cancel landed are the ones already open to you:
- If you (or your caller) are the one holding the streaming response — the SSE connection from
POST /agents/{name}/chat?stream=true— the run’s owncontrol.observedandrun.paused/run.cancelledevents arrive on that stream, in that order, and are the authoritative answer. - If you are not holding that stream — you signaled from a second terminal, or a dashboard
that only has the
run_id— the only feedback available is callingresume: a409tells you the run is not currently paused (it may still be running toward your cancel, or it may already be done), and a200with events tells you it was paused and your resume just played it. Callingresumeto check status has a side effect — it lifts a pending pause — so only do this if lifting the pause is what you actually want next.
There is no way today to poll “is run X paused yet” without either of those two paths. If your
pause or cancel is taking longer than the bound Run Control describes,
the run is not stuck — it is either still finishing the tool call it was in, or nobody has
touched it since it paused. A paused run that nobody resumes holds its session open until
AGENTDECK_RUNTIME_STALE_RUN_AFTER_SECONDS takes it over; it is not consuming a live loop in
the meantime, so there is nothing to cancel out of urgency beyond recording the cancel itself.