# Logical model profiles → small models served on Modal. # # offline: # null = live inference required (default). The build refuses to start if no # backend is configured (MODAL_WORKSPACE / MODAL_LLM_BASE_URL / HF_TOKEN); # there is no silent fallback to the stub. # false = live inference (same as null, stated explicitly). # true = deterministic stub for every profile — a test/dev seam (the test suite # sets this), NOT a product mode. # # Each profile binds to a model by its **catalogue key** (`endpoint:`) — the model # slug in `modal/catalogue.py`, the single source of truth for what is deployed. # The loader (`Registry.from_dir`) expands that key into the concrete binding: # model = openai/ # base_url = https://${MODAL_WORKSPACE}---.modal.run/v1 # (or $MODAL_LLM_BASE_URL if set; a backend is required to run) # api_key = $MODAL_LLM_KEY (a self-served vLLM endpoint accepts any token) # # So adding/retuning a model is a one-line edit in `modal/catalogue.py`, and # pointing a tier at a different model is a one-line `endpoint:` change here — # nothing duplicates the served-id or URL. Env vars MODEL_TINY / MODEL_FAST / # MODEL_BALANCED / MODEL_STRONG still override the model string per profile # (highest priority). You may also bind a profile explicitly with `model:` + # `base_url:` instead of `endpoint:` (an escape hatch for non-catalogue endpoints). # # `endpoint:` keys may name a model on EITHER inference backend (ADR-0024): a bare # slug is a Modal-served model (as below); a `hf:` key is a Hugging Face # serverless model from `src/models/hf_catalogue.py`, resolved against HF_TOKEN. E.g. # balanced: {endpoint: "hf:google/gemma-2-9b-it", temperature: 0.8, max_tokens: 320} # The Fishbowl Lab picks the backend + per-agent model interactively; this file is the # headless/default-run binding. offline: null profiles: tiny: endpoint: nemotron-3-nano-4b # NVIDIA Nemotron 3 Nano 4B (≤4B, Tiny Titan) temperature: 0.7 max_tokens: 192 fast: endpoint: minicpm-4-1-8b # OpenBMB MiniCPM4.1-8B temperature: 0.9 max_tokens: 320 # balanced/strong are served WITH a reasoning parser (modal/catalogue.py: # reasoning_parser="gemma4") — they THINK before answering, and that thinking # counts against max_tokens. Budget too low → the model is truncated mid-thought # and emits an EMPTY answer (the "agents stopped working / just …" symptom). Give # the reasoning room; the thinking is captured separately as the mind-reader thought. balanced: endpoint: gemma-4-12b # Google Gemma 4 12B (reasoning) temperature: 0.8 max_tokens: 768 # strong: # endpoint: gemma-4-26b # Google Gemma 4 26B-A4B-it (MoE, ~4B active; reasoning) # temperature: 0.6 # max_tokens: 1024