# Tara Studio: Instructors, Classes & Coaching

```mermaid
sequenceDiagram
  participant Teacher as Verified instructor
  participant Studio as Tara Studio
  participant Student as Student client
  participant Aja as On-device Aja
  Teacher->>Studio: Publish credentialed class plan
  Studio-->>Student: Offer schedule and consent terms
  Student->>Studio: Join with explicit permissions
  Studio-->>Teacher: Admit eligible participant
  Student->>Aja: Provide local camera frames
  Aja-->>Student: Private pose cue
  Note over Student,Aja: Frames and body landmarks stay on device
  Teacher->>Studio: Close class and record governed outcome
```

The sequence exposes the privacy boundary: Studio coordinates identity,
eligibility, schedule, and consent, while Aja processes the student's body data
locally. The instructor receives only the class-level information permitted by
the student's explicit choices.

Tara Studio is V3's contemplative-instruction tenant — the place inside the
Lilith metaverse where a verified human yoga teacher leads a room of embodied
students through an authored asana sequence, and where each student's _real
body_ is coached from a webcam whose footage never leaves their device. Tara is
the one V3 loop that grades a _person_ rather than pixels or text, and that
changes the burden of proof: where a V2 combat realm had to be _provably
deterministic_, Tara has to be _provably safe and consent-shaped_. An
instructor's avatar may not close its eyes or touch a student without explicit
permission; an unverified teacher may not be scheduled for a class that admits
minors; a student's pose data may not be shipped off the laptop. This page
covers the three features that carry that promise: how an instructor is
**onboarded and verified** into the studio, how a **live class session** is
scheduled and governed end to end, and how **Aja body-aware coaching** turns a
camera frame into a spoken alignment cue without ever transmitting the frame.
The data, policy, and pose-math tier behind all of it lives in three `libs/v3/`
packages — `@oshun/tenant-tara-studio` (`libs/v3/tara-studio`),
`@oshun/aja-pose` (`libs/v3/aja-pose`), and `@oshun/isis-motion`
(`libs/v3/isis-motion`); the in-world rendering and room runtime are the UE5
client and world server documented in the architecture companion. The section
hub is [../V3_features.md](../V3_features.md).

## What ships, honestly

Tara's hardest-to-fake parts are real, tested TypeScript; its room runtime and
its accuracy numbers carry honest caveats. Here is the split before the details.

- **Verification is a real launch gate over a real data shape.** Instructor
  identity persists through the `V3InstructorProfile` and
  `V3InstructorCredential` Prisma models
  (`libs/v3/tara-studio/prisma/schema.prisma`), and
  `evaluateTaraGaInstructorInventory()` (`ga-inventory.ts:123`) is an
  all-or-nothing readiness gate that fails unless twelve seeded instructors are
  each verified, onboarded, audition-approved, insured, and carrying a published
  four-week schedule.
- **The in-class consent stack is real, fail-loud code** — five independent
  policies (eyes-open, physical-adjustment, invitational-language linter,
  class-style mode, lineage grounding), each a small decision engine with an
  audit event, across 16 spec suites (69 cases).
- **Aja is a full on-device pipeline**, not a wrapper: a MediaPipe→MoveNet
  fallback cascade, an 18-feature biomechanical extractor, a nearest-centroid
  classifier over 30 canonical asanas, ten alignment-cue rules, six known-risk
  modification ladders, and a recursive raw-pose egress blocker — 21 automation
  specs.
