Adding an Environment Variable
This guide covers when and how to add an environment variable to TomoriBot, how to choose
its tier in .env.optional.example, and the naming and formatting standards required.
When to Use an Environment Variable
Section titled “When to Use an Environment Variable”Not every configurable setting belongs in an environment file. Use this decision tree before adding a variable:
| Target Location | Use Case | Examples |
|---|---|---|
| PostgreSQL (Slash Command / Model / Server Setting) | Per-server or per-user configurations that administrators or members change at runtime. | Channel whitelists, persona prompts, temperature overrides, server prefixes. |
.env.example | Core required credentials and endpoints without which the bot cannot boot. | DISCORD_TOKEN, DATABASE_URL. |
.env.optional.example | Global operational limits, timeouts, feature flags, cache lifetimes, and opt-in integrations. Every variable here must have a safe working default in code. | WEB_SEARCH_TIMEOUT_MS, MAX_DOCUMENT_SIZE_MB. |
Code Constant (src/constants/ or module-local) | Fixed architectural invariants, protocol constraints, or Discord API limits that cannot be safely tuned. | Discord interaction token 15-minute window, modal text input limits. |
The Eight-Tier Taxonomy
Section titled “The Eight-Tier Taxonomy”Optional variables in .env.optional.example are organized into eight tiers, ordered by how frequently an administrator tunes them:
- Tier 1: Bot Identity and Everyday Behavior: Knobs that shape what Tomori says or how she behaves without altering infrastructure (e.g. trigger words, emoji penalty, short-term memory depth).
- Tier 2: Optional Features and Integrations: Opt-in external services where leaving the variable unset disables the whole feature (e.g. Matrix bridge, S3 storage, external search APIs, Documents and RAG, MCP servers).
- Tier 3: Self-Hosted Sidecars and Local Services: Settings for optional local AI containers, TTS sidecars (Fish Audio S2, VoxCPM2, CosyVoice 3, Chatterbox, MOSS, Irodori), Crawl4AI, SearXNG, and ComfyUI.
- Tier 4: AI Providers and Models: Per-provider LLM and image generator tuning (e.g. Gemini max output tokens, OpenRouter safety factors, NovelAI parameters, tool-loop execution bounds).
- Tier 5: Limits and Quotas: Caps on counts, sizes, payload lengths, and rates (e.g. memory counts, import archive limits, cooldowns, media attachment byte limits).
- Tier 6: Caches, TTLs, and Component Timeouts: Cache lifetimes, interactive component expiration, and lock cleanup (e.g. user cache TTL, channel lock timeout, button interactive durations).
- Tier 7: Diagnostics and Development Tooling: Knobs that only matter with a debugger attached, during local testing, or in CI pipelines (e.g. verbose fetch logging, test database credentials,
bun run vlgate limits). - Tier 8: Production Hosting and Operations: Sizing, pool recycling, PSI pressure detection, and metrics sinks needed in dedicated 24/7 production hosts, a VPS, or cloud deployments (e.g. Azure, AWS).
Placement Rule: Tier the Section, Not the Variable
Section titled “Placement Rule: Tier the Section, Not the Variable”When adding a variable:
- Does it belong to an existing subsystem? Put it in that subsystem’s
## Sectionblock, even if the variable itself is a timeout, limit, or flag. Subsystems stay together in one block because administrators tune features as cohesive units. - Is it a brand-new subsystem or integration? Pick its tier using the first matching rule from the taxonomy above, create a new
## Sectionblock in that tier, and add your variable with documentation.
Naming and Documentation Standards
Section titled “Naming and Documentation Standards”Follow these rules when defining an environment variable:
- Use
UPPER_SNAKE_CASEfor variable names. - Embed the unit in the variable name when applicable:
_MS,_SECONDS,_MINUTES,_HOURS,_DAYS,_MB,_BYTES. - Always document the unit and working default in the preceding comment unless the section banner already states them.
- Follow the repository comment policy: explain constraints and rationale, avoid restating the obvious, and avoid prose em dashes or en dashes.
- Provide a safe fallback in code so that running without the variable in
.envworks out of the box.
// Example: parsing an optional integer with fallbackconst TIMEOUT_MS = Number.parseInt(process.env.EXAMPLE_TIMEOUT_MS || "5000", 10);
// Example: parsing an optional boolean flagconst FEATURE_ENABLED = process.env.ENABLE_EXAMPLE_FEATURE === "true";Quality Gate
Section titled “Quality Gate”Run these checks after updating .env.optional.example and code:
bun run check # TypeScript validationbun run lint # Biome lint and formattingRelated Docs
Section titled “Related Docs”docs/en/contributing/development-tasks.md: general coding standards and gate checklistdocs/en/contributing/comment-policy.md: durable comment conventions and prose dash prohibitiondocs/en/wiki/production-tuning.md: deep operational rationale for Tier 8 production settings