Oshun Platform · Features

Domain Templates and the Scene Score Editor

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

11sections21 minread9tables

On this page

Living Scenes ship at V1 as five authored domain templates — one per practicing domain (Tara, Nyx, Veritas, Metis, Arete) — plus the authoring surfaces creators and operators use to build, rehearse, and promote new scores from those templates. A template is not a loose theme: it is a real workflow class with a machine ID, a locked cue-verb allowlist, a tone band, grounding and intent requirements, and a per-fixture eval gate that blocks promotion. This page sits in the living-scenes-core area alongside Scene Score Schema, Live Direction, Conductor Runtime, and Blend Kernel, and Composition Surface and Cinematographic Technique Catalog; it is where the abstract Score contract meets concrete, named, tested domain content.

What is real vs. modeled. The template catalog, the per-template cue and technique allowlists, the tone-gating with crisis collapse, the breath-cycle pacing, the fixture eval gates, the compose-assist budgets, and the editor's pure decision helpers are all implemented and tested today. What is modeled but not computed here is everything pixel- and frame-level: latent video generation, optical-flow warps, FVD computation, and GPU determinism. Those arrive as score inputs the pure functions consume — the runtime and blend kernel state plainly that "Actual GPU dispatch lives outside this module" and "Implementations of the actual DSP / shader code live downstream." This page keeps that line honest.

Where the code lives#

Concern Package / path
Template catalog, eval gates, Tara breath specs libs/isis/workflow-classes/src/living-scene/template-catalog.ts, tara.ts
Score / Segment / RenderEnvelope / CarryState contracts libs/contracts/src/living-scene/score.ts (mirrored at libs/yemaya/living-scenes-runtime/src/score/score-schema.ts)
Conductor state machine @yemaya/living-scenes-runtime (v0.1.0) — src/conductor/conductor.ts
Transitions, technique catalog, tone gating, compatibility scorer @yemaya/blend-kernel (v0.1.0) — src/transitions.ts, src/catalog/, src/compatibility/scorer.ts
Compose Assist budgets + gold-set eval libs/yemaya/living-scenes-runtime/src/compose-assist/compose-assist.ts
Scene telemetry events libs/oshun/embodiment-psyche/src/events/scene-events.ts
AAA Score Editor (web + desktop) apps/yemaya/studio-web/src/score-editor/ScoreEditorPage.tsx, apps/yemaya/studio-desktop/src/renderer/pages/ScoreEditorPage.tsx
Lilith Studio Tara venue editor (contemplative web app) apps/oshun/web/src/app/lilith-studio/scenes/TaraSceneEditor.tsx, /lilith-studio/scene/new

The two runtime packages carry exactly the names the docs cite: @yemaya/living-scenes-runtime and @yemaya/blend-kernel.

Domain Templates#

All five templates ship at V1 launch as workflow-class instances under the living-scene.* family. Each template has its own grounding, persona, locale, accessibility, and eval pipeline; none is descoped to V1.x. Templates are versioned and promotable through Isis; the contemplative-product Studio Living Scene card resolves the available templates from the user's tier and tenant entitlements.

The catalog (LIVING_SCENE_TEMPLATES in template-catalog.ts) pins five canonical machine IDs — these, not the friendly names, are what the runtime, the allowlists, and the gates key off:

Machine ID Display name Allowed cue verbs morphForbidden Reduced-motion required Strictest Lilith tone Sophia grounding Intent capture
tara-contemplative-arc Tara Contemplative Arc linger, advance, save-moment, abandon, kill no yes yes no no
nyx-sky-briefing Nyx Sky Briefing linger, advance, morph, shift-style, save-moment, abandon, kill no yes no yes no
veritas-grounded-explainer Veritas Grounded Explainer linger, advance, recall, save-moment, abandon, kill yes no no yes no
metis-lesson-visualizer Metis Lesson Visualizer linger, advance, recall, shape-by, save-moment, abandon, kill no yes no yes no
arete-living-offering Arete Living Offering linger, advance, morph, shape-by, save-moment, abandon, kill no yes yes no yes

templateAllowsVerb({ templateId, verb }) enforces this allowlist, and it short-circuits morph whenever morphForbidden is set — which is why Veritas can never accept a morph cue even though morph is a valid verb in the global ScoreCueVerbSchema. The complete verb vocabulary is the nine-member ScoreCueVerbSchema (score.ts:62-72): linger, advance, morph, recall, shift-style, shape-by, save-moment, abandon, kill, mirrored verbatim as CUE_VERBS_VOCABULARY in the cue parser so the parser and the contract can never drift.

