Oshun Platform · Architecture

Persona, Avatar, and Voice Packs

A focused page within the Oshun Platform Architecture documentation. The full map and every sibling page live in the Architecture hub.

9sections14 minread5tables

On this page

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.tsz.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:

text
drafted → in-review → rehearsal → approved-for-test → approved-for-release → released → deprecated → retired

Aliases (draftdrafted, approvedin-review, publishedreleased) 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 (illustrativestylised-3dsemirealistic-3dphotoreal-genericphotoreal-specific), voices across VOICE_NATURALNESS_TIERS (flat-ttswarm-syntheticnaturalisticcloned-consentedcloned-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:

ts
// 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#

text
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:

What's real vs. spec-only — the honest split#

  • Real and tested in-repo: the PersonaRecord registry with deterministic fingerprinting; the frozen seven-role catalog with capability ceilings; AvatarPack, VoiceProfile, and ConsentLedger contracts with validators and authorisePersonaConsent; the realism/impersonation/provenance thresholds with requireC2paManifest wired 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.