Gateway Config Reload
Hot-reload gateway.yaml without dropping in-flight turns ā SIGHUP, watchdog, drain-coordinated restart
Edit gateway.yaml while the gateway is running and the change applies automatically ā no restart, no dropped connections.
pip install "praisonai[gateway]"
praisonai gateway start --config gateway.yaml
# Edit gateway.yaml and save ā reload applies within secondsThe operator edits gateway.yaml; the gateway diffs and hot-reloads without dropping in-flight turns.
How It Works
sequenceDiagram
participant Op as Operator
participant FS as gateway.yaml
participant GW as Gateway
participant Ch as Channel (bot)
Op->>FS: edit gateway.yaml
FS-->>GW: watchdog event (or mtime poll)
Op->>GW: kill -HUP pid (equivalent)
GW->>GW: diff config
GW->>Ch: drain(reload_drain_timeout)
Ch-->>GW: in-flight turns finished
GW->>Ch: restart with new config
GW-->>Op: log "reload applied: agents; restart[telegram]"Quick Start
- Install the gateway extra
bash pip install "praisonai[gateway]"This installs
watchdog>=3.0.0for event-driven file watching. The gateway falls back to mtime polling whenwatchdogis not installed. - Edit gateway.yaml ā auto-detected
yaml # gateway.yaml agents: assistant: instructions: "You are a helpful assistant." gateway: reload_drain_timeout: 10.0 # seconds to drain a channel on reloadSave the file. The running gateway detects the change within the watchdog debounce window and reloads automatically.
- Trigger a reload manually via SIGHUP
bash systemctl reload praisonai-gateway # or kill -HUP $(pgrep -f 'praisonai gateway start')SIGHUPruns the samereload_configpath as a file change ā no shutdown. Best-effort on Windows (SIGHUP unavailable, see fallback below). - Tune the drain window
yaml gateway: reload_drain_timeout: 10.0 # seconds to drain a channel on reload # If unset, falls back to gateway.drain_timeout drain_timeout: 5.0reload_drain_timeoutcontrols how long a channel restart waits for in-flight turns to finish. If unset, it falls back todrain_timeout. - Watch the audit log
reload applied: agents; restart[telegram]Each line tells you exactly what changed and which channels were restarted.
What Triggers a Reload
Two paths trigger the same reload_config routine:
| Trigger | Mechanism |
|---|---|
| File edit | watchdog filesystem event (if installed) or mtime polling every 5 s |
| Manual | kill -HUP <pid> or systemctl reload praisonai-gateway |
Both paths debounce rapid consecutive writes before applying ā a burst of saves from an editor doesn’t cause multiple reloads.
File-based (event-driven)
With watchdog installed, filesystem events (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows) trigger a reload within the debounce window (default 1 s) automatically ā no signal required.
File-based (polling fallback)
Without watchdog, the gateway falls back to mtime polling every 5 s automatically. Zero config change needed to opt in or out ā the gateway chooses the best available mechanism at startup.
Operator-triggered (SIGHUP)
Send SIGHUP to the gateway process to force an immediate reload:
kill -HUP <gateway-pid>
# or via systemd
systemctl reload praisonai-gatewayConcurrent SIGHUPs are serialized ā the second waits for the first to finish.
What Gets Restarted
| Config change | Action | Scope |
|---|---|---|
Agent instructions / model / tools | Recreate affected agents in-place | ReloadScope.AGENTS |
Channel token or platform args | Drain + restart that single channel | ReloadScope.CHANNEL |
Gateway host / port | Full restart (WebSocket server stays up) | ReloadScope.FULL |
| Scheduler / routes / guardrails | Full channel restart | ReloadScope.FULL |
identity: block | Rebuild + re-stamp the identity resolver, restart affected channels | ReloadScope.FULL |
The WebSocket server itself keeps running throughout ā connected clients are not disconnected unless their channel is restarting.
To pre-check what a path will do, call classify_reload(path) ā see Programmatic reload-scope classification.
Reloadable Sections āŗ identity:
Editing the top-level identity: block reconciles the cross-platform identity resolver on the next reload, so paired users keep one continuous session across channels (#3020).
| Reload behaviour | Details |
|---|---|
Block added (enabled: true) | A new StoreBackedIdentityResolver is built and stamped onto every channel; existing channels restart so they pick it up. |
Block removed or enabled: false | Resolver is cleared back to None ā channels revert to per-platform session keys. |
Block changed (store: re-pointed) | Old resolver replaced with a new one pointing at the new store; channels restart. |
| Block unchanged across reload | Existing resolver is reused (link cache + store handle survive) ā no churn. |
| Constructor / CLI resolver present | YAML changes are ignored for the resolver ā the explicit override always wins. |
Reconciliation runs before channels (re)start, via _reconcile_identity_resolver(), so freshly restarted bots pick up the new resolver rather than a stale one. The gateway records the block’s (enabled, store) signature via _signature_for_identity() ā an unchanged signature preserves the live resolver so the link cache and store handle survive reloads that don’t touch identity:.
The gateway also stamps a shared turn-lock onto every channel bot so a user unified across channels cannot scramble their transcript when two channels deliver at once ā see Concurrent Turns Across Adapters.
Drain-Coordinated Restart
Channel restarts drain in-flight turns before bouncing, using the same drain_timeout coroutine as shutdown.
# gateway.yaml
gateway:
drain_timeout: 30 # shutdown drain budget
reload_drain_timeout: 10 # reload-specific budget (wins over drain_timeout for reloads)Windows / No-Watchdog Fallback
| Environment | File watching | SIGHUP |
|---|---|---|
Linux / macOS + watchdog installed | Event-driven (instant) | Supported |
Linux / macOS without watchdog | mtime polling (5 s interval) | Supported |
Windows + watchdog installed | Event-driven (instant) | Not available |
Windows without watchdog | mtime polling (5 s interval) | Not available |
YAML Reference
identity:
enabled: true # reconciled on reload; false clears the resolver
store: ~/.praisonai/identity.json # re-pointing rebuilds the resolver
gateway:
drain_timeout: 30 # shutdown drain window (seconds)
reload_drain_timeout: 10 # reload drain window; falls back to drain_timeout if unsetRotating the shared secret during reload
The reload path also picks up changes to gateway.auth_token. When the reloaded config carries a new secret, the gateway adopts it inline and ā by default ā revokes every live session still on the old secret.
Adopting a new secret has three side effects:
self.config.auth_tokenis updated to the new value.GATEWAY_AUTH_TOKENin the environment is updated so all auth paths (HTTP, magic-link, WS) read the same value.- Stale live sessions are force-closed with WebSocket close code
4001and reasoncredentials_rotatedā unlessrevoke_on_secret_rotation: false.
Situation in reloaded gateway.yaml | Outcome |
|---|---|
auth_token absent | Full no-op. Running secret + live sessions untouched. |
auth_token present, resolves to empty ("" or unset ${VAR}) | Running secret kept. A warning is logged. |
auth_token present, same as running secret | No-op (0 revoked). |
auth_token present, differs | Adopt new secret, export to env, revoke stale sessions (unless opted out). |
auth_token not a string (e.g. 12345) | Coerced to str, avoiding a mid-reload TypeError. |
revoke_on_secret_rotation: false | Secret adopted for new connections only; live sessions stay on the old secret. |
revoke_on_secret_rotation: "false" / "0" / "no" / "off" | Also disables revocation. |
revoke_on_secret_rotation truthy (default) | Every session on the old secret is force-closed with (4001, "credentials_rotated"). |
Client-side recovery, the rotation mermaid, and the full behaviour matrix
Optional Dependency
On Windows, use the file-watch path only. kill -HUP is not available; trigger reloads by saving the config file.
Reading the Log Line
A concise summary is logged after each reload:
reload applied: agents; restart[telegram]| Part | Meaning |
|---|---|
agents | Agent definitions were updated (no channel restart) |
restart[telegram] | The telegram channel was restarted with drain |
restart[telegram,discord] | Multiple channels were restarted |
| _(empty)_ | Config was identical; no changes applied |
Best Practices
Install watchdog for instant detection
Without watchdog, the gateway polls every 5 seconds. With watchdog installed (pip install "praisonai[gateway]"), changes are detected within milliseconds via filesystem events.
Test reloads on a canary channel first
Apply config changes to one channel (e.g. a staging Telegram bot) before rolling to all channels. The per-channel restart scope makes this safe ā the reload touches only what changed.
Set reload_drain_timeout shorter than drain_timeout
A short reload drain (5sā10s) keeps channel restarts fast. A longer shutdown drain (15sā30s) gives in-flight conversations more time to complete. Keep them separate.
Validate before saving
A bad config (YAML syntax error, missing required field) is rejected at reload time ā the gateway keeps the last-known-good config and logs an error. Test with praisonai gateway validate gateway.yaml before saving to the watched path.
Grep the audit trail after every reload
Log lines like reload applied: agents; restart[telegram] are your audit trail. Ship them to your log aggregator and alert on unexpected full restarts.
Use SIGHUP in CI/CD pipelines
After updating gateway.yaml in a deploy pipeline, send kill -HUP $(cat /var/run/praisonai.pid) to trigger reload without downtime. No restart, no new process.
Related
WebSocket control plane overview and full YAML reference
Gateway CLICLI commands for starting, stopping, and managing the gateway
Gateway ReliabilityGraceful drain and admission control presets
Gateway Graceful DrainDrain-only knob ā full reference and sequence diagram
Credential RotationRevoke live sessions when the shared secret rotates
Cross-Platform SessionsThe identity: block ā one session per user across channels