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 answer | What 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 reason | The 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 call | The request to the model provider is aborted, so it stops generating and stops billing. |
| Usage | The model usage row of the aborted call is recorded with status cancelled, not error. Tokens produced before the stop are still counted. |
| The session | Goes 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:
| Tool | On Stop |
|---|---|
| OpenAPI tools | The upstream HTTP request is aborted. |
| MCP tools | The upstream request is aborted. |
Sandbox (exec, run_code) | The call is aborted. |
| Built-in web search | The 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
409with the codeaction_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
curl -X POST "$CONSOLE_URL/api/client/v1/agents/conversations/6f1c0a2b9d3e4f5a6b7c8d9e/cancel" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN"{
"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.
curl -X POST "$CONSOLE_URL/api/client/v1/agents/responses/resp_6f1c0a2b9d3e4f5a6b7c8d9e/cancel" \
-H "Authorization: Bearer $COGNIPEER_API_TOKEN"{
"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
| Field | Meaning |
|---|---|
cancelled | true when a running turn was told to stop, or open approvals and questions were cancelled. false when there was nothing to stop. |
run_ids | The runs that were told to stop. Empty when nothing ran. |
conversation_id | The session. |
id | The 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:
POST /api/agents/{agentId}/sessions/{sessionId}/cancelIt 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:
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 with403 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:
POST /api/client/v1/threads/{threadId}/runs/{runId}/cancelIt 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:
{ "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:
{ "jsonrpc": "2.0", "id": 7,
"result": { "kind": "task", "id": "task_6f1c0a2b9d3e4f5a6b7c8d9e_3",
"contextId": "6f1c0a2b9d3e4f5a6b7c8d9e",
"status": { "state": "canceled", "timestamp": "2026-10-14T09:00:00.000Z" } } }| Error | When |
|---|---|
-32001 | The task is not found. |
-32004 | Nothing 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. |
-32010 | The 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.

