Two agents, one limit, one refused.
All posts
5 min read
by

An AI agent kill switch: what it has to stop, and what it can't

How to build a kill switch that stops an AI agent in production, including queued work and approved calls. What a real halt must reach and what it can't recall.

kill-switchagentsenvironmentsincident

An AI agent kill switch is a control that stops an agent from starting any further effect in the world, from one place, without a redeploy. Most of the ones in production today stop less than their owners think.

This post is about what a halt has to reach to deserve the name, and the one thing no halt can do.

The usual kill switch, and where it leaks

The common version is a feature flag the agent loop checks before each step:

if (await flags.isEnabled('agent-halted')) return;

It stops the agent from starting new work. It does not stop:

  • Work already queued. A webhook scheduled for five minutes from now still fires. A retry already in backoff still retries.
  • Scheduled jobs. The nightly cron that the agent set up yesterday still runs tonight.
  • Calls already approved. A refund a human approved ten minutes ago, still waiting to execute, still executes.
  • Other processes. A second worker, a background job, a different entry point that does not check the flag.
  • The agent's own writes. It keeps writing memory and files that it, or its next run, will act on later.

From outside, none of that looks like a stopped system. And the moment you reach for a kill switch is exactly the moment you need all of it stopped.

What a halt has to reach

The rule is simple to state: a halt must be checked where effects leave, not only where work starts. If the check lives in the agent loop, anything that has already left the loop is out of reach.

That is only possible if the halt sits on the path every side effect takes. For an action routed through Anlyon, the agent does not make the production call itself. It names an action, and Anlyon makes the call. So the halt can be enforced at the point of dispatch. A tool your own code calls directly is not on that path, and the halt does not reach it.

Halting an environment in Anlyon does the following:

StoppedHow
Action invocationsBlocked and recorded on the invocation, including approved calls that have not yet run
The earlier namespacesMessage deliveries are deferred without consuming a retry attempt, including those a schedule publishes. Workflow runs park until resume. The agent's writes to memory, files, sessions and cache return 409
Still workingWhy
PublishingMessages and events are accepted and queued. Nothing you send is lost
Deciding approvalsSo you can deny the ones that piled up during the incident
ReadsSo you can inspect what happened while nothing new starts
ResumeThe switch never disables its own undo

When you resume, deferred work is released and picks up where it left off. That matters more than it sounds. A stop button people are afraid to press, because pressing it loses data, is not a control.

The one-line version

From a runbook, with an operator key that holds the environments:halt scope:

import { Client } from '@anlyonhq/sdk';

const operator = new Client({ apiKey: process.env.ANLYON_OPERATOR_KEY! });

await operator.environment.halt({ reason: 'runaway refund loop' });

// ...when the incident is over:
await operator.environment.resume();

Or from the console: Environments → Halt. The reason is recorded.

Three details worth copying, whatever you build with:

  • The halt uses its own credential. environments:halt is not granted by default, and the agent's key should not hold it. The key an incident responder uses should not be the key a model is driving.
  • A halt is scoped to an environment. A staging key halts staging and cannot reach production, for the same reason it cannot read production. Halting one does not freeze the other.
  • It fails closed. In Anlyon the halt flag sits in a fast cache for the hot path, the database is the source of truth, and the flags re-sync on boot, so a cache flush cannot un-halt an environment. If neither can be read, the check reports halted. An outage is when you are most likely to be reaching for the switch, so being wrong in that direction is the only safe way to be wrong.

What happens to calls already in flight

Halting stops new dispatch. A request that an external server already accepted is not recalled. If the refund reached Stripe one second before you pressed halt, Stripe has it.

This is not a limitation of one product. It is what a network call is. Anything that tells you otherwise is describing a rollback it cannot perform. What you can do is make that window small and visible: every invocation records whether it was sent and what the destination replied, and a halted call is recorded as blocked. After an incident you can list exactly which calls went out before the halt and which did not.

Two related controls are often confused with a kill switch:

  • Rollback repoints an agent at an earlier immutable version and the action definitions it pinned. It changes what happens next. It does not undo an effect already dispatched.
  • Budgets cap the Anlyon operations a key can perform in a month (messages, event publishes, cache lookups, memory operations and action invocations). At the cap, requests stop with a clear error. A budget is a ceiling on volume, not a cap on model tokens or on the money an approved refund moves.

A kill switch checklist

  1. Find every path by which your agent causes an effect: direct API calls, queued jobs, cron, webhooks, other workers.
  2. Route them through one place where a halt is checked at dispatch. If a path cannot be routed, write it down. That is what your halt does not stop.
  3. Put the halt behind its own credential and an on-call runbook.
  4. Practise it. Halt staging on a Tuesday and check that nothing left.
  5. Make sure resume loses nothing, or nobody will press halt in time.

Halt, isolation and approvals are included on every plan, and Early Beta Access is free with no credit card.

Start building → · Environments and the kill switch in the docs →

Free tier, no credit card. One command if you use Claude or Cursor.

$ claude mcp add anlyon -- npx -y @anlyonhq/mcp-server