Oshun Platform · Features

Scene Score Schema

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

10sections8 minread6tables

On this page

The Scene Score is the durable, portable contract at the heart of every Living Scene: an ordered list of Segments plus a Cue Plan plus a Render Envelope, which the Conductor runtime streams and from which a kept Living Offering is deterministically re-rendered. It serves the runtime (the schema the Conductor trusts), the BFF and queues (the schema that validates payloads crossing process boundaries), and persistence (the shape that is stored and replayed). It is the sibling contract to the Living Scenes — Concept and Customer Promise page and the input to Live Direction, Conductor Runtime, and Blend Kernel.

The canonical schema is defined twice, on purpose, so both sides of every process boundary validate against the same shape: the Zod contracts in libs/contracts/src/living-scene/score.ts (@oshun/contracts/living-scene/score) mirror the runtime TypeScript types in libs/yemaya/living-scenes-runtime/src/score/score-schema.ts. The contract file says so in its own header: "Mirror the runtime types … so cross-process boundaries (BFF, queues, persistence) can validate Score payloads against the same schema the runtime trusts." Everything below quotes the real symbol and field names; where the older prose in V1/features.md described different fields, this page corrects it and says what is actually there.

The Score envelope#

A Score (ScoreSchema) is the top-level object. Its fields:

Field Type / constraint Meaning
scoreId non-empty string stable identity of the score
template non-empty string the template/workflow-class this score instantiates (e.g. tara-contemplative-arc)
tenantId non-empty string owning tenant
authorUserId non-empty string author
intent ScoreIntentLayer | null (optional) the private-vs-public intent layer (see below) — lives on the Score, not on a Segment
segments SegmentSpec[], min 1 the ordered segment list
cuePlan CueSpec[] the accepted Live Direction record
renderEnvelope RenderEnvelope the reproducibility envelope
createdAtUnixSeconds positive int creation timestamp

Note the important structural fact corrected from the older docs: intent is a property of the Score (ScoreIntentLayer), not of each Segment. Transitions are likewise not stored on segments — they are decided at boundaries by the Blend Kernel — and grounding, persona, and accessibility roles are not Segment fields either. (See "What the older docs got wrong" below.)

SegmentSpec — the real eight fields#

