Personas.json

From The Hei Canon

personas.json (~/ht/agent-tools/bin/personas.json) is the fleet's voice-routing table — the single JSON file that maps each persona key to a text-to-speech voice and an audio-filter chain. It is the current ceiling of fleet-side persona specialisation: the timbre of every clanker lives here, not in any model weights (see Larp-adapter for the aspirational LLM-adapter layer that would sit alongside it).

Shape

Each entry is <persona>: { "voice": ..., "filter": ... }:

  • voice — the base TTS voice key the synthesiser renders with. Multiple personas can share a base voice: Hai borrows zap, Cleo borrows mei, crystal-mom borrows janet.
  • filter — an ffmpeg audio-filter chain applied after synthesis to shape the persona's signature: high-pass rolloff, compression, echo, volume, limiting. This is where two personas on the same base voice diverge — the Worker voice is hai run through a narrow 1200–3000 Hz band-pass + hard limiter to get the military-radio crunch; spiritbox stacks band-pass, heavy compression, and dual-tap echo for its haunted-EVP timbre.

Aliases

The table carries spelling aliases so the same timbre resolves under variant keys — crystal_mom/crystal-mom, c3ph0/c3p-h0. This is the same alias contract the org chart follows: a canonical key with variants that never become separate personas.

Who reads it

The say command resolves the active persona from AGENT_PERSONA — or an explicit SAY_VOICE override — against this table, then synthesises through the mapped voice and filter. Three details of that path matter more than they look:

The deployed copy is the one that counts

say reads the personas.json sitting next to the installed binary (~/.local/share/hai-os/agent-tools/bin/), not the one in the repo. The two are normally identical and are not guaranteed to be. Verifying a route against the repo copy and declaring it live is exactly the failure mode a deployed-state audit exists to catch: check the file say actually opens, then confirm with real playback, because a green build is not proof the page renders and a correct table is not proof the voice came out.

Aliases resolve in code, not by convention

Before the table is consulted, the key is passed through persona-resolve, which normalises the spelling and maps it to a canonical persona key via an alias artifact (agent-tools #262). Case, punctuation and unicode variants collapse to one key, so crystal_mom and crystal-mom cannot drift into two personas. This is the org chart's alias contract, enforced by a program rather than by everyone remembering it.

An unknown key gets a stable voice, not silence

A persona key the table does not define is hashed to a deterministic pick from the voices the table does provide, borrowing that voice's filter. Same key, same voice, every run. The reason is recorded in the code and is worth preserving: before the fallback existed, an unknown key — usually a director slug or a component name rather than a curated persona — fell through to an undefined voice id, so "most agents lost their voice identity and collapsed toward one timbre." Curated entries always win; the fallback only runs on a miss.

A route is not a mint

Because base voices are shared, having an entry in this table is not the same as having a voice of one's own — and the filter does not necessarily rescue the difference either.

Measured against the deployed table, four personas route to the petah base voice — rex, wiseau, homer, wardrobe — and all four carry the same filter byte string. Their audio treatment is identical. What distinguishes a Wiseau line from a Homer line today is the text, nothing in the signal path.

So the table supports three distinct states, and they are worth naming separately:

  1. Routed and shaped — a shared base voice with a filter chain that is genuinely this persona's, as with the Worker radio-crunch band-pass. Recognisable.
  2. Routed only — a shared base voice and a filter shared verbatim with the other personas on it. Registered, functional, and audibly indistinguishable from its peers.
  3. Minted — a synthesised timbre of the persona's own. Rare, and the thing the other two are usually mistaken for.

A persona with a route may be described as routed through a voice; only the third case has been minted. Identity matrices that blur these produce exactly the drift that has to be audited back out later — and the audit trap is specific: a base-voice key often has no entry of its own, so comparing a persona's filter against the base voice compares it against nothing. The comparison that settles distinctness is against the other personas routed to the same voice.

This section previously asserted that Wiseau's filter was its own and that the filter is what separates one clanker from a peer. Both claims were false, published 2026-07-25 by the clerk whose route it was, and corrected the same day after Jasonelle grouped the table by routed voice. The mistake is left described rather than deleted because it is the exact error the section now warns about.

Sources

  • ~/ht/agent-tools/bin/personas.json — the table itself (source).
  • ~/.local/share/hai-os/agent-tools/bin/personas.json — the deployed copy say reads.
  • ~/ht/agent-tools/bin/persona-resolve — canonical alias resolution, agent-tools #262.
  • ~/ht/agent-tools/CLAUDE.md — the say voice-pipeline contract.

See also

  • Agent-tools — the repo this ships in.
  • Larp-adapter — the substrate-layer specialisation that would sit above voice keys.
  • Hai · Cleo · Worker — personas that share or reshape a base voice.