- **The honest caveats.** First, the **lifecycle state machines** that
  `V3_features.md` narrates — the
  `applied → credentials-verified → audition-passed → listed` onboarding flow
  and the pre-class-lobby → opening → close session flow — are _product
  narrative plus a persisted data shape_, not a runtime FSM in this library; the
  class runtime (room spawn, the 90-second instructor-disconnect grace hold)
  lives in `lilith-world-server` and the `V3Mode_TaraLiveClass` UE plugin.
  Second, Aja's "≥ 92 % per-asana accuracy" and "≥ 90 % cue relevance" gates are
  computed against a **deterministic synthetic corpus**
  (`buildCanonicalAsanaTrainingCorpus`, `canonicalAsanaPrototype`), so they are
  self-consistency proofs of the algorithm, not field accuracy. Third, the pose
  models and the TTS voice are **runtime/provider-gated** behind dynamic imports
  and an injected adapter. Fourth, `@oshun/isis-motion` is a **descriptor-only
  placeholder**. Each section below says where its claim is backed.

## Instructor onboarding and verification

### The data shape behind a verified teacher

Before any lifecycle, there is a record. `V3InstructorProfile`
(`schema.prisma:90`) carries `credentials`, `insurance`, `lineageAttestation`,
and `traumaInformedTraining` as structured JSON, a `backgroundCheckStatus`
string, an `avatarBinding`, and the `v1UserId` tying the studio identity back to
the member's V1 account. `V3InstructorCredential` (`schema.prisma:110`) is one
row per credential — `credentialType`, `issuingOrg`, `issuedAt`, an optional
`expiresAt` (so an expired RYT is detectable), and a `yogaAllianceLookup` blob
recording the registry-API result where one exists. Those two models are the
durable spine the gates below decorate.

### The gates, mapped to the fields

`V3_features.md` defines a staged flow with six gates; each maps onto the data
shape above:

| Gate                         | What it checks                                                                                                                                                | Where it lands                                                                      |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| **Credential check**         | Yoga Alliance RYT-200/500, E-RYT, Ayurveda/somatic certs; registry API lookup plus operator confirmation                                                      | `V3InstructorCredential.yogaAllianceLookup` + `credentialType`                      |
| **Lineage attestation**      | a named lineage (Iyengar / Ashtanga / Kripalu / Yin / …) and a teacher attribution; an unverifiable attribution lists as "self-attested" rather than blocking | `V3InstructorProfile.lineageAttestation`; grounded by Sophia (see live-class stack) |
| **Trauma-informed training** | recommended, not required; earns a badge and is **required** to teach `prenatal` / `recovery` / `grief-adjacent` classes                                      | `V3InstructorProfile.traumaInformedTraining`                                        |
| **Background check**         | required before scheduling any class that admits minors; blocked at the scheduling step, not the room gate                                                    | `V3InstructorProfile.backgroundCheckStatus`                                         |
| **Live audition**            | one 30-minute observed class scored on tone, cueing clarity, safety, and platform fluency; one re-audition after 30 days                                      | `auditionStatus` in the GA inventory record                                         |
| **Liability insurance**      | required to list any live class above 25 seats; carrier/policy/expiry stored                                                                                  | `V3InstructorProfile.insurance`                                                     |

The honest line: the **transition machine** between `applied`,
`credentials-verified`, `audition-passed`, and `listed` is described in the
product spec and run by the Tara editorial pool at a 5-business-day SLA; the
repository ships the persisted shape those transitions write to, not an
executable state graph.

### The launch gate that proves the cohort — real code

What _is_ executable is the readiness gate over the whole launch cohort.
`createTaraGaVerifiedInstructorInventory()` (`ga-inventory.ts:97`) seeds twelve
named instructors (`TARA_GA_REQUIRED_VERIFIED_INSTRUCTOR_COUNT = 12`), each
generated with four published catalog classes and a four-week recurring schedule
(`FREQ=WEEKLY;COUNT=4`). `summarizeTaraGaInstructor()` (`ga-inventory.ts:198`)
reduces each instructor to a readiness summary — `verified`,
`onboardingComplete`, `auditionApproved`, `catalogPublished`, `activeInsurance`,
and a `fourWeekRecurringSchedulePublished` flag computed by actually counting
four published classes in each of four weeks — and
`evaluateTaraGaInstructorInventory()` (`ga-inventory.ts:123`) refuses to pass
unless _every_ instructor is ready, the count is exactly twelve, and there are
no duplicates. This is the bright line between "we have a yoga tenant" and "we
have a yoga tenant with twelve real, fully-credentialed, fully-scheduled
teachers": it is a gate that fails loud with a per-instructor error list, not a
checkbox.

