Every voice the customer hears, every face they see, and every persona they
converse with is governed by one cross-cutting catalog with consent, lineage,
and lifecycle metadata. This catalog is not a per-domain concern: a single
registry binds a persona's role and policy pack, its voice profile, its avatar
pack, and the consent artifacts that license its likeness, then enforces a
release gate before any of it reaches a member. This page sits beside
Cross-Domain Support and
Trust, Safety, and Privacy; the product scope
is
V1/features.md § Persona, Avatar, and Voice Packs
and the backlog is §14.
The headline fact, often missed: the registry is genuinely, deeply
implemented, far beyond a contract skeleton. libs/oshun/persona-registry/
holds a fingerprinted PersonaRecord registry, a frozen catalog of the seven
canonical roles with capability ceilings, real AvatarPack / VoiceProfile /
ConsentLedger contracts, ~14 individually tested evaluation modules, drift
detectors, disclosure-visibility gates, and end-to-end signoff tests. What is
not yet real — actual avatar rendering and lip-sync against ground-truth video,
and live cloned-voice TTS output — is called out below with which kind of
not-yet it is, because "provider-gated" covers three different states and only
one of them is a key away; the registry is the contract and policy layer, not
the render farm.
The registry — libs/oshun/persona-registry/#
The package @oshun/persona-registry is the contract layer: pure data plus pure
functions (get, list, filter, validate, fingerprint), no IO, no clock —
timestamps are inputs, not outputs. The registry is modeled as an immutable
snapshot (PersonaRegistrySnapshot): a map of records keyed by a stable
PersonaId, plus an orderedIds index and a deterministic fingerprint
(fingerprintPersonaRegistrySnapshot) so distributed callers can verify they
agree on the same registry state without a deep-equal.
An architecturally important design choice: the registry is dependency-free by
intent. Its index.ts explicitly does not import
@oshun/persona-policy-lilith; instead each PersonaRecord carries an opaque
policyPackReference string that the caller resolves against the Lilith
policy package. This keeps the registry consumable by any domain without pulling
in the policy engine, and keeps the policy substrate swappable.
The five files the architecture hub names all exist and are real:
realism-impersonation-thresholds.ts, voice-provider-abstraction.ts,
avatar-pack.ts, watermark-provenance.ts, launch-multimodal-assignments.ts.
PersonaRecord — the canonical record#
The registry's own record type is PersonaRecord (index.ts), not a type
named Persona. (A Persona does exist, but it is the Zod-inferred contract
type at libs/contracts/src/common/persona.ts — z.infer<typeof PersonaSchema>
— a different artifact in a different package. The registry's record is
PersonaRecord.) Its core fields:
| Field | Meaning |
|---|---|
id / displayName / summary |
Stable URL-safe id (lowercase, digits, hyphens, 3–64 chars) + labels |
family |
One of the nine PERSONA_FAMILY_KINDS |
status |
One of the PERSONA_APPROVAL_STATUSES (lifecycle state) |
surfaces |
Subset of PERSONA_SURFACES (tara, arete, metis, studio, admin, shell surfaces, …) |
policyPackReference |
Opaque Lilith policy-pack reference the caller resolves |
ownerTeam / rationale |
Governance owner + human-readable rationale surfaced in admin |
lineage |
Declared tradition lineage slugs the persona may speak from (e.g. theravada-vipassana) |
scope |
Explicit bound on the conversational territory the persona may inhabit |
The coarse PERSONA_FAMILY_KINDS taxonomy has nine members —
contemplative, coach, explainer, guide, scholar, support,
moderator, reviewer, executive — distinct from the seven roles below.
The seven canonical roles#
roles/catalog.ts defines exactly seven canonical roles, in order:
teacher, coach, explainer, steward, comparative, narrator,
assistant — matching
V1/features.md § Persona Taxonomy
and the hub. roles.test.ts asserts the Set of exactly those seven. The
catalog is frozen at build time (runtime modification forbidden), and each
role declares a capability ceiling, a default tone band, and a set of
hard-bans:
| Role | Capability ceiling (examples) | Default tone | Notable hard-bans |
|---|---|---|---|
teacher |
voice-tts, avatar-render, living-scene-render, memory-read/write, scheduled-invite |
contemplative |
cross-lineage-mix, medical/financial/legal-advice, sales-language |
coach |
voice-tts, avatar-render, memory-read/write, scheduled-invite |
reflective |
cross-lineage-mix, commanding-imperative, guarantee-language |
explainer |
voice-tts, avatar-render, living-scene-render, memory-read |
neutral |
(read-only memory; explanatory register) |
comparative |
includes cross-lineage-mix |
— | the only role permitted to cross lineages |
The capability ceiling is what makes this enforceable rather than advisory: a
teacher cannot mix lineages because cross-lineage-mix is in its hard-bans
and absent from its ceiling. The test suite proves comparative is the sole
role holding cross-lineage-mix in its ceiling. roles/types.ts and
roles/construct.ts back the catalog (constructing a concrete persona from a
role plus per-persona deltas).
The approval lifecycle#
PERSONA_APPROVAL_STATUSES carries both the canonical states and legacy
aliases. The canonical path (PERSONA_APPROVAL_STATUSES_CANONICAL) is:
drafted → in-review → rehearsal → approved-for-test → approved-for-release → released → deprecated → retired
Aliases (draft→drafted, approved→in-review, published→released) are
normalized by canonicalizePersonaApprovalStatus. The lifecycle is not just an
enum — lifecycle/ implements a state-machine, a review-queue, a
provenance-bundle, champion-challenger testing, re-eval-cadence,
revocation-cascade, and retirement-recovery. The completeness audit
(2026-06-22) marks the persona-voice-avatar-approval-workflow as
PARTIAL: registry lifecycle and signoff are well covered, but full
deprecate/retire/recovery and the consumer-picker round-trip are thinner than
the create→approve→release path. The honest read is "approval and signoff are
real; the back-half retirement/recovery and end-to-end consumer picker are in
progress."
Contracts — VoiceProfile, AvatarPack, ConsentLedger#
The registry's multimodal contracts are real interfaces with validators, not placeholders.
AvatarPack (avatar-pack.ts) binds a persona to its visual assets — a
baseMesh, blendshapesAsset, and textureSet (glTF / USDZ / FBX references),
a riggingVersion, and the full exposedBlendshapes set and visemeMap for
lip-sync. It also carries renderingConstraints, license, and watermark
metadata. License validity is checked at runtime by checkAvatarPackLicense,
which returns an AvatarPackLicenseCheckResult (active and reason) —
refusing to render when a license is not yet in effect or has expired.
VoiceProfile (voice-profile.ts) binds a persona to a voice: a kind,
internal engineId and opaque engineModelRef, BCP-47 supportedLocales /
defaultLocale, an acousticBaseline, provenance requirements, and the
requiredConsent artifact kinds. The §14.3 fields make the consent story
explicit: a commercial vendor (ElevenLabs, Microsoft, …) distinct from the
internal engine, and a sourceIdentityRef (a stable reference to the
talent/source person for cloned voices, null for synthetic). It also carries
a consentReference into the ledger, a prosodyProfile, and a
naturalnessBaseline.
ConsentLedger (consent-ledger.ts) is the real consent primitive. Note
that there is no type named ConsentRecord anywhere in the registry; the
docs' older "every persona binds a ConsentRecord" phrasing names a type that
does not exist. The actual primitive is the ledger: a byKind map indexing
ConsentArtifacts by kind, each artifact carrying an expiry, a
ConsentArtifactScope, and an optional ConsentRevocation. Runtime
authorization flows through authorisePersonaConsent, which takes a
ConsentAuthorisationRequest (persona id, required kinds, surface, modality,
commercial-use flag, current time, the ledger) and returns a
ConsentAuthorisationResult with per-kind findings of
ok | missing | expired | revoked | scope-mismatch. An authorized result
requires every required kind to have at least one unrevoked, non-expired
artifact whose scope covers the requested surface, modality, and commercial use.
Revocations propagate through lifecycle/revocation-cascade (a takedown
cascade), so pulling consent retracts the avatar and voice that depended on it.
Realism, impersonation, and provenance thresholds#
realism-impersonation-thresholds.ts ships the canonical realism ceilings.
Avatars are ranked across an ordered AVATAR_PHOTOREALISM_TIERS ladder
(illustrative → stylised-3d → semirealistic-3d → photoreal-generic →
photoreal-specific), voices across VOICE_NATURALNESS_TIERS (flat-tts →
warm-synthetic → naturalistic → cloned-consented → cloned-unconsented).
There are universal prohibitions: VOICE_PROHIBITED_TIERS bars
cloned-unconsented outright — a voice cloned from a specific real person
without consent is always impermissible, enforced regardless of context. Each
policy class also declares a requireC2paManifest flag.
requireC2paManifest is a real, recurring policy input, referenced across
voice-profile.ts, voice-provider-abstraction.ts,
realism-impersonation-thresholds.ts, and watermark-provenance.ts. The last
of these defines PROVENANCE_RULES per ProvenanceScenario, most of which set
requireC2paManifest: true; checkProvenanceCompliance then verifies a
ProvenanceClaim actually carries a C2PA manifest (and issuance time) before
the content is allowed out. So "C2PA provenance" is not aspiration — it is a
boolean the release gate reads.
Voice-quality and voice-abuse policy is enforced by
@oshun/persona-policy-lilith (the package the registry references via the
opaque policyPackReference) together with these registry thresholds, including
requireC2paManifest as one input at release-gate time.
The evaluation suites — real, tested modules#
Where V1/features.md § Evaluation Suites describes the eval
strategy in prose, the registry implements each suite as an individual,
independently tested module (each eval-*.ts ships with a sibling
eval-*.test.ts):
| Module | What it scores |
|---|---|
eval-lipsync-alignment |
Lip-sync vs. audio alignment across LIPSYNC_ALIGNMENT_AXES (default threshold 0.85, hard-fail axes at min 0.9) |
eval-coherence |
Persona conversational coherence |
eval-deceptive-realism-risk |
Risk that realism is deceptive (avatar/voice too real for the context) |
eval-impersonation-risk |
Risk the persona impersonates a real, specific person |
eval-cloned-voice-red-team |
Red-team battery against cloned-voice abuse |
eval-disclosure-visibility |
Whether the "AI persona" disclosure is actually visible enough |
eval-expression-quality |
Quality of facial/vocal expression |
eval-multimodal-identity-coherence |
Whether voice + avatar + text present one coherent identity |
eval-style-consistency-drift |
Drift from the persona's established brand/style |
These are backed by drift detectors and disclosure measurement: the "disclosure
visibility ≥ N time-on-screen" trust gate is implemented concretely by
disclosure-axis-drift-detector.ts, disclosure-visibility-measurement.ts
(per-surface DISCLOSURE_VISIBILITY_FLOORS_PER_SURFACE floors), and
token-time-disclosure-filter.ts. The "prompt-time impersonation-attempt
detection" trust gate is implemented by prompt-impersonation-detection.ts
(detectPromptImpersonationAttempt over PROMPT_IMPERSONATION_PATTERNS).
End-to-end signoff is exercised by tests like
admin-approval-rollback.e2e.test.ts and launch-family-signoff.e2e.test.ts.
Launch cast assembly#
The actual launch cast is assembled — not merely "signed off" — by four modules:
launch-roster.ts (the LAUNCH_ROSTER of LaunchRosterEntry records across
LAUNCH_WAVES wave-1/2/3, with per-entry and whole-roster validators),
launch-family-signoff.ts (evaluateLaunchPersonaSignoff,
evaluateLaunchFamilySignoff, evaluateAllLaunchFamiliesSignoff, with rollback
evidence and a summarizeLaunchSignoff), launch-persona-config.ts
(per-persona prompt / policy / grounding / memory / disclosure config), and
launch-multimodal-assignments.ts. The last binds each launch persona to its
concrete modalities:
// libs/oshun/persona-registry/src/launch-multimodal-assignments.ts
export interface LaunchMultimodalAssignment {
readonly personaId: string;
readonly familyId: PersonaFamilyId;
readonly enabledModalities: PersonaModalitySet;
readonly voiceProfile: VoiceProfile | null; // null for text-only families
readonly avatarPack: AvatarPack | null; // null for text-only / voice-only
readonly rationale: string;
}
listLaunchVoiceProfiles() and listLaunchAvatarPacks() flatten the launch
roster into the concrete voice/avatar assets the runtime must provision, with a
launch-wide commercial-use license window and canonical watermark algorithm ids
(oshun-phase-watermark-v1, oshun-avatar-watermark-v1) the runtime must
recognize. This is the cast-assembly machinery — richer than a generic "release
signoff" line.
Voice provider abstraction — swappable, ElevenLabs-backed#
voice-provider-abstraction.ts keeps providers swappable. The canonical
provider set (CANONICAL_VOICE_PROVIDERS) is oshun-native, elevenlabs,
xtts-local, tortoise-local, openvoice-local, custom, each with declared
capabilities (synthetic-tts, cloned-tts, multilingual, streaming,
emotion-conditioning, long-form) and a fallback order
(CANONICAL_VOICE_PROVIDER_FALLBACK_ORDER, e.g. synthetic →
oshun-native, xtts-local, elevenlabs, custom). Cloned-voice safeguards
(CANONICAL_CLONED_VOICE_SAFEGUARDS) and per-output-class watermark
requirements are declared alongside.
The "ElevenLabs (and future providers) swappable" claim is backed by real
provider integration files:
libs/isis/ai-providers/src/providers/tts/elevenlabs-provider.ts and
libs/isis/audio-generation/src/voice/elevenlabs-client.ts. Honest caveat: the
registry layer defines the abstraction, fallback order, and quality
requirements; the provider modules exist but live cloned-voice TTS output is
credential-gated — the code path is complete and no key has been supplied
here, so it was not exercised end-to-end in this architecture pass. Actual
avatar rendering and lip-sync against ground-truth video is a different
state again: see the vocabulary below, and
Phase 182 for where that
work now lives. The registry ships the eval-lipsync-alignment scorer and
lipsync-ground-truth-pairs fixtures; the ground-truth render comparison itself
has no route that has ever produced a comparable render.
Phase 182 — where the rendering half actually stands#
The registry above is the contract and policy layer. The thing it governs — a
video of a real person saying approved words — is built in Phase 182
(TODOS/phase-182.md), and its architecture is worth stating here because this
page is where people come looking for it.
The path#
authorized references -> approved script -> controlled speech audio -> shot plan
-> native audio-conditioned video candidates
-> audiovisual / identity / safety evaluation -> select or retry
-> encode / provenance -> release
The delivery guarantee, stated honestly#
Phase 182 treats perfect lip sync as a delivery requirement, not a claim that every model's first sample will be perfect. Oshun may use a native audio-conditioned model so that generation and synchronisation happen in one render, and it still inspects the actual output frames and audio. A take that misses words, assigns speech to the wrong person, drifts out of sync, hides the mouth for an unjustified duration, damages identity, or crosses a release threshold is rejected, retried, rerouted, or sent for explicitly disclosed corrective processing.
So the promise is narrow and checkable: no known bad-sync take is released, while measured pass rates and limitations stay visible. It is not "the lip sync is perfect".
Exact dialogue is not a post-processing step#
The approved audio is supplied to the video model, not synchronised onto its output afterwards. Prompt-only generated dialogue remains an optional creative mode and is not the exact-dialogue mode, because a video model may paraphrase, omit words, mispronounce names, or change timing — and nothing downstream can tell you it did unless the words were fixed first.
Today, of the five model families in the adapter registry, only Wan 2.7/2.6 carries supplied audio on the OpenRouter route; every other family refuses exact-dialogue mode for want of an audio input. That is measured from the registry rather than asserted, and it means exact dialogue and multi-speaker currently rest on a single family.
The active-scene lane is not the avatar lane#
This is the distinction this page's avatar-render capability does not express.
An avatar lane renders a head speaking a script against a chosen background.
An active-scene lane renders a person performing in a place. The two can
match on every capability a request names — duration, resolution, references,
seed — and produce products a creator would never confuse.
So routing across that boundary is the wrong product, not a degraded one, and it is a refusal rather than a warning. Five capability classes exist — active-scene, avatar, video-edit, dubbing, corrective-sync — and the last two are declared with no mode reaching them yet, because a class with no mode is a visible gap while an undeclared class is an invisible one.
Status, so nobody reads this as shipped#
The Phase 182 modules are implemented and unit-tested; none of them is
mounted — the human-video routes have no caller outside their own specs. No
route has a continuity benchmark, no evaluator has scored a real generated clip,
and nineteen threshold sets are exported as PROVISIONAL pending calibration.
The detail lives in these, all of which state their own gaps:
- Creator workflow
- Honest limitations and troubleshooting
- API guide
- Provider operations
- Evaluator operations
- Review and escalation
- Media lifecycle and provenance
- V1 migration guide
What's real vs. spec-only — the honest split#
- Real and tested in-repo: the
PersonaRecordregistry with deterministic fingerprinting; the frozen seven-role catalog with capability ceilings;AvatarPack,VoiceProfile, andConsentLedgercontracts with validators andauthorisePersonaConsent; the realism/impersonation/provenance thresholds withrequireC2paManifestwired into the gate; ~14 eval modules plus drift / disclosure detectors; and the launch roster / family-signoff / multimodal-assignment cast-assembly machinery — all with sibling test suites. - Not yet real, in three distinct states — see below.
"Provider-gated" is three states, and they need three different actions#
The phrase reads as "a key away" to every reader, and at least two of the things
it covered here are not. 182.C.10.04 already drew this line for provider
probes — probe_inconclusive is not probe_failed, region_unrecorded is not
region_unavailable — and the same distinction belongs in the prose:
| State | What it means | What closes it |
|---|---|---|
| credential-gated | the code path is complete and nobody has supplied a key here | supply a key and run it |
| unverified | the code exists and has never run against the provider at all, so whether it works is unknown | a live probe (182.C.10.04), bound to the dated model slug |
| unreachable | no route we have can do this, whatever keys exist | build it or drop the claim |
Applied to this page's own edges:
| Claim | State | Evidence / gap |
|---|---|---|
| Live cloned-voice TTS output | credential-gated | provider modules exist (elevenlabs-provider.ts, elevenlabs-client.ts); no key supplied in this pass |
| Avatar rendering + lip-sync vs ground truth | unreachable | no route has produced a comparable render; the scorer and fixtures exist, the render does not. Phase 182 §17–§20 is the work |
| Exact-dialogue video (approved words) | unverified | one model family carries supplied audio on the OpenRouter route; the path is implemented and not mounted — no caller outside its specs |
| Back half of the approval workflow | partial | deprecate/retire/recovery and consumer-picker round-trip, audited PARTIAL on 2026-06-22 |
Matching the V1 docs' candor: honest "not yet, and here is which not-yet" beats
both fake "shipped" and a phrase that quietly implies somebody just needs to
find the API key. The policy and contract layer is genuinely shipped. The
backlog is §14, and the rendering half's own tracker is
docs/domains/isis/human-video/implementation-tracker.md, whose status
vocabulary
(not-started → implemented → wired → live-probed → benchmark-passed → release-enabled)
is the finer-grained version of the three states above.