Gateway Overview
WebSocket gateway for multi-channel agent coordination and communication
from praisonaiagents import Agent
agent = Agent(name="gateway-agent", instructions="Route messages through the PraisonAI gateway.")
agent.start("Set up the gateway to handle Telegram and Slack messages.")The PraisonAI Gateway enables multi-channel agent deployment through a WebSocket server that coordinates communication between agents and various platforms.
The user starts the gateway and sends a message on a channel; the gateway routes it to the right agent and returns the reply.
Quick Start
- Install Gateway Dependencies
Install both bot and API dependencies for gateway functionality:
bash pip install "praisonai[bot,api]"The
[api]extra provides uvicorn, fastapi, and starlette required by the gateway server. - Zero-config path: just an env var
Set a platform token and start — no
channels:block required:bash export TELEGRAM_BOT_TOKEN=123:abc praisonai gatewayOne log line confirms the bot is live:
text Auto-enabled channel 'telegram' from TELEGRAM_BOT_TOKENSee Zero-Config Gateway for the full precedence rules and the env-var-to-channel mapping.
- Custom config path: route, restrict, or use a non-env token
When you want to route, restrict, or use a non-env token, create a
gateway.yamlfile:yaml gateway: host: "127.0.0.1" port: 8765 agents: assistant: model: gpt-4o-mini instructions: "You are a helpful assistant." channels: telegram: platform: telegram token: ${TELEGRAM_BOT_TOKEN} routes: default: assistant - Start the Gateway
Launch the gateway server. The env-only path needs no
--configflag:bash # Zero-config (env var only) praisonai gateway # With a custom config praisonai gateway start --config gateway.yamlThe gateway starts on port 8765 with WebSocket and HTTP health endpoints.
How It Works
| Component | Role |
|---|---|
| Gateway Server | Manages WebSocket connections and routes messages |
| Channels | Platform-specific integrations (Telegram, Discord, etc.) |
| Router | Directs messages to appropriate agents based on rules |
| Agents | Process messages and generate responses |
| Inbound Triggers | POST /hooks/<path> — start agent runs from external HTTP events (webhooks, CI, IoT). See Inbound Hooks. |
| Outbound Sinks | gateway.hooks_outbound — run a shell command on lifecycle events (session_end, gateway_stop, …). See Outbound Hooks. |
| Webhook Ingress | POST /webhooks/<channel> — signature-verified platform-webhook ingress for mode: webhook channels. See Shared Webhook Ingress. |
Two inbound surfaces on one listener
The gateway’s single listener hosts two distinct inbound paths:
/hooks/<path>— the declarative inbound trigger surface: ad-hoc HTTP-to-agent triggers you define withhooks:entries./webhooks/<channel>— signature-verified platform-webhook ingress formode: webhookchannels, each verified fail-closed by its own per-channel verifier.
Leave webhook_port unset on a mode: webhook channel and it serves through /webhooks/<channel> on gateway.port — one URL for every channel. See Shared Webhook Ingress.
Architecture Principles
Single Instance Rule
Critical: Only run one gateway process per state root (~/.praisonai). Two gateways sharing it would:
- Both poll the same Telegram token (causes 409 conflicts)
- Both write the same outbox, DLQ and pairing stores (split-brain state)
The gateway enforces this at startup with a state lock — a second gateway on another port but the same ~/.praisonai is refused. See Single-Instance Enforcement.
Channel Isolation
Each channel operates independently:
- Separate token per platform
- Independent routing rules
- Isolated session management
- Per-channel error handling
Live Config Reload
The gateway diffs gateway.yaml against the running config and restarts only affected agents or channels — and for three timing/logging keys, applies the change in place with no restart at all. Invalid YAML saves keep the last-known-good config. The WebSocket server is never restarted. See Gateway Hot-Reload.
Fail-Safe Design
The gateway implements fail-safe patterns:
- Health checks at
/healthendpoint - Automatic reconnection for platform polling
- Graceful degradation when agents are unavailable
- Request timeout handling (25-30 seconds for RAG)
- Versioned
hellohandshake with capability negotiation — see Handshake Protocol. Connecting from Python, the bundledpraisonai-botclient performs this handshake automatically and exposes the negotiatedclient.features/client.policy/client.heartbeat_ms— see Client-side wiring. - Shutdown forensics — on every exit, one log line and one diagnostic file record why the gateway stopped. See Crash Forensics.
- Opt-in close-the-loop notice on permanent delivery failure — see Undelivered Message Notice.
Config Validation
A typo in gateway.yaml won’t start a broken gateway — it fails fast with exit 78 before your users notice.
praisonai gateway start and praisonai bot serve now validate the same gateway.yaml to the same standard: canonical configs start, legacy configs auto-migrate then start, and typos exit 78.
The validator consults the platform registry — built-in platforms plus any published via praisonai.channels / praisonai.bots entry points plus any registered at runtime with register_platform().
Typo channel (telegran) — rejected, exit 78
agents:
assistant:
instructions: "You are a helpful assistant."
channels:
telegran: # typo — should be 'telegram'
token: ${TELEGRAM_BOT_TOKEN}Before: warns, gateway starts, /health returns 200 — but Telegram is silently dead.
WARNING Channel 'telegran' platform 'telegran' is not a known platform
(agentmail, discord, email, linear, local, slack, telegram, whatsapp)
INFO Gateway started on http://127.0.0.1:8765After: rejected before startup, exit 78 — the supervisor does not crash-loop.
ERROR Gateway config invalid (gateway.yaml):
- Unknown channel 'telegran' (platform 'telegran').
Supported platforms: agentmail, discord, email, linear, local, slack, telegram, whatsapp
$ echo $? # 78Real adapter (linear) — accepted cleanly
channels:
linear:
token: ${LINEAR_API_KEY}Before: spurious “not a known platform” warning, then starts.
After: accepted with no warning — linear is a shipped adapter on the registry.
Plugin channel (mattermost) — accepted via register_platform()
from praisonai.bots._registry import register_platform
class MattermostBot:
def __init__(self, **kwargs): self.cfg = kwargs
async def start(self): ...
async def stop(self): ...
register_platform("mattermost", MattermostBot)channels:
mattermost:
token: ${MATTERMOST_TOKEN}Before: warned “not a known platform”, gateway started, channel silently dead on the runtime path.
After: accepted on both praisonai bot serve and praisonai gateway start.
Gateway Modes
Multi-Channel Mode
Run multiple platforms simultaneously:
channels:
telegram_support:
platform: telegram
token: ${TELEGRAM_SUPPORT_TOKEN}
routes:
default: support_agent
discord_community:
platform: discord
token: ${DISCORD_BOT_TOKEN}
routes:
default: community_agentEach channel requires a unique token and can route to different agents.
WebSocket-Only Mode
Pure WebSocket server without chat platforms:
praisonai gateway start --host 127.0.0.1 --port 8765Provides WebSocket endpoint for custom client integration.
Agent File Mode
Load agents from separate configuration:
praisonai gateway start --agents agents.yamlSeparates agent definitions from gateway configuration.
Safe Mode (Incident Triage)
Boot core-only — external plugins and inbound YAML hooks disabled — to isolate a fault:
praisonai gateway start --config gateway.yaml --safe-modeAliases --pure / --no-plugins are equivalent. See Gateway Safe Mode.
Health Monitoring
The gateway exposes health information:
curl http://127.0.0.1:8765/healthResponse:
{
"status": "healthy",
"uptime": 3600.5,
"agents": 3,
"sessions": 12,
"clients": 8,
"channels": {
"telegram_support": {
"platform": "telegram",
"running": true
},
"discord_community": {
"platform": "discord",
"running": true
}
}
}Push Transport Health
When push is enabled with Redis fan-out, health() adds a push block reporting the cross-instance transport:
| Field | Type | Meaning |
|---|---|---|
push.redis_connected | bool | true only when the client is live and not degraded |
push.redis_degraded | bool | true while a reconnect is in flight |
push.redis_dropped_writes | int | Cumulative writes that hit a disconnected adapter |
A degraded transport is also listed under degraded_owners as route:redis-pubsub. See Redis Pub/Sub Resilience.
Auth brute-force budgets are also shared across replicas when push.redis is set — see Cluster-Wide Auth Rate Limiting.
Pressure Telemetry
When the back-pressure machinery is wired, health() adds a top-level pressure block that classifies gateway load as nominal, elevated, or saturated — so you see the gateway backing up before it sheds turns:
{
"status": "healthy",
"pressure": {
"admission_in_flight": 15,
"admission_queued": 40,
"outbox_pending": 412,
"outbox_dead_letter": 2,
"event_loop_lag_p99_ms": 740.0,
"pressure": "saturated"
}
}Admission, queues, dead-letter and loop-lag on one health block.
Health Check Limitations
Current implementation limitations:
"running": truedoesn’t guarantee platform polling health- No detection of Telegram 409 conflicts until PraisonAI fix ships
- Manual verification required for silent bot issues
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
host | str | "127.0.0.1" | Gateway bind address |
port | int | 8765 | Gateway port |
auto_enable_from_env | bool | true | Auto-register a channel for any known platform whose credential env var(s) are all present and that isn’t declared or disabled. See Zero-Config Gateway. |
channels.<name>.enabled | bool | true | Explicit per-channel opt-out. false drops the channel and suppresses credential-presence auto-enable for that platform. |
max_connections | int | 100 | WebSocket connection limit |
heartbeat_interval | int | 30 | WebSocket ping interval |
session_timeout | int | 3600 | Session expiration in seconds |
session.persist | bool | true | Persist sessions across restarts (durable-by-default as of PR #3595 — set to false to opt back to ephemeral, in-memory sessions). See Gateway Session Persistence. |
gateway.durable_runs | bool | auto (on when session store is durable) | Auto-enables durable runs (ExecutionConfig(durable=True)) when a durable session store is present (the default). Explicit false or gateway.reliability: "off" opts back out. Per-agent durable overrides. See Gateway Durable Runs. |
session.persist_path | str | ~/.praisonai/sessions/ | Storage directory |
session.store | str | "sqlite" | Transcript backend when persist is on — "sqlite" (SQLite Transcript Store) or "file" (legacy JSON) |
session.resume_window | int | 86400 | Seconds detached sessions stay resumable |
Best Practices
Edit gateway.yaml live
You can change agent instructions, models, or a single channel section while the gateway runs. Agent-only edits recreate agents without restarting channels. See Hot-Reload.
Use environment variables for secrets
Never hardcode tokens in configuration files:
# Good
channels:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
# Bad
channels:
telegram:
token: "123456:hardcoded_token"Store tokens in .env file or environment variables.
Implement proper error handling
Monitor gateway logs for platform-specific errors:
# View gateway logs
tail -f ~/.praisonai/logs/gateway.log
# Common error patterns
grep "409\|Conflict\|getUpdates" ~/.praisonai/logs/gateway.logConfigure resource limits
Set appropriate limits based on expected load:
gateway:
max_connections: 100 # Adjust for concurrent users
session_timeout: 3600 # 1 hour default
message_queue_size: 1000Use unique tokens per channel
Each channel must have its own platform token:
channels:
telegram_support:
token: ${TELEGRAM_SUPPORT_TOKEN} # Bot 1
telegram_sales:
token: ${TELEGRAM_SALES_TOKEN} # Bot 2 (different bot)Never reuse tokens across channels.
Cost Optimization
Running a PraisonAI gateway on serverless hosts (Fly.io Machines, Modal, Cloud Run) charges for every second the machine is alive — even when no users are chatting. The Scale-to-Zero feature lets the gateway stand transports down after a configurable period of silence and wake back up on the next inbound message, so you pay only for active time.
Scale-to-Zero GatewaySuspend the gateway when idle and wake on the next message — pay only for active time.
Lifecycle
Three opt-in lifecycle policies wire into the primary WebSocketGateway through a lifecycle: block in gateway.yaml, matching CLI flags, and a Python surface. Each is off unless enabled, and configured features surface under health()["lifecycle"].
Quiesce when idle for idle_minutes and self-wake on the next message.
Epoch-safe external drain marker with a built-in watcher.
Crash-Loop GuardHalt auto-resume of a channel that keeps crashing on resume.
Event-Loop WatchdogDetect a wedged asyncio loop and restart before it becomes a zombie.
Startup WatchdogDetect a wedged pre-loop startup and restart before it becomes a zombie.
Idle CompactionCompact idle sessions off the critical path — no first-message latency for returning users.
Related
Bring a bot up with just an env var — no channels: block required
Admission ControlBound concurrent inbound agent runs with a fair queue, overflow policy, and memory-aware backpressure with max_rss_mb
Survive restarts and resume mid-conversation
Session AddressStable, shareable session URLs that never leak the raw UUID
Live Session HandoffMove an in-progress conversation onto a thread on another platform
Windows DeploymentComplete Windows setup guide
Multi-Channel TelegramHermes-style workforce deployment
Scale to ZeroPay only for active time — idle-dormancy for serverless hosts
Safe ModeBoot core-only to isolate whether a fault is in the core or an extension
Tracing HookEmit OpenTelemetry spans across each pipeline stage
Undelivered NoticeNotify users and operators when a reply can’t be delivered
Durable PollingAt-least-once delivery for the long-poll fallback transport
Redis Pub/Sub ResilienceMulti-instance transport reconnect and outage surfacing