Documentation Index
Fetch the complete documentation index at: https://mintlify.com/moeru-ai/airi/llms.txt
Use this file to discover all available pages before exploring further.
Event Routing
The server runtime implements a flexible event routing system that determines how events are distributed to connected peers.
Routing Flow
Route Middleware
Middleware functions intercept events and make routing decisions.
Middleware Interface
type RouteMiddleware = (context: RouteContext) => RouteDecision | void
interface RouteContext {
event: WebSocketEvent
fromPeer: AuthenticatedPeer
peers: Map<string, AuthenticatedPeer>
destinations?: Array<string | RouteTargetExpression>
}
type RouteDecision
= | { type: 'drop' } // Discard the event
| { type: 'broadcast' } // Send to all peers
| { type: 'targets', targetIds: Set<string> } // Send to specific peers
Custom Middleware Example
import { setupApp, RouteMiddleware } from '@proj-airi/server-runtime'
// Only allow events from specific plugins
const pluginFilterMiddleware: RouteMiddleware = (context) => {
const pluginId = context.fromPeer.identity?.plugin?.id
if (!pluginId || !['allowed-plugin-1', 'allowed-plugin-2'].includes(pluginId)) {
return { type: 'drop' }
}
// Return void to continue to next middleware
}
// Rate limiting middleware
const rateLimiters = new Map<string, { count: number, resetAt: number }>()
const rateLimitMiddleware: RouteMiddleware = (context) => {
const peerId = context.fromPeer.peer.id
const now = Date.now()
const limit = rateLimiters.get(peerId)
if (!limit || now > limit.resetAt) {
rateLimiters.set(peerId, { count: 1, resetAt: now + 60000 })
return
}
if (limit.count >= 100) {
return { type: 'drop' }
}
limit.count++
}
const { app } = setupApp({
routing: {
middleware: [pluginFilterMiddleware, rateLimitMiddleware]
}
})
Routing Policies
Policies provide declarative access control without custom middleware code.
Policy Configuration
interface RoutingPolicy {
allowPlugins?: string[] // Whitelist of plugin IDs
denyPlugins?: string[] // Blacklist of plugin IDs
allowLabels?: string[] // Required labels (any match)
denyLabels?: string[] // Forbidden labels (any match)
}
Policy Examples
Plugin Whitelist
const { app } = setupApp({
routing: {
policy: {
allowPlugins: ['core-module', 'trusted-plugin']
}
}
})
Label-Based Access Control
const { app } = setupApp({
routing: {
policy: {
allowLabels: ['tier=premium', 'env=production'],
denyLabels: ['deprecated=true']
}
}
})
Combined Policy
const { app } = setupApp({
routing: {
policy: {
allowPlugins: ['core-module', 'ai-module'],
denyPlugins: ['legacy-module'],
allowLabels: ['tier=premium'],
denyLabels: ['beta=true', 'deprecated=true']
}
}
})
Route Destinations
Events can specify destinations using module names or label selectors.
Destination Types
type RouteTargetExpression = {
name?: string // Module name
index?: number // Module instance index
labels?: Record<string, string> // Label selectors
}
Specifying Destinations
By Module Name
client.send({
type: 'custom:event',
data: { message: 'Hello' },
route: {
destinations: ['ai-module', 'chat-module']
}
})
By Module Instance
client.send({
type: 'custom:event',
data: { message: 'Hello' },
route: {
destinations: [
{ name: 'ai-module', index: 0 },
{ name: 'ai-module', index: 1 }
]
}
})
By Labels
client.send({
type: 'custom:event',
data: { message: 'Hello' },
route: {
destinations: [
{ labels: { role: 'processor' } },
{ labels: { tier: 'premium', region: 'us-east' } }
]
}
})
In Event Data
Destinations can also be specified in the data payload:
client.send({
type: 'custom:event',
data: {
message: 'Hello',
destinations: ['ai-module']
}
})
Bypass Mode
Devtools peers can bypass routing policies for debugging and monitoring.
Enabling Bypass
A peer is considered a devtools peer if:
- It has the label
devtools=true or devtools=1, OR
- Its module name contains “devtools”
// Module with devtools label
client.send({
type: 'module:announce',
data: {
name: 'monitoring-tool',
identity: {
kind: 'plugin',
plugin: { id: 'monitoring', labels: { devtools: 'true' } },
id: 'monitor-1'
}
}
})
// Send event with bypass
client.send({
type: 'monitor:observe',
data: { target: 'all' },
route: {
bypass: true // Only works for devtools peers
}
})
Disabling Bypass
const { app } = setupApp({
routing: {
allowBypass: false // Disable bypass even for devtools
}
})
Label Matching
Label selectors support exact matching with AND logic.
// Peer labels
const peerLabels = {
tier: 'premium',
region: 'us-east',
env: 'production'
}
// Match: all selector labels must match peer labels
const selector1 = { tier: 'premium' } // ✓ Matches
const selector2 = { tier: 'premium', region: 'us-east' } // ✓ Matches
const selector3 = { tier: 'free' } // ✗ No match
const selector4 = { tier: 'premium', region: 'eu-west' } // ✗ No match
Built-in Routing Rules
Self-Exclusion
Events are never sent back to the originating peer:
// Peer A sends event
client.send({
type: 'broadcast:message',
data: { text: 'Hello' }
})
// Server sends to all peers EXCEPT Peer A
Module Registry Access
The routing context provides access to the module registry:
const customMiddleware: RouteMiddleware = (context) => {
const targetIds = new Set<string>()
for (const [id, peer] of context.peers) {
if (peer.name === 'ai-module') {
targetIds.add(id)
}
}
return { type: 'targets', targetIds }
}
Minimize middleware chain
Keep middleware functions lightweight. Heavy computation or I/O operations will block event processing.
Use policies over middleware
Built-in policies are optimized for common access control patterns. Use custom middleware only when policies are insufficient.
If performing complex label matching, cache results within middleware to avoid repeated computation.
High-frequency events benefit from explicit destinations rather than broadcast-then-filter approaches.
Debugging Routing
Enable WebSocket logging to trace routing decisions:
const { app } = setupApp({
logger: {
websocket: {
level: 'debug',
format: 'pretty'
}
}
})
Log output includes:
- Received events with peer information
- Routing decisions (broadcast, targets, drop)
- Delivery status to each peer
- Connection health (heartbeat, timeout)
Next Steps
WebSocket Protocol
Review the WebSocket message protocol
Server SDK
Use the client SDK with built-in routing