### Verified instructors as the root of trust

Verification is not just a profile badge; it is a _capability_. Tara permits AI
instructor personas, but `registerTaraAiPersonaSponsor()`
(`ai-persona-sponsor.ts:107`) throws
`Only verified human instructors may sponsor a Tara AI persona` (`:182`) unless
the sponsor's `verificationState === 'verified'` and they carry credential
references. And `evaluateTaraAiPersonaRegionalSchedulingCap()`
(`ai-persona-region-cap.ts:50`) ties persona supply to human supply by floor
division: `Math.floor(verifiedHumanInstructorCount / 4)` personas per region, so
a region with three verified humans gets _zero_ personas. The verified human is
the unit of trust the whole persona economy is rationed against — personas,
voice contracts, and economics get their own deep treatment in
[./tara-authoring-personas-tone-economics.md](./tara-authoring-personas-tone-economics.md).
A listed instructor's verified badge also renders on the avatar nameplate at the
close LOD bands — see
[./avatars-nameplates-spatial-audio.md](./avatars-nameplates-spatial-audio.md).

## Live class sessions

### What a class is, and where it runs

A live class is a scheduled, instructor-led session in a class-tier room
(capacity ≤ 64). It persists as `V3LiveClassSession` (`schema.prisma:13`), whose
columns are exactly the things an instructor fixes at schedule time: `startsAt`
/ `endsAt`, `capacity`, `instructorId`, `sequenceId` (the authored asana
sequence), `roomId`, `venueId`, `recordingEnabled`, `attendance`, and a
`tonePolicyMode`. The authored sequence itself is `V3AsanaSequence`
(`schema.prisma:51`) — asana refs, breathwork/meditation blocks, transitions,
prop prompts, a lineage tag, difficulty and pace — and per-student coaching
events are `V3AjaCueEvent` (`schema.prisma:127`), each row carrying the
`cueText`, `riskFlag`, `modificationLadderStep`, and a `poseErrorVector`.

The runtime that walks a class through pre-class lobby, opening, practice body,
and close — including the rule that an instructor disconnect beyond 90 seconds
converts the session to guided on-demand playback rather than stranding students
— is the world-server + UE pipeline: schedule → booking (V1 BFF) → room spawn
(`lilith-world-server`) → UE clients load `V3Mode_TaraLiveClass` while Tier-2
fallback clients render the three.js scene. `tara-studio` owns the **contract
and policy** tier under that runtime; the architecture companion
[../architecture/tara-classes-aja-and-commons.md](../architecture/tara-classes-aja-and-commons.md)
traces the full spawn-and-cue-fan-out path.

### The consent and safety stack that governs the room — real code

The heart of the tenant is five independent gates, each a small decision engine
that emits an audit event:

| Policy                    | File                              | Rule                                                                                                                          |
| ------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Eyes-open default**     | `eyes-open-policy.ts`             | Avatar eyes stay open; an eyes-closed cue is suppressed unless the invitation text matches an explicitly permissive pattern   |
| **No-surprise contact**   | `physical-adjustment-consent.ts`  | Default `hands-off`; in-world contact is `dialog-required`/`blocked` until the student grants a dialog-matched consent record |
| **Invitational language** | `invitational-language-linter.ts` | 13 directive phrases flagged `blocking`, forcing `needs-editorial-revision` before a script ships                             |
| **Class style mode**      | `class-style-mode.ts`             | Directive cueing is allowed only in an explicitly-declared `advanced-directive-opt-in` class, surfaced on the listing         |
| **Lineage grounding**     | `lineage-grounding.ts`            | A lineage claim needs a cited, supporting source or it is marked `needs-citation` and blocks editorial review                 |

