Personas
Werbos's voice is composed, not hardcoded. Three rungs: the dialed default, the always-full character, and the named opt-ins (tom, html, ...).
A persona is a small markdown file that shapes how werbos sounds in a session. Personas are layered, not flat — what the model sees as its system prompt is composed at session start from up to three pieces:
character → the always-full 13% (werbos's own taste, hard rules)
+ voice → the dialed 87% (the persona that takes the mirror slider)
+ user.md → who YOU are (operator profile from `werbos init`)
= the system prompt's persona section
The mirror slider (Sprint F.2) controls the blend ratio at the character / voice line. The user-profile rung was added in Sprint A.5 day 3.
Where personas live
Two locations:
werbos/personas/<slug>.md # ships in the package — load by slug
~/werbos/personas/<slug>.md # operator-owned — load by file path
Packaged personas (the slugs that ship with werbos):
| Slug | Role | Loaded via |
|---|---|---|
default | Voice baseline. The dialed 87%. | implicit (no flag) |
werbos-character | The always-full 13%. | implicit + --character-file to override |
tom | Memoryless persona (anterograde-amnesia primitive). Replaces both default and character entirely. | werbos chat --tom |
html | Single-file-HTML expert (Sprint Sk-day-8). | werbos chat --persona-name html |
How to swap the voice
Four CLI surfaces, in precedence order (later wins over earlier when both are set):
werbos chat # 1. built-in default (lowest precedence)
werbos chat --persona "be a pirate" # 2. literal string override
werbos chat --persona-name html # 3. load packaged persona by slug
werbos chat --persona-file ~/x.md # 4. load arbitrary file (highest precedence)
Why --persona-name and --persona-file are both surfaces.
Once werbos is pip install-ed, the package data lives inside a
wheel; there’s no obvious filesystem path for --persona-file to
point at. --persona-name html resolves through
werbos.personas.load_persona("html") which works whether werbos is
running from source or from a wheel. --persona-file stays for
operator-authored personas in ~/werbos/personas/ or anywhere else
on disk.
Unknown name fails loudly:
$ werbos chat --persona-name nope
--persona-name 'nope': no packaged persona by that name. Available
personas live in werbos/personas/*.md.
The mirror slider — character vs voice
The blend ratio between the always-full character (13%) and the dialed voice (87%) is the mirror slider:
werbos chat --mirror 87 # baseline best-friend (default)
werbos chat --mirror 0 # pure character — no voice mirroring
werbos chat --mirror 100 # full voice — character still appears, voice at full strength
Inside the REPL:
werbos> /mirror # show current mirror %
werbos> /mirror 50 # change persistently for this session
werbos> /mirror 30 disagree with me on this
# one-turn override; resets after the AI replies
The character layer is never scaled by the slider — at
--mirror 0, the voice persona is omitted entirely but the
character file still anchors the session. The slider scales the
voice’s strength, not the character’s existence.
The mirror also persists per-chat in the .pfm archive
(SessionMeta.mirror_ratio) and in localStorage in the SPA, so
resumed chats remember their setting.
The user profile — who YOU are (Sprint A.5 day 3)
werbos init writes a six-section template at ~/werbos/user.md:
~/werbos/user.md
├── # Background (career, projects, what you're building)
├── # Working style (how you like to work)
├── # Communication (terse / verbose, hedging tolerance, ...)
├── # Recurring contexts (codebases, domains you live in)
├── # Anti-scope (things werbos should NOT do for you)
└── # Evolving notes (things in flux this week/month)
Fill it in. ContextBuilder hydrates it on every session AFTER the
persona and project_context, BEFORE history. A
KIND_USER_PROFILE_LOADED event records {path, sha256, byte_size}
into the chat archive — metadata only, never the body. Same shape
as Tom’s KIND_TOM_ATTESTATION: auditors get an envelope they can
verify without the contents leaking.
16 KB cap; over-cap returns a truncated profile + a warning flag.
The Tom rung — memoryless by design
--tom replaces both the character and the voice entirely with a
single memoryless primitive. Every turn, the model receives:
werbos/personas/tom.md(the persona)- The notebook (
~/werbos/tom_notebook.json, 4 KB cap) if non-empty - The current user message
That’s it. No prior turns. No memory bank. No tools except
notebook_write. LEASH_EPHEMERAL enforces “shell forbidden by
design” regardless of --shell-yolo.
werbos chat --tom
Each Tom turn emits a KIND_TOM_ATTESTATION event proving
history_length: 0. Auditors grep archived .pfm files for the
event kind and verify the value. The signature of
build_tom_history(*, system, notebook, current_turn) cannot
accept prior turns — type-level enforcement of the no-leak claim,
not just a runtime check.
The Tom rung is the floor of the memory ladder (K.0 → K.1 → K.2 → K.3 = no memory → repo context → user memory bank → proactive surfacing). The 10-second-Tom consumer wedge (“Hi, I’m Tom”) doubles as the marketing surface for a B2B zero-retention agent (HIPAA / multi-tenant / injection containment).
The HTML rung — voice-layer specialization
--persona-name html swaps the dialed voice with a character that
treats every HTML/CSS/JS task as a chance to ship a complete,
runnable, single-file artifact:
werbos chat --persona-name html --vision-model llama3.2-vision:11b
Style mined from werbos/ui/public/tom.html (the canonical
single-file reference in this repo). Pins:
- Single-file HTML — markup, styles, script all inlined
- No build pipeline, no
package.json, no React, no transpiler - CSS custom properties at
:root; system fonts; semantic markup textContentfor user data (neverinnerHTML)- Head-comment + script-comment discipline
Anti-patterns named explicitly: no Vite, no Tailwind CDN, no
localStorage by default, no speculative abstractions. The
character + user.md still apply — --persona-name swaps the
voice slot, not the whole stack.
File format
Persona files are markdown with an HTML-comment header:
<!--
version: 0.1.0
created: 2026-05-09
updated: 2026-05-09
tuned-for: any base model. Voice-layer persona; pair with the
default werbos-character.md for the always-on 13%.
role: One-line description of when this persona applies.
notes: Pinning rationale, source of style data, gotchas.
-->
You are Werbos in HTML-expert mode.
[full persona body]
The HTML-comment block is metadata for human readers + future tooling; werbos doesn’t parse it. The body is what gets injected as the persona section of the system prompt.
Writing your own
- Decide which slot you’re filling: voice or character.
- Drop the file at
~/werbos/personas/<slug>.md(orwerbos/personas/<slug>.mdif you want it to ship in the package). - Use a similar comment-header to the packaged personas — versioned,
tuned-for,role,notes. - Use second person (“You are…”, “You don’t…”) and at least one
specific don’t per persona. The
tom.mdandhtml.mdfiles are good references. - Test:
werbos chat --persona-file ~/werbos/personas/myslug.md
The runtime never validates the body; the model is the only judge. Iterate until the responses feel right.
See also
- Chat surfaces —
/mirror,/private,/notebook,/skill, every other slash. - Skills — knowledge-injection alongside the persona; skills append to the system prompt as a SystemMessage AFTER the persona.
- Vision —
/seeslash + the--vision-modelflag pair, useful with--persona-name html.