Skip to content

gateway: phase-6 kill switches + hot-state/revoke admin routes - #1683

Merged
Evanfeenstra merged 1 commit into
mainfrom
gateway-kill-switches
Sep 14, 2026
Merged

Evanfeenstra merged 1 commit into
mainfrom
gateway-kill-switches

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Collaborator

Phase-6 read side, part 1: kill switches. Doesn't depend on accumulated spend data, so it's useful before the cap walk lands.

Hot path

  • CheckRevocations (PIPELINE 1) now also issues EXISTS bifrost:kill:<run_id> for every distinct run_id in Claims.Chain (parent kill ⇒ every descendant dies) and EXISTS bifrost:kill:agent:<agents[last]>.
  • Rejects with 402 run_killed / agent_killed. Revocation 401s win when both apply.
  • Gated by enforce_macaroons like the rest of the hook; shadow mode logs the would-be rejection.
  • 402 short-circuits carry Type=enforcement_rejected (vs macaroon_verification_failed for 401s) so clients can tell "re-issue" from "an operator stopped you".

Admin routes

Route Auth Effect
POST/DELETE /_plugin/runs/:id/kill cookie-or-bearer SET bifrost:kill:<id> 1 EX 3600 / DEL
GET /_plugin/runs/:id/state cookie-or-bearer cost, steps, last-10 tools, killed, ttl
POST/DELETE /_plugin/agents/:name/kill cookie-or-bearer SET bifrost:kill:agent:<name> 1 EX 86400 / DEL
GET /_plugin/agents/:name/state?window= cookie-or-bearer current-bucket spend, cap, killed (configured window wins)
POST/DELETE /_plugin/revoke/nonce/:nonce bearer tombstone with TTL from {exp} (default 7d ceiling)
PUT/GET/DELETE /_plugin/revoke/user/:id bearer revoke_user_before cutoff (default now)

The revoke routes wire up the auth/admin.go helpers that existed but had no HTTP surface.

Not in this PR

  • Cost/step cap walk (PIPELINE 2) + budget 402s — next PR, behind its own shadow knob.
  • Tool-loop detection, hard_ceiling, /_plugin/config/* overrides.
  • UI kill buttons (phase 9). types.ts not regenerated — make tygo currently rewrites the whole hand-maintained file (Gateway: reconcile tygo codegen with SPA and enforce in CI #1563).

Tests

miniredis-backed: leaf kill, ancestor kill kills child, agent kill (leaf name only), revoked-beats-killed, end-to-end 402 through ApplyToLLMPre in enforce mode / pass-through in shadow, admin round-trips, 503 without Redis, 401 without auth.

PreLLMHook PIPELINE 1 now also checks bifrost:kill:<run_id> for every
chain layer and bifrost:kill:agent:<leaf agent>, rejecting with 402
run_killed / agent_killed (gated by enforce_macaroons; shadow logs).
Revocation 401s win when both apply.

New admin routes:
- POST/DELETE /_plugin/runs/:id/kill, GET /_plugin/runs/:id/state
- POST/DELETE /_plugin/agents/:name/kill, GET /_plugin/agents/:name/state
- POST/DELETE /_plugin/revoke/nonce/:nonce, PUT/GET/DELETE /_plugin/revoke/user/:id
  (wires the previously unreachable auth/admin.go helpers)

402 short-circuits carry Type=enforcement_rejected so clients can tell
them apart from macaroon_verification_failed.
@Evanfeenstra
Evanfeenstra merged commit 544eed8 into main Sep 14, 2026
6 checks passed
Evanfeenstra added a commit that referenced this pull request Sep 14, 2026
)

Operator half of the phase-6 kill switches (#1683, #1684) in the admin
SPA. Scope is kill/unkill + live hot state only; no config editing,
no bulk actions, no analytics changes.

- api/types.ts: RunStateResponse, AgentStateResponse, KillRunResponse,
  KillAgentResponse mirroring hotstate.go (hand-maintained; no tygo).
- api/queries.ts: useRunState (2s in flight / 30s done / 500ms for 30s
  after a kill), useAgentState (10s), useAgentStates (30s per row),
  useKillRun / useUnkillRun / useKillAgent / useUnkillAgent. 503
  (no Redis) folds into data === null with a 60s retry.
- components/KillConfirmModal: one modal, two modes — plain confirm
  for runs, typed agent name for agents. Explains scope, TTL (1h /
  24h) and "takes effect on the next LLM call; enforced only with
  enforce_macaroons=true". No window.confirm (Hive iframe sandbox).
- components/StatusBadge: running / killed / exceeded / done with the
  derivation documented in one place.
- RunDetail: live-state card (cost, steps, last tools, kill flag,
  "state expires in") + Kill / Unkill; 503 renders as an inline note
  with the switch disabled.
- AgentDetail: killed / exceeded badge in the header + Kill / Unkill
  agent. Agents: kill-state column read from /state per row.
- icons: StopIcon.
- vite.config: bypass /_plugin/ui/* in the dev proxy so Vite serves
  the SPA instead of the gateway's embedded bundle (local edits never
  showed otherwise); GATEWAY_URL override for the proxy target.
- .claude/launch.json: gateway-ui dev-server entry.

dist/ stays the tracked placeholder — the repo doesn't commit builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant