Oshun Platform · Architecture

Lilith — Contemplative Policy Substrate

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

18sections16 minread1diagram3tables

On this page

Lilith is the V1 platform substrate that decides how Oshun is allowed to speak in contemplative and spiritually sensitive contexts: it sets the contemplative tone, holds the line on teacher-persona safety, detects and frames crisis, governs voice quality and voice abuse, and refuses unsafe medical / financial / prophetic / boundary-crossing claims. Like Sophia, Iris, and Psyche, it is a substrate, not a shell peer — it has no consumer tab of its own and instead binds policy into the assistant, the persona/voice publish paths, Tara invitations, and Living-Scenes renders through a single adapter. This page sits among the platform-substrate deep-dives hubbed at ../ARCHITECTURE.md, alongside Sophia, Iris, Psyche, Isis, and Aje.

Read this page for what is shipping vs. spec. The Lilith policy library is overwhelmingly real: deterministic detectors with regex phrase catalogs, versioned policy sets, validated taxonomies, and named evaluation suites — the catalogs and detectors below are quoted verbatim from code, not aspiration. What is partial is the runtime fan-out: the architectural claim that policy is "enforced on every assistant turn, every Living Scenes render, every Tara invitation" is the design intent. The detectors and catalogs exist and are unit-tested; uniform enforcement across every surface is not yet proven end-to-end. The V1 completeness audit rates crisis-aware-tone-policy and arete-living-offering-create (the Lilith crisis pre-screen) as partial, and the customer-facing LilithExplore surface is currently imported by no route. The live cloned-voice provenance pipeline and the operator-override governance UI are catalog-complete, but their full runtime/UI wiring is unverified. This page is candid about that line throughout.

Canonical home (§13). Lilith is a cross-product substrate, so its canonical reference home is the domain space docs/domains/lilith and its code-linked entity catalog at systems/lilith. This page is V1's view — how the V1 platform composes Lilith; the substrate itself is documented in full at its canonical home, which this page references rather than duplicates.

The three things called "Lilith" — disambiguation first#

"Lilith" is overloaded in this monorepo across three distinct things, and conflating them is a real source of confusion. They are:

# What Path Role
(a) V1 persona-policy substrate (this page) libs/oshun/persona-policy-lilith/ The contemplative-policy adapter library — tone, crisis, voice, boundary, unsafe-claim catalogs and detectors.
(b) /lilith design-system showcase route apps/oshun/web/src/app/lilith/page.tsx A broadsheet card grid that renders "the whole design system as one page" (a 57-card catalog of every shipped surface). It is the public-facing showcase, not the policy engine.
(c) apps/lilith meditation product apps/lilith/, libs/lilith/ An entirely separate full meditation app (bff, svc-ai, svc-analytics, mobile, desktop, cli, plus the libs/lilith substrate with fastify-core, sdk, service-lib, partner-sdk, event-publisher, continuous-video-policy).

This page is only about (a), the V1 persona-policy substrate. The original ARCHITECTURE.md correctly notes that this Lilith is "distinct from the Lilith meditation app elsewhere in the monorepo" — that disambiguation refers to (c). Note also that, unlike Psyche, there is no services/lilith, infrastructure/lilith, or libs/contracts/lilith; the persona-policy substrate is a single library, consumed in-process.

Operator-facing Lilith depth surfaces — distinct again from the /lilith showcase — live at apps/oshun/web/src/lib/lilith-data/operator-depth.ts, components/lilith/operator.tsx, and the operator workspace under app/operator/{admin,incidents,personas,studio}, with a lilith-studio console at app/lilith-studio/.

What it is and where it sits#

The substrate's purpose is contemplative tone policy, teacher-persona safety, crisis-aware behavior, voice-quality and voice-abuse policy, and spiritual-domain boundaries. It exposes a single typed adapter that consuming surfaces call to (1) select a persona policy pack, (2) assess the safety of a user message and/or draft response, (3) check topic scope, and (4) evaluate a whole interaction into a prompt overlay plus disclaimers.

The canonical entry point is createCanonicalLilithPersonaPolicyAdapter in libs/oshun/persona-policy-lilith/src/canonical-adapter.ts. Its companion deep-dive in the features set is V1/features.md § Persona Taxonomy, Tone Band Catalog, and Crisis Recovery Journey.

