| name | shared-env-contract |
| description | Audit and enforce the shared environment loading contract across all services. Use when: adding, renaming, or removing an environment variable; changing config loading logic; debugging missing env values at runtime; adding a new service or dependency profile; reviewing whether a change affects chatbot only, MCP only, or multiple services; or checking for hardcoded ports, URLs, or secrets. |
Shared Environment Contract
When to use this skill
- Adding, renaming, or removing an environment variable.
- Changing how or where
.env files are loaded.
- Debugging a service that cannot find an expected env value.
- Adding a new API key, provider, or external service URL.
- Reviewing whether a config change is scoped to one service or crosses boundaries.
- Checking for hardcoded ports, URLs, or secrets that should come from env.
- Changing dependency profiles (
venv-core vs venv-image).
Source of truth — configuration files
| File | Role | Authority |
|---|
services/shared_env.py | Authoritative loader — load_shared_env(__file__) | How env is loaded |
app/config/.env | Shared env values (production/dev) | Runtime values |
app/config/.env_{env} | Environment-specific override (e.g. .env_dev) | Runtime values (preferred when exists) |
app/config/.env.example | Shared variable template | What variables exist |
services/chatbot/.env | Chatbot-local overrides (loaded without override by run.py) | Service-local keys only |
services/chatbot/.env.example | Chatbot variable template | What chatbot-specific vars exist |
services/chatbot/core/config.py | Reads all env vars via os.getenv() | What the chatbot actually uses |
app/config/config.yml | Service ports, hosts, logging, cache settings | Static config reference |
app/config/model_config.py | HubConfig, ServiceConfig dataclasses | Service registry and hub config |
Environment loading architecture
Process start
│
├─ services/chatbot/run.py (universal dispatcher)
│ ├─ load_shared_env(__file__) ← loads app/config/.env_{env} or .env
│ └─ load_dotenv(services/chatbot/.env) ← WITHOUT OVERRIDE, local-only keys
│
├─ services/chatbot/chatbot_main.py (Flask legacy)
│ └─ load_shared_env(__file__) ← same shared loader
│
├─ services/chatbot/core/config.py (imported by either entry point)
│ └─ load_shared_env(__file__) ← safe re-call (dotenv is idempotent)
│
└─ services/mcp-server/server.py
└─ (no explicit env loading — reads os.environ directly)
Key rules
- One loader function:
services/shared_env.py → load_shared_env(__file__).
- One shared env file:
app/config/.env_{env} (preferred) or app/config/.env (fallback).
- One allowed local override:
run.py loads services/chatbot/.env without override — shared values always win.
- Never add a second
load_dotenv(override=True) anywhere in services/.
- MCP server does not call
load_shared_env — it inherits from the process environment or is launched with env already set.
Variable registry — what the chatbot reads
LLM provider keys (core/config.py)
| Variable | Provider | Required |
|---|
GROK_API_KEY | xAI Grok (default model) | Yes (for default) |
OPENAI_API_KEY | OpenAI GPT + Sora 2 video | For OpenAI/video |
DEEPSEEK_API_KEY | DeepSeek Chat | For DeepSeek |
QWEN_API_KEY | Qwen (Alibaba) | For Qwen |
GEMINI_API_KEY_1 … _4 | Google Gemini (rotation pool) | For Gemini |
HUGGINGFACE_API_KEY | HuggingFace models | For local models |
OPENROUTER_API_KEY | OpenRouter multi-model | Optional |
STEPFUN_API_KEY | StepFun image gen | Optional |
Search and tool keys
| Variable | Tool | Required |
|---|
SERPAPI_API_KEY | SerpAPI (Google/Bing/Baidu/Lens/Images) | For web search |
GOOGLE_SEARCH_API_KEY_1, _2 | Google CSE fallback | For CSE fallback |
GOOGLE_CSE_ID | Google Custom Search Engine ID | For CSE fallback |
SAUCENAO_API_KEY | SauceNAO reverse image | For anime source |
GITHUB_TOKEN | GitHub API | For GitHub search |
Image generation keys (chatbot-local .env)
| Variable | Provider | Loaded from |
|---|
FAL_API_KEY | fal.ai FLUX | services/chatbot/.env |
BFL_API_KEY | Black Forest Labs | services/chatbot/.env |
IMGBB_API_KEY | ImgBB image hosting | services/chatbot/.env |
REPLICATE_API_TOKEN | Replicate | services/chatbot/.env |
Infrastructure
| Variable | Purpose | Default |
|---|
env | Environment name | dev |
MONGODB_URI | MongoDB connection string | mongodb://localhost:27017 |
MONGODB_DB_NAME | Database name | chatbot_db |
MONGODB_TLS_ALLOW_INVALID_CERTIFICATES | Allow invalid MongoDB TLS certificates | false |
REDIS_URL | Redis cache URL | — |
SD_API_URL | Stable Diffusion WebUI | http://127.0.0.1:7861 |
FLASK_SECRET_KEY | Flask session secret | required outside dev/test |
Startup flags
| Variable | Effect | Default |
|---|
TESTING | CI test mode (disables DB) | False |
MONGODB_ENABLED | Enable MongoDB | True (disabled in CI) |
Dependency profiles — do not mix
| Profile | venv | Requirements | Services |
|---|
| core-services | venv-core | app/requirements/profile_core_services.txt | Chatbot, MCP server |
| image-ai-services | venv-image | app/requirements/profile_image_ai_services.txt | Stable Diffusion, Edit Image |
Rule: chatbot and MCP work always uses venv-core. Never install image-ai packages into venv-core.
Good and bad examples
Good: adding a new API key
NEW_SERVICE_API_KEY = os.getenv('NEW_SERVICE_API_KEY')
from core.config import NEW_SERVICE_API_KEY
Bad: hardcoding or duplicating config
response = requests.get("http://localhost:7861/sdapi/v1/txt2img")
from core.config import SD_API_URL
response = requests.get(f"{SD_API_URL}/sdapi/v1/txt2img")
from dotenv import load_dotenv
load_dotenv("my_custom.env", override=True)
from services.shared_env import load_shared_env
load_shared_env(__file__)
app.run(port=5000)
PORT = 5000
requests.get("http://localhost:5000/health")
PORT = int(os.getenv('CHATBOT_PORT', '5000'))
key = os.getenv('GROK_KEY')
from core.config import GROK_API_KEY
Monitor — what can drift
- Variable name mismatch —
.env.example says GROK_API_KEY, but code reads GROK_KEY.
- Default value drift —
config.yml says port 5000, but os.getenv('CHATBOT_PORT', '5001') defaults to 5001.
- Orphaned variables — key removed from code but still in
.env.example, or vice versa.
- Override creep — a new
load_dotenv(override=True) call introduced anywhere.
- Hardcoded secrets or URLs — API keys,
localhost:PORT, or connection strings in source code.
- Cross-profile contamination — image-ai package added to
profile_core_services.txt.
- MCP server env gap — MCP server expects a variable that only exists in chatbot's local
.env.
.env file proliferation — new .env files created outside the sanctioned locations.
Do not touch unless the task requires it
services/shared_env.py — only if the loading mechanism itself needs changing.
app/config/.env — only to add/remove variable values; never restructure.
app/config/config.yml — only if service ports or infrastructure settings change.
services/stable-diffusion/ or services/edit-image/ — image services have their own env needs.
venv-core/ or venv-image/ — generated; never edit.
Required output — config impact summary
After any change that touches environment variables, configuration files, or env loading logic, include this summary:
- Variables affected — which env vars were added, renamed, removed, or had defaults changed?
- Scope — chatbot only / MCP only / shared (multiple services)?
- Loading path — does the variable come from shared env (
app/config/.env) or local override (services/chatbot/.env)?
- Profile impact — does this change affect
venv-core dependencies, venv-image dependencies, or neither?
- Files updated — list every file that was changed.
- Docs updated — was
README.md, .env.example, or config.yml updated to match?
- Risk — could this break another service, override a shared value, or introduce a hardcoded secret?
Verification checklist
After any env-related change: