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.Quick Start
1
Send everyone to one agent (baseline)
Start with a single default route — no bindings needed.
2
Send one VIP user to a dedicated agent
Add a
bindings: entry with peer: set to the user’s Telegram numeric id. The VIP agent handles that user; everyone else still goes to general.3
Mix peer, role, channel, and chat-type
Stack multiple bindings — the most specific rule wins automatically.A user with id
12345678 always gets vip. A support-role member who DMs the bot gets support (role beats chat type). The ops channel routes to the ops agent. All other DMs go to assistant. Everything else falls back to general.4
Route a whole Discord server to one tenant
Bind one A single rule handles every channel in the guild — no need to list them all, and new channels are covered automatically.
guild_id: and every current and future channel in that server routes to the same agent.5
Send one support thread to a specialist
Layer a A thread rule beats a channel or guild rule — the specialist agent picks up messages in that one thread, everything else falls through.
thread_id: binding on top of a channel or guild binding — a thread rule beats them both.6
Restrict tools by trust tier
Add See Gateway Tool Policy for the full security reference.
trust: to any binding to scope the toolset the model sees — strangers get a safe subset, your operator keeps full power.7
Isolate tenants with a per-route profile
Add The gateway reads
profile: to any binding to enter a per-route memory namespace — the same support agent serves two customers with completely separate conversation histories.RouteMatch.profile and keys the turn’s memory/session state off the name, so each tenant’s transcript is stored separately (secret-scope and home isolation are follow-ups, not yet live — see Gateway Tenant Profiles). An unmatched route carries profile=None and stays unscoped, so misrouted traffic never leaks into another tenant.How It Works
The gateway resolves the target agent in four deterministic steps:Parent-chain matching
Achannel_id condition now matches the channel itself and any thread/forum-post whose parent is that channel — one channel rule naturally covers the conversations beneath it.
If you want a specific thread to escape the parent’s route, add a thread_id: binding — it beats the channel/guild rule via specificity.
A legacy channel_id-only binding still resolves exactly as before — parent-chain only widens what it matches, never narrows.
Routes vs Bindings
Configuration Options
Each entry in thebindings: list is a RouteBinding:
All non-
None conditions in a binding must match the inbound message for that binding to apply. A binding with no conditions always matches.
Specificity weights — when two bindings both match, the one with the higher total specificity wins:
Ties on
(priority, specificity) are broken by declaration order — the first matching binding in your list wins.
thread_id, guild_id, and parent_channel_id are all optional and default to None. Existing configs resolve exactly as today, and positional RouteBinding(...) constructors keep their meaning — the new fields are appended, never inserted mid-list.Fail-Fast Validation
A typo in aroutes: slot or a bindings.agent value is caught the moment the config is loaded — the gateway refuses to start with an error naming the channel, the bad target, and the closest valid agent id.
A single mistyped target used to slip through as a WARNING and silently serve the wrong agent for the whole deployment. Now it stops the gateway before it starts.
bindings.agent fails the same way:
default slot, any custom slot (dm, group, …), routing: overrides, and every bindings.agent. A blank target (dm: "") is rejected too — only omitted slots are skipped.
Validation runs only when
agents: is declared (multi-agent configs). Single-bot configs — a top-level platform + token with no agents: map — are unaffected and behave exactly as before.Common Patterns
profile=None so the wrapper stays unscoped rather than falling back into another tenant’s namespace.
VIP customer gets a dedicated agent
priority
incident_responder binding has priority: 100 so it wins over the peer match for user 12345678, even though peer has higher specificity. Use this for incident-mode overrides.
Lock down stranger DMs while keeping operator at full power
peer: "operator-123") retains the full toolset. See Gateway Tool Policy for the complete security reference.
Route a whole Discord server to one tenant profile
Best Practices
Always declare a default in routes:
Always declare a default in routes:
Bindings are evaluated first, but
routes.default is the safety net when nothing matches — always include it. In a multi-agent config (with an agents: map), a typo in the default target no longer falls through silently: it fails at load with a clear Fail-Fast Validation error naming the closest valid agent.Prefer specificity over priority
Prefer specificity over priority
Let the resolver pick by specificity —
peer beats role beats channel_id beats account beats chat_type. Reach for priority only when you genuinely need to override, such as an incident-mode binding that must win regardless of user identity.Use stable peer and channel ids
Use stable peer and channel ids
Telegram numeric ids and Discord channel ids are stable. Display names and usernames change. Use the numeric id from the platform — never a username or handle.
Keep bindings short and reviewable
Keep bindings short and reviewable
If your list grows past ~10 entries, consider grouping users by role at the platform level and binding on
role instead of individual peer ids. A long list of peer bindings is hard to audit and easy to break.Name the isolation scope on the binding, not the agent
Name the isolation scope on the binding, not the agent
The same agent can serve many customers if each route carries its own
profile. The gateway reads RouteMatch.profile and keys the turn’s memory namespace off the name (secret-scope / home remain follow-ups). Leaving profile off means the route is unscoped; a fallback match never inherits another tenant’s profile, so misrouted traffic can’t leak conversation history across customers.Use guild_id for whole-tenant routing
Use guild_id for whole-tenant routing
When each tenant owns a Discord server or Slack workspace, bind on
guild_id: instead of listing every channel. A newly-created channel in that server automatically inherits the guild’s route, and pairing it with profile: gives each tenant its own memory namespace. See Gateway Tenant Profiles.Escalate to a thread, not a new channel
Escalate to a thread, not a new channel
For triage patterns — a general support agent spins up a thread for a hard case — bind the thread id to the specialist agent. The thread rule beats the parent channel’s rule automatically, and messages in every other thread still route through the parent.
Related
Bot Message Routing
The simpler chat-type routing surface — route by dm, group, or channel.
Multi-Channel Bots
Run one bot per role on the same platform using multiple channel entries.
Gateway Tool Policy
Full reference for trust-tiered toolset scoping — keep stranger DMs from running shell on your server.
Approval
Second line of defence — require human confirmation before risky tools run.
Gateway Tenant Profiles
Isolate tenants per route with the
profile: field — a separate memory namespace per tenant.Gateway CLI
praisonai gateway doctor surfaces route/binding typos before you start.Gateway Readiness
Pre-flight checklist — route/binding targets resolve to declared agents.