These are not config tables. `evaluateTaraSequenceEyeState()`
(`eyes-open-policy.ts:43`) suppresses an eyes-closed cue to `eyes-open` with
`autoCloseSuppressed: true` unless `explicitInvitation` is set _and_ the
invitation text matches one of `EXPLICIT_EYES_CLOSED_INVITATION_PATTERNS`
(`:36`: "if it feels", "you may", "optional", "invitation to close") — and the
same default is mirrored by the C++ `V3TaraEyesOpenPolicy` on the avatar runtime
side, so authoring and rendering cannot disagree about whether a teacher's eyes
may close. `evaluateTaraPhysicalAdjustmentConsent()`
(`physical-adjustment-consent.ts:85`) returns `dialog-required` when there is no
consent record, `blocked` when the record's `dialogId` or `studentUserId` does
not match the attempt, and `authorized` only on an explicit `granted` record —
contact is off by default and stays off on any mismatch.
`lintTaraInvitationalLanguageSequenceScript()`
(`invitational-language-linter.ts:56`) walks every cue line against
`DIRECTIVE_LANGUAGE_RULES` (`:40`) and flips a single
`must`/`force`/`push`/`hold` to `needs-editorial-revision` with a per-issue
replacement suggestion. The lineage grounder,
`buildTaraLineageEditorialReview()` (`lineage-grounding.ts:204`), is a genuine
little reasoning engine: it resolves each claim's cited source IDs against a
registry with authority scores, checks the source actually supports the claimed
lineage tag, and blocks the review on the fixture's uncited "direct Mysore
family line from 1932" claim.

### The asana sequence and its Aja cue bundles

A class teaches a sequence drawn from `TARA_CANONICAL_ASANA_LIBRARY`
(`asana-library.ts:234`). The library is built combinatorially as 30 base asanas
× 10 lineage framings = 300 entries (`TARA_ASANA_LIBRARY_GA_MINIMUM`), and
`validateTaraCanonicalAsanaLibrary()` (`asana-library.ts:240`) is another
all-or-nothing GA gate: every entry must be editorial-signed, design-approved,
and carry ≥ 3 lineage variants, ≥ 4 modifications, ≥ 2 **family-keyed**
contraindications (an inversion warns on neck and hypertension; a backbend on
lumbar and pregnancy), and a ≥ 3-cue Aja bundle. The honest qualification: this
is 30 distinct shapes through ten teaching framings, not 300 distinct shapes —
substantial content with real per-family safety metadata, generated rather than
hand-authored entry by entry.

### After class: practice logs, plans, and continuity

Each session writes a practice log to the participant's V1 Arete record, and the
broader arc is a Tara practice plan. `completeTaraGuidedOnboarding()`
(`practice-plan.ts:170`) maps a 5-minute intake
(`TARA_GUIDED_ONBOARDING_DURATION_SECONDS`) of goal/experience/intensity/time to
the best-scoring starter template (the scorer down-weights crow-prep for a
wrist-sensitive user by 40 points) and emits a validated four-week, 20-session
schedule of on-demand-class and self-practice slots, persisted as
`V3PracticePlan` (`schema.prisma:147`) with its `areteLinkage`.
`createTaraFourWeekAdjustmentJourney()` (`practice-plan-adjustment.ts:66`) then
re-tunes the plan from attendance, Aja coverage signals, and Arete weekly-review
energy — proposing `add-foundation-class`, `add-self-practice`, or
`reduce-weekly-load` — mirroring V1 Arete's humane, user-confirmed re-scoping.

## Body-aware coaching (Aja)

Aja is the loop that grades a real human body, and its safety property is that
the camera frame never leaves the device. The pipeline in `libs/v3/aja-pose/src`
is six honest stages.