Tara — Contemplative Arcs (tara-contemplative-arc)#

  • Five sub-variants ship at V1 (TARA_SUBVARIANTS): loving-kindness, body-scan, breath-anchor, nature-immersion, gratitude.

  • Score templates pace visuals to a breath-cycle clock. Each sub-variant carries a concrete BreathCycle of {inhaleSeconds, holdInSeconds, exhaleSeconds, holdOutSeconds} plus a target arc length, a palette bias for the blend kernel, and a captions cadence:

    Sub-variant Breath cycle (in / holdIn / ex / holdOut) Arc minutes Palette Captions WPM
    loving-kindness 4 / 1 / 6 / 1 12 dawn-rose 80
    body-scan 4 / 0 / 8 / 0 15 stone-grey 70
    breath-anchor 4 / 4 / 4 / 4 (box breathing) 8 cool-sea 60
    nature-immersion 5 / 0 / 7 / 0 18 forest-moss 75
    gratitude 4 / 1 / 6 / 1 10 warm-amber 85

    buildBreathPacedShape sizes a score by dividing the arc seconds by the cycle seconds (Math.floor(arcSeconds / secondsPerCycle)), clamping the arc to (0, 30] minutes, so a user-configurable override still produces a whole number of breath cycles. A cue like linger extends the current segment without breaking continuity — useful when the user wants to dwell in a phase.

  • Persona binding to approved contemplative voice profiles only; Lilith contemplative-tone band on the strictest setting (strictestToneBand: true); no Sophia source pin (sophiaGroundingRequired: false) because there are no empirical claims.

  • The reduced-motion alternative ships in lockstep (reducedMotionVariantRequired: true). assertReducedMotionLockstep fails loud with a TaraVariantParityError if a normal variant has no reduced-motion sibling, or if the two variants disagree on captions WPM — caption cadence must match across the pair, or promotion is blocked.

Nyx — Sky Briefings (nyx-sky-briefing)#

  • Tonight-only sky briefing tied to the Nyx celestial-event clock; the score advances camera/visual focus across visible objects as the narrator names them, time-aligned to ±60 ms.
  • Requires observer location (sensitive PII); explicit opt-in per session, with locale fallback to a regional-default location.
  • Northern and Southern hemisphere parity at V1 launch; ephemeris computed from Skyfield-class libraries; constellation art from Yemaya-cleared rights or Isis-generated under licensed style anchors.
  • Event coverage: planetary alignments, moon phases, meteor showers, ISS passes, aurora forecasts (where available), comets, conjunctions.
  • Cue verbs include linger (stay on a constellation), morph (to a target sky region), shift-style, and shape-by (e.g. narrator pace). Grounding is required (sophiaGroundingRequired: true) because positions and timing are empirical, and its groundingCorrectness gate is set at a 0.92 pass rate.
  • Caching: tonight's briefing materializes once per location-bucket per night; per-user personalization is on top of the cached base render. See Domain — Nyx.

Veritas — Grounded Explainers (veritas-grounded-explainer)#

  • Score template binds to a Sophia source pin set; visuals are mood/style anchors drawn from the topic hub, never empirical claims. Specific facts arrive via narration/text only, never via inferred imagery.
  • The cue verb set excludes morph (morphForbidden: true), since free-form visual morph would risk visual claim drift; the available verbs are linger, advance, recall, save-moment, abandon, kill.
  • The counterclaim, retraction, and source-update cascade reaches into kept Veritas Living Offerings: an artifact whose pinned source is later corrected surfaces a banner and offers re-render under the corrected source. The compatibility scorer reinforces this at the boundary level — a Veritas pair where both segments cite only retracted Sophia source pins is declared hard-incompatible regardless of mechanical scores (see below).
  • Eval fixtures include adversarial would-be-misleading cases (e.g., a cited claim about a 19th-century printing press where the visual could plausibly assert a date or location not in the source); the template must hold the line. Veritas carries the strictest gates of all five — golden 0.97, adversarial-cue 0.99, grounding-correctness 0.97. See Domain — Veritas and Sophia Grounding.

