Gateway CLI
Command-line interface for managing the PraisonAI Gateway daemon and server
from praisonaiagents import Agent
agent = Agent(name="gateway-cli-agent", instructions="Manage gateway via CLI commands.")
agent.start("Start the gateway and show its status.")Gateway CLI provides commands for starting, monitoring, and managing the PraisonAI Gateway server and its daemon service, including channel supervision controls for resilient bot management.
# gateway.yaml
agents:
assistant:
instructions: "You are a helpful AI assistant."
model: gpt-4o-mini
channels:
telegram:
token: "${TELEGRAM_BOT_TOKEN}"
platform: telegram# Onboarded: no flag needed — the CLI discovers ~/.praisonai/bot.yaml
praisonai gateway start
# Explicit path still wins
praisonai gateway start --config gateway.yamlThe user runs praisonai gateway start; the CLI launches the daemon, supervises channels, and keeps the WebSocket gateway reachable.
How It Works
Quick Start
- Start Gateway
bash # Canonical (bot-tier, no wrapper required) praisonai-bot gateway start # Wrapper alias (requires pip install praisonai) praisonai gateway startWith no
--config,startauto-discovers the onboarded config and prints the file it picked before binding:bash $ praisonai gateway start Using gateway config: /Users/you/.praisonai/bot.yaml - Check Status
bash praisonai gateway status - Test Health Endpoint
bash curl http://127.0.0.1:8765/health
Commands
Which verb do I need?
Gateway Management
| Command | Description | Example |
|---|---|---|
praisonai gateway start | Start the gateway server (foreground) | praisonai gateway start --port 9000 |
praisonai gateway stop | Stop a running gateway instance | praisonai gateway stop --force |
praisonai gateway status | Check gateway and daemon status | praisonai gateway status --daemon-only |
praisonai gateway doctor | Validate channel credentials and route/binding targets before start, run plugin health checks, and optionally auto-repair weak auth tokens (--fix) | praisonai gateway doctor --config gateway.yaml --fix |
praisonai gateway test | One-shot readiness (probes + shell wiring + optional turn) | praisonai gateway test --config gateway.yaml |
praisonai gateway channels | List configured channels (add --probe to check creds) | praisonai gateway channels --probe |
praisonai gateway channels {add,update,disable,remove} | Edit one channel in place (validated before writing) | praisonai gateway channels add discord --set group_policy=mention_only |
Daemon Service
| Command | Description | Example |
|---|---|---|
praisonai gateway install | Install as OS daemon | praisonai gateway install --no-start |
praisonai gateway uninstall | Remove daemon service | praisonai gateway uninstall |
praisonai gateway logs | Show daemon logs | praisonai gateway logs -n 100 |
praisonai gateway restart | Graceful drain + relaunch (daemon-aware) | praisonai gateway restart --drain-timeout 30 |
The installed unit stops (not restarts) on the gateway’s fatal-config exit code (78, EX_CONFIG). See Gateway Exit Codes for the full contract.
Channel Control
| Command | Description | Example |
|---|---|---|
praisonai gateway pause | Pause a channel | praisonai gateway pause telegram |
praisonai gateway resume | Resume a paused channel | praisonai gateway resume telegram |
praisonai gateway reconnect | Force reconnect a channel | praisonai gateway reconnect telegram |
Inbound Hooks
| Command | Description | Example |
|---|---|---|
praisonai gateway hooks add <path> | Register an inbound webhook trigger | praisonai gateway hooks add gmail --agent assistant |
praisonai gateway hooks list | List registered hooks | praisonai gateway hooks list |
praisonai gateway hooks remove <path> | Remove an inbound hook | praisonai gateway hooks remove gmail |
See Gateway Inbound Hooks for full details.
Testing & Debugging
| Command | Description | Example |
|---|---|---|
praisonai gateway test | Full readiness check (see Gateway Readiness) | praisonai gateway test --config gateway.yaml --channel slack --turn "Say OK" |
praisonai gateway send | Send test message | praisonai gateway send --channel telegram --channel-id 12345 -m "test" |
praisonai gateway diagnostics export | Write a pre-sanitised support bundle (see Diagnostics Export) | praisonai gateway diagnostics export --config gateway.yaml |
Diagnostics
praisonai gateway diagnostics export writes a portable, pre-sanitised .zip you can attach to a bug report. Full details in Diagnostics Export.
| Flag | Type | Default | Description | |
|---|---|---|---|---|
--config, -c | str | "gateway.yaml" | Path to gateway.yaml. | |
--output-dir | `str \ | None` | ~/.praisonai/diagnostics | Directory to write the bundle. |
--log-lines | int | 200 | Number of recent (redacted) log lines to include. | |
--json | bool | False | Emit JSON ({path, manifest}) instead of the human summary. |
Command Reference
start
praisonai gateway start [OPTIONS]
Options:
--host TEXT Host to bind to [default: 127.0.0.1]
--port INTEGER Port to listen on [default: 8765 or $GATEWAY_PORT]
--agents TEXT Path to agent configuration file
--config TEXT Path to gateway.yaml for multi-bot mode.
When omitted, auto-discovers the onboarded
config (./bot.yaml → ~/.praisonai/bot.yaml →
./gateway.yaml alias) and prints
"Using gateway config: <path>". Exits 78 with
an onboard hint if none is found.
--reliability [production|default|off]
Reliability preset — composes drain + admission.
Omitting the flag is safe by default (#3442): the CLI
resolves a bounded admission ceiling + fair queue with a
bind-aware drain (5s on loopback, 15s on external binds).
production (15s drain regardless of bind), default (5s
drain, no admission), off (pre-3438 immediate teardown).
Overrides `reliability:` from YAML but is overridden
by explicit --drain-timeout / --max-concurrent-runs.
--preflight / --no-preflight Validate channel credentials before starting.
A channel with a bad token is reported and
skipped (parked degraded, auto-recovers); the
gateway still serves every healthy channel.
Use --no-preflight to skip probing entirely,
or --strict-preflight to abort on any bad token
[default: --preflight]
--strict-preflight / --no-strict-preflight
Fail fast and abort the whole gateway if ANY
channel credential is bad, instead of isolating
that one channel as degraded. Defaults to the
config's gateway.preflight.strict (off) (#4862)
--strict-tools / --no-strict-tools
Fail fast if any tool named in the config
cannot be resolved. Use --no-strict-tools to
skip unresolved tools and start anyway (#3553)
[default: --strict-tools]
--verify-turn / --no-verify-turn
Run one real agent turn before serving so a
missing/invalid model credential fails at
startup, not on the first user message.
Default: on (reads
gateway.preflight.verify_turn, defaults to
true). Use --no-verify-turn for constrained/
offline environments (#4042).
--identity-store TEXT Path to the cross-platform identity link-map
JSON (default ~/.praisonai/identity.json).
Enables one continuous session + memory per
paired/linked user across channels (#3020).
Overrides the `identity:` block in gateway.yaml.
--scale-to-zero Quiesce the gateway when idle for --idle-minutes
(scale-to-zero; #3021). Overrides
lifecycle.scale_to_zero.enabled in gateway.yaml.
--idle-minutes FLOAT Minutes of no inbound / in-flight work before
quiescing (#3021). Overrides
lifecycle.scale_to_zero.idle_minutes.
--drain-marker TEXT Path to watch for an epoch-aware external drain
marker file (#3021). Overrides
lifecycle.drain.marker_path.
--trusted-proxy TEXT CIDR/IP of an upstream proxy or tunnel to trust
for real-client-IP resolution (repeatable,
e.g. --trusted-proxy 10.0.0.0/8). Behind a
reverse proxy this keeps per-IP rate-limits and
the operator id keyed on the real client;
unattributable proxies fail closed (#5312).
Overrides gateway.trusted_proxies in YAML.
Examples:
praisonai gateway start # onboarded: discovers ~/.praisonai/bot.yaml
praisonai gateway start --config gateway.yaml # explicit path still wins
praisonai gateway start --agents agents.yaml --port 9000
praisonai gateway start --config gateway.yaml --no-preflight
praisonai gateway start --config gateway.yaml --strict-preflight # opt into fail-closed
praisonai gateway start --config gateway.yaml --no-strict-preflight # override YAML strict:true
praisonai gateway start --config gateway.yaml --no-strict-tools
praisonai gateway start --config gateway.yaml --no-verify-turn
praisonai gateway start --config gateway.yaml --reliability production
GATEWAY_PORT=9000 praisonai gateway start
# Enable cross-platform continuity without touching gateway.yaml
praisonai gateway start --config gateway.yaml \
--identity-store ~/.praisonai/identity.json
# Enable scale-to-zero + external drain marker without touching gateway.yaml
praisonai gateway start --config gateway.yaml \
--scale-to-zero --idle-minutes 10 \
--drain-marker /data/gateway.drain
# Trust upstream proxies for real-client-IP resolution (repeatable, additive)
praisonai gateway start --config gateway.yaml \
--trusted-proxy 10.0.0.0/8 \
--trusted-proxy 192.168.1.10stop
praisonai gateway stop [OPTIONS]
Options:
--host TEXT Gateway host [default: 127.0.0.1]
--port INTEGER Gateway port [default: 8765 or $GATEWAY_PORT]
--force Force stop (kill process)
Examples:
praisonai gateway stop
praisonai gateway stop --port 9000
praisonai gateway stop --forcestatus
praisonai gateway status [OPTIONS]
Options:
--host TEXT Gateway host [default: 127.0.0.1]
--port INTEGER Gateway port [default: 8765 or $GATEWAY_PORT]
--daemon-only Show only daemon status
Examples:
praisonai gateway status
praisonai gateway status --port 9000
praisonai gateway status --daemon-onlyWhat it reports
status probes /health and /info, then reports reachability and whether your credential is accepted.
| Line | Meaning |
|---|---|
Access: authorized (scopes: read, write) | /info returned 200; your token’s operator scopes are listed |
Access: credential refused (HTTP 403) | The gateway answered 401/403 |
Access: unknown (/info returned HTTP 500) | No auth decision was made (timeout, 404, 5xx) |
Next step: ... | A follow-up action, when one applies |
$ praisonai gateway status
Gateway: reachable at http://127.0.0.1:8765
Access: authorized (scopes: read)
Next step: credential lacks operator write/admin scope; grant it via
gateway.auth_scopes in the gateway config$ praisonai gateway status
Gateway: reachable at http://127.0.0.1:8765
Access: credential refused (HTTP 403)
Next step: GATEWAY_AUTH_TOKEN was rejected; it does not match the
running gateway's auth tokenJSON output
--json adds an access block next to reachable.
{
"reachable": true,
"access": {
"authorized": true,
"scopes": ["read", "write"]
}
}| Field | Type | When present | Meaning |
|---|---|---|---|
authorized | true / false / null | Always, inside access | true → /info returned 200; false → 401/403; null → no auth decision (timeout, 404, 5xx) |
scopes | list[str] | authorized is true | Your token’s effective operator scopes |
http_status | int / null | authorized is false or null | HTTP status /info returned, or null if nothing answered |
next_step | str | When a follow-up applies | Named action from the table below |
access is omitted when reachable is false.
| Situation | next_step |
|---|---|
Authorized, but no write or admin scope | credential lacks operator write/admin scope; grant it via gateway.auth_scopes in the gateway config |
| 401/403 on a non-loopback host | `the CLI only sends GATEWAY_AUTH_TOKEN to loopback; run praisonai gateway status on the gateway host` |
| 401/403 on loopback, no token found | `no GATEWAY_AUTH_TOKEN found; export it or run praisonai gateway doctor –fix ` |
| 401/403 on loopback, token present | GATEWAY_AUTH_TOKEN was rejected; it does not match the running gateway's auth token |
doctor
praisonai gateway doctor [OPTIONS]
Options:
-c, --config TEXT Path to gateway.yaml [default: gateway.yaml — resolves to
the onboarded ~/.praisonai/bot.yaml when present]
--json Output JSON
--channel TEXT Channel a --turn probe uses (default: first configured)
--turn TEXT Run one offline inbound agent turn via
BotSessionManager.chat (requires LLM API key)
--fix Repair safe findings (mint a strong gateway auth token
when weak/missing), then re-validate
--dry-run With --fix, preview repairs without writing anything
Examples:
praisonai gateway doctor
praisonai gateway doctor --fix
praisonai gateway doctor --fix --dry-run
praisonai gateway doctor --config my-gateway.yaml --json
praisonai gateway doctor --config gateway.yaml --channel slack --turn "Say OK"
praisonai gateway doctor --config gateway.yaml --json --channel slack --turn "Say OK"test
praisonai gateway test [OPTIONS]
Options:
-c, --config TEXT Path to gateway.yaml [default: gateway.yaml — resolves to
the onboarded ~/.praisonai/bot.yaml when present]
--json Output JSON (single top-level document)
--channel TEXT Target channel for --turn (must exist in config)
--turn TEXT Offline inbound agent turn via BotSessionManager.chat
(requires LLM API key)
--check-running Probe REST /info on the running gateway to confirm
reachability
Examples:
praisonai gateway test
praisonai gateway test --config gateway.yaml
praisonai gateway test --config gateway.yaml --channel slack \
--turn "run uname -a with execute_command"
praisonai gateway test --config gateway.yaml --check-running
praisonai gateway test --config gateway.yaml --jsontest combines credential probes with offline shell wiring validation, then layers optional checks (--turn, --check-running). See Gateway Readiness for the full three-tier checklist.
mint-link
praisonai gateway mint-link [OPTIONS]
Options:
--ttl INTEGER Link time-to-live in seconds [default: 600]
Examples:
praisonai gateway mint-link
praisonai gateway mint-link --ttl 3600mint-link prints a fresh pairing/magic link that binds a cross-platform identity to the gateway, and also writes it to ~/.praisonai/last-link.txt (mode 600). The link resolves against GATEWAY_HOST / GATEWAY_PORT (defaulting to 127.0.0.1:8765). It exits 1 when magic-link support is unavailable.
sessions
praisonai gateway sessions <list|show|export|import> [OPTIONS]
Examples:
praisonai gateway sessions list # inspect per-channel entries
praisonai gateway sessions show bot_slack_U123 --tail 20
praisonai gateway sessions export --out backup.json # back up all sessions
praisonai gateway sessions import --in backup.json # restore (inert until re-bound)sessions inspects — and can prune, back up, or restore — the per-channel session store so an operator can manage live conversations without opening the store by hand.
list / show:
| Subcommand | Flags | Description |
|---|---|---|
list | --platform, --active SECONDS, --json | List active/known sessions |
show SESSION_REF | --tail 20, --json | Show one session’s last N transcript lines |
export:
| Flag | Type | Default | Description |
|---|---|---|---|
--session-id, -s | str? | None | Export just this session (lineage-aware); default = all |
--out, -o | str? | stdout | Atomic write via temp file + os.replace |
--no-lineage | bool | False | Skip compacted/rotated ancestors for a single-session export |
import:
| Flag | Type | Default | Description |
|---|---|---|---|
--in, -i | str | required | Read payload from this file |
--overwrite | bool | False | Overwrite sessions that already exist |
--keep-live-fields | bool | False | Do not reset live routing/activity fields |
--max-sessions | int | 10_000 | Cap ingest |
--json | bool | False | Output the ImportReport as JSON |
See Gateway Session Portability for the full backup / migrate / restore story.
logs
praisonai gateway logs [OPTIONS]
Options:
-n INTEGER Number of trailing lines to show
Examples:
praisonai gateway logs
praisonai gateway logs -n 100logs tails the gateway’s stdout/stderr under the daemon runner (~/.praisonai/logs/bot-stderr.log on macOS; journalctl --user -u praisonai-bot on Linux).
send
praisonai gateway send [OPTIONS]
Options:
--channel TEXT Channel to push through (e.g. telegram)
--channel-id TEXT Destination id on that channel
-m, --message TEXT Message body
Examples:
praisonai gateway send --channel telegram --channel-id 12345 -m "test"send pushes a one-off message through the outbound messenger so you can smoke-test delivery to a real channel without waiting for an inbound trigger.
channels
praisonai gateway channels [OPTIONS] [SUBCOMMAND]
Options (list / probe when no subcommand is given):
-c, --config TEXT Path to gateway.yaml [default: gateway.yaml — resolves to
the onboarded ~/.praisonai/bot.yaml when present]
--json Output JSON format
--probe Probe each channel's credentials
--available List all registered platforms
Subcommands (edit one channel in place):
add <name> [--platform P] [--token T] [--set k=v]...
update <name> [--token T] [--set k=v]...
disable <name>
remove <name>
Examples:
praisonai gateway channels # list
praisonai gateway channels --probe # list + credential probe
praisonai gateway channels add telegram # token defaults to ${TELEGRAM_BOT_TOKEN}
praisonai gateway channels add discord --set group_policy=mention_only
praisonai gateway channels update telegram --token '${NEW_TOKEN}'
praisonai gateway channels disable slack # keep config, stop serving
praisonai gateway channels remove slack # delete the blockSee Gateway Channel Lifecycle for every option and validation rule.
pause
praisonai gateway pause <channel-name> [OPTIONS]
Options:
--host TEXT Gateway host [default: 127.0.0.1]
--port INTEGER Gateway port [default: 8765 or $GATEWAY_PORT]
--url TEXT Gateway WebSocket URL — resolved from --host/--port when omitted
Examples:
praisonai gateway pause telegram
praisonai gateway pause telegram --port 9000 # non-default port
GATEWAY_PORT=9000 praisonai gateway pause telegram # via env
praisonai gateway pause discord --url ws://localhost:8000 # explicit URL still winsresume
praisonai gateway resume <channel-name> [OPTIONS]
Options:
--host TEXT Gateway host [default: 127.0.0.1]
--port INTEGER Gateway port [default: 8765 or $GATEWAY_PORT]
--url TEXT Gateway WebSocket URL — resolved from --host/--port when omitted
Examples:
praisonai gateway resume telegram
praisonai gateway resume telegram --port 9000 # non-default port
GATEWAY_PORT=9000 praisonai gateway resume telegram # via env
praisonai gateway resume discord --url ws://localhost:8000 # explicit URL still winsreconnect
praisonai gateway reconnect <channel-name> [OPTIONS]
Options:
--host TEXT Gateway host [default: 127.0.0.1]
--port INTEGER Gateway port [default: 8765 or $GATEWAY_PORT]
--url TEXT Gateway WebSocket URL — resolved from --host/--port when omitted
Examples:
praisonai gateway reconnect telegram
praisonai gateway reconnect telegram --port 9000 # non-default port
GATEWAY_PORT=9000 praisonai gateway reconnect telegram # via env
praisonai gateway reconnect discord --url ws://localhost:8000 # explicit URL still winsConfig discovery
You no longer need --config if you have onboarded — praisonai gateway start discovers the config praisonai onboard wrote. Every gateway command (start, doctor, test, status, send, channels) uses the same canonical resolution order, so onboard, start, and doctor cannot drift apart.
praisonai onboard # writes ~/.praisonai/bot.yaml
praisonai gateway start # discovers it — no flag needed| Precedence | Source | Notes |
|---|---|---|
| 1 | --config <path> / --agents <path> | Explicit path always wins |
| 2 | ./bot.yaml (cwd) | Local project override |
| 3 | $PRAISONAI_BOT_CONFIG / ~/.praisonai/bot.yaml | Env var — PRAISONAI_HOME-aware; where praisonai onboard writes |
| 4 | ./gateway.yaml (cwd) | Backward-compatible alias — legacy name |
| — | none | Print onboard hint, exit 78 |
start with no config
praisonai gateway start (no --config, no --agents) discovers the onboarded config, prints Using gateway config: <path>, then binds with channels instead of the earlier silent WebSocket-only no-op. If nothing is found it prints a hint and exits 78:
$ praisonai gateway start
No gateway config found. Run 'praisonai onboard' to create one, or pass
--config <path> (or --agents <path> for single-agent mode).
$ echo $?
78Passing --agents <path> selects single-agent mode and skips channel-config discovery.
doctor / test / status / send / channels
These commands default --config to a gateway.yaml sentinel. When you leave it at the default, they run the same discovery order as start — so praisonai gateway doctor after praisonai onboard inspects the onboarded ~/.praisonai/bot.yaml, not a file onboarding never wrote. An explicit --config always wins.
Version validation
praisonai gateway start also validates the config’s config_version before binding, applying the same forward-migration doctor --fix uses. A config from a newer build refuses to start (exit 78) rather than being silently downgraded. See Gateway Config Migration › Config version stamp.
What the user sees
The whole reason this discovery exists: before, an onboarded operator who ran praisonai gateway start with no flag started silently channel-less, so their Telegram user got no reply.
For a focused, standalone walkthrough of this resolution, see Gateway Config Discovery. Start-time version validation is documented in Config Migration → At start time.
Pre-flight credential check
praisonai gateway doctor validates every channel’s token before the gateway starts, so a bad or expired credential fails fast with a precise per-channel reason instead of disappearing into the supervisor’s silent reconnect loop.
Examples
Quick health check:
$ praisonai gateway doctor
telegram ✓ @my_support_bot
slack ✗ SSL certificate verify failed (network/proxy?). Token may still be valid. Try SSL_CERT_FILE=/path/to/corp-ca.pem or gateway start --no-preflight
discord ✓ @MySupportA non-SSL failure keeps the bare error string:
$ praisonai gateway doctor
telegram ✓ @my_support_bot
slack ✗ invalid_auth (token expired)
discord ✓ @MySupportCI-friendly JSON — a single document with a probes block (and a secrets block when any channel uses a secret reference):
$ praisonai gateway doctor --json
{
"probes": {
"telegram": {"ok": true, "platform": "telegram", "bot_username": "my_support_bot"},
"slack": {"ok": false, "platform": "slack", "error": "invalid_auth"},
"discord": {"ok": true, "platform": "discord", "bot_username": "MySupport"}
},
"secrets": {
"telegram": {"token": "available"},
"slack": {"token": "available", "app_token": "configured-but-unavailable"},
"discord": {"token": "configured"}
}
}Same verdict via the listing command:
$ praisonai gateway channels --probeCredential availability (without revealing values)
praisonai gateway doctor also prints a per-channel credential availability table so operators can validate secret wiring — including the secret-reference form on token, app_token, and verify_token — without ever printing a value.
$ praisonai gateway doctor
Credential availability (values never shown):
telegram token ✓ available
slack token ✓ available
slack app_token ✗ configured-but-unavailable
whatsapp verify_token ✗ missing| State | Meaning |
|---|---|
available | Resolved successfully (env set, file present and non-empty) |
configured-but-unavailable | Configured but cannot resolve (empty file, unreadable, empty env var) |
configured | An exec reference — configured, but not executed at probe time |
missing | Not set (env var unset, file not found) |
An exec-sourced reference reports configured without running its command: the command has side effects (a one-shot / rate-limited / rotating secret-manager call) and the network probe resolves the same reference moments later, so executing it here would run it twice.
These states now map cleanly to runtime isolation at boot — a configured-but-unavailable or missing channel credential isolates just that channel as degraded rather than aborting the gateway. See Degraded Channel Isolation.
After boot, GET /health emits the same degraded / reason: "credential unavailable" verdict for a channel whose token is rejected at runtime (401/403, revoked/rotated/expired) — no new flags. See Runtime credential rejection.
The --json output is a single document with probes and secrets keys:
{
"probes": { "telegram": {"ok": true }, "slack": {"ok": false} },
"secrets": { "telegram": {"token": "available"},
"slack": {"token": "available", "app_token": "configured-but-unavailable"} }
}Weak-secret check
gateway doctor also flags the gateway’s own auth_token when it matches a well-known placeholder — fails on an external bind, warns on loopback.
On an external bind with a weak token, doctor prints the full refusal message and exits 1:
$ praisonai gateway doctor --config gateway.yaml
Refusing to start: gateway.auth_token is a known-weak/placeholder value.
A publicly-known secret provides no real authentication.
Fix: praisonai onboard (30 seconds, 3 prompts)
Or: export GATEWAY_AUTH_TOKEN="$(openssl rand -hex 16)" (run in a shell so the command is expanded, not pasted literally)On a loopback bind, doctor prints a warning and still exits 0:
$ praisonai gateway doctor --config gateway.yaml
⚠ gateway.auth_token is a known-weak/placeholder value (loopback bind 127.0.0.1). Rotate before exposing externally.A strong token prints nothing extra — silence is a pass, consistent with the credential-availability table.
The --json output gains a top-level gateway_auth_token key (present only when the verdict is weak), alongside probes and secrets. When --fix is passed, an optional fix key carries the repair report:
{
"probes": { "telegram": {"ok": true} },
"secrets": { "telegram": {"token": "available"} },
"gateway_auth_token": "weak",
"fix": "gateway_auth_token: weak → generated a strong token… done\nre-validated: gateway_auth_token now strong"
}| Verdict | Bind | Exit code |
|---|---|---|
Weak / missing auth_token | External | 1 |
Weak auth_token | Loopback | 0 (warning only) |
Strong auth_token | Any | 0 |
Full denylist, bind-aware behaviour matrix, and how to fix a weak change-me-style token
Auto-repair with --fix
--fix repairs safe findings, then re-validates that each finding cleared.
Repair a weak token, then confirm it cleared:
$ praisonai gateway doctor --config gateway.yaml --fix
gateway_auth_token: weak → generated a strong token… done
re-validated: gateway_auth_token now strongPreview the repair without writing anything:
$ praisonai gateway doctor --config gateway.yaml --fix --dry-run
gateway_auth_token: weak → would mint a strong token (--dry-run)| Finding | --fix action | Persistence |
|---|---|---|
gateway.auth_token weak/missing | Mint secrets.token_hex(16), export to env, persist to ~/.praisonai/.env | Survives daemon restart |
gateway.auth_token explicit weak in YAML | Also rewrites gateway.yaml in place (preserves ${ENV} references) | Config file updated |
| Any other finding | Not repaired yet (no dead-end: still surfaced with its own retry_hint) | — |
--dry-run still exits 1 when a repairable finding is present, so CI catches un-fixed states before anything is written.
Pre-flight gate on start
praisonai gateway start runs the same probe automatically before launch when invoked with --config gateway.yaml. By default a bad token isolates only that channel — every healthy channel keeps serving:
# Default: isolate the failing channel, serve the rest
$ praisonai gateway start --config gateway.yaml
telegram ✗ invalid_auth (token expired)
slack ✓
discord ✓
Pre-flight: 2 channel(s) OK; skipping telegram as configured-unavailable
(auto-recovers when the credential is fixed). See `gateway status`.Opt into strict mode to restore the old fail-closed behaviour — or preflight fails closed on its own when no channel is serviceable:
# --strict-preflight (opt-in) or when NO channel is serviceable: fail closed
$ praisonai gateway start --config gateway.yaml --strict-preflight
telegram ✗ invalid_auth (token expired)
slack ✓
Pre-flight check failed — aborting start (--strict-preflight set). Fix the
channel credentials above, or pass --no-preflight to skip /
--no-strict-preflight to isolate the degraded channel and serve the rest.To bypass probing during local dev (e.g. flaky probe network):
praisonai gateway start --config gateway.yaml --no-preflightRoute/binding target check
praisonai gateway doctor also validates that every routes, routing, and bindings.agent target names an agent declared in the sibling agents: map. A typo — including in the default slot — fails with a “did you mean X?” hint instead of silently misrouting traffic.
$ praisonai gateway doctor --config gateway.yaml
ERROR: Invalid gateway/bot configuration:
channel 'telegram' route 'dm' -> unknown agent 'personl'; did you mean 'personal'? valid agents: personal, supportThis runs only for multi-agent configs (an agents: map is present). Single-bot configs (top-level platform + token) are unaffected. See Fail-Fast Validation for the full behaviour.
Corporate CA bundle (SSL-inspecting networks)
On networks that intercept TLS with a corporate CA (proxy / MITM), the probe’s HTTP client can reject the certificate chain even though the token is valid and the runtime adapter connects fine. Preflight classifies these SSL cert-verify failures separately and soft-fails when they are the only failures.
$ praisonai gateway start --config gateway.yaml
slack ✗ SSL certificate verify failed (network/proxy?). Token may still be valid. Try SSL_CERT_FILE=/path/to/corp-ca.pem or gateway start --no-preflight
Pre-flight found SSL certificate-verify failures only (likely a proxy/MITM
network). Tokens may still be valid — continuing start. Set SSL_CERT_FILE /
REQUESTS_CA_BUNDLE / PRAISONAI_SSL_CA_BUNDLE to your corporate CA, or pass
--no-preflight to skip this check.The probe decides its action from the mix of failures and whether strict mode is set:
| Probe outcome (across all channels) | strict off (default) | strict on |
|---|---|---|
All channels ok | Start proceeds silently | Start proceeds silently |
| ≥1 channel fails with a non-SSL error, ≥1 healthy | Isolate the failing channel(s) as CREDENTIAL_UNAVAILABLE, serve the rest, exit 0 | Hard-abort, exit 1 |
| Every channel fails with a non-SSL error (no healthy channel) | Hard-abort (nothing left to serve), exit 1 | Hard-abort, exit 1 |
| All failures are SSL cert-verify only | Soft-fail — print the SSL warning, continue to start, exit 0 | Soft-fail (unchanged) |
| Mixed: ≥1 SSL cert-verify and ≥1 non-SSL failure | Isolate the non-SSL failure(s), SSL warning printed, exit 0 if any channel remains healthy | Hard-abort, exit 1 |
Point the probe at your corporate CA with one of three env vars, highest precedence first:
| Variable | Precedence | Behaviour |
|---|---|---|
PRAISONAI_SSL_CA_BUNDLE | Highest — explicit PraisonAI override | Overrides pre-existing SSL_CERT_FILE and REQUESTS_CA_BUNDLE for the probe. |
REQUESTS_CA_BUNDLE | Middle | Used only if PRAISONAI_SSL_CA_BUNDLE is unset. Left alone when the winner came from elsewhere. |
SSL_CERT_FILE | Lowest — Python default | Read by Python’s default ssl context (used by aiohttp). Used only if the two above are unset. |
export PRAISONAI_SSL_CA_BUNDLE=/path/to/corp-ca.pem
praisonai gateway start --config gateway.yamlA configured-but-missing path warns and leaves the SSL env vars untouched:
Warning: CA bundle path '<path>' does not exist — SSL_CERT_FILE / REQUESTS_CA_BUNDLE not updated for probe.Token resolution
The probe loads ~/.praisonai/.env first, so ${VAR} placeholders set by praisonai onboard resolve exactly like they do at runtime — doctor, channels --probe, and start --preflight all share the same token-resolution path.
Per-channel timeout
Each probe is bounded by a 15-second deadline; a stuck adapter is reported as "probe timed out after 15s" and does not hang the aggregate.
Exit codes
| Outcome | Exit code |
|---|---|
| All channels probe OK | 0 |
| Any channel fails | 1 |
Weak / missing gateway auth_token on an external bind | 1 |
Weak gateway auth_token on a loopback bind (warning only) | 0 |
gateway doctor with no channels configured | 0 |
When to use which
Programmatic check (Python)
import asyncio
from praisonai.bots import Bot, BotOS
botos = BotOS(
bots=[
Bot("telegram", token="..."),
Bot("slack", token="..."),
],
enable_supervision=False,
)
results = asyncio.run(botos.probe_all(timeout=15.0))
for platform, r in results.items():
print(platform, "ok" if r.ok else r.error)Pre-flight tool check
praisonai gateway start validates every tool named in your config before the gateway binds, so a typo or an uninstalled optional package fails fast with a per-name reason instead of silently starting a quietly under-powered bot.
This gate runs one layer above the core resolver’s PRAISONAI_STRICT_TOOLS / ToolResolutionError mechanism — it fires at start-time, before GatewayHandler.start() builds any agent. See Tool Resolution for the complementary core-resolver behaviour.
Strict mode (default) — fail fast
$ praisonai gateway start --config gateway.yaml
✗ Tool pre-flight failed:
- 'duckduckgo_serch' not found. Did you mean 'duckduckgo'?
Fix the names in your config, or start with --no-strict-tools to run without them.
$ echo $?
78Non-strict — warn and continue
Two equivalent ways:
CLI flag
praisonai gateway start --config gateway.yaml --no-strict-toolsYAML key
# gateway.yaml
strict_tools: false
agents:
assistant:
instructions: "..."
tools: [duckduckgo_serch] # unresolved but toleratedEither produces:
⚠ Tool pre-flight (non-strict) — starting without:
- 'duckduckgo_serch' not found. Did you mean 'duckduckgo'?“Did you mean?” mechanics
Suggestions use stdlib difflib.get_close_matches (cutoff 0.7, one suggestion per unknown). A tool that is registered but currently unloadable (e.g. an optional package not installed) is excluded from its own candidates, so it yields an install hint rather than suggesting the same name back:
'firecrawl' not found in any source. Check the spelling, install the package
that provides it, or set PRAISONAI_ALLOW_LOCAL_TOOLS=true for a local tools.py.A project tools.py that exists but is gated by the local-tools env var is flagged explicitly:
'my_local' not found. A local tools.py exists but local tools are disabled;
set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable it.An unknown toolset name is reported the same way:
- toolset:research not found (unknown toolset)Precedence
| Setting | Result |
|---|---|
YAML strict_tools: false | Gate disabled (regardless of CLI flag) |
CLI --no-strict-tools | Gate disabled for this invocation |
CLI --strict-tools (default) with no YAML override | Gate strict → fail fast on any unresolved tool |
| No unresolved tools | Pre-flight is silent, start proceeds |
YAML strict_tools: false opts a bot out permanently; --no-strict-tools is per-invocation. A strict CLI default does not override a YAML strict_tools: false.
Env loading
The gate loads ~/.praisonai/.env before resolving, so PRAISONAI_ALLOW_LOCAL_TOOLS=true stored there by praisonai onboard is honoured and a gated local tools.py is not falsely reported as unresolved. The load is idempotent and existing process env wins — GatewayHandler.start() re-loads the same file moments later.
Exit code
Strict failure exits with 78 (EX_CONFIG), which the OS units generated by praisonai gateway install treat as do-not-restart — an unresolvable tool in the config would loop the daemon otherwise. See Gateway Exit Codes.
Environment Variables
| Variable | Description | Default |
|---|---|---|
GATEWAY_PORT | Port for start/stop/status when –port is not passed | 8765 |
Supervisor exit codes
praisonai gateway start returns a supervisor-friendly exit code so a service manager (launchd / systemd / a scheduled task) knows whether to restart the gateway or stop and wait for an operator.
| Code | Meaning | When you see it |
|---|---|---|
0 | Clean shutdown / success | Ctrl+C, SIGTERM drain, stop, a healthy status |
75 | Transient failure — supervisor should restart | Network blip, port contention, transient IO |
78 | Fatal configuration error — supervisor should stop | Bad gateway.yaml, no platforms, duplicate token, unresolved strict tool |
The installed OS unit maps 78 (EX_CONFIG) to do-not-restart so a broken config cannot crash-loop the daemon, and treats 75 (EX_TEMPFAIL) as restartable. See Gateway Exit Codes for the full contract and per-platform wiring.
Single-Instance Enforcement
PraisonAI enforces two locks at gateway start: a port lock (one gateway per host:port) and a state lock (one gateway per shared state root, ~/.praisonai). At most one gateway may own ~/.praisonai at a time, whatever port it uses.
| Lock | File | Keyed on |
|---|---|---|
| Port lock | ~/.praisonai/gateway-<safe_host>-<port>.pid | host + port |
| State lock | ~/.praisonai/gateway.state.pid | home root (~/.praisonai) |
The safe_host replaces : and . with _ (so 127.0.0.1 becomes 127_0_0_1).
State lock file
Two gateways on different ports that share ~/.praisonai would poll the same bot tokens and write the same outbox, DLQ, pairing and hook stores. That causes Telegram 409 errors and duplicate delivery, so the second gateway is refused:
Error: another gateway already owns the state at /home/user/.praisonai (PID 12345 on 127.0.0.1:8765).
Two gateways sharing state poll the same bot tokens and write the same
queues, causing 409 Conflict errors and duplicate delivery.
Stop it: praisonai gateway stop
Or run an isolated gateway with its own state and bot tokens:
HOME=/srv/gateway-b praisonai gateway start- Path:
~/.praisonai/gateway.state.pid, with a sidecargateway.state.pid.guard. - Keyed on the home root. The state always lives at
~/.praisonai. ChangingPRAISONAI_HOMEor the port does not move it, so a second gateway with only a differentPRAISONAI_HOMEor port is still refused. - Held for the whole run. Claimed right after the port lock — or, when starting from
gateway.yaml, before channels start (before the inbound journal is replayed). Released only after channels stop; a failed start releases it so restarts are not blocked. - Exclusive claim. A POSIX
flockon the.guardfile serialises the check, and the lock is created withos.link(falling back toO_EXCL), so two simultaneous starts can never both win. The port lock uses the same claim. - Stale reclaim. Uses the same PID + start-time check as the port lock, so a lock left by a crashed gateway is reclaimed on the next start.
- Same-process safe. Re-claiming from the same PID succeeds, so in-process restarts are not blocked.
Run a second, isolated gateway
To run two gateways on one host, give the second one its own HOME (so its own ~/.praisonai state), its own bot tokens and its own port:
# Shell 1 — default gateway
praisonai gateway start
# Shell 2 — isolated gateway with its own state and bot tokens
HOME=/srv/gateway-b GATEWAY_PORT=8766 praisonai gateway startI changed PRAISONAI_HOME but still get the state collision
PRAISONAI_HOME does not move the gateway state — the lock is keyed on ~/.praisonai. Set HOME= instead.
Both isolated gateways still see 409 from Telegram
The state is isolated but both gateways use the same bot token. Each gateway needs its own bot and token.
PID-Lock Status Reference
praisonai gateway status prints one PID-lock line for the port lock before the /health probe (the state lock is not shown). Look up any line you see:
| Line printed | When |
|---|---|
Gateway PID lock: Process <pid> running (<host>:<port>) | Lock file exists, PID responds to os.kill(pid, 0) and the recorded start-time fingerprint matches |
Gateway PID lock: Stale lock (process <pid> not running) | Lock file exists, but the PID no longer responds or its start time no longer matches the fingerprint (including Windows SystemError/ValueError cases) |
Gateway PID lock: No lock file found | No lock file at ~/.praisonai/gateway-<safe_host>-<port>.pid |
PID lock status: Utilities not available | port_utils import failed (ImportError) |
PID lock status: Unavailable (<error>) | Any other unexpected exception during PID-lock inspection; the /health probe still runs |
The port line (Port <host>:<port>: In use / Available) follows the same section unless PID-lock inspection fails, in which case the single PID lock status: Unavailable (<error>) line replaces both.
Restart the Gateway
Use praisonai gateway restart — it drains in-flight turns, then relaunches (via the installed service manager when present, otherwise directly).
# Simple restart with default 10s drain
praisonai gateway restart
# Restart pointing at a config (needed only for the direct-relaunch fallback)
praisonai gateway restart --config gateway.yaml
# Longer drain window for slow agent turns
praisonai gateway restart --drain-timeout 30
# Restart a gateway running on a non-default endpoint
praisonai gateway restart --host 127.0.0.1 --port 9000
# equivalently: GATEWAY_PORT=9000 praisonai gateway restartOptions
| Flag | Type | Default | Description |
|---|---|---|---|
--host | str | 127.0.0.1 | Gateway host (used to locate the running instance) |
--port | int | GATEWAY_PORT env, else 8765 | Gateway port |
--config | str | none | Path to gateway.yaml for direct-relaunch fallback |
--agents | str | none | Path to agent configuration file for direct-relaunch fallback |
--drain-timeout | Optional[float] | _persisted start value, else 10.0_ | Seconds to wait for in-flight agent turns before relaunch. Omit to replay the persisted start value; explicit value wins over the persisted one and applies to both the OLD-process drain and the relaunch. |
Persisted start-flags artefact
praisonai gateway start persists the CLI-only runtime flags it was launched with to ~/.praisonai/gateway.start.<host>.<port>.json so a later praisonai gateway restart (direct, non-service path) reproduces the exact posture instead of silently reverting to defaults.
sequenceDiagram
participant Op as Operator
participant Start as gateway start
participant Disk as gateway.start.host.port.json
participant Restart as gateway restart
Op->>Start: gateway start --openai-api --reliability production --max-concurrent-runs 8
Note over Start: validate config / agents / bind slot
Start->>Disk: write { openai_api, reliability, max_concurrent_runs }
Start-->>Op: bound on 127.0.0.1:8765
Op->>Restart: gateway restart
Restart->>Disk: load persisted flags
Restart->>Start: relaunch (persisted, drain = persisted OR 10.0)
Start-->>Op: same posture, no silent revert
classDef op fill:#8B0000,stroke:#7C90A0,color:#fff
classDef cli fill:#189AB4,stroke:#7C90A0,color:#fff
classDef disk fill:#6366F1,stroke:#7C90A0,color:#fff
class Op op
class Start,Restart cli
class Disk diskPath: ~/.praisonai/gateway.start.<safe_host>.<port>.json (or $PRAISONAI_HOME/gateway.start.<safe_host>.<port>.json when set). safe_host replaces : with _, so IPv6 hosts work. The artefact is keyed by host:port — multiple gateways on one machine each keep their own posture.
Persisted keys (only non-None values are stored — None means “fall back to YAML”):
| Key | Corresponds to |
|---|---|
agent_file | --agents |
config_file | --config |
drain_timeout | --drain-timeout |
max_concurrent_runs | --max-concurrent-runs |
queue_depth | --queue-depth |
overflow_policy | --overflow-policy |
reliability | --reliability |
openai_api | --openai-api |
mcp | --mcp |
identity_store | --identity-store |
scale_to_zero | --scale-to-zero |
idle_minutes | --idle-minutes |
drain_marker | --drain-marker |
Write timing: the artefact is written only after startup validation passes (config parse, agent-file parse, admission wiring), immediately before the gateway binds. A start attempt that fails validation on the same host:port never clobbers the running gateway’s saved posture — otherwise the next restart would faithfully replay a rejected attempt.
Precedence on restart:
An omitted --drain-timeout on restart no longer forces a fixed 10s window; it replays the persisted value (and only falls back to 10.0 when nothing is persisted). An explicit --drain-timeout still wins over the persisted value for both the OLD-process drain and the relaunch, so a long configured drain is not silently cut off. Fixes #3349.
Advanced: OS-native restart
If you need to bypass the CLI (e.g. from a systemd ExecStartPre), the same platform commands the daemon dispatcher uses are:
macOS
launchctl kickstart -k gui/$(id -u)/ai.praison.botLinux
systemctl --user restart praisonai-botWindows
schtasks /End /TN PraisonAIGateway && schtasks /Run /TN PraisonAIGatewayStatus Output Examples
Healthy Gateway
$ praisonai gateway status
Gateway PID lock: Process 12345 running (127.0.0.1:8765)
Port 127.0.0.1:8765: In use
Daemon service: Running (launchd)
Process ID: 12345
Gateway server: Reachable at http://127.0.0.1:8765/health
Status: healthy
Uptime: 3600.5 seconds
Agents: 2
Sessions: 1
Clients: 3
Channels: 2 configuredPID Lock Unavailable (Windows)
$ praisonai-bot gateway status --port 18789
PID lock status: Unavailable (kill returned a result with an exception set)
Gateway Status: healthy
Uptime: 11.4s
Agents: 1
Sessions: 0
Clients: 0Degraded Owners
praisonai gateway status prints a Degraded: section whenever health()["degraded_owners"] is non-empty. It is always printed (not gated on --deep) — an at-a-glance status must reveal a degraded state.
$ praisonai gateway status
...
Degraded:
✗ gateway:durability:idempotency — durable idempotency store unavailable (running in-memory) fix: praisonai gateway doctor --fix
✗ channel:slack — credential unavailable fix: praisonai gateway doctor --fixEach line follows the format ✗ <owner_kind>:<owner_id> — <reason> fix: <retry_hint>. The fix: suffix is omitted when retry_hint is empty. A durable webhook-idempotency or session store that fell back to in-memory shows here as gateway:durability:idempotency / gateway:durability:session; run praisonai gateway doctor --fix to repair, or a hot-reload that fixes the store path clears the entry automatically. See Gateway State Durability and Degraded Capabilities.
Daemon Issues
$ praisonai gateway status
Daemon service: Installed but not running (launchd)
Gateway not reachable at http://127.0.0.1:8765/healthNot Installed
$ praisonai gateway status --daemon-only
Daemon service: Not installed (systemd)Platform Support
Gateway CLI works across platforms with native daemon integration:
| Platform | Service Type | Log Location | Management |
|---|---|---|---|
| macOS | LaunchAgent (ai.praison.bot) | ~/.praisonai/logs/bot-stderr.log | launchctl kickstart -k gui/$(id -u)/ai.praison.bot |
| Linux | systemd user service (praisonai-bot) | journalctl --user -u praisonai-bot | systemctl --user restart praisonai-bot |
| Windows | Scheduled Task (PraisonAIGateway) | Windows Event Log | schtasks /End /TN PraisonAIGateway && schtasks /Run /TN PraisonAIGateway |
Configuration Files
agents.yaml
# Simple agent configuration
agent:
name: "support"
instructions: "You are a helpful support agent"
model: "gpt-4o-mini"
tools:
- search_web
memory: truegateway.yaml
# Multi-bot configuration
identity:
enabled: true # one session per paired user across channels (#3020)
store: ~/.praisonai/identity.json # optional; ~ expanded, `path:` accepted as alias
# Opt into the pre-2026-07-31 warn-and-continue behaviour permanently.
# Omit (or set true) to fail fast on any unresolved tool at start (#3553).
strict_tools: false
agents:
support:
instructions: "You are a support agent"
model: "gpt-4o-mini"
sales:
instructions: "You are a sales agent"
model: "claude-3-5-sonnet-20241022"
channels:
telegram:
token: "${TELEGRAM_BOT_TOKEN}"
routing:
default: support
discord:
token: "${DISCORD_BOT_TOKEN}"
routing:
default: sales
slack:
# Secret-reference form: read from a mounted file / secret manager
# instead of plaintext or a process-wide env var.
token: { source: file, id: /run/secrets/slack_token }
app_token: { source: exec, id: "vault read -field=token secret/slack" }
routing:
default: supportSee Secret References for the full { source, id } form on token, app_token, and verify_token.
Common Patterns
Diagnose → repair → verify
Inspect first, repair only if needed, then confirm the gateway came back healthy.
praisonai gateway doctor # read-only report
praisonai gateway doctor --fix # apply safe repairs, then re-validate
praisonai gateway status --deep # per-channel rows + reload stateZero-downtime posture change
restart drains in-flight turns for the given window, then relaunches — replaying the launch posture saved in ~/.praisonai/gateway.start.<host>.<port>.json, so the process comes back with the same flags it was started with.
praisonai gateway restart --drain-timeout 60Add an inbound trigger
Register a webhook that hands inbound events to an agent and delivers the reply to a channel.
praisonai gateway hooks add gmail --agent triage-agent --deliver-to slack:C123
praisonai gateway hooks listBest Practices
Use daemon-only for health checks
Use --daemon-only flag when monitoring daemon status in scripts or CI/CD pipelines to avoid gateway connection attempts.
Check logs for troubleshooting
Always check praisonai gateway logs when the daemon is running but gateway is unreachable – this reveals startup errors.
Test channel configuration
Use praisonai gateway send to test channel bot configuration before deploying to production environments.
Monitor daemon status regularly
Set up monitoring that runs praisonai gateway status --daemon-only to detect service failures quickly.
Set a runtime flag once — every restart replays it
Stop, restart with the new flag once, and every subsequent praisonai gateway restart replays it automatically — ~/.praisonai/gateway.start.<host>.<port>.json is your on-host record of what the process was launched with. To reset, delete the artefact.
Reset to YAML defaults by deleting the artefact
rm ~/.praisonai/gateway.start.<safe_host>.<port>.json before the next restart to revert to the YAML defaults (or an empty CLI baseline). A corrupt or unreadable artefact is treated as absent — the restart falls back to explicit flags and defaults.
Related
Gateway architecture and configuration
Config DiscoveryWhere every gateway command looks for the config when --config is omitted
Common gateway issues and solutions
Secret ReferencesLoad credentials from files, env vars, or secret managers
Cross-Platform Sessions--identity-store — one session per user across channels
Fail-fast validation — route/binding typos caught at config load
Exit CodesExit 78 from the strict tool pre-flight — do-not-restart contract
Core-resolver PRAISONAI_STRICT_TOOLS — complementary to this start-time gate