### The on-device runtime and its fallback

`AjaPosePipeline` (`runtime.ts:399`) is a real fallback cascade. The primary
estimator is the MediaPipe Tasks-Vision pose-landmarker (33 named landmarks);
the fallback is a TensorFlow MoveNet lightning detector (17 keypoints). On any
`init-failed`/`estimate-failed` the pipeline advances to the next adapter and
fires `onFallback`, so a GPU-delegate failure degrades to CPU MoveNet rather
than dropping the student — only when _every_ adapter fails does a frame throw.
Both device profiles (`web-m1-mac`, `mobile-iphone-15-pro`) target **15 Hz**
(`AJA_POSE_MINIMUM_HZ`, a 66.7 ms budget), and `assertAjaPosePerformanceGate()`
(`runtime.ts:731`) throws if observed Hz or average inference blows it. The
estimators load through a `dynamicImport` seam, which is also the honest caveat:
the models are runtime-gated, so the geometry unit-tests without a wasm bundle.

### Features and classification

`extractAsanaFeatureVector()` (`classifier.ts:391`) turns raw landmarks into 18
rotation- and scale-robust biomechanical features (`ASANA_FEATURE_NAMES`):
shoulder/hip slope, spine angle, four limb joint angles each computed as
`acos(dot / |a||b|)`, wrist/ankle spans, shoulder–hip twist, and side lengths.
`trainAsanaCentroidClassifier()` (`classifier.ts:239`) averages per-asana
vectors into centroids over the 30 canonical asanas (`AJA_CANONICAL_30_ASANAS`,
Tadasana through Savasana); `classifyAsanaFeatureVector()` (`classifier.ts:345`)
returns the nearest centroid by Euclidean distance with a
first-to-second-distance confidence; and `evaluateAsanaClassifier()`
(`classifier.ts:274`) builds a confusion matrix and enforces the ≥ 92 %
per-asana gate (`AJA_ASANA_VALIDATION_MIN_PER_ASANA_ACCURACY`). The extractor
and classifier run on real MediaPipe landmarks at runtime; the _corpus_ they are
graded on is synthesized from sinusoidal prototypes (`canonicalAsanaPrototype`,
`classifier.ts:461`), so the 92 % is a proof of the algorithm's separability,
not a field measurement.

### Alignment cues, risk flags, and the modification ladder

Ten rules (`AJA_ALIGNMENT_CUE_RULES`, `cue-generator.ts:93`) compare features
against thresholds — `absolute-greater-than` for a tilted shoulder line,
`centroid-deviation` for a spine that has drifted from the target asana's
centroid, `less-than` for a collapsed stance. `generateAjaAlignmentCues()`
(`cue-generator.ts:206`) sorts candidates by `priority + deviation`, slices to
the top _N_, and, when a classifier model is supplied, decorates each cue with
ML assistance (predicted vs. target asana, distance, max feature deviation). If
nothing triggers but the classifier says the student is in the _wrong shape
entirely_, it emits one `return-to-target` cue. Relevance is gated at ≥ 90 %
(`AJA_CUE_RELEVANCE_MIN_HUMAN_APPROVAL`) against a review set that is, again,
built programmatically.

Risk is a separate, sterner channel. Six rules
(`AJA_KNOWN_RISK_MODIFICATION_RULES`, `risk-flagger.ts:102`) cover
shoulderstand, plow, lotus, chaturanga, upward-dog, and tree, each keyed to
anatomical factors (`neck`, `lumbar`, `knee`, `wrist`, `shoulder`, `balance`,
`hypertension`, `pregnancy`) and carrying a real three-step modification ladder
with cue text and props ("place a block under your sacrum for supported bridge"
→ "legs up the wall" → "rest in savasana"). A `new` practitioner trips every
rule for their target asana; an `experienced` one trips only rules matching
their declared risk factors. `routeAjaRiskModification()`
(`risk-flagger.ts:275`) routes within a 250 ms budget
(`AJA_RISK_MODIFICATION_BUDGET_MS`), and the gate requires every validation case
to route to the correct ladder, on time.