Metis — Lesson Visualizers (metis-lesson-visualizer)#

  • Score template binds to a lesson's knowledge-graph anchor set; the "what if X?" branch is constrained to what-ifs the lesson's KG supports, with graceful fallback ("let's see — that'll need a different lesson") when the cue exits the supported manifold.
  • Themis academic-integrity interlock: the tone-gating module blocks every technique on any segment whose KG node is in an in-progress assessment set. When templateId === 'metis-lesson-visualizer', metisSegmentNodeId is set, and that node is in metisAssessmentLockedNodes, assertTechniqueAllowed throws GatingError('themis-assessment-locked') — the lesson visualizer simply cannot run on an assessment surface.
  • Standards-aligned for institutional delivery: tenant-specific standards bindings (Common Core, IB, NGSS, etc.) are honored where the tenant's curriculum metadata is wired.
  • Persona binding to approved teacher personas; Lilith teacher-persona safety policy applies (no diagnostic statements, no treatment plans). Grounding is required, with a groundingCorrectness gate at 0.95.
  • Its cue verb set is among the most expressive: linger, advance, recall, shape-by (e.g. student pace), save-moment, abandon, kill. See Domain — Metis.

Arete — Living Offerings (arete-living-offering)#

  • The most personal template; the user states an intention (voice/text), and the score generates a contemplative arc tuned to it. Default duration: 4–8 minutes; user-configurable within Arete's pace bands. This is the one template with intentCaptureRequired: true.
  • The intent is captured on the Score, not on a segment, in the ScoreIntentLayer (score.ts:121-129): intentId, privateText, publicRedactedText, privateIntentHash (64-hex), capturedAtUnixSeconds, and redactionCategoriesHit. The private intent is kept for the originating user only; the public-redacted intent (a Lilith-redacted version) is the only intent text ever exposed in shared artifacts or in operator inspection without elevated authorization. The redaction categories themselves are an enum: named-person, deceased, medical-condition, abuse-disclosure, location-of-safety, minor-identity, financial-account, legal-status.
  • Strongest crisis-signal pre-screen on the intent text; an intent that triggers a crisis frame produces no offering and routes to the existing Lilith crisis pathway.
  • Reusable by the user: re-engaging the same offering produces a deterministic re-render unless the user requests a branch from this moment (which forks a new score with the same intent and a new seed).
  • Sharing is opt-in per artifact, governed by the Shareability Matrix; a shared Living Offering carries only the public-redacted intent, never the private intent. See Domain — Arete and Keep, Share, Shareability, Takedown, and Lineage.
  • Arete is the only template whose technique allowlist spans the full catalog, including the three operator-curated high-energy cuts; those three are gated separately (see Tone gating below).

The Segment is leaner than it reads — the real schema#

Earlier drafts of this material described a thirteen-field segment ({intent, public_redacted_intent, duration_band, style_anchor, motion_descriptor, audio_role, narration_script_pin, sophia_grounding_pin, lilith_tone_band, persona_binding, transition_in, transition_out, accessibility_role}). None of those fields exist verbatim. The real SegmentSpecSchema (score.ts:47-60) is:

jsonc
{
  "segmentId": "string (1..80)",
  "kind": "opening | breath-anchor | visual-meditation | narration-bed | sky-event-arc | lesson-visualizer | grounded-explainer | living-offering | closing",
  "displayName": "string (1..120)",
  "durationSeconds": "number, (0, 3600]",
  "tone": "contemplative | gentle-instructive | celebratory | reverent | reflective | wonder",
  "workflowClassId": "string",
  "parameters": "Record<string, number | string>",
  "inboundCarryState": "CarryState | null",
}

Several facts follow from this and matter for authoring:

  • Intent lives on the Score, not the SegmentScoreIntentLayer, above.
  • Transitions are not stored on segments. A segment has no transition_in / transition_out; transitions are computed at the boundary between two adjacent segments by the blend kernel and recorded by Compose Assist as techniquesByBoundary (a boundary id → technique id map).
  • Grounding, persona, and accessibility are not Segment fields. They are template-level requirements (the catalog flags above), persona/voice bindings, and accessibility variants enforced elsewhere — not columns on the segment.
  • Free-form per-segment knobs live in the open parameters record, constrained by the segment's workflowClassId. The full schema, including CueSpec and the deep-validation invariants, is documented in Scene Score Schema.

The RenderEnvelope is richer than "engine version + model hashes"#

RenderEnvelopeSchema (score.ts:91-107) pins concrete, literal-union production parameters so a kept artifact re-renders identically:

