Boot Flow
At startup, the runtime initializes in this order:- Load config - Reads
config.tomland environment variables - Connect to Postgres - Runs migrations via SQLx
- Connect to Redis - Initializes cache client with exponential backoff reconnect policy
- Initialize V8 - Once per process via
v8_init::init() - Load SDK bundle - Bundles the flora SDK into the default runtime for all workers
- Restore deployments - Loads cached guild scripts from the database
- Start services - Launches Discord client and HTTP server concurrently
The runtime uses a current-thread Tokio runtime per worker (apps/runtime/src/runtime/worker.rs:94-97), not a multi-threaded pool. This avoids V8 thread safety issues.
Worker Pool
TheBotRuntime manages a pool of worker threads:
Worker Initialization
Workers are initialized sequentially to avoid V8 race conditions (apps/runtime/src/runtime/mod.rs:68-75):- Runs in its own OS thread
- Maintains a map of guild ID → isolate state
- Hosts a default runtime with the SDK bundle
- Processes commands via an unbounded channel
Guild Routing
Guilds are assigned to workers using consistent hashing (apps/runtime/src/runtime/mod.rs:90-110):guild_routes for fast lookups.
Event Dispatch
Discord Event Flow
- Gateway event arrives at
DiscordHandler::dispatch()(apps/runtime/src/discord_handler.rs:26) - Event serialized to JSON payload (apps/runtime/src/discord_handler.rs:66-72)
- Guild ID extracted; non-guild events are dropped (apps/runtime/src/discord_handler.rs:74-77)
- Event routed to appropriate worker (apps/runtime/src/runtime/mod.rs:179-207)
- Worker dispatches to guild isolate’s event handlers
- Handlers registered via
on()execute within timeout - Ops map back to Discord REST calls
Dispatch Timeout
Each event handler has a configurable timeout (default 3s, configured viaRUNTIME_DISPATCH_TIMEOUT_SECS):
- The isolate’s event loop is terminated
- An error is logged
- The event is dropped
Event Queueing During Migration
When a guild runtime is being migrated to another worker, incoming events are queued (apps/runtime/src/runtime/mod.rs:186-199):Isolate Lifecycle
Creation
When a guild script is deployed (apps/runtime/src/runtime/worker.rs:152-217):- Worker receives
DeployGuildcommand - Creates new
JsRuntimewith ops extension - Injects guild-scoped state (HTTP client, KV service, secrets)
- Executes runtime prelude (SDK setup)
- Loads and evaluates guild script bundle
- Extracts dispatch function from script
- Runs event loop until quiescence
State Management
Each isolate maintains:runtime- The Deno Core JS runtimedispatch_fn- Cached reference to the script’s dispatch functionlast_dispatch_end- Timestamp for metrics and debugging
Termination
Isolates are terminated when:- A new deployment replaces the script
- The bot leaves the guild
- A fatal error occurs during execution
- Migration moves the guild to another worker
JsRuntime::v8_isolate().terminate_execution() to forcefully stop the event loop.
Cron Scheduler
Each worker runs a per-second cron tick (apps/runtime/src/runtime/worker.rs:102-116):Cron Job Registry
TheCronRegistry tracks jobs per guild (apps/runtime/src/ops/cron.rs):
- Stored in
Arc<parking_lot::Mutex<CronRegistry>> - Keyed by guild ID
- Uses the
cronercrate for POSIX/Vixie-cron compatible parsing - Enforces per-guild job limit (default 32)
Cron Execution
When a cron job is due:- Registry checks
next_runtimestamp - If due and not running (or
skipIfRunning: false), dispatches synthetic event - Event type is
__cron:<name> - Handler executes with cron-specific timeout (default 5s)
is_runningflag cleared on completion
Cron jobs are not persisted. They are re-registered when the script loads (apps/www/limitations.md:12-30).
KV Store
The KV store is scoped per guild and per store name:Storage Backend
- Sled: Embedded key-value database on disk (
data/kv) - Postgres: Metadata index (store names, key counts)
Constraints
| Constraint | Limit |
|---|---|
| Value size | 1 MB |
| Key length | 512 characters |
| Store name | 64 characters |
| List default limit | 100 |
| List max limit | 1000 |
Features
- Optional TTL per key
- Optional metadata (any JSON value)
- Prefix filtering
- Cursor-based pagination
Logs and Metrics
The runtime exposes logs via the HTTP API:- Query logs:
GET /logswith filters (guild, level, timestamp) - Stream logs:
GET /logs/streamusing Server-Sent Events - CLI streaming:
flora logs -ffollows logs in real time
LogSink (apps/runtime/src/log_sink.rs) that intercepts script console output and runtime tracing.
Configuration
Runtime behavior is controlled viaflora_config::RuntimeConfig (crates/flora_config/src/lib.rs:79-114):
RUNTIME_MAX_WORKERS=8).