The adapter understates itself — the real module map#

The original ARCHITECTURE.md named the adapter as src/adapter.ts (plus contemplative-tone-policy.ts, assistant-persona-binding.ts, content-qa-hooks.ts). All four files exist, but that list is 3 of ~30 modules and omits the largest, most important policy files. The canonical entry is canonical-adapter.ts, and index.ts re-exports 30 modules. The substrate is materially larger than that list implied:

Module Size What it holds
crisis-behavior-policy.ts 68 KB LILITH_CRISIS_TYPES, signal taxonomy, detectLilithCrisisSignals, per-type resources
unsafe-claim-policy.ts 65 KB LILITH_UNSAFE_CLAIM_CLASSES (9) + per-class detection phrases & default responses
voice-policy.ts 62 KB voice domains, naturalness floor, abuse patterns, watermark algorithms, consent statuses, provenance
spiritual-boundary-policy.ts 43 KB boundary rule IDs, stances, prohibited prophecy/soteriological/karmic classes
policy-model.ts 38 KB the evaluation core (buildLilithPolicyEvaluationResult, buildLilithSafetyAssessment, …)
surface-policy-binding.ts 34 KB binds policy to named consumers (assistant / domain / admin / studio / support)
generation-gentleness-floor.ts 33 KB prohibited generation-language categories + authority-positioning gates
teacher-safety-policy.ts 32 KB prohibited-promise categories + required disclaimers per persona family
policy-versioning.ts 30 KB versioned policy sets and migration
operator-override-governance.ts 19 KB scoped, audited operator overrides
tenant-policy-constraints.ts 11 KB tenant-can-tighten-not-loosen floor
crisis-recovery/ stillness-window, reentry-flow, check-ins, reframe-protection, incident-record, locales
sacred-symbols/ lineage-binding (the 7-role lineage taxonomy + attestation)
tone-bands/ catalog (8 bands + caps), enforcement, transitions

Plus the named evaluation suites (eval-tone-quality, eval-crisis-handling, eval-clone-abuse-resistance, eval-spiritual-boundary, eval-unsafe-claim, eval-regression-blockers) and two cross-product harnesses (tone-cross-product-harness, lineage-per-tradition-harness). The earlier three-file list was a real understatement of the substrate.

The no-bypass crisis guarantee — concrete teeth, not prose#

The most important property of the canonical adapter is a defense-in-depth crisis guarantee that is testable, not aspirational. The docs previously described "no-bypass guarantees" only abstractly; here is the actual mechanism.

createCanonicalLilithPersonaPolicyAdapter takes an injected apiAdapter (the backend that actually classifies safety) and an optional crisisSafetyAnalyzer. When the analyzer is omitted, the canonical adapter binds createLilithCrisisSafetyAnalyzer() — the validated 13-rule crisis catalog — ON by default. Every safety analysis at the adapter boundary then runs both the injected backend and the crisis catalog in parallel and merges them with mergeLilithSafetyAnalyses, which always takes the more severe signal:

ts
// canonical-adapter.ts — defense-in-depth overlay
const crisisSafetyAnalyzer =
  options.crisisSafetyAnalyzer ?? createLilithCrisisSafetyAnalyzer();
// …per assessment:
const [injected, crisis] = await Promise.all([
  runInjected(),
  crisisAnalyzer(message, options),
]);
return mergeLilithSafetyAnalyses(injected, crisis); // escalates, never downgrades

The consequence: an injected backend that is unimplemented, or an always-"safe" stub, can never silently bypass the crisis catalog, because the merge only ever escalates severity. Enabling the overlay by default can only make the per-turn disposition stricter, never looser. These are the real teeth behind the features doc's "no-bypass guarantees." Callers may pass an explicit analyzer to bind region/resource-registry defaults; tests that need to isolate the injected backend may pass a benign analyzer deliberately. This overlay is applied in assessSafety, checkTopicScope, and evaluateInteraction alike.

Tone-band catalog — fully data-modeled#

