Skip to main content
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 from preferences.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

<workspace>/preferences.json
Created with structural defaults (no LLMs registered) during hirocli setup. The user must register at least one LLM for the agent to function.

Schema

Top level

{
  "version": 1,
  "llm": { ... },
  "audio": { ... }
}
FieldTypeDescription
versionintSchema version for future migrations
llmobjectLLM registry and default selections
audioobjectVoice and audio behavior settings

llm — LLM registry

{
  "registered": [
    {
      "id": "uuid",
      "name": "GPT-4.1 Mini",
      "provider": "openai",
      "model": "gpt-4.1-mini",
      "temperature": 0.7,
      "max_tokens": 1024,
      "capabilities": ["chat", "stt"]
    }
  ],
  "default_chat": "uuid-or-null",
  "default_stt": "uuid-or-null",
  "default_tts": "uuid-or-null"
}
FieldTypeDescription
registeredarray of LLMEntryAll LLMs the workspace can use
default_chatstring | nullID of the default chat LLM
default_sttstring | nullID of the default STT LLM
default_ttsstring | nullID of the default TTS LLM

LLMEntry

FieldTypeDefaultDescription
idstringauto UUIDUnique identifier
namestringHuman-readable label
providerstringLangChain provider identifier (e.g. openai, google-genai)
modelstringModel name understood by the provider
temperaturefloat 0–20.7Sampling temperature
max_tokensint ≥ 11024Maximum tokens in generated output
capabilitieslist of strings[]Purpose tags: chat, stt, tts

audio — voice and audio behavior

{
  "agent_replies_in_voice": false,
  "accept_voice_from_user": true,
  "selected_voice": "uuid-or-null",
  "voice_options": []
}
FieldTypeDefaultDescription
agent_replies_in_voiceboolfalseWhether the agent should reply with synthesized voice
accept_voice_from_userbooltrueWhether the server processes incoming audio messages
selected_voicestring | nullnullID of the active VoiceOption
voice_optionsarray of VoiceOption[]Registered TTS voice configurations

VoiceOption

Each entry represents a configured TTS voice that the server can use for speech synthesis.
FieldTypeDefaultDescription
idstringauto UUIDUnique identifier
providerstringTTS provider name (e.g. openai, gemini) — used to select the provider class in server_process.py
modelstringTTS model ID (e.g. gpt-4o-mini-tts, gemini-2.5-flash-preview-tts)
voicestringVoice name (e.g. sage, Kore)
instructionsstring""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:
  1. Explicit defaultdefault_chat, default_stt, or default_tts ID lookup.
  2. First with matching capability — first entry whose capabilities list includes the purpose.
  3. First registered — if only one LLM exists, it answers for everything.
  4. None — means “not configured.” Caller raises a clear error or disables the feature.

resolve_voice(prefs)

Returns the active VoiceOption. Resolution order:
  1. selected_voice ID lookup.
  2. First option in the list.
  3. None — voice output disabled.

Consumers

ComponentCallsPurpose
Agent Managerresolve_llm(prefs, "chat")Select the chat LLM at agent build time
Agent Managerresolve_voice(prefs), agent_replies_in_voiceDetermine whether to run TTS post-processing and which voice to use
STT Service / server_process.pyresolve_llm(prefs, "stt")Select which STT provider and model to load
TTS Service / server_process.pyresolve_voice(prefs), agent_replies_in_voiceSelect which TTS provider and model to load, or disable TTS
Admin UI agents pageresolve_llm(prefs, "chat")Display current LLM configuration
Setup toolsave_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.