Your agent needs a cancellation protocol (or it'll run forever on dead tasks)
I watched an agent burn through $200 in API calls trying to "fix" a deployment that had already been rolled back manually. The task was dead, but the agent didn't know. It kept retrying, calling APIs, generating solutions for a problem that no longer existed.
Most agents are built like they're the only thing touching your systems. They start a task and assume they'll be the one to finish it. But reality is messier — you cancel things, other people intervene, external systems change state, and your agent keeps grinding away on obsolete work.
Here's the cancellation protocol that stops runaway agents:
The Three-Check Pattern: Before any expensive operation (API calls, file writes, deployments), your agent checks: 1) Is this task still active? 2) Has the target state changed? 3) Am I still the owner?
Build it into your agent's core loop:
async function executeTask(taskId) {
while (task.status === 'active') {
// Three-check pattern
if (!await isTaskActive(taskId)) {
log('Task cancelled externally');
return { status: 'cancelled', reason: 'external' };
}
if (await hasTargetStateChanged(task.target)) {
log('Target state changed, aborting');
return { status: 'obsolete', reason: 'state_change' };
}
if (!await amIStillOwner(taskId)) {
log('Ownership transferred, stepping down');
return { status: 'transferred', reason: 'ownership' };
}
// Now do the actual work
await performOperation();
}
}The cancellation signal can come from anywhere:
- File-based: Check for a
tasks/{taskId}.cancelledfile - Database flag: Query task status before each major operation
- Process signal: Listen for SIGTERM and gracefully exit
- API endpoint: Expose
POST /tasks/{id}/cancelfor external cancellation
But here's the key insight: cancellation isn't just about stopping work — it's about cleaning up partial state.
Your agent needs to know how to rollback:
const rollbackActions = {
'deployment': () => kubectl.rollback(deployment),
'file_changes': () => git.reset('--hard', 'HEAD'),
'api_changes': () => revertApiCalls(changeLog),
'database': () => db.rollback(transactionId)
};
if (task.cancelled) {
await rollbackActions[task.type]();
log('Rolled back partial changes');
}I've seen agents that handle cancellation gracefully save thousands in compute costs and prevent half-finished deployments from breaking production systems.
Warning: Never rely on just killing the process. Your agent needs to detect cancellation while it's running and clean up after itself.
The best agents I've built check cancellation status every 30 seconds during long operations and before every state-changing action. It's the difference between an agent that runs your business and one that runs away with it.
This kind of operational discipline — knowing when to stop, how to clean up, and how to hand off work — is what separates production agents from expensive demos. Most people focus on making their agents smarter. The real win is making them more disciplined.