The contemplative tone is governed by an 8-band catalog, ordered from most-contemplative to most-affordance-permissive. TONE_BAND_IDS (tone-bands/catalog.ts:14) is, in exact order:

contemplative-strictcontemplativereflectiveneutralbriefinginstructionalcelebratoryurgent-safe.

Each band is a real ToneBandCap record with an order field plus per-band caps that downstream surfaces (Isis generation dispatch, the Living Scenes Conductor, the share-control flow) enforce:

Band order motion ceiling motionMax audacityMax share policy generation tiers
contemplative-strict 0 reduced-motion 0.1 0 opt-in-redacted-intent customer (gated)
contemplative 1 low 0.25 1 opt-in customer
reflective 2 low 0.25 1 opt-in customer
neutral 3 medium 0.5 2 allowed customer, curated-creator
briefing 4 medium 0.5 2 allowed-with-citations customer, curated-creator
instructional 5 medium 0.5 2 allowed-with-citations customer, curated-creator
celebratory 6 high-pse-safe 0.8 3 allowed customer, curated-creator
urgent-safe 7 low 0.25 0 operational-only operator

Audacity is a 0–5 scale; Isis clamps requested audacity to the band ceiling via clampAudacity. The clamp is monotone-narrowing: a tenant policy can narrow further, a user preference can narrow further still — neither can widen. narrowestBand(...) merges a persona's default band with tenant/user narrowings by lowest order, and validateToneBandCatalog asserts canonical order, motion range, and a structural invariant — e.g. operational-only must not allow user share, and shareable bands must allow some share. The features-doc per-band capability table maps 1:1 to these real ToneBandCap records.

The contemplative wording floor lives in contemplative-tone-policy.ts, which models pace (LILITH_TONE_PACE_LEVELS), silence tolerance (LILITH_TONE_SILENCE_TOLERANCES), metaphor usage (sparing / moderate / generous / poetic), warmth (reserved / warm / tender / effusive), and certainty ceilings (LILITH_CONTEMPLATIVE_TONE_CEILINGS). Its prohibited phrase catalog bans false-certainty markers — 'obviously', 'everyone knows', 'the truth is', 'beyond debate' — directly implementing the features-doc certainty-marker policy.

Crisis-aware behavior — the validated 13-rule catalog#

crisis-behavior-policy.ts is the substrate's heart. LILITH_CRISIS_TYPES (crisis-behavior-policy.ts:46) is the canonical 13-entry severity-ordered enum that the package calls the "validated 13-rule crisis catalog":

suicide-ideation, active-self-harm, acute-panic, dissociation, trauma-resurfacing, substance-crisis, interpersonal-violence, violence-toward-others, abuse-disclosure, eating-disorder, child-safety, psychosis-adjacent, acute-grief.

A second, finer detection enum LILITH_CRISIS_SIGNAL_TYPES (crisis-behavior-policy.ts:356) carries the sub-qualified signals the detector matches on — suicide-active, suicide-passive, suicide-planned, self-harm-active, self-harm-ideation, violence-toward-others, abuse-current, abuse-historical, domestic-violence, eating-disorder, substance-crisis, panic, dissociation, psychotic-symptom, child-protection. Each is a LilithCrisisSignalTaxonomyRule in LILITH_CRISIS_SIGNAL_TAXONOMY with phrasePatterns, a mapped responseCrisisType, and requiredResources. detectLilithCrisisSignals() runs the taxonomy and returns matches; the canonical resource types include suicide-hotline, domestic-violence-hotline, and eating-disorder-hotline (plus crisis-text-line and emergency-services).

Doc reconciliation (staleness). The prose signal taxonomy in V1/features.md ("suicidal ideation (active, passive, planned), self-harm (active, ideation)… child-protection signals") does not map 1:1 to the code. The 13-type response enum uses child-safety (not "child-protection"), psychosis-adjacent (not "psychotic-symptom indicators"), and adds trauma-resurfacing and acute-grief that the prose omits; the active/passive/planned sub-qualifiers are members of the signal enum, not the response enum. The canonical 13-type response enum above is authoritative; the prose should be reconciled to it.

Crisis recovery journey — modules, not just prose#