Field Type / allowed values
envelopeId, displayName strings
widthPx literal 1080 | 1440 | 1920 | 2560 | 3840
heightPx positive int ≤ 7680
fps literal 24 | 30 | 60
maxBitrateKbps positive int
gpuClass rtx-4090 | a100-40gb | a100-80gb | h100-80gb
minLookaheadSegments literal 2 | 3 | 4

deepParseScore enforces a hard ≥ 2 lookahead release-gate invariant on top of the literal union (score.ts:165-167): even though the type already excludes 1, the deep parse throws renderEnvelope must require ≥ 2 lookahead segments so that cross-process payloads (BFF, queues, persistence) cannot smuggle a single-segment lookahead past the schema. The same function deduplicates segment ids and rejects any cue that targets an unknown segment (a cue may target the wildcard *).

CarryState — the boundary handoff protocol#

The "CLIP-style style anchor, last-frame conditioning, motion-vector descriptor, color-LUT alignment, audio tail" prose maps almost one-to-one onto a real schema, CarryStateSchema (score.ts:38-44), which the docs previously never named:

Field Constraint Carries
clipAnchorAssetId non-empty the style anchor asset
lastFrameConditioningHash regex /^[0-9a-f]{32,}$/ last-frame conditioning (≥ 32 hex chars)
motionDescriptor ≤ 120 chars motion-vector descriptor
lutId non-empty color-LUT alignment
audioTailDescriptor ≤ 120 chars the audio tail handed to the crossfade

This is the contract the Conductor propagates across boundaries (next section) and the Latent-carry inspector visualizes.

How a template's content reaches frames#

Templates do not render pixels. They produce decisions that the Conductor, the Blend Kernel, and the compatibility scorer consume. The full runtime is covered in Live Direction, Conductor Runtime, and Blend Kernel; here is the slice that template authors must understand.

The Conductor state machine (modelled, deterministic)#

@yemaya/living-scenes-runtime's conductor.ts is a pure segment-slot state machine. Each slot moves through a SegmentRenderState:

