Cancel a run
This guide stops a run that's running or waiting, along with the agent turns inside it.
Before you start
Section titled “Before you start”- A run that's running or waiting, and its id.
kindgi runs listlists runs, as List runs shows. kindgi devrunning in your pack's directory, with Kindgi 0.1's CLI.- For the app's half: Node 22.12 or later, Python 3.11 or later, or
Java 17 or later, with Kindgi 0.1's client, and
KINDGI_API_URLandKINDGI_API_TOKENset.
Cancel it
Section titled “Cancel it”A cancel stops the run wherever it is, and ends it as cancelled.
pnpm exec kindgi runs cancel <run-id>uv run kindgi runs cancel <run-id>./kindgiw runs cancel <run-id>./kindgiw runs cancel <run-id><run-id> is the run's id. It prints:
{ "ok": true, "runId": "265627c7-1ac7-4d18-a926-6da9fcc18577"}The run's status is now cancelled, and its journal ends with
run.cancelled:
{"sequence": 6, "kind": "wait.suspended", "nodeId": "hold", "payload": {"nodeId": "hold", "tokenId": "child:d8c2c7dc-7ceb-4e4b-8986-ee75e7c9bcc2:25"}, …}{"sequence": 7, "kind": "run.cancelled", "payload": {}, …}That run was waiting for an approval. A run can be cancelled while it runs too, and cancelling a flow's run cancels the agent turns inside it. What a cancel stops has the details.
A run that already ended can't be cancelled:
Error [conflict]: Cannot cancel run: already cancelledWhat a cancel stops
Section titled “What a cancel stops”A cancel takes effect at once, wherever the run is:
- No step starts after it. A flow between two steps, or in the middle of
a loop, starts nothing more, and its journal records no start after
run.cancelled. - A step running when it lands stops. An agent turn stops waiting for its
model and ends
cancelled. A start that was waiting for the turn (wait: true) answers within seconds with422 agent-turn-aborted, "Agent turn cancelled". - A step that ends as it lands keeps its end. Its
step.completedcomes afterrun.cancelledin the journal: it did finish.
Here a flow running a loop over 2,000 orders, each answered by an agent, was cancelled twice. The first time, the turn for order A-1012 was running: the cancel stopped it, and the journal ends at the cancel:
{"sequence": 77, "kind": "step.started", "nodeId": "answer", "payload": {"input": "A-1012", …}, …}{"sequence": 78, "kind": "run.cancelled", "payload": {}, …}The second time, the turn for A-1030 finished as the cancel landed. Its end
follows run.cancelled, and nothing starts after it:
{"sequence": 185, "kind": "step.started", "nodeId": "answer", "payload": {"input": "A-1030", …}, …}{"sequence": 186, "kind": "run.cancelled", "payload": {}, …}{"sequence": 187, "kind": "step.completed", "nodeId": "answer", "payload": {"output": {"text": "You said: \"A-1030\"", …}, …}, …}{"sequence": 188, "kind": "edge.evaluated", "payload": {"to": "$end", "from": "answer", "edgeId": "b1", "decision": true, …}, …}{"sequence": 189, "kind": "iteration.completed", "nodeId": "each", "payload": {…, "iteration": 30, …}, …}A step that was running when the run was cancelled, such as the loop itself
or A-1012's step, has no end of its own: run.cancelled ends it, as it ends
a run waiting for an approval.
An agent turn started with wait: true, and cancelled while its model call
ran, answers the waiting start with:
{ "error": { "code": "agent-turn-aborted", "message": "Agent turn cancelled", "details": { "reason": "external", "runId": "8619fb26-6370-48df-b9bc-01bcd91602b6" }, "requestId": "req-fb51fd31-fe44-4a44-ad97-88baa95a23e4" }}From your app
Section titled “From your app”Your app cancels a run with runs.cancel. Ask your coding agent: "I want my
app to cancel a run it started." For reference, this code starts a run in the
background, then cancels it:
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN
const run = await kindgi.runs.start({ flow: 'acme.reserve-order', input: { orderId: 'A-500' }, options: { wait: false },});const cancelled = await kindgi.runs.cancel(run.id);console.log(cancelled.status);cancelledfrom kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN
run = kindgi.runs.start( flow="acme.reserve-order", input={"orderId": "A-500"}, options={"wait": False})cancelled = kindgi.runs.cancel(run.id)print(cancelled.status)cancelledpackage com.acme.app;
import com.kindgi.client.Kindgi;import com.kindgi.client.models.Run;import com.kindgi.client.models.StartRunBody;import java.util.Map;
public final class CancelRun { public static void main(String[] args) { Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN
Run run = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow("acme.reserve-order") .input(Map.of("orderId", "A-500")) .options(StartRunBody.WithFlow.Options.builder().wait_(false).build()) .build()); Run cancelled = kindgi.runs().cancel(run.id()); System.out.println(cancelled.status()); }}cancelledpackage acme.app
import com.kindgi.client.Kindgiimport com.kindgi.client.models.StartRunBodyimport scala.jdk.CollectionConverters._
object CancelRun { def main(args: Array[String]): Unit = { val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN
val run = kindgi.runs().start(StartRunBody.WithFlow.builder() .flow("acme.reserve-order") .input(Map("orderId" -> "A-500").asJava) .options(StartRunBody.WithFlow.Options.builder().wait_(false).build()) .build()) val cancelled = kindgi.runs().cancel(run.id) println(cancelled.status) }}cancelledcancel returns the run, now cancelled. It answers with a conflict error
when the run had already ended.
Approvals of a cancelled run
Section titled “Approvals of a cancelled run”Cancelling a run withdraws the approvals it was waiting for, in the same
step. So does it for the approvals of the runs inside it, such as a flow's
agent steps. They leave the pending list as withdrawn, with
withdrawnBecause: "run-cancelled". A reviewer who opens one can no longer
decide it: it answers 409 approval-terminal.
An approval the run asked for without waiting on it stays pending, and can still be decided. A project-wide memory fact's review is one, and a memory erasure's is another.
To see them before you cancel, list the run's approvals with the runs inside
it: approvals.list({ runId, includeDescendants: true }) in TypeScript,
approvals.list(run_id=…, include_descendants=True) in Python.
Decide an approval shows how reviewers decide them.
In the console
Section titled “In the console”A run's page has Cancel run while it's going, for project editors and up. Its dialog says what a cancel does before anything happens:
Cancel this run?This stops the run and every step still going inside it. It ends as cancelled, and can't be continued.- A run waiting for an approval adds: "If it's waiting for an approval, that approval is withdrawn: it leaves the reviewers' list." See Approvals of a cancelled run.
- Keep it running closes the dialog. Cancel the run cancels it, and the dialog says "Cancelled.".
- A run that finished meanwhile: "It had already finished: there was nothing to stop."

After a cancel
Section titled “After a cancel”The run's page says Cancelled, and so does each step that was still going when the cancel landed: a loop's node, and the round that was running. Those steps have no end of their own in the journal, as What a cancel stops explains. So the Overview shows each with a plain circle and "Cancelled", not as still running. A step that finished as the cancel landed keeps its end.
Here a loop over six orders was cancelled while the turn for A-1002 ran:

The Journal tab says so beside each of those starts. Its timeline for that run, without the times:
The run startedStarted: step "each" Cancelled with the run: no end of its ownDone: step "answer" 1.7 sDone: step "answer" 1.6 sStarted: step "answer" Cancelled with the run: no end of its ownThe run was cancelledA failed run shows its unfinished steps the same way: "Stopped: the run failed" on the Overview, and "Stopped when the run failed: no end of its own" in the Journal.