How it works

Kill Switch sits in the path of your own model calls rather than in front of your traffic. There is no proxy to operate and no DNS to change. Two function calls, one stateless endpoint, and a record you can hand to an auditor.

01

The call reaches us before your model does

Your code calls screen_user_message before the prompt goes out, and screen_ai_reply before the answer comes back. Agents call screen_agent_action before a tool runs. The endpoint is stateless; all state lives in Postgres.

02

Cheap checks run first

Is the org halted? Is this conversation already dead? Both are single indexed reads, so a call on a killed conversation returns in milliseconds and never touches a model.

03

A small model scores the message

The scorer sees the message, the last few turns, your category list and your active rules. It returns strict JSON: a 0 to 100 score per harm category, an overall risk, the ids of any rules matched, and one sentence of reasoning.

04

Autopilot and your rules decide

Thresholds you set decide whether a score becomes a block or a kill. A matched rule applies the action written on that rule. Rules are authoritative; autopilot escalation keys off harm scores alone, so a policy breach that is not harmful blocks rather than killing the thread.

05

The verdict is written before it is returned

The event is appended to an insert-only table, hash-chained to the row before it, and only then does the call return or raise. What you were told and what was recorded cannot disagree.

Tune it against real traffic first

Every deployment has a monitor-only mode that scores and logs but never blocks. Move the thresholds, replay the last seven days against them, and see how many more or fewer stops you would have had before you save.

Autopilot thresholds
Autopilot thresholds

The log, and why it holds

Each event stores a hash computed from its own contents plus the hash of the row before it. Alter one row and its hash stops matching, and because the next row was derived from it, every row after it breaks too. You cannot quietly change one thing in the middle.

Update and delete are revoked from every database role, ours included, and a trigger rejects them on top of that. Overrides never edit an event; they append a new one pointing back at what they reverse. Releasing a halt appends a release rather than removing the halt.

A nightly job walks the entire chain, records the verdict as evidence whether it passed or failed, and withholds that day's anchor if the chain is broken, so a break can never be laundered by a published hash.

sql
-- every row is chained to the one before it
hash = sha256(prev_hash || canonical_json(row))

-- and the table refuses to be rewritten, by anyone
revoke update, delete on events
  from public, anon, authenticated, service_role;

create trigger events_no_update_delete
  before update or delete on events
  for each row execute function reject_update_or_delete();

Your model, or ours

The scorer is any OpenAI-shaped endpoint. Run it hosted to start, then point three environment variables at your own vLLM box so message content never leaves your infrastructure.

Halts reach every process

A halt is a row in a table, pushed to every connected SDK over Realtime in about a second and re-checked on every call. It does not depend on the scorer being up.

Built on Postgres

Row level security scopes every read to your organization. Retention, anchoring and verification all run as scheduled jobs in the same database that holds the log.

Try it on your own traffic

Start in monitor-only mode and watch what it would have stopped.