What a runaway AI agent looks like
A runaway agent keeps spending time, tokens or money without getting closer to a finished result. It rarely crashes, which is what makes it expensive: from the outside it looks busy. The common patterns are worth knowing because each one leaves a different trace.
- Tool-call loops. The model calls the same tool with the same or nearly the same arguments, reads a result it does not accept, and calls it again. A search that keeps returning nothing useful is a typical example.
- Retry storms. A failing API or a rate limit meets retry logic at several layers (the HTTP client, the provider SDK, the framework and the agent’s own plan), and each layer multiplies the others.
- Planning loops. The agent keeps revising its plan, reflecting on its last answer, or handing work to a sub-agent that hands it back, and never reaches a step that produces output.
- Processes left behind. The agent has exited, or you stopped it, but a shell it spawned, a headless browser or a local MCP server is still running and sometimes still calling out.
A growing context makes the first three worse over time, because every repeated turn resends a longer history and costs more than the one before it.
Signals that catch a runaway run early
You do not need a model to judge whether an agent is stuck. A few plain measurements, compared with what a healthy run of the same agent normally does, show a loop while it is still cheap.
- Runtime against the usual range. If a nightly job normally takes 12 minutes, a run still going at 40 deserves a look (the numbers are an example).
- Calls per minute. A sudden rise in model or tool calls per minute is the clearest sign of a loop, especially when the latency of each call stays flat.
- Cost rate as well as cost. Spend per minute shows a runaway while it is happening; a total only tells you afterwards.
- No progress between steps. Repeated tool calls with identical arguments, the same step name appearing again and again, and no new files or records written all point to a loop.
Alerts on these signals tell a person that something is wrong. They do not stop anything, which is why you also need a way to end the run.
The three layers of an AI agent kill switch
A dependable stop is usually three mechanisms, because each one covers a gap in the others.
- Limits that trigger on their own: a maximum runtime, spend, token count or number of model calls per run. They work at 3 a.m. with nobody watching, and they belong outside the agent’s own code so a bug in the agent cannot skip them.
- A manual stop someone can press from anywhere: a dashboard button or a command that reaches the running process without anyone logging in to the machine it runs on.
- Cooperative cancellation: checks at safe points in the agent’s code where it asks whether it should continue, and finishes cleanly if not.
Limits and the manual stop are enforcement: they end the run whether or not the agent agrees. Cooperative cancellation depends on the agent reaching the check. Use it to stop cleanly, and rely on the other two to stop at all.
Why a real stop has to end the whole process tree
Agents start other programs. A coding agent runs shell commands and test runners, a research agent opens a headless browser, and MCP clients launch each local MCP server as a child process, often through a wrapper such as npx or uvx. Killing only the top-level process can leave those children running, still holding API keys and open connections.
A correct stop does three things in order. It sends a polite terminate to the process and all of its descendants, so each can flush logs, close files and release locks. It waits a short grace period. Then it hard-kills whatever is still running, including anything that started while it waited.
On Linux, GNU timeout gives you a basic version of this for a single command. It runs the command in its own process group and signals the whole group, so ordinary child processes are included:
timeout --kill-after=10s 45m python agent.pyA child that moves itself into a new session or process group escapes this, and it gives you no manual stop or spend limit. It is a reasonable floor for a cron job, not a full answer.
Cooperative cancellation at safe points in your code
Inside the agent, pick the points where stopping leaves nothing half done: between plan steps, after a tool result is saved, before the next model call. At each one, check a cancellation flag and leave through your normal cleanup path if it is set. Python’s asyncio task cancellation and the AbortSignal in Node.js both give you the primitive.
The weakness is built in: code that never reaches a check, such as a tool call that hangs or a loop without one, is never cancelled. Checkpoints complement an external stop; they do not replace it.
Design agents so that being stopped is safe
If stopping an agent is risky, people hesitate to press stop, and a hesitant stop is a slow one. A few habits make a stop cheap:
- Make steps idempotent. Give each external write a stable key, such as the run ID plus the step number, so a retried step updates the same record instead of creating a second one.
- Checkpoint before side effects. Save the agent’s state before it sends an email, opens a pull request or charges a card, so a restarted run knows what already happened.
- Separate preparing from doing. Build the full change first and apply it in one short step, so a stop during the long part leaves nothing external behind.
- Record why a run stopped. Store the status, which limit or person stopped it, and when. A run canceled by a runtime limit needs a different follow-up from one that failed on its own.
Test the stop before you need it
A stop that has never been exercised is a guess. Test it on purpose, with a small budget:
- Run the agent with a limit far below normal, for example a two-minute runtime or a handful of model calls.
- Confirm the run ended, and check its exit code and the status that was recorded.
- List processes afterwards and make sure no shell, browser or MCP server from that run is still alive.
- Press the manual stop from a different machine than the one running the agent.
- Start the agent again and check that it picks up or restarts without repeating a side effect.
Stopping a supervised run with Toolcaise Connect
Toolcaise Connect is a command-line connector that runs on your machine. Its run command starts your agent behind a local gateway, points the OpenAI and Anthropic SDK base URLs at it, and counts every model call from the provider’s own usage fields. Limits are enforced locally, so they hold even with no network path to Toolcaise.
toolcaise-connect run --agent "Nightly research" --max-runtime 45m --max-total-tokens 2000000 -- python agent.py
toolcaise-connect run --agent "Refactor bot" --policy policy.json --max-cost-usd 5 -- node bot.js
toolcaise-connect run --agent "Nightly research" --remote-control -- python agent.pyThe limits are --max-runtime, --max-cost-usd, --max-total-tokens, --max-input-tokens, --max-output-tokens and --max-model-calls, plus --allow-model to refuse models you have not approved. Cost is computed only from prices you supply in a policy file, and with a cost limit set, a model without a price is refused rather than assumed free.
At a limit, the default --on-limit stop ends the command and everything it started: a polite terminate, a grace period set by --stop-grace (10 seconds by default), then a hard kill, including processes that appeared while it waited. The run exits with 124, as timeout(1) does, and is recorded as canceled with the limit as its error code, for example guard_max_runtime. With --on-limit block, Connect refuses further model calls and lets the command finish on its own.
With --remote-control, the dashboard can pause, resume and stop that one run. Pause holds new model calls at the gateway until you resume. Stop ends the process tree, exits with 125 and records the run as canceled with error code remote_cancel. Connect asks for instructions every ten seconds over outbound HTTPS, so no port is opened on the machine. An MCP server wrapped with toolcaise-connect mcp --name and --remote-control can be stopped the same way, which ends the server and its session with exit code 125; that needs Connect 0.3 or later.
Cancelling at checkpoints with the Toolcaise SDKs
If you own the agent’s code, the Python and Node.js SDKs provide the cooperative layer. Call checkpoint() between safe steps: it waits while a person has paused the agent or the run, and raises AgentCancelled if it was cancelled. A run wrapped in client.run() then reports itself as canceled rather than failed. If Toolcaise cannot confirm the decision, checkpoint() raises instead of carrying on.
from toolcaise import Toolcaise
client = Toolcaise()
with client.run("nightly-research"):
for source in sources:
client.checkpoint() # waits while paused, raises AgentCancelled if cancelled
summarize(source)This is cooperative by design. A step already in progress finishes, and code that never calls checkpoint() is not stopped. For a stop the agent cannot skip, also run it under toolcaise-connect run.
What Toolcaise cannot stop or undo
- It cannot undo an external action that already happened. An email sent, a payment made or a record deleted before the stop stays done.
- Limits are checked when a model response finishes, so calls already in flight complete and a spend or token limit can be overshot by those calls. A call-count limit is exact.
- Token, cost and model limits cover calls made through OpenAI- or Anthropic-compatible APIs using the standard base-URL variables. An agent that hard-codes a provider endpoint bypasses them, though the runtime limit and the stop still end it.
- A dashboard stop can arrive one poll late (ten seconds), and after failed polls up to two minutes late. Local limits are not affected.
- SDK controls apply only at checkpoints your code calls. OpenTelemetry ingest and the Claude Code and Codex adapters observe only; they cannot pause or stop anything.
Frequently asked questions
How do I stop an AI agent stuck in a loop?
End the process and everything it started: send a polite terminate, wait a few seconds, then hard-kill anything left. Killing only the parent can leave shells, browsers or MCP servers running. To catch the next loop earlier, put a runtime or model-call limit on every run and watch calls per minute.
What is an AI agent kill switch?
A way to end a running agent that does not depend on the agent agreeing to stop. In practice it has three parts: limits that trigger automatically, a manual stop someone can press remotely, and cooperative checks in the agent’s code so it can finish cleanly. The first two are enforcement; the third only works when the code reaches a check.
How do I cancel an agent run without losing work?
Cancel at a safe point rather than mid-step. Save state before each side effect, make external writes idempotent with a stable key, and check for cancellation between steps. With the Toolcaise SDKs, checkpoint() raises AgentCancelled at the next safe point and the run is recorded as canceled, so a restart can pick up from the last saved step.
How do I stop an AI agent infinite loop from running up costs?
Set a spend or token limit that sits outside the agent, on the path its model calls take, so a bug in the agent cannot skip it. Toolcaise Connect does this for supervised runs by metering each call on your machine and stopping the whole process tree at the limit. Cost limits need prices you supply.
Does killing the agent process stop its child processes?
Not always. Child processes can outlive their parent, and wrappers such as npx, uvx or sh -c add extra layers. Signal the whole tree or process group, wait a grace period, then kill what remains and check again for anything started in the meantime. Test this by listing processes after a stop.