### Voice delivery

Cues are spoken in the instructor's own voice through `deliverAjaVoiceCue()`
(`voice-cue.ts:151`), which builds an `AjaPsycheTtsCueRequest` (Opus,
low-latency, routed `psyche-tts → v3voice` on a `student-private-cue` channel)
and measures pose-to-first-audio against a 250 ms budget
(`AJA_VOICE_CUE_DELIVERY_BUDGET_MS`). The instructor tone profile is validated
(warmth/energy ∈ [0,1], speech rate ∈ [0.75, 1.25]). The TTS provider is an
injected adapter with a deterministic test double, so the gate proves the budget
and the routing, not a live network.

### The privacy gate — the load-bearing property

This is what makes camera coaching defensible. `containsAjaRawPoseData()`
(`privacy.ts:161`) recursively walks any egress payload and returns true if it
contains _any_ raw-pose key (`image`, `frame`, `poses`, `landmarks`,
`worldLandmarks`, `world`, `keypoints`, `segmentationMasks`).
`buildAjaIrisAggregateMetricsEnvelope()` (`privacy.ts:100`) produces an
Iris-bound envelope of counts and averages only, then runs it through
`assertAjaNoRawPoseDataLeavesDevice()`, which **throws** if raw data is present.
The only thing that can reach Iris memory is aggregate metrics, and the gate
fails loud rather than leaking silently. Camera estimation is opt-in per session
and never recorded; this code is why that claim holds.

## isis-motion: honestly labeled

`@oshun/isis-motion` (`libs/v3/isis-motion`) is **descriptor-only** —
`src/index.ts` exports a capability descriptor (`motion-brief`, `retarget-pass`,
`cue-export`) and a readiness scorer, with no implementation behind it. It is
the planned seam for a motion-authoring service between Tara's asana sequences
and the retarget runtime; reading it expecting a motion engine will mislead. The
real motion lives in the `V3Avatar` retarget/posture code and in the asana
catalog above.

## How it connects

Tara is the consumer of V3's embodiment substrate, not a standalone app. The
instructor and student avatars, the verified-instructor nameplate badge, the
eyes-open/posture policies on the avatar runtime, and the spatial voice that
carries an instructor's cues all come from
[avatars, nameplates & spatial audio](./avatars-nameplates-spatial-audio.md).
The AI-persona machinery that verification unlocks — sponsorship, voice
contracts, the persona economy, and class tone — is the sibling
[Tara authoring, personas, tone & economics](./tara-authoring-personas-tone-economics.md).
And the room-spawn, cue-fan-out, on-demand-library, and Lilith Commons context
around a class is mapped in the architecture companion,
[Tara live classes, Aja coaching & Lilith Commons](../architecture/tara-classes-aja-and-commons.md).
The honest line throughout: the verification gate, the consent policies, the
asana catalog, the practice-plan generator, and the Aja pose math are real and
tested; the class runtime, the pose models, and the TTS voice are the runtime
and providers they gate.

## Related

- [Tara Authoring, Personas, Tone & Economics](./tara-authoring-personas-tone-economics.md)
  — AI instructor personas, voice contracts, and the persona economy that
  verified humans gate (forthcoming sibling page)
- [Avatars, Nameplates & Spatial Audio](./avatars-nameplates-spatial-audio.md) —
  the embodiment substrate: the avatar body, the verified-instructor badge, the
  eyes-open policy on the runtime, and the voice that carries Aja cues
- [Tara Live Classes, Aja Coaching & Lilith Commons (architecture)](../architecture/tara-classes-aja-and-commons.md)
  — the class runtime, room spawn, on-demand library, and the full Aja/consent
  internals
- The section hub: [../V3_features.md](../V3_features.md)
