Skip to main content
The gateway now ships in the praisonai-bot package. praisonai serve gateway still works exactly as documented here; for a standalone install see praisonai-bot Migration.
The gateway diffs gateway.yaml against the running config and restarts only affected agents or channels. The WebSocket server keeps running.
The user edits gateway.yaml on disk; the watcher diffs changes and reloads only affected agents or channels while the WebSocket server stays up.

How It Works

Quick Start

1

Run the gateway

2

Edit live

Change agent instructions or a single channel token in gateway.yaml and save. The watcher applies a selective reload within ~5 seconds (1s debounce). Credential fields can also be secret references ({source, id}) instead of plaintext.
3

Trigger reload manually with SIGHUP

Send SIGHUP to reload without editing a file — useful for orchestration scripts and systemd:

Event-driven vs Polling

The watcher prefers event-driven file notifications via the optional watchdog package and falls back gracefully to mtime polling when watchdog is unavailable or an observer cannot start. Both modes apply the same 1s debounce to coalesce rapid saves. Install watchdog for faster reload detection:
watchdog is optional — without it, polling continues exactly as before. Install it only when faster reaction times matter.

Operator-triggered Reload via SIGHUP

start_with_config installs a SIGHUP handler that runs the same reload_config path as a file-change reload — no shutdown, no dropped connections. Reload via SIGHUP is best-effort — it is silently skipped on platforms without SIGHUP support (e.g. Windows).

Drain-coordinated Channel Restart

When a reload triggers a channel restart, the gateway drains in-flight turns before bouncing the channel — no mid-conversation cuts. The drain window for reload-triggered restarts is controlled by a new YAML key:

Hot-Apply (no restart)

Some config paths are safe to apply in place — the gateway mutates the live subsystem without restarting agents or channels. When the diff only touches these paths, no drain runs, no client sees a blip. An operator raising gateway.reload_drain_timeout at 3am does not disconnect a live conversation:
Editing the key in gateway.yaml triggers the hot-apply on the next SIGHUP / file save — no restart, no client disconnect, no drain:
The health check confirms the change landed without a restart — the only changed path is hot-applied:
The full registry of hot-appliable paths: Anything not in this registry falls through to the existing restart-based plans — restart is always the safe default for unknown or structural changes. Invalid values (e.g. a non-numeric timeout) are ignored so one bad key never aborts an otherwise-good reload; the applied paths appear in the reload summary line as hot[...] and in health().reload.changed_paths.

Restart Scope

Each Effect is prefixed with the canonical ReloadScope value that classify_reload(path) returns for that section. Full restart stops and starts all channels but does not restart the WebSocket server — connected clients stay connected.
Changing unknown_user_policy on a channel with an empty allowed_users re-fires the startup warning with the new policy’s text (PR #2856). Existing sessions are unaffected — the change applies to inbound DMs after the reload commits.

Observability

The gateway records every reload outcome so operators can confirm the last edit took effect without scraping logs. Reloads still log a concise summary line on completion:
The format is reload applied: <changed-sections>; restart[<channels>]. Grep for reload applied to trace all reloads in your log stream.

Reload status in health()

health() surfaces the reload outcome and config revisions when the gateway runs from a config file:
These four keys are additive — they only appear when the gateway runs from a config file, so existing health() consumers see no change:

ReloadStatus fields

ReloadStatus is a frozen dataclass describing the most recent reload attempt:

Compare configs offline

compute_config_revision returns the same 12-character revision id used by health(), so you can check an on-disk config before deploying it:
Identical logical configs produce identical revisions regardless of key order or whitespace; an empty or None config returns the sentinel "000000000000".

Inspect the hot-apply registry

HOT_APPLIABLE_KEYS and is_hot_appliable are public exports from praisonaiagents.gateway, so you can query which paths are zero-restart before an edit:
The SupportsHotReload protocol is runtime_checkable — a gateway implementation opts in by defining an apply_hot_reload(paths, new_config) -> None method. Third-party gateway subclasses that want the wrapper’s hot-apply behaviour implement this method; core owns the classification so every runtime reloads identically.

Programmatic reload-scope classification

ReloadScope and classify_reload are public exports from praisonaiagents.gateway that tell you exactly what an edit to gateway.yaml will do — before you save it.
The four canonical scopes are plain strings, so wrapper and runtime code can compare against them without importing the class:
classify_reload is canonical and side-effect-free — every runtime (core, wrapper, third-party gateway subclasses) uses the same classifier, so a pre-flight check reflects exactly what happens at reload time. Anything not explicitly recognised falls through to ReloadScope.FULL, keeping full restart the fail-safe default.

Reload observability flow

Inspect reload from the CLI

praisonai gateway status prints the reload result, watcher state, and config drift alongside the existing status:
When the running config no longer matches disk, the drift is shown with both revisions:

When config_drift is true

A drift means the config on disk has not taken effect — walk this path to recover:
The reload machinery is additive: ReloadStatus and compute_config_revision are new exports from praisonaiagents.gateway, and the existing reload applied: … log line is unchanged. Pre-existing health() consumers are unaffected.

Tuning


Backward compatibility: Leaving reload_drain_timeout unset preserves the prior immediate-restart behaviour. Not installing watchdog keeps polling as before. This is a fully additive change. The HOT_APPLIABLE_KEYS set is a closed core registry; unknown or structural changes still trigger the existing restart plans, so upgrading is drop-in and cannot silently skip a needed restart.

Best Practices

Changing agents.* avoids dropping live Telegram/Discord sessions.
Edit one channel block to restart only that platform.
Invalid saves are ignored — the previous config keeps running.
Add ExecReload=kill -HUP $MAINPID to your systemd unit so systemctl reload triggers a drain-coordinated reload without stopping the process.

Bot Gateway

Gateway server overview

Gateway Channel Supervision

Self-healing channels

Code-Skew Guard

Detect in-place code updates and refuse hot operations until the process restarts.

Gateway Reliability Preset

One switch to compose drain + admission control