Anatomy of persona.yaml
Every field, top to bottom. With examples.
A persona is one YAML file at server/personas/<name>/persona.yaml. This page walks every field in the order they typically appear, with a short example for each.
The minimal persona
The smallest valid persona:
version: 1
name: example
voice: alloy
language_code: en
transcription_language: en
timezone: America/New_York
system_prompt: |
You are a helpful assistant. Speak in short, plain sentences.
skills:
system: {}That's seven required fields and a one-skill list. A real persona has more, but the structure is the same.
Required fields, top to bottom
version
version: 1The schema version. Always 1 today. Future-compatibility hook for if the schema ever changes meaningfully.
name
name: kitchenhelperThe persona's identifier. Used:
- As the directory name (
server/personas/kitchenhelper/). - As the database name (
server/personas/kitchenhelper/data/kitchenhelper.db). - In log events (every event tagged with
persona=kitchenhelper). - As the
HUXLEY_PERSONAenv var value.
Use lowercase, no spaces, no special characters. Match the directory name.
voice
voice: coralWhich OpenAI Realtime voice the model uses. Common choices:
| Voice | Character |
|---|---|
alloy | Neutral, balanced, default-feeling |
coral | Warm, conversational, slightly slow |
echo | More formal, clipped |
shimmer | Light, bright |
verse | Stylized, expressive |
sage | Calm, measured |
OpenAI ships more — try them in their playground before committing. Voice is overridable at runtime via HUXLEY_OPENAI_VOICE=foo, useful for A/B testing.
language_code
language_code: enISO 639-1 (two letters). The persona's primary language. Used for:
- Default tool descriptions (skills should write descriptions in this language).
- Default system prompt (the
system_promptfield below should be in this language). - Constraint instruction text (the framework injects constraint reminders in this language).
If the persona is multilingual, set this to the primary language; per-language overrides go in i18n.
transcription_language
transcription_language: enA hint to OpenAI's Whisper transcription. Often the same as language_code, but can differ:
- A persona that speaks English to the user but transcribes user speech as Spanish (the user speaks Spanish, the persona narrates English translations).
- A persona that's bilingual — set to whichever language the user is most likely to speak.
If you're not sure, match language_code.
timezone
timezone: America/BogotaIANA timezone string. Used by skills like system.get_current_time to speak local time correctly. List at iana.org/time-zones.
system_prompt
system_prompt: |
You are an enthusiastic cooking assistant. The user has their hands
in ingredients, so keep replies under three sentences. Never ask
clarifying questions before answering — give your best guess.
When the user asks for a measurement and you're not sure of the
exact amount, give a confident estimate and tell them it's approximate.Multi-line. In the persona's language_code. This is where personality lives.
The framework injects three things into this prompt automatically — you don't write them yourself:
- Available tools — the LLM gets the function-calling schema for every skill's tools.
- Constraint reminders — for each constraint enabled, an instruction is appended.
- Time context — current time and timezone, so "what time is it" works.
Practical guidance:
- Be specific. "Be helpful" is useless. "Reply in under three sentences" is actionable.
- Establish character. Personality the user can feel — warmth, dryness, formality.
- List quirks. Things this persona does that others don't. "Always responds 'Hola, mijo' when the user says 'Hey Huxley.'"
- Don't list tools. The framework does that.
- Don't list constraint rules verbatim. Use
constraints:and let the framework inject them.
We cover prompt-craft more in Voice and language.
skills
skills:
audiobooks:
library: audiobooks
sounds_path: sounds
sounds_enabled: true
news:
location: "Bogotá"
latitude: 4.71
longitude: -74.07
country_code: "CO"
language_code: "es"
system: {}
timers: {}A map of skill name → config dict. Each key must match a skill's entry-point name in pyproject.toml.
Empty config = {}. The skill must accept that (most do).
Relative paths in skill config resolve against server/personas/<name>/data/. So library: audiobooks resolves to server/personas/<name>/data/audiobooks/.
Listed but not installed → startup fails. The framework refuses to silently ignore a missing skill.
Optional fields
constraints
constraints:
- never_say_no
- confirm_destructiveA list of behavioral rules. See Concepts: Constraints for the registry. Personas pick which to enable; skills opt into respecting them.
Default: empty list (no constraints).
ui_strings
ui_strings:
listening: "Escuchando..."
too_short: "Muy corto, intenta de nuevo."
sent: "Listo."
responding: "Pensando..."
ready: "Listo."Localized strings the dev client UI displays. The browser PWA reads these to label its states. The firmware client uses earcons instead of text, so it ignores these strings.
If omitted, the framework uses English defaults.
i18n
i18n:
en:
transcription_language: en
system_prompt: |
You're a warm assistant. Speak in short sentences.
ui_strings:
listening: "Listening..."
ready: "Ready."
fr:
transcription_language: fr
system_prompt: |
Tu es un assistant chaleureux. Parle lentement.
ui_strings:
listening: "J'écoute..."Per-language overrides. Each top-level key is an ISO 639-1 language code. The value is a partial persona — you only override the fields you want different.
When a client connects with ?lang=fr, the framework merges the persona's primary language config with the fr overrides. Skills (and their tool descriptions, completion prompts, etc.) can also be overridden via i18n inside their skill block:
skills:
audiobooks:
library: audiobooks
i18n:
en:
on_complete_prompt: "The book is finished. Tell the user warmly."
es:
on_complete_prompt: "El libro terminó. Anúncialo con calidez."We cover the full multilingual pattern in Multilingual personas.
Field order
The framework doesn't care about field order — YAML is a map, order is for humans. But there's a convention that makes personas easier to scan:
- Identity:
version,name - Voice and locale:
voice,language_code,transcription_language,timezone - Behavior:
system_prompt,constraints,ui_strings - Multilingual:
i18n - Capability:
skills
Identity at the top, behavior in the middle, big stuff (skills, i18n blocks) at the bottom where you scroll through them.
Validation
The framework validates personas at startup. Common failures:
- Missing required field. Error names the field. Fix it and restart.
- Skill not installed. Error names the skill.
uv add huxley-skill-<name>and restart. - YAML syntax error. Error gives line and column. Fix indentation.
- Invalid timezone. Error names the bad value. Check IANA list.
- Invalid voice. OpenAI rejects on first session creation, not at startup. You'll see it in the first turn.