Skip to Content
OperatingPause, Resume, Cancel

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/resume

What 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.sqlite3

Then 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 own control.observed and run.paused / run.cancelled events 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 calling resume: a 409 tells you the run is not currently paused (it may still be running toward your cancel, or it may already be done), and a 200 with events tells you it was paused and your resume just played it. Calling resume to 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.

Last updated on