preferences.json is the single source of truth for all configurable runtime choices in a workspace — which LLMs are available, which is default for each purpose, voice settings, and audio behavior. No other code path provides a backup answer. Services call resolve_llm() and use what they get; if the result is None the service fails clearly.
Design principle: single source of truth
Every service that needs an LLM or voice configuration (Agent Manager, STT Service, TTS Service) reads frompreferences.json through the resolver functions. There are no fallback chains, no environment variable overrides, and no hardcoded defaults in consuming code. If preferences say “not configured,” the system reports the problem instead of silently falling back.
This keeps the configuration surface predictable: one file controls everything, and there is exactly one place to look when debugging model selection.
File location
hirocli setup. The user must register at least one LLM for the agent to function.
Schema
Top level
| Field | Type | Description |
|---|---|---|
version | int | Schema version for future migrations |
llm | object | LLM registry and default selections |
audio | object | Voice and audio behavior settings |
llm — LLM registry
| Field | Type | Description |
|---|---|---|
registered | array of LLMEntry | All LLMs the workspace can use |
default_chat | string | null | ID of the default chat LLM |
default_stt | string | null | ID of the default STT LLM |
default_tts | string | null | ID of the default TTS LLM |
LLMEntry
| Field | Type | Default | Description |
|---|---|---|---|
id | string | auto UUID | Unique identifier |
name | string | — | Human-readable label |
provider | string | — | LangChain provider identifier (e.g. openai, google-genai) |
model | string | — | Model name understood by the provider |
temperature | float 0–2 | 0.7 | Sampling temperature |
max_tokens | int ≥ 1 | 1024 | Maximum tokens in generated output |
capabilities | list of strings | [] | Purpose tags: chat, stt, tts |
audio — voice and audio behavior
| Field | Type | Default | Description |
|---|---|---|---|
agent_replies_in_voice | bool | false | Whether the agent should reply with synthesized voice |
accept_voice_from_user | bool | true | Whether the server processes incoming audio messages |
selected_voice | string | null | null | ID of the active VoiceOption |
voice_options | array of VoiceOption | [] | Registered TTS voice configurations |
VoiceOption
Each entry represents a configured TTS voice that the server can use for speech synthesis.
| Field | Type | Default | Description |
|---|---|---|---|
id | string | auto UUID | Unique identifier |
provider | string | — | TTS provider name (e.g. openai, gemini) — used to select the provider class in server_process.py |
model | string | — | TTS model ID (e.g. gpt-4o-mini-tts, gemini-2.5-flash-preview-tts) |
voice | string | — | Voice name (e.g. sage, Kore) |
instructions | string | "" | Voice affect/style prompt — controls accent, pacing, emotion (provider-specific) |
Resolution logic
resolve_llm(prefs, purpose)
Returns the LLMEntry to use for a given purpose (chat, stt, or tts). Resolution order — all within this one function, nowhere else:
- Explicit default —
default_chat,default_stt, ordefault_ttsID lookup. - First with matching capability — first entry whose
capabilitieslist includes the purpose. - First registered — if only one LLM exists, it answers for everything.
None— means “not configured.” Caller raises a clear error or disables the feature.
resolve_voice(prefs)
Returns the active VoiceOption. Resolution order:
selected_voiceID lookup.- First option in the list.
None— voice output disabled.
Consumers
| Component | Calls | Purpose |
|---|---|---|
| Agent Manager | resolve_llm(prefs, "chat") | Select the chat LLM at agent build time |
| Agent Manager | resolve_voice(prefs), agent_replies_in_voice | Determine whether to run TTS post-processing and which voice to use |
| STT Service / server_process.py | resolve_llm(prefs, "stt") | Select which STT provider and model to load |
| TTS Service / server_process.py | resolve_voice(prefs), agent_replies_in_voice | Select which TTS provider and model to load, or disable TTS |
| Admin UI agents page | resolve_llm(prefs, "chat") | Display current LLM configuration |
| Setup tool | save_preferences() | Create empty preferences on workspace init |
Future: refactoring to SQLite JSONB
The I/O layer (load_preferences / save_preferences) is the only code that touches the file. Swapping to SQLite JSONB later means changing only those two functions; the Pydantic model, resolvers, and all consumers stay untouched.
See also
Agent Manager
How the agent reads its LLM selection from preferences.
Message adapter pipeline
How STT provider loading is driven by preferences.
Workspace data store
The SQLite database and file layout that preferences.json lives alongside.
