Kill switch is the most reassuring phrase in trading automation and one of the least examined. Every bot framework claims one. Ask what it actually guarantees and the answers thin out fast: often it is a boolean the bot’s own loop checks between iterations, which means it is a promise the bot makes about itself. When the thing placing orders is an AI agent, that is not enough, because the failure you are guarding against is precisely the agent not doing what it was told. The general argument for moving every control out of the model is made in risk controls an LLM cannot override. This piece goes deep on the one control the others lean on, and what it has to guarantee to deserve the name.
Five guarantees, or it is a status message
A kill switch for an AI trading agent has to hold five properties at once. Enforcement out of process: the stop runs where the model is not, so no output the model produces can route around it. A reduce-only carve-out: stopping new risk must not trap you in the risk you already hold. Human-only release: engaging can be easy, releasing must be a deliberate human act the agent cannot perform. Persistence: a restart must not release it. And a trail: you must be able to prove afterward when it was engaged and what was refused while it held. Miss any one and the other four leak. The rest of this piece takes them in turn.
Enforced outside the model, checked before everything else
In PitBridge the kill switch is not an instruction, it is state the engine reads. Engaged means one thing: a file named KILL exists in the PitBridge home directory. Every order, whether it arrives as an MCP tool call or a REST request, enters one fixed pipeline, and the guardrail chain inside that pipeline evaluates the kill switch first, before any other check. Once it is engaged, nothing runs after it: the order comes back blocked with reason_code=KILL_SWITCH and the detail kill switch engaged: all order flow blocked (unkill is CLI-only). That evaluation order is frozen. Plugin guardrails can be appended to the chain, but the kill switch stays first, and the engine refuses to start if the built-in chain is missing a member.
Contrast that with a halt flag inside the agent’s own process. The flag lives in the same runtime as the thing being stopped, so it stops working exactly when that runtime misbehaves: a hung loop never checks it, a crashed process forgets it, and a model that was talked past its instructions was never going to consult it honestly. The agent can see PitBridge’s kill state, because get_guardrail_status reports it. Seeing is all it can do. The state lives below the caller, in a process the model has no way into.
Engage from anywhere trusted, release from exactly one place
The two directions of a kill switch deserve opposite designs. Engaging is deliberately over-available: the CLI does it with a recorded reason, the REST API accepts an engage, and because engaged simply means the file exists, a person can touch the kill file and stop all new entries even if the daemon is wedged and answering nothing else. Releasing is the opposite: pitbridge unkill on the operator CLI, and nothing else. There is no REST route for release, and no MCP tool in either direction.
| Property | engage | release |
|---|---|---|
| operator CLI | pitbridge kill --reason "..." | pitbridge unkill, the only path |
| REST API | POST /v1/kill | no route, deliberately absent |
| agent tool surface | none of the nine tools | none of the nine tools |
| a file on disk | touch the KILL file, works even if the daemon is wedged | the CLI removes the file for you |
engage
- operator CLI
- pitbridge kill --reason "..."
- REST API
- POST /v1/kill
- agent tool surface
- none of the nine tools
- a file on disk
- touch the KILL file, works even if the daemon is wedged
release
- operator CLI
- pitbridge unkill, the only path
- REST API
- no route, deliberately absent
- agent tool surface
- none of the nine tools
- a file on disk
- the CLI removes the file for you
Why insist on human-only release when an agent could arguably de-escalate too? Because an agent that can release its own stop does not have a stop. The scenario a kill switch exists for is the one where the agent’s judgment is exactly what you no longer trust: a runaway retry loop, a session steered by injected text, a model confidently misreading its position. In that scenario every capability the agent holds is a liability, so the release capability must not exist where the model runs. It is the absence-as-safeguard principle applied to the control that backstops the others.
$ pitbridge kill --reason "runaway retry loop" kill switch ENGAGED (~/.pitbridge/KILL) $ pitbridge status PitBridge status daemon : bind 127.0.0.1:8873 (paired_mode=false) kill : ENGAGED $ pitbridge unkill kill switch released (~/.pitbridge/KILL)
Real commands and output text, path shortened. Engaging stamps a UTC timestamp and your reason into the kill file. Releasing is the same deliberate act in reverse, available only here.
Blocked does not mean trapped: the reduce-only carve-out
The naive kill switch blocks everything, and that creates a second emergency: you are holding a position and your own tooling refuses to take you out of it. So the carve-out matters as much as the block. Under an engaged kill, every new entry is refused, but cancel_order, close_position, and flatten_account still run. Close and flatten are reduce-only verbs: they can take exposure toward zero, never away from it, and they stay reduce-only even under the kill switch. The door is shut for new risk and open for getting flat.
The carve-out is narrow, not a bypass. A de-risk action still honors the account mode, so a read-only account refuses it, because closing a position mutates the broker. It still refuses any account on the daemon’s deny-list. And every de-risk action is written to the audit log with its outcome, whether it was sent, blocked, or failed because the AddOn link was down.
place_order account=sim instrument="MES 09-26" side=BUY qty=1 -> BLOCKED reason_code=KILL_SWITCH close_position account=sim instrument="MES 09-26" -> DONE, close_position sent flatten_account account=sim -> DONE, flatten_account sent
Decisions under an engaged kill. Real tool names, reason code, and result text. Opening intent is refused; getting flat stays possible.
It has to survive a restart
A kill switch that resets when the process restarts can be released by accident, or by anything with the power to bounce a daemon. File-backed state closes that hole: the engaged state is the file, not a variable in memory, so a restart changes nothing. The daemon comes back up, reads the same file, and keeps refusing entries until a person runs unkill. A restart is not an argument that the emergency ended.
The same discipline extends to the state around the kill. Since 0.2.2, the day ledger, meaning the day’s realized profit and loss, the sticky day-halt latch, and the set of booked execution ids, is persisted to disk and restored on startup for the current trading date. A daemon restarted in the middle of a session no longer forgets the day’s losses, so a daily loss halt keeps holding across the restart instead of starting the day fresh, and a prior day’s ledger is rolled over rather than resurrected. A restart is an operational event. It must never be a state reset for any control that was holding.
The trail: who stopped trading, and what happened while it was stopped
A control you cannot inspect afterward is a control you take on faith. Engaging the kill writes who, when, and why into the kill file itself: a UTC timestamp and the reason you passed. Everything that happens while it holds lands in the append-only, hash-chained audit log: every refused entry as a KILL_SWITCH decision, every de-risk action with its outcome. pitbridge audit why <order_id> reconstructs any single decision after the fact, and pitbridge audit verify proves the chain has not been edited. The engaged state is also impossible to overlook in operation: pitbridge status reports it, and pitbridge doctor flags it in its self-check, so a kill left engaged from last week announces itself rather than silently eating orders.
One interaction is worth calling out. If an order is being held for human confirmation and the kill engages while it waits, the approval does not resurrect it: the daemon re-checks the full guardrail chain at confirm time, kill switch included. There is no window where a stale approval outruns the stop.
What a kill switch does not promise
Precision about the guarantee is part of the guarantee. A kill switch stops new orders from the moment it engages. It is not a promise about market outcomes. If you are holding a position when you hit it, that position keeps moving with the market until it is closed, which is exactly why close and flatten stay available under the kill. It does not prevent all losses, and it does not predict anything. What it promises is narrow and checkable: from engage until a human release, no new opening order passes, whatever the agent proposes and however it was prompted, and every decision made under it is in the log.
The kill switch is one of twelve controls, and the only one this piece covers; the general case, why every limit must live out of the model’s reach, is the subject of risk controls an LLM cannot override, and the full set with reason codes is on the guardrails page. Where each part runs is in the security model. Everything here runs against paper and simulation accounts in the open core: live execution ships separately and is gated behind an operator-only arm step. Live execution has been in production on a funded futures account since 30 July 2026, behind that same step. If you want a stop like this between your agent and your account, tell us your platform on the waitlist. PitBridge is trading infrastructure, not financial advice: it enforces the limits you configure and does not promise any trading outcome. Futures trading carries a substantial risk of loss.