text
pending → pre-warming → ready → streaming → done
                                   (or → aborted)
  • initialiseConductor({ score }) builds one SegmentSlot per segment, all pending, playhead at index 0, backpressure off, seeding each slot's carryStateInbound from the segment's inboundCarryState.
  • planPreWarm marks every pending slot in the window [playheadIdx + 1, playheadIdx + minLookaheadSegments] as pre-warming. This is where minLookaheadSegments earns its ≥ 2 invariant — there is always at least one slot warming ahead of the playhead.
  • setBackpressure(active: true) truncates the lookahead to 1: when downstream is slower than realtime, the conductor only pre-warms the immediate next slot, shedding speculative work rather than piling up.
  • completePreWarm flips a pre-warming slot to ready (and fails loud with a ConductorError('invalid-transition') if the slot wasn't pre-warming).
  • advancePlayhead({ carryStateOutbound }) marks the current slot done, writes its carryStateOutbound, and promotes the next slot to streamingcarrying the outbound carry-state forward as the next slot's inbound state (carryStateInbound: input.carryStateOutbound ?? next.carryStateInbound). It refuses to advance into a slot that isn't ready/streaming.
  • planReconnect({ atSegmentId }) answers the resumability question after a client drops: it returns { resumable, resumeCarryState, reason }, resumable only when the slot is streaming or ready, handing back the inbound carry-state the client needs to rejoin mid-segment.

Pure logic throughout — the module's own header is explicit: "the conductor models scheduling decisions in deterministic state. Actual GPU dispatch lives outside this module."

Transitions and the technique catalog#

The blend kernel owns nine transition kinds — the low-level DSP/shader primitives (TRANSITION_KINDS, transitions.ts:13-23): latent-warm-start, optical-flow-morph, color-lut-match, audio-crossfade, narrative-pivot, motion-descriptor-handoff, motion-descriptor-reset, audio-level-jump, variable-rate-sequencer. (Earlier prose listed only the first five; the catalog has nine.) Each has real, specific validateTransition parameter ranges that fail loud with a TransitionValidationError when violated:

Transition Validated ranges
latent-warm-start bridgeSteps ∈ [2, 12]; cfgScale (nullable) ∈ [0, 30]
optical-flow-morph morphFrames ∈ [1, 48]; warpStrength ∈ [0, 1]
color-lut-match non-empty source/target LUT ids; matchFrames ∈ [1, 240]
audio-crossfade fadeMs ∈ [50, 8000]; curve ∈ {linear, equal-power, log}
narrative-pivot non-empty pivotPromptId
motion-descriptor-handoff non-empty descriptor ids; blendFrames ∈ [1, 120]
motion-descriptor-reset resetStrategy ∈ {clean-break, new-anchor}; holdFrames ∈ [0, 48]
audio-level-jump deltaDb ∈ [−18, 18]; recoveryMs ∈ [0, 3000]
variable-rate-sequencer non-empty beatGridId; 0.25 ≤ minRate ≤ maxRate ≤ 4

Above those primitives sit the twelve cinematographic techniques authors and customers actually pick (TECHNIQUE_IDS, cinematographic-catalog.ts:16-29): hard-cut, match-cut, l-cut, j-cut, dissolve, smash-cut, whip-pan, jump-cut, match-action, cross-cut, montage, fade-to-black. Each entry binds a kernelComposition of transition kinds, an overlap-window range, tone bands, a per-template allowlist, an accessibility fallback, a provenanceTag matching technique:[a-z0-9-]+, and concrete evalThresholds — not just qualitative descriptions. A few illustrative numbers from the real catalog:

Technique maxFvd maxFlicker maxColorJump minNarrationAlignment Reduced-motion fallback
dissolve 0.3 0.03 0.08 0.90 dissolve
match-cut 0.3 0.04 0.10 0.92 dissolve
smash-cut 0.5 0.06 0.18 0.85 hard-cut
whip-pan 0.5 0.05 0.18 0.85 fade-to-black
fade-to-black 0.3 0.03 0.10 0.92 fade-to-black

fade-to-black is the universal fallback: the only technique allowed in every tone band, and the one reached for when a clean recovery is needed. These thresholds are gate inputs: the blend kernel's isBlendContinuityAcceptable compares a downstream-supplied ContinuityScorecard (fvd, flickerScore, colorJump, motionVectorContinuity, audioRmsDeltaDb) against the thresholds and returns a pass/fail with reasons. The FVD and flicker numbers themselves are computed downstream, not by this module — this is the honest line between the deterministic gate and the modeled perceptual metrics. The full technique table and overlap windows live in Composition Surface and Cinematographic Technique Catalog.

Compatibility scoring at a boundary#

scoreSegmentPair (scorer.ts) returns a verdict for any adjacent pair across seven dimensions: style-anchor, motion-descriptor, audio-role, narration, tone-band, grounding, persona; the composite is the plain mean of the seven. Two honesty notes about the style-anchor and motion-descriptor dimensions: the doc once described "CLIP-embedding distance between the outgoing and incoming anchors," but the implemented scorer uses exact-ID equality, not an embedding distance — styleScore returns 1 when styleAnchorId matches and 0.35 otherwise (scorer.ts:75-77); motionScore likewise returns 1 vs 0.5 on exact descriptor equality. Grounding overlap is a real set metric — a Dice coefficient over the two segments' Sophia source-pin hashes. A CLIP-style embedding metric is modeled as a possible future input, not computed in this pure module.

Before any mechanical scoring, a hard-incompatible policy gate runs and can veto a pair outright:

  • crisis ↔ non-crisis pair without a fade-to-black recovery transition;
  • pedagogical ↔ entertainment intra-Score pairing;
  • a Tara contemplative arc paired with a forbidden high-energy technique (smash-cut, whip-pan, jump-cut);
  • a Veritas pair where both segments cite only mutually-retracted Sophia pins.

Tone gating, allowlists, and crisis collapse#

assertTechniqueAllowed (tone-gating.ts) is the enforcement point that runs in three places — the customer technique picker, Compose Assist candidate generation, and score promotion (ENFORCEMENT_POINTS). Its precedence order matters:

  1. Crisis-frame collapse takes absolute precedence. Regardless of template, within one frame of crisis-frame activation the technique allowlist collapses to the safe trio CRISIS_COLLAPSE_ALLOWLIST = {hard-cut, dissolve, fade-to-black}. Anything else throws GatingError('crisis-frame-collapse').
  2. Themis interlock for Metis — every technique blocked on in-progress assessment segments (above).
  3. Per-template allowlist — the technique must be in technique.templateAllowlist.
  4. Arete operator-curated cuts. OPERATOR_CURATED_ARETE_TECHNIQUES = {smash-cut, whip-pan, jump-cut} are never allowed under Lilith's strictest tone band (GatingError('strictest-lilith-tone-block')) and otherwise require an explicit operatorCuratedTechniqueGrant (GatingError('operator-curated-technique-required')).

Two allowlists, kept in sync. There are intentionally two technique allowlists in code: the per-technique templateAllowlist in cinematographic-catalog.ts, and the per-template TEMPLATE_TECHNIQUE_ALLOWLISTS map in compose-assist.ts. They currently agree for all five templates (e.g. Tara allows hard-cut, match-cut, l-cut, j-cut, dissolve, match-action, fade-to-black; Arete spans the full twelve), but they are a duplicated source of truth that can drift, so any new technique must be added in both places. The docs describe a single conceptual allowlist; the code has two enforcement copies.

Compose Assist — budgets and gold-set eval#

Compose Assist runs as an Isis AgentRun with budget caps, tool grants, a cost ledger, and operator audit (per §18). Its request carries an AgentRunBudget (maxTokens, maxCostCents, maxLatencyMs) and AgentRunGrants (toolIds, allowEgress), a tier (contemplative | curated-creator | aaa-creator | operator-admin), the templateId, locale, and a priorSegmentSummary. The output is a candidate segment plus ranked techniques with confidence bands, a rationale, and a continuityPreScore; a soft-refusal fires when no viable suggestion exists.

Tier caps (TIER_CAPS, compose-assist.ts:447-452):

Tier Segment cap Duration cap Routing
free 6 segments 90 seconds inline compose surface
paid 24 segments 8 minutes inline compose surface
aaa routesToStudioEditor: true (full AAA editor)

A draft can only be promoted to a score when its continuityPreScore (overall and per boundary) is ≥ 0.85 — both validateCompositionDraft and the per-boundary assertPromotableBoundary throw ComposeAssistError('continuity-pre-score-out-of-range') below that threshold. They also check that each boundary connects adjacent segment cards and clears the workflow-class policy gate. Promotion produces a PromotedCompositionScore with a segmentOrder, a techniquesByBoundary map, and a parallel reducedMotionTechniquesByBoundary map so the reduced-motion variant is fixed at promotion time, not improvised at playback.

The gold-set harness evaluateGoldSet returns a real GoldSetEvaluation = {precision, recall, refusalRate, correctRefusals, falseRefusals} over labeled items (each marked expectsSuggestion or not), and a champion/challenger comparison gates promotion of the assistant model itself against minPrecision / minRecall floors. See Agent Invocation, Budgets, Memory, and Feedback Loops.

Template eval gates#

Before any template version can be promoted to production, its fixture set must clear the per-template gate in TEMPLATE_EVAL_GATES (template-catalog.ts:147-199). evaluateFixtureSet reports { pass, failures }, listing every threshold a FixtureSetReport misses. All five templates require a minimum of 30 fixtures; the pass-rate thresholds are tuned per template:

Template min fixtures golden adversarial-cue crisis-trigger locale-parity a11y-parity grounding-correctness
tara-contemplative-arc 30 0.95 0.98 1.00 0.90 0.95 n/a
nyx-sky-briefing 30 0.95 0.97 1.00 0.90 0.95 0.92
veritas-grounded-explainer 30 0.97 0.99 1.00 0.90 0.97 0.97
metis-lesson-visualizer 30 0.95 0.97 1.00 0.90 0.97 0.95
arete-living-offering 30 0.95 0.99 1.00 0.90 0.95 n/a

The crisis-trigger gate is 1.0 for every template — there is zero tolerance for a crisis-signal fixture that fails to route correctly. Templates without empirical claims (Tara, Arete) carry no groundingCorrectness gate (null); the grounded templates (Nyx, Veritas, Metis) require it, and evaluateFixtureSet fails loud if a grounding gate is configured but no grounding pass-rate was reported. More on the eval philosophy in Latency, Accessibility, Eval Sets, and Tests.

Scene telemetry — the Live Direction Channel#

Every authored scene emits a uniform, traceable event feed on the Psyche envelope. scene-events.ts defines eight SCENE_EVENT_KINDS: scene.segment-start, scene.segment-end, scene.transition-start, scene.transition-end, scene.live-direction-applied, scene.policy-intervention, scene.crisis-frame, scene.fallback-engaged. Each event extends a SceneEventEnvelope carrying eventId, sessionId, sequence, emittedAtUnixMillis, traceId, parentSpanId, and spanId. verifyEventStream enforces a monotonic, gap-free sequence (sequence strictly lastSequence + 1) and a single trace-id for the whole session — any trace-id change mid-stream or any sequence gap throws a SceneEventError. This is the real telemetry surface the contemplative viewer and the Score Editor's replay scrubber both consume; see Psyche Real-Time Runtime.

Authoring (the Scene Score Editor)#

AAA Scene Score Editor — Yemaya Studio#

The primary Scene Score Editor lives in Yemaya Studio (apps/yemaya/studio-{web,desktop}) and is gated to the AAA-creator tier and to operators. It is built from the runtime's pure decision helpers — the editor UI calls real functions, not mocks: runFixtureRehearsal, getReplayEventAtFrame, getTransitionFindings, isTransitionGatePassed, createPromotedVersion, and updateSegmentParameter (imported into ScoreEditorPage.tsx).

  • Timeline view: ordered Segment cards (SegmentCard) with duration bands, transition pickers between cards (TransitionPicker), narration-script overlay, motion-descriptor strip, narrator-cadence overlay, audio-bus tracks.
  • Segment inspector (SegmentInspector): editable fields from the score schema, with workflow-class-locked parameter ranges (updateSegmentParameter(segment, range.key, value)), persona/voice picker (allowlist), Lilith tone-band selector, Sophia source pin selector, accessibility-role selector.
  • Transition inspector (TransitionInspector): typed transition with overlap window slider, preview against the fixture set, an eval scorecard surfaced via getTransitionFindings, and manual override blocked when isTransitionGatePassed is false — the editor will not let an author save a boundary below the release-gate threshold.
  • Latent-carry inspector: visualizes the CarryState handoff across each boundary; surfaces violations of the carry-state contract.
  • Fixture-rehearsal harness (runFixtureRehearsal(score)): runs the score against the workflow class's fixture set; reports per-fixture eval scores; promotion to the contemplative tier is blocked until all fixtures pass the thresholds in the table above.
  • Replay scrubber (getReplayEventAtFrame(score, frame)): scrubs a previously rendered session frame-by-frame with per-event annotations (cue applied, transition fired, policy intervention, fallback engaged) drawn from the scene event feed.
  • Score versioning (createPromotedVersion): every save creates a new score version; promotion through Isis environments (dev → staging → prod) is gated through workflow-class release gates and Lilith policy review.
  • Score templates library: the editor seeds new scores from approved templates (the five domain templates above plus any tenant-specific templates) with locked policy bindings.

A second editor exists on the contemplative product#

The AAA editor is not the only authoring surface. A distinct Lilith Studio Tara venue editor ships on the contemplative Oshun web app at apps/oshun/web/src/app/lilith-studio/scenes/TaraSceneEditor.tsx, with a new-scene flow at /lilith-studio/scene/new. It is a venue/staging editor for Tara contemplative sessions — exporting concrete authoring data such as TARA_SCENE_EDITOR_ZONES, TARA_SCENE_EDITOR_LIGHTING, TARA_SCENE_EDITOR_AUDIO, and accessibility profiles (TARA_SCENE_MOTION_PROFILES with reduced-motion remediation, TARA_SCENE_CAPTION_PROFILES). So the earlier claim that the Scene Score Editor "does not appear on the contemplative product under any entitlement" is only partly accurate: the full AAA Score Editor is Studio-only, but a Lilith-Studio scene authoring surface does live on the contemplative product. See Creator Roles and the Authoring Workspace and Lilith Persona Policy.

Customer-facing surfaces and the BFF#

Customers see authored scenes through the public scene viewer (apps/oshun/web/src/app/scene/[id]/page.tsx, plus /scene/[id]/embed and /scene/[id]/report) and compose new ones through the inline compose surfaces (apps/oshun/web/src/app/studio/compose/ComposeClient.tsx and the mobile apps/oshun/mobile/app/compose.tsx). All of these talk to the Living Scenes BFF route (apps/oshun/bff/src/routes/living-scenes.ts, ~1,871 lines), which owns the real keep / share / idempotency logic and is covered by end-to-end tests (scene-viewer-deepening.spec.ts, living-scene-card.spec.ts, public-scene-abuse-report.spec.ts). The surface-by-tier boundaries are detailed in Product Surfaces and Generation Audience Tiers and Surface Boundaries.