You click Stop.
The spinner disappears. The input unlocks. You give the agent a different task.
Then the old tool finishes, and its result lands in the new conversation.
Or worse: the old tool sends the message you thought you had stopped.
These are different failures, and they need different fixes. One is about which run owns a result. The other is about whether an external system actually stopped doing work.
A single cancelled: true field hides that distinction surprisingly well.
Let's build a tiny TypeScript example that separates them. It stops waiting locally, rejects obsolete results, and deliberately proves that an uncooperative tool can still perform a side effect.
The interesting test is the one where the external write still happens.
Runnable code: https://github.com/bobbyhalljr/tiny-agent-cancellation-gate
1. There are three acknowledgments hiding behind Stop
Consider a hypothetical agent that starts a document export.…