← Back to Blog

The Kill Switch Problem: How to Stop an AI Agent Mid-Run

You're watching an AI agent run a migration. It's two minutes in. Something looks wrong — the agent is touching tables it shouldn't be touching. You need to stop it. Now.

What do you do?

If your answer is "kill the process and pray," you don't have a kill switch. You have a prayer. This post is about building real stop mechanisms before the moment you need them.

Why AI Agents Are Hard to Stop

Traditional programs are easy to interrupt. Hit Ctrl+C, send SIGTERM, close the socket — done. AI agents are different for a few reasons:

The coordination problem: killing an AI agent cleanly requires stopping the LLM loop, halting in-flight tool calls, draining or abandoning any queue, and cleaning up side effects — in the right order. None of this happens automatically.

The Three Layers of a Kill Switch

A real stop mechanism operates at three layers: the agent loop, the execution surface, and the approval gate.

Layer 1: The Agent Loop

The LLM runs in a loop: observe → plan → act → observe. You need a way to break this loop cleanly. The simplest approach is a global run flag:

// Pseudo-code: agent loop with stop flag
loop {
    if STOP_FLAG.load(Ordering::Relaxed) {
        log::warn!("Agent stopped by operator");
        cleanup_in_progress_tasks().await;
        break;
    }

    let next_action = model.plan(&context).await?;

    if STOP_FLAG.load(Ordering::Relaxed) {
        // Check again before executing — model may have taken time to respond
        break;
    }

    execute(next_action).await?;
}

The flag check needs to happen at every loop boundary: before planning, and before executing. An agent that checks once at startup is not stoppable.

Layer 2: The Execution Surface

Even with the loop stopped, the agent may have in-flight tool calls. These need their own stop paths:

Tool Type Stop Mechanism Risk If Not Stopped
Shell commands SIGTERM to process group File writes continue, scripts complete
DB queries Close connection or call pg_cancel_backend() Long-running UPDATE/DELETE finishes
HTTP requests Cancel request context Remote side-effect already triggered
File operations Close file handles, don't rename temp files Partial file left on disk
Background jobs Queue drain + job status check Workers continue processing

The key insight: stopping the agent is not the same as stopping what the agent started. Shell commands, subprocesses, and external API calls have their own lifecycle.

Layer 3: The Approval Gate

If commands require human approval before execution, you get a natural kill switch for free. An agent waiting for approval can be stopped simply by denying the pending command — or by revoking the session token.

This is the most reliable kill switch because it prevents execution rather than interrupting it. A command that hasn't run yet has no side effects to clean up.

expacti's approach: every command queued by an agent goes through the approval queue. The reviewer can deny individual commands or terminate the entire session. No command executes until it's explicitly approved — which means stopping mid-run is as simple as not approving the next step.

Stop vs Pause vs Abort

These three operations are often conflated, but they have very different semantics:

Operation What It Does When to Use
Pause Halt the loop at the next boundary; preserve state; resumable Need to review what's happening; unsure
Stop Halt the loop; clean up in-progress work; not resumable Task completed or no longer needed
Abort Immediate halt; rollback if possible; may leave partial state Something is clearly wrong; time-critical

Most systems only implement abort (kill the process). This is fine when nothing is wrong. When something is wrong, it leaves a mess. Design for pause and stop first; abort is the last resort.

Designing a Practical Kill Switch

1. Revocable Session Tokens

Every agent session should authenticate with a short-lived, revocable token. When you revoke the token, all in-flight requests with that token get rejected immediately — even if the agent loop is still running.

# Stop an agent by revoking its session token
expacti sessions revoke --session-id ses_abc123

# The agent's next command attempt will get 401 Unauthorized
# and the loop will terminate cleanly

2. Session-Scoped Permissions

An agent session should only be able to do what it was given permission to do for that session. Revoking the session removes all permissions instantly, without needing to hunt down individual API keys or credentials.

3. Atomic Command Boundaries

Design agent tasks so each command is a discrete, observable unit. Instead of:

# One big command (uninterruptible)
./migrate-all.sh && ./reindex.sh && ./verify.sh && ./notify.sh

Break it up:

# Four separate commands (each requires approval)
./migrate-all.sh
./reindex.sh
./verify.sh
./notify.sh

With atomic boundaries, you can stop after any step. The "run everything as one command" pattern is also an approval-evasion pattern — watch for it.

4. Idempotent Cleanup

Whatever cleanup you run after a stop should be safe to run multiple times. If the agent dropped a temp file, the cleanup script should handle "file doesn't exist" without failing. If the agent started a transaction, the cleanup should roll it back if it's still open — and be a no-op if it already committed.

5. The Panic Button in the UI

If you have a reviewer interface, the panic button should be prominent and fast. It should:

Don't gate the panic button on confirmation. The UX cost of a false positive (accidentally stopping an agent) is much lower than the cost of a delayed stop when something is going wrong. Make it one click.

What Happens After You Stop

Stopping an agent mid-run usually leaves some state to clean up. The audit log is your friend here. Everything the agent did before you stopped it should be logged with timestamps, so you can reconstruct exactly where it was in its task when you pulled the plug.

The recovery checklist:

  1. Check the audit log — what did the agent do? What was the last approved command?
  2. Check for partial writes — any temp files, open transactions, half-migrated tables?
  3. Check running processes — did the agent start any subprocesses that are still running?
  4. Check external state — did the agent call any external APIs that may have triggered side effects?
  5. Decide: roll back or continue manually? With a good audit trail, you can often pick up where the agent left off rather than rolling back everything.

The "Human Override" Contract

Any system that gives an AI agent execution access should have a documented human override procedure. This isn't just good practice — it's increasingly a compliance requirement. SOC 2 CC7.5 (incident response), ISO 27001 A.16 (incident management), and NIST CSF DE.CM all expect you to be able to detect and respond to anomalous automated behavior.

"We killed the process" is not a documented override procedure. It doesn't answer:

A real kill switch produces answers to all of these as a natural side effect of the stop procedure.

The Broader Point

The kill switch isn't just an emergency feature. It's evidence that you designed the system with controllability in mind from the start. An agent that can be stopped cleanly is an agent that was built with clear execution boundaries, observable state, and explicit authorization at each step.

If you can't stop it, you don't control it. And if you don't control it, you can't be responsible for what it does.

Expacti gives you session-level kill switches built in.
Every agent session is revocable. Every pending command can be denied. Every action is logged. When something goes wrong, you're one click from a clean stop.
See the demo