The crisis-frame cascade (the fan-out across Tara, Living Scenes, Isis, the assistant, and Iris when crisis fires) describes the system response; see the Crisis-frame cascade in Trust and Safety. The recovery journey is the user-facing path back into the product after the frame has done its work, and it is implemented as real modules under crisis-recovery/, not narrated prose:

stateDiagram-v2 [*] --> normal: session start normal --> crisis_frame: Lilith detects crisis crisis_frame --> stillness_window: cascade applied · all non-safety surfaces suspended stillness_window --> reentry_prompt: minimum interval elapsed (default 10 min) reentry_prompt --> idle_safe: user declines reentry_prompt --> reentry_home: user accepts reentry_home --> normal_conservative: contemplative-strict band · no scheduled invitations normal_conservative --> normal: user re-enables categories crisis_frame --> stillness_window_extended: new indicator within window or 24h stillness_window_extended --> reentry_prompt: extended interval elapsed idle_safe --> reentry_prompt: fresh session normal_conservative --> followup_24h: 24h check-in (opt-in) followup_24h --> followup_7d: 7d check-in (opt-in) followup_7d --> [*]
  • Stillness window (crisis-recovery/stillness-window.ts): after a frame fires, every non-safety surface is suspended. STILLNESS_WINDOW_DEFAULT_SECONDS = 600 (10 minutes), the floor is never less than 3 minutes (STILLNESS_WINDOW_FLOOR_SECONDS = 180), and extensions are capped at STILLNESS_WINDOW_EXTENSION_CEILING_SECONDS = 7200 (2 hours). The module enumerates exactly which surfaces are suspended (NON_SAFETY_SURFACES: teaching, invitation, scheduled-generation, companion-suggestion, studio, gallery, voice-clone, live-scene) versus which stay live (SAFETY_SURFACES: crisis-resources, safety-resources, audit-platform, support-handoff, incident-record, reentry-prompt).
  • Re-entry flow (reentry-flow.ts): the conservative re-entry into a contemplative-strict band with no scheduled invitations until the user re-enables categories.
  • Soft check-ins (check-ins.ts): CHECK_IN_KINDS = ['24h', '7d'] with CHECK_IN_DELAY_SECONDS = { '24h': 86400, '7d': 604800 }. Check-ins are opt-in and fire only through the assistant surface (surface: 'assistant') — never via push or platform notification — and the member can disable them at any time.
  • Reframe protection (reframe-protection.ts): a new indicator landing inside an open stillness window, or within REFRAME_PROTECTION_AFTER_CLOSE_SECONDS = 24h of close, extends the existing frame (by REFRAME_EXTENSION_SECONDS = 600) rather than re-firing — so the member is not shown resources twice or re-traumatized by a fresh restart.
  • Incident record (incident-record.ts) + locales (locales.ts): the visible-audit record (the member owns the record of what occurred) and localized recovery copy.

Generation gentleness floor#

generation-gentleness-floor.ts is the floor that any generated content (text about to be spoken or rendered) must clear before synthesis. LILITH_PROHIBITED_GENERATION_LANGUAGE_CATEGORIES includes fear-inducing-framing, matched by a real regex — e.g. /\b(if you don'?t|or else|you'?ll fail|you are unsafe unless|something bad will happen|you will suffer|dangerous to stop)\b/i. The module also models LILITH_INTERPRETIVE_UNCERTAINTY_MARKERS (the assistant must hedge interpretive claims), LILITH_AUTHORITY_POSITIONING_CATEGORIES — which blocks the assistant from presenting itself as a guru / prophet / healer / master / sole authority via /\bi am (?!not\b)(?:your\s+)?(?:guru|prophet|healer|master|sole authority)\b/i — and LILITH_MEDITATION_FRAMING_CATEGORIES (prescriptive meditation commands are disallowed). Patterns like /\byou will\b/ and /\bthe truth is\b/ directly implement the features-doc certainty-marker policy. If a draft fails this floor before voice synthesis, the policy is to not synthesize and fall back to text until the draft is revised.

Unsafe-claim engine#

