Configuration Model¶
Two config levels. Every operator knob, organised by goal: Tuning.
1. Bot instance config¶
Secrets and install selector the host needs to run the bot at all. Set by the admin, never exposed through Discord.
DISCORD_BOT— Discord bot tokenOPENROUTER_API_KEY— shared across every LLM call siteCARTESIA_API_KEY— Cartesia TTS (required when[tts].provider="cartesia")AZURE_SPEECH_KEY/AZURE_SPEECH_REGION— Azure SpeechGOOGLE_API_KEY(orGEMINI_API_KEY) — Gemini TTSDEEPGRAM_API_KEY— Deepgram STT credential. Every other Deepgram knob lives in[providers.stt.deepgram]. Full list: Tuning — STT — Deepgram.FAMILIAR_ID— picks the character folder underdata/familiars/this process runs.
Lives in environment variables or a .env file. Never checked into
git. Never editable from inside Discord.
2. Character config¶
Per-familiar, loaded once from
data/familiars/<familiar_id>/character.toml, deep-merged over
data/familiars/_default/character.toml.
Surface today:
display_tz— IANA timezone (default"UTC") the final-reminder clock renders in (e.g."It is now: … 2:30PM PDT"). Invalid names (e.g."PST") fail fast at config load.[sleep]— sleep schedule, character-domain wall-clock config localized viadisplay_tz.window = "HH:MM-HH:MM"(may wrap midnight; bad format fails fast) andgrace_minutes(default 30) drive the reservedsleepactivity (catalog entry inactivities.toml). Omit the table to leave the schedule disarmed. See Sleep § The window.aliases— names the familiar answers to.[providers.history].voice_window_size/.text_window_size— recent-history layer windows, tiered by responder (defaults 100 / 200). Safety nets behind the token-aware[budget.<tier>]caps.[providers.history].coalesce_max_gap_seconds— at prompt-render time, collapse consecutive same-speaker voice fragments when the gap between them is within this many seconds. Default45.0;0disables. Discord text turns are unaffected (they carryplatform_message_id, which suppresses coalescing).[providers.turn_detection].strategy—"deepgram"(default) or"ten+smart_turn". See Tuning — local turn detection.[providers.stt]+[providers.stt.deepgram]— STT backend selector + per-backend knobs (endpointing_ms,keyterms, …). Onlydeepgramtoday; V3 widens. Per-knob env override available. See Tuning — STT — Deepgram.[providers.memory]— memory projector selection (projectorslist) plus per-worker tuning tables ([providers.memory.<name>]— cadences, batch sizes, thresholds). See Tuning — Memory projectors.[llm].image_description_model— model name for vision-based image descriptions (e.g."openai/gpt-4o"). Shared across all slots; empty string (default) disables the description step. When set,create_llm_clientsbuilds a reserved"__image_description__"client.[llm].max_concurrent_requests— process-wide cap on in-flight LLM requests across every slot (default4).[llm.fast]/[llm.prose]/[llm.background]— tiered LLM slots (model, temperature, optionaltop_p/top_k/presence_penalty,provider_order,reasoning,think_prepend,tool_calling,image_tools,multimodal). Schema and call-site → slot mapping at Tuning — LLM slots.tool_callingis wired end-to-end: whentrue, the responder for that slot installs the in-processToolRegistry(today:set_alarm,cancel_alarm, and optionallyview_image) and runs the agentic loop.image_tools(defaultfalse) independently gatesview_imageregistration — the loop runs when either flag is set.multimodal(defaultfalse) controls whetherImageResulttool-result messages include JPEG content blocks (true) or text description only (false). See Tool calling and Image viewing.[tts]— provider (azure/cartesia/gemini) + provider-specific voice / model fields.[focus]— attentional unread-nudge controls (unread_nudge_enabled,nudge_debounce_seconds). See Tuning — Attentional focus.[tools]— agentic loop bounds (loop_max_iterations, default5), shared by voice and text responders.[prompt].post_history_instructions— free-text block appended to the trailing reminder, the system message that sits after recent history (right before the model's next turn). The deepest, most recency-biased slot, so behavioral nudges land hardest here. Rendered verbatim (markdown fine); empty string omits the block. The shipped default is a short roleplay-etiquette note nudging the familiar to lean on<silent>. See Context pipeline — Final reminder.[prompt].sleep_consolidation_system,sleep_stance_system,sleep_synthesis_system,dream_extraction_clause— static instruction text for the sleep passes and the fact-extractor's dream-framing clause. Dynamic window data is interpolated in code; only the wording is configurable. Placeholders: the stance / synthesis fields take{self_name};dream_extraction_clausetakes{self_name},{self_key},{ids}. Validation rails stay code-enforced regardless of this text. See Sleep — Prompt text is config, rails are code.
Default profile¶
Reference familiar at data/familiars/_default/, checked into the
repo. Two purposes:
- Fallback source. Any field missing from the user's
character.tomlfalls back to the corresponding value in_default/character.toml. No hardcoded defaults live in Python — the default profile is the single source of truth. - Documentation-by-example. A new operator copies
_default/todata/familiars/my-familiar/and edits from there.
The leading underscore keeps FAMILIAR_ID=_default from being a
meaningful selection.
TTS providers¶
| Provider | Env vars | Character fields |
|---|---|---|
azure (default) |
AZURE_SPEECH_KEY, AZURE_SPEECH_REGION |
azure_voice |
cartesia |
CARTESIA_API_KEY |
cartesia_voice_id, cartesia_model |
gemini |
GOOGLE_API_KEY / GEMINI_API_KEY |
gemini_voice, gemini_model (+ optional style / scene / pace / accent / context / audio-profile) |
Subscriptions¶
data/familiars/<id>/subscriptions.toml — which Discord channels the
bot listens in. Written by /subscribe-text and /subscribe-voice.
Not meant for hand edits — the slash commands rewrite the whole file on
every mutation.