Each Segment is a SegmentSpec (score.ts:47-60). The older V1/features.md prose claimed a thirteen-field shape ({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 thirteen names exist verbatim. The real schema is:

Field Type / constraint Meaning
segmentId string, 1–80 chars unique within the score (enforced; duplicates throw)
kind SegmentKind enum the segment's role (see enum below)
displayName string, 1–120 chars human label
durationSeconds positive number, ≤ 3600 segment length
tone ToneClass enum tonal register
workflowClassId non-empty string the Isis workflow class that generates it
parameters Record<string, number | string> generation parameters
inboundCarryState CarryState | null the continuity handoff from the prior boundary
jsonc
// A single SegmentSpec (illustrative shape from score.ts)
{
  "segmentId": "seg-02-moonrise",
  "kind": "sky-event-arc",
  "displayName": "Moon rises over the sea",
  "durationSeconds": 42,
  "tone": "wonder",
  "workflowClassId": "nyx-sky-briefing.v3",
  "parameters": { "azimuth": 118, "phase": "waxing-gibbous" },
  "inboundCarryState": null,
}

SegmentKind (SegmentKindSchema, score.ts:15-25) is one of nine values: opening, breath-anchor, visual-meditation, narration-bed, sky-event-arc, lesson-visualizer, grounded-explainer, living-offering, closing.

ToneClass (ToneClassSchema) is one of contemplative, gentle-instructive, celebratory, reverent, reflective, wonder.

CarryState — the continuity handoff protocol#

CarryState (CarryStateSchema, score.ts:38-44) is the concrete continuity protocol carried across a segment boundary. The older docs gestured at the right ideas ("CLIP-style style anchor, last-frame conditioning, motion-vector descriptor, color-LUT alignment, audio tail") but never gave the field names or constraints. They map almost one-to-one onto the real schema:

Field Constraint Continuity role
clipAnchorAssetId non-empty string style/clip anchor asset to seed the next segment
lastFrameConditioningHash /^[0-9a-f]{32,}$/ (≥32 hex chars) last-frame conditioning fingerprint
motionDescriptor string, 1–120 chars motion-vector descriptor to continue motion
lutId non-empty string color-LUT alignment between segments
audioTailDescriptor string, 1–120 chars audio tail to crossfade into the next bed

The Conductor propagates this as the outbound state of the leaving segment becomes the inbound state of the next; see Live Direction, Conductor Runtime, and Blend Kernel.

The Cue Plan — accepted Live Direction#

cuePlan is an array of CueSpec (CueSpecSchema, score.ts:78-85). Each cue records an accepted Live Direction event:

Field Constraint Meaning
cueId string, 1–80 chars cue identity
verb ScoreCueVerb enum the constrained verb (see below)
args Record<string, string | number> verb arguments
appliesToSegmentId non-empty string (or *) target segment; * is the wildcard, others must resolve to a real segment id
channels CueChannel[], min 1 source modality — voice, tap, and/or text (serialized as an array because a Set is not JSON-serializable)

The cue verb vocabulary is a closed set of nine verbs (ScoreCueVerbSchema, score.ts:62-72), mirrored in the runtime cue parser as CUE_VERBS_VOCABULARY (libs/yemaya/living-scenes-runtime/src/cues/cue-parser.ts):

linger, advance, morph, recall, shift-style, shape-by, save-moment, abandon, kill.

Free-text cues are parsed and constrained onto this vocabulary — a cue that does not map to one of these verbs is not accepted. (Per-template subsets further narrow which verbs a given template will honor; e.g. Veritas forbids morph.) The full Live Direction behavior is in Live Direction, Conductor Runtime, and Blend Kernel.

Render Envelope — the reproducibility contract#

RenderEnvelope (RenderEnvelopeSchema, score.ts:91-107) captures the inputs that make a re-render reproducible. The older prose summarized this only as "engine version + model hashes + locale/persona/seeds," but the real schema is a tightly-typed resolution/frame-rate/GPU contract with literal-union value constraints (so an out-of-range resolution or fps cannot even parse):

Field Constraint Meaning
envelopeId non-empty string envelope identity
displayName string, 1–120 chars human label
widthPx literal 1080 | 1440 | 1920 | 2560 | 3840 render width
heightPx positive int ≤ 7680 render height
fps literal 24 | 30 | 60 frame rate
maxBitrateKbps positive int encode ceiling
gpuClass GpuClass enum: rtx-4090 | a100-40gb | a100-80gb | h100-80gb the approved GPU class
minLookaheadSegments literal 2 | 3 | 4 how far the Conductor pre-warms ahead

minLookaheadSegments is not a free integer — it is the literal union 2|3|4, and there is an additional release-gate invariant enforced in deepParseScore (score.ts:165-167): a Render Envelope must require ≥ 2 lookahead segments, or validation throws renderEnvelope must require ≥ 2 lookahead segments. This is the contract-level expression of the runtime's "always pre-warm at least the next segment" rule.

jsonc
// A RenderEnvelope (illustrative shape from score.ts)
{
  "envelopeId": "env-contemplative-1080p30",
  "displayName": "Contemplative 1080p / 30fps / A100-40GB",
  "widthPx": 1080,
  "heightPx": 1920,
  "fps": 30,
  "maxBitrateKbps": 9000,
  "gpuClass": "a100-40gb",
  "minLookaheadSegments": 2,
}

The envelope is what makes a Living Offering portable: re-rendering a score under the same envelope is intended to produce a pixel-equivalent video within Isis-defined tolerance on any approved GPU node, and determinism is a release gate — failing reproducibility blocks promotion of a workflow class to the contemplative tier. (As noted on the overview page, actual frame-level determinism is the downstream renderer's responsibility; the schema pins the inputs that make it checkable.)

The intent layer — private by default#

ScoreIntentLayer (ScoreIntentLayerSchema, score.ts:121-129) is the private-vs-public intent contract, attached to the Score (not per Segment):

Field Constraint Meaning
intentId string, 1–120 chars intent identity
privateText string, 1–20,000 chars the user's real intent — never exposed off-device without elevated authorization
publicRedactedText string, 1–20,000 chars the only intent text exposed off-device or in operator inspection
privateIntentHash /^[0-9a-f]{64}$/ (SHA-256) hash binding the private text
capturedAtUnixSeconds positive int capture time
redactionCategoriesHit LilithRedactionCategory[] which Lilith categories were redacted

LilithRedactionCategory (LilithRedactionCategorySchema) enumerates the sensitive classes that drive redaction: named-person, deceased, medical-condition, abuse-disclosure, location-of-safety, minor-identity, financial-account, legal-status. The score is private by default; the public-redacted intent field is the only intent text exposed off-device or in operator inspection without elevated authorization. The cue-privacy and redaction story is detailed in Scene Safety, Determinism, Provenance, and Cue Privacy.

Cross-field validation — deepParseScore#

Schema parse alone is not enough; deepParseScore (score.ts:151-169) layers three cross-field invariants on top of ScoreSchema.parse, mirroring the in-runtime validateScore semantics so callers crossing process boundaries get identical guarantees:

  1. Segment-id uniqueness — a duplicate segmentId throws duplicate segment id <id>.
  2. Cue target resolution — every cue.appliesToSegmentId that is not the wildcard * must resolve to a real segment, else cue <id> targets unknown segment <id>.
  3. Lookahead policyrenderEnvelope.minLookaheadSegments must be ≥ 2.

Because the contract and the runtime mirror each other, the BFF, the queues, and persistence all reject a malformed score the same way the Conductor would.

Transitions are not stored on segments#

The older V1/features.md text listed transition specs as if they were Segment fields (transition_in / transition_out) and named only five typed transitions (latent-warm-start, optical-flow-morph, color-LUT-match, audio-crossfade, narrative-pivot). Both points are stale:

  • Transitions are decided at boundaries by the Blend Kernel, not stored on segments. The score carries CarryState for continuity; the technique chosen at a boundary is a Blend Kernel / catalog concern.
  • There are nine transition kinds, not five (TRANSITION_KINDS, libs/yemaya/blend-kernel/src/transitions.ts:13-23): the original five plus motion-descriptor-handoff, motion-descriptor-reset, audio-level-jump, and variable-rate-sequencer. Each has real parameter-range validation (validateTransition) — e.g. latent-warm-start bridgeSteps ∈ [2, 12], optical-flow-morph morphFrames ∈ [1, 48] and warpStrength ∈ [0, 1], audio-crossfade fadeMs ∈ [50, 8000] with a linear | equal-power | log curve, and variable-rate-sequencer rate window inside [0.25, 4].

The transition catalog, technique allowlists, and the 12 cinematographic techniques are documented in Composition Surface and Cinematographic Technique Catalog.

What the older docs got wrong (accuracy notes)#

For readers cross-checking against the original V1/features.md "Scene Score Schema" prose:

  • Segment fields — the documented thirteen-field Segment shape does not exist; the real SegmentSpec has eight fields (segmentId, kind, displayName, durationSeconds, tone, workflowClassId, parameters, inboundCarryState). intent lives on the Score, not the Segment; transitions are not stored on segments; grounding/persona/accessibility are not Segment fields.
  • Transition list — five named transitions is stale; the real TRANSITION_KINDS array has nine.
  • Render Envelope — it is far richer than "engine version + model hashes": it pins resolution, fps, bitrate ceiling, GPU class, and the lookahead literal with a ≥2 release-gate invariant.