Bot Modules
Bot modules are extensions that handle chat messages, platform events, and timed actions. All bot modules -- built-in and community -- run as handler-based server functions inside V8 isolate sandboxes via the Bot Module Worker.
The native Rust BotModule trait, ModuleRegistry, and crates/lo-bot-modules/ crate have been removed. Built-in modules (Link Protection, Spam Protection, Word Filter, Timed Messages) are now system extensions in extensions/system/ and run through the same V8 execution model as third-party bot modules. See Extensions for the full list.
Architecture
Chat message / Platform event
│
Bot Module Worker
├─ Subscribes to Redis channel: bot_modules:{account_id}
├─ Loads active triggers from internal API
├─ Matches incoming messages/events against triggers (built-in + community)
└─ Dispatches to V8 isolate handler
├─ Handler receives ctx (db, fetch, secrets, auth, user, cache, defer)
├─ Returns action: send_message | delete | timeout | ban | none
└─ Worker executes action via platform API
Bot Module Worker
The Bot Module Worker (apps/bot-module-worker/) is a standalone service that bridges the chat event stream with bot module handlers. Bots match triggers in Rust (<1ms) and dispatch to the Worker via sync HTTP (commands/moderation, 500ms deadline) or async Redis (keywords/patterns/events/timers).
Lifecycle:
- On startup, the Worker loads all active bot module triggers for connected accounts via
GET /v1/internal/bot-modules/{account_id}/triggers(SystemKey auth). - It subscribes to
bot_modules:{account_id}Redis channels for each active account. - When a chat message or platform event arrives, the Worker evaluates it against the loaded trigger set.
- On a match, the Worker dispatches the message to the corresponding V8 isolate handler via
crates/lo-extensions-v8/. - The handler's return value (action + optional message) is executed by the Worker using the account's channel connection credentials.
Trigger refresh: When an account installs, uninstalls, enables, or disables a bot module, a Redis pub/sub message on bot_module_triggers:{account_id} triggers the Worker to reload that account's trigger set.
Redis Channels
| Channel | Purpose |
|---|---|
bot_modules:{account_id} | Chat messages and events forwarded to extension bot modules |
bot_module_triggers:{account_id} | Trigger set invalidation (install/uninstall/enable/disable) |
V8 Execution
Extension bot module handlers execute inside the same V8 isolate sandbox used by all extension server functions (crates/lo-extensions-v8/). The handler context (ctx) includes:
| Property | Description |
|---|---|
ctx.db | Extension-scoped database access (ext_{extension_id}.* tables) |
ctx.fetch | HTTP client (egress allowlist enforced) |
ctx.secrets | Server-side secrets |
ctx.auth | Caller identity (userId, accountId) |
ctx.user | Chat user context (platform, platform_user_id, display_name, roles) |
ctx.cache | In-memory key-value cache scoped to the extension install (TTL-based) |
ctx.defer() | Schedule a follow-up action after the handler returns (e.g., delayed response) |
The same sandbox limits apply: 256 MB memory, 10s CPU timeout, no filesystem access, no dynamic code evaluation.
Trigger Matching Flow
1. Message arrives on bot_modules:{account_id} Redis channel
2. Worker iterates over active triggers for the account (ordered by priority)
3. For each trigger:
a. command → check if message starts with the configured prefix
b. keyword → check if message contains any configured keyword
c. pattern → match message against configured regex
d. event → check if the event type matches
e. timer → check if the interval has elapsed since last fire
f. moderate → always matches (runs on every message)
4. First matching trigger wins (like built-in modules)
5. Worker spawns V8 isolate, passes message + trigger config to handler
6. Handler returns { action, message?, duration? }
7. Worker executes the action via platform API
Priority order: moderate triggers run first (they are moderation gates), followed by command, keyword, pattern, event, and timer in registration order.
Key Files
| Path | Description |
|---|---|
apps/api/src/workers/bot_module_worker.rs | Bot Module Worker service |
crates/lo-extensions-v8/src/bot_module.rs | V8 isolate handler execution |
crates/lo-extensions/src/triggers.rs | Trigger types, matching, and serialization |
crates/lo-extensions/src/manifest.rs | Bot module manifest validation |
apps/api/src/rest/bot_modules.rs | REST endpoints (kill switch, trigger resolution) |
Security Headers
The Extension Supervisor and Runtime apps serve HTTP security headers via Cloudflare Pages _headers files:
- Supervisor (
apps/extension-supervisor/_headers):frame-srcrestricts which origins can be iframed (onlyext.lumio.visionand staging/prod preview).frame-ancestorsrestricts who can embed the Supervisor (onlylumio.visionand staging/prod preview). - Runtime (
apps/extension-runtime/_headers):worker-srcrestricts Worker origins.frame-ancestorsrestricts embedding to Supervisor origins only.connect-srcallows Sentry error reporting.
When adding a new environment domain, both _headers files must be updated alongside DNS and CF Pages configuration.
Config Validation
Extension config values are validated server-side against the extension's config_schema before writing to extension_installs.config. The validator (crates/lo-extensions/src/config_validator.rs) checks:
- Type match (string, number, boolean, color, select)
- Required fields present and non-null
- Select values in declared options
- Color hex format (#RRGGBB)
- Unknown keys rejected
Both GraphQL (configureExtension mutation) and REST (PATCH /extension-installs/\{id\}) enforce validation. The config is loaded from the database and passed to V8 handlers via ctx.config.