unsafe-claim-policy.ts enforces what the assistant may never assert. LILITH_UNSAFE_CLAIM_CLASSES (unsafe-claim-policy.ts:97) is the exact 9-class taxonomy: medical, psychiatric, financial, legal, prophetic, conspiratorial, defamatory, retaliatory, electoral-influence. Each class is a LILITH_UNSAFE_CLAIM_CLASS_TAXONOMY entry with detectionPhrases and a default response — e.g. the psychiatric class detects 'depression', 'ptsd', 'therapy', 'medication', …; the financial class detects 'investment', 'debt', 'wealth', 'financial breakthrough', …. These are real deterministic catalogs, not classifiers behind a flag.

Voice quality, voice abuse, and cloned-voice provenance#

voice-policy.ts governs synthesized and cloned speech. Voice is scoped by LILITH_VOICE_DOMAINSguided-practice, teaching, sacred-reading, breathwork, silent-sitting, crisis-response — each with its own per-domain pace (LILITH_VOICE_DOMAIN_TIMING_POLICY_SET) and a LilithVoiceNaturalnessFloor. checkLilithVoiceQuality and checkLilithVoiceDomainTiming are the real checks.

Abuse is enumerated, not vibe-checked. LILITH_VOICE_ABUSE_PATTERNS is a 16-entry catalog including sustained-shout, coercive-command-cadence, manic-cadence, deceptive-warmth, simulated-confidant-intimacy, hypnotic-induction-without-disclosure, subliminal-layer, impersonation-of-real-teacher, impersonation-of-clinician, parasocial-intimacy-escalation, sexualized-prosody, infrasound-entrainment, unwatermarked-cloned-output, and identity-drift-from-consented-profile.

Provenance is enforced via LilithVoiceProvenanceRequirements, LILITH_VOICE_WATERMARK_ALGORITHMS (oshun-phase-watermark-v1, oshun-spread-spectrum-v1), a LilithVoiceRerenderPolicy (with an exhaustion fallback), and LILITH_CLONED_VOICE_CONSENT_STATUSES = ['signed', 'revoked', 'expired'] — synthesis is gated on a signed consent and blocked on revoked. These catalogs are real; what I could not verify end-to-end is the live cloned-voice provenance pipeline — the catalog logic exists, but its runtime wiring (watermark embedding/verification on real synthesized output) is less proven.

Spiritual-domain boundaries and lineage attestation#

spiritual-boundary-policy.ts holds LILITH_SPIRITUAL_BOUNDARY_RULE_IDS and the four LILITH_BOUNDARY_STANCESpermit, permit-with-attribution, redirect, refuse. Prohibited claim classes include prophecy, soteriological-guarantee, and karmic-pronouncement, each with phrase catalogs — e.g. 'you will reach nirvana', 'you will enter samadhi', 'karmic punishment'. The assistant may explain a tradition's teaching with attribution but must not issue a soteriological guarantee as fact.

Cultural and lineage sensitivity is backed by sacred-symbols/lineage-binding.ts. LINEAGE_PERSONA_ROLES (lineage-binding.ts:20) is the exact 7-role taxonomy: teacher, coach, comparative, explainer, narrator, steward, assistant. A LineageBinding attestation is emitted per artifact, and there is a comparative-only mixing gate: content declaring more than one lineage with a persona role other than comparative is refused (cross-lineage-without-comparative). Only a comparative persona may mix lineages, and even native single-lineage framing can be softened to a comparative soft-notice. Attestations attach to the Living-Scenes render envelope and to audit records.

Teacher-persona safety#

teacher-safety-policy.ts defines prohibited-promise categories per persona family (contemplative_teacher, coach, support, …). Promise classes include mental-health-support and financial-advice; where a topic requires it, the policy attaches a required disclaimer — e.g. the financial-disclaimer-missing category requires "For financial decisions, consult a qualified financial advisor." This is how a coach persona stays within bounds when a member asks a money-shaped question.

Surface bindings — the wiring points#

surface-policy-binding.ts is the concrete wiring the docs previously listed as prose. It binds policy to named LILITH_POLICY_CONSUMERSassistant, plus domain consumers, plus admin, studio, and support — and maps surfaces such as contemplative-assistant-personas, support-copilot, and the Tara binding (consumer: 'tara'). The Tara binding is also exposed directly via tara-policy-binding.ts: a crisis frame blocks scheduled invitations (Tara) and downgrades generation audacity (Isis), exactly as the architecture states. The assistant binding lives in assistant-persona-binding.ts, and content-QA hooks in content-qa-hooks.ts.

Enforcement candor. These bindings model the wiring points, and the detectors/catalogs behind them are real. But the architectural promise that policy is enforced on every surface is the design intent, not a proven invariant: the V1 completeness audit rates crisis-aware-tone-policy and the arete-living-offering-create Lilith crisis pre-screen as partial, and the customer LilithExplore surface (components/lilith/customer-shell.tsx:54) is currently imported by no route. Treat full runtime fan-out as in-progress.

Governance — overrides, tenant floors, versioning#

  • Operator override governance (operator-override-governance.ts): scoped, audited overrides (LilithOperatorOverrideScope, LilithOperatorOverrideRequest, LilithOperatorOverrideAuditRecord, LilithOperatorOverrideDecision) with elevated-authorization and recurring-policy support. The catalog logic is real; the operator override UI is not verified end-to-end here.
  • Tenant policy constraints (tenant-policy-constraints.ts): a tenant can tighten but never loosenLILITH_TENANT_POLICY_CONSTRAINT_POLICY_ID = 'tenant-policy-constraint-floor' with ordered modality / grounding / memory-permissiveness scales, all monotone-narrowing.
  • Policy versioning (policy-versioning.ts) + moderation queue binding (moderation-queue-binding.ts) + persona release metadata / lifecycle (persona-release-metadata.ts): versioned policy sets, the bridge into the moderation queue, and persona-publish lifecycle.

Evaluation suites — real modules#

The features doc's "evaluation suites" are concrete, testable modules, each with a sibling .test.ts: eval-tone-quality, eval-crisis-handling, eval-clone-abuse-resistance, eval-spiritual-boundary, eval-unsafe-claim, and eval-regression-blockers, plus tone-cross-product-harness (every tone band × scenario) and lineage-per-tradition-harness (per-tradition lineage checks). These exercise the detectors against known-correct expectations — they are the regression net that keeps the catalogs honest.

Localization#

The substrate's launch locales come from libs/oshun/i18n/src/index.ts: OSHUN_LAUNCH_LOCALES (:26) = en-US, es-US, fr-FR, de-DE, ar, he, ja-JP, pt-BR8 locales, with ar/he carrying RTL — and OSHUN_DEFAULT_LAUNCH_LOCALE = 'en-US'. The web consumer config resolves at apps/oshun/web/src/i18n/config.ts. Crisis-recovery copy and resource framing are localized through crisis-recovery/locales.ts.

Data-flow walkthrough — one assistant turn#

  1. The assistant calls evaluateInteraction(input) on the canonical adapter with the user message, draft response, persona/consumer/domain, and region.
  2. The adapter resolves a policy pack (resolvePackForInput) — by explicit personaId, else by matching persona-to-context, else by default selection.
  3. In parallel, it runs the safety overlay (injected backend merged with the 13-rule crisis catalog, escalating only), draft-response validation, persona topic-scope check, experience-level adaptation, and runtime disclaimers.
  4. Results compose into a LilithPolicyEvaluationResult — a prompt overlay plus deduped disclaimers — which the assistant applies before responding.
  5. If the merged safety verdict is a crisis, the crisis frame fires: the stillness window opens, Tara invitations are blocked, Isis audacity is downgraded, an incident record is written, and the recovery journey begins.

The key invariant throughout: every path that touches safety runs through analyzeSafetyWithOptionalCrisisOverlay, so the crisis catalog is in the loop on assessSafety, checkTopicScope, and evaluateInteraction — there is no adapter method that consults the injected backend without the overlay.

Backlog and where the line is#

Policy library: real and domain-specific. Runtime fan-out and operator/voice UI: partial. The remaining work — uniform per-surface enforcement, the live cloned-voice provenance pipeline, and the operator-override governance UI — is tracked under the crisis-recovery and persona-policy items in ../TODOS.md (the § 12 extensions referenced from the recovery journey). Where this page says "partial," "unverified," or "design intent," that is the honest state today, not a finished claim.