Skip to main content
One shared brake every new-work seam can consult — halt new admissions instantly, keep in-flight runs, resume without a restart.

Quick Start

1

Default — no config, behaviour unchanged

A gateway with no control block defaults to backend: "off", a no-op brake that is byte-for-byte today’s behaviour.
2

Enable the durable file brake

Selecting backend: "file" instantiates a durable sentinel that survives a restart and is shared across replicas that point at the same path.
3

Or configure it in gateway.yaml

4

Drive the brake from Python (works today)

Engage, inspect, and release the durable brake directly — the sentinel behaviour is available today.

How It Works

The operator engages one durable sentinel; every admission seam consults it to refuse new work, while runs already in flight are untouched and finish normally. engage and disengage are idempotent — engaging twice or disengaging a missing sentinel is safe.

Fail-Safe Behaviour

An ambiguous brake holds new work rather than letting it run freely: any sentinel that cannot be read with confidence counts as engaged.

Configuration Options

EmergencyStopConfig from praisonaiagents.gateway. Properties and methods:
  • enabledboolTrue when backend != "off".
  • to_estop() → the concrete brake (NullEmergencyStop for "off", FileEmergencyStop(path) for "file").
  • to_dict() / from_dict() — YAML/JSON roundtrip; from_dict(None) tolerates a missing block.
Wired into GatewayConfig as control and surfaces in GatewayConfig.to_dict() as {"control": {"backend": "off", "path": None}} when defaults hold.
Full field, type, and default reference for EmergencyStopConfig, EmergencyStopProtocol, NullEmergencyStop, FileEmergencyStop, and EmergencyStopState

When to Enable

Match the backend to whether an on-call operator needs an instant, durable hold.

Backward Compatibility

off is the default and reproduces today’s behaviour byte-for-byte — no brake is engaged, is_engaged() is always False, and no new dependency is added.

Roadmap (what this ships vs. what comes next)

Kept intentionally minimal per the lightweight-and-powerful mandate: this PR ships the shared core contract + fail-safe policy + config selector only. The heavy bot-side wiring (WS inbound, kanban dispatch, scheduler seams) and the praisonai gateway pause/resume CLI belong in the wrapper/bot packages and are a natural follow-up that consumes this contract.
Today’s release ships the pure protocol, the off/file selector, and the audit-safe state snapshot. Selecting backend: "file" instantiates the durable brake and wires it into GatewayConfig.control; you can already engage it and inspect state() from Python. Consultation at the WebSocket inbound, kanban dispatch, and scheduler seams — plus a praisonai gateway pause/resume CLI — is follow-up work in the bot/wrapper packages.

Best Practices

Point path at a location that survives a restart and is visible to every replica — a mounted volume, not a container’s ephemeral /tmp. That is what makes the hold outlast a crash and apply fleet-wide.
engage(reason="cost spike", actor="mervin") records who paused and why in the sentinel, surfaced by state() for /health and audit. An unlabelled hold is hard to explain later.
An unreadable or corrupt sentinel counts as engaged on purpose — new work stays held. Treat state().reason == "unreadable-sentinel" as a signal to inspect the file, not as a normal engaged state.
Single-node dev and deployments without on-call control gain nothing from a durable sentinel. Keep backend: "off" there for zero cost and unchanged behaviour.

Gateway Turn Lock

Serialise turns cluster-wide.

Gateway Admission Control

Concurrency ceiling & backpressure.

Gateway Graceful Drain

Drain toward shutdown — orthogonal to the brake.

Gateway Channel Supervision

Per-channel pause vs. this fleet-wide brake.