Skip to content

Stopping a turn ​

A turn can take a while: a long answer, a slow tool, a tool loop. Stop ends the turn that is running right now, from Studio, from a portal or embedded chat, or from your own code over the API. The model call stops, so spending stops with it, and what the agent had already written is kept.

Not yet released

Stopping a turn from the API, A2A, the Assistants API and portals ships with the next Console release. The Stop button in Studio's session composer ships with it too. These pages describe that release; earlier builds close the connection and let the turn finish in the background.

What a stop does ​

The answerWhat the agent had streamed so far is kept as its answer. Nothing is thrown away and nothing is invented to finish the sentence.
The stop reasonThe turn ends with stop_reason: cancelled (in a response, in the session events, in the transcript). Chat UIs show it as Stopped before it finished.
The model callThe request to the model provider is aborted, so it stops generating and stops billing.
UsageThe model usage row of the aborted call is recorded with status cancelled, not error. Tokens produced before the stop are still counted.
The sessionGoes back to Idle. You can send the next message at once.

Nothing running is not an error. A Stop that lands just as the turn ends is normal, so the API answers 200 with cancelled: false.

What happens to tools that are running ​

The stop reaches a tool while it is running when the tool is one of these:

ToolOn Stop
OpenAPI toolsThe upstream HTTP request is aborted.
MCP toolsThe upstream request is aborted.
Sandbox (exec, run_code)The call is aborted.
Built-in web searchThe call is aborted.

Every other tool is not interrupted: it finishes, its result is dropped, and the turn ends without calling the model again. A 10-second OpenAPI tool does not hold a stopped turn for 10 seconds; a tool that cannot be aborted may.

A side effect a tool has already caused (a record created, an email sent) is not undone by a stop.

In Studio ​

While an agent is answering, the composer's send button becomes Stop. Press it and the answer stays, marked as stopped. The same Stop is on a session that is answering in another window: a second tab or a reload follows the turn live, and Stop there ends it for everybody.

If the session is waiting for an approval or an answer, Stop cancels those open requests too, as if they were answered "no". See Approvals.

Portals and embedded chat ​

A portal and the embedded chat use the same Stop button. It ends the turn on the server, not just the page's connection.

Two rules apply:

  • Closing the page does not stop a turn. A closed connection leaves the turn running, which is intended: a person can reload and pick the answer up again.
  • Stop is for a turn that runs. A portal conversation that only waits for the person (an approval or a question is open, nothing runs) answers 409 with the code action_pending, and leaves the request exactly as it was. The chat turns that into Answer the request above, or send a new message.

Over the API ​

All the client-API routes take the same bearer token as the rest of /api/client/v1 and see only the token's project. A session of another project, or an unknown one, is 404 agent_run_not_found.

By session ​

bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/conversations/6f1c0a2b9d3e4f5a6b7c8d9e/cancel" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN"
json
{
  "conversation_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "cancelled": true,
  "run_ids": ["65f1c0a2b9d3e4f5a6b7c8d9"]
}

{conversationId} is the session id, the part of resp_{id} after the prefix.

By response id ​

Use the id the call already returned. For a streaming call that is the id in the first event.

bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/responses/resp_6f1c0a2b9d3e4f5a6b7c8d9e/cancel" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN"
json
{
  "id": "resp_6f1c0a2b9d3e4f5a6b7c8d9e",
  "conversation_id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "cancelled": true,
  "run_ids": ["65f1c0a2b9d3e4f5a6b7c8d9"]
}

POST /api/client/v1/responses/{id}/cancel is the same route under the Responses API path. The id is resp_{conversationId} or, for a background run, run_{runId}.

The result ​

FieldMeaning
cancelledtrue when a running turn was told to stop, or open approvals and questions were cancelled. false when there was nothing to stop.
run_idsThe runs that were told to stop. Empty when nothing ran.
conversation_idThe session.
idThe response id you asked about (response route only).

The route answers when the turn has been told to stop, not when it has ended. The request that is running the turn returns its own response a moment later, with stop_reason: cancelled and the partial answer. Read it from there, or from the session events.

Who may stop ​

Stopping a session is acting on it. The owner of a session may always stop it. Anybody else needs the agent.session_manage_others permission, otherwise the call is refused as for any other session action.

Ending an open approval is answering it "no", so it needs the right to answer approvals (agent.approve). Without that right the turn still stops, and the approval stays open, with cancelled: false if there was no running turn. See Access control.

Dashboard route (Studio) ​

Studio uses its own route, behind the dashboard sign-in and the agent's permissions:

http
POST /api/agents/{agentId}/sessions/{sessionId}/cancel

It answers 200 with { "cancelled": true, "runIds": ["…"], "cancelledActions": false } (camelCase, as the dashboard API does). It works for a turn started from anywhere: the playground, the API, a portal.

Background runs ​

A background run is stopped with its own route, or with either route above:

http
POST /api/client/v1/agents/runs/{runId}/cancel
  • A queued run is canceled at once and never starts.
  • A running run is canceled cooperatively. It ends with status: canceled, stop_reason: cancelled, the partial answer kept, and its callback is delivered.
  • A run waiting on an approval (requires_action) is ended by canceling it, which answers the open request "no". The caller needs the right to answer it, or the call is refused with 403 action_forbidden.
  • A run that already ended is 409 agent_run_already_terminal.

Assistants API ​

The legacy Assistants API runs inside one request, so the way to stop it is a second request on the same thread:

http
POST /api/client/v1/threads/{threadId}/runs/{runId}/cancel

It stops the turn that is running on the thread and answers 200 with a run object whose status is cancelling. The request that runs the turn then records the run as cancelled, with the partial answer kept in the thread. A thread with nothing running answers 404 Run not found. A run id of a run that has already been recorded answers 400: it ended already.

A2A ​

An A2A client stops a task with JSON-RPC tasks/cancel:

json
{ "jsonrpc": "2.0", "id": 7, "method": "tasks/cancel",
  "params": { "id": "task_6f1c0a2b9d3e4f5a6b7c8d9e_3" } }

The task id is the one the server handed out, task_{conversationId}_{messageIndex}. A running task answers:

json
{ "jsonrpc": "2.0", "id": 7,
  "result": { "kind": "task", "id": "task_6f1c0a2b9d3e4f5a6b7c8d9e_3",
              "contextId": "6f1c0a2b9d3e4f5a6b7c8d9e",
              "status": { "state": "canceled", "timestamp": "2026-10-14T09:00:00.000Z" } } }
ErrorWhen
-32001The task is not found.
-32004Nothing to cancel. The message says why: the task cannot be canceled: it is not running, or waiting for an approval or an answer, so there is no running turn to stop with the open ids in data.pending_action_ids.
-32010The caller may not answer the approval the task waits on (with a decision_id).

A JSON-RPC error is an HTTP 200, as everywhere in A2A.

Across nodes ​

Console may run on several servers behind a load balancer, and the Stop request does not have to reach the server that runs the turn. Every turn holds the session's single run slot in the database. A Stop writes a cancel request on it:

  • On the same server, the turn is aborted immediately.
  • On another server, that server notices the request within about a second and aborts the turn.

So a Stop works from any node, for a turn started from any door, with at most a second of delay when the two differ.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub