Engineering · · 4 min read
Stopping an agent does not automatically undo its last tool call
Separate stopping new work, canceling external operations, reconciling accepted writes, and compensating completed effects.
A user presses stop while an agent is creating a report and sending a notification. The chat stops immediately, and a notification arrives afterward. The user asks why the agent ignored the stop request, and the service owner points to a canceled conversation.
Both are accurate. Stopping generation, stopping tool dispatch, canceling a provider operation, and undoing a completed side effect are four distinct controls. A public MCP validator discussion asks what cancellation behavior a real integration should test beyond discovery.
Define what the stop button promises#
The application at minimum:
- Stops scheduling new work for the canceled task.
- Propagates cancellation to active components whose interfaces support it.
- Accounts for external operations already accepted.
For the protocol revision discussed here, the MCP cancellation specification describes cancellation notifications and race conditions. A cancellation notification rolls back nothing in the downstream business system.
Make the user-facing state precise: "Stopped" means no more agent work is dispatched, and a separate detail shows whether an external job is still active, was canceled, completed before cancellation, or has an unknown outcome.
Follow the notification race#
Take a task that generates a report, uploads it, then notifies a channel. The user stops after upload, just as notification submission begins.
- If the application has not submitted the notification, it prevents the dispatch.
- If the provider already accepted it, the application reconciles the result.
- If the message was delivered, deleting it is a separate action with its own permissions and consequences.
Do not report "nothing was sent" because the local request was aborted, since the provider can accept the write before the client disconnects. The unknown write outcome guide describes how to resolve that ambiguity without creating another effect.
| Stop arrives at this point | Expected treatment |
|---|---|
| Before tool dispatch | Do not send the request |
| During a cancellable read | Abort waiting and release resources |
| After provider job acceptance | Request cancellation if permitted and track result |
| During an ambiguous write | Reconcile the original operation |
| After completed side effect | Preserve the result; evaluate any compensating action separately |
Propagate cancellation through the scheduler#
A common gap is a queued retry: the active request stops, and a timer later submits another attempt. Cancellation has to invalidate future attempts for the logical task, including SDK retries, queue redelivery, and child-agent work that no longer serves the request.
Use a task generation or cancellation state that dispatch checks immediately before sending. That check complements propagation to active requests, and neither alone covers every race. Keep operation identity stable so a late response attaches to the canceled task.
Keep late success responses, since they can be the only evidence a write occurred. Store the result and update the task's external-effect status without restarting the workflow.
Let unrelated work continue#
Scope cancellation to the user's task and the operations it owns, because closing a shared connector session or a whole worker pool interrupts other users' work.
For delegated work, record parent and child relationships and cancel children that exclusively serve the stopped task. If a shared background refresh also serves independent requests, apply its own ownership rules and do not let the parent terminate it. Keep task identity separate from connection identity, since one connection carries many tasks.
Test both sides of the race#
Place controlled barriers around dispatch, provider acceptance, and response delivery, and cancel at each point. Verify that no new work starts after cancellation and that accepted operations stay discoverable for reconciliation.
Include a late success, a failed cancellation request, a queued retry, and an independent task on the same connector, which proves that stopping one user does not interrupt another.
Document any provider operation that cannot be canceled once accepted. Show that limitation at the relevant moment with the operation status and recovery path, not as a blanket warning on every action.
Deixic's pause guidance and action lifecycle explain how to pause an agent and read action states for routed work.