Shared Platform · Domain Orchestration

Domain Orchestration (libs/oshun)

The dependency direction is real and verifiable, not aspirational.

9sections13 minread2diagrams

On this page

libs/oshun/ is the layer where a substrate primitive becomes a product capability. It is the 49-package tree that sits directly above the domain-agnostic infrastructure (libs/shared/, the @oshun/* packages) and the validated boundary (libs/contracts/), and directly below the backend-for-frontend tier (apps/*/bff). Iris is a memory store; memory-iris turns it into a consent-scoped, tiered, conflict-resolving memory service that the assistant and all six domains share. Sophia is a grounding engine; evidence-sophia turns it into a versioned, role-scoped evidence contract that Veritas, Nisaba, and Metis bind to. The same move repeats 49 times: substrate in, product service out. As of this writing the tree holds approximately 1,885 TypeScript files (about 1,198 excluding tests) across those 49 packages, every one carrying a package.json name under the @oshun/* namespace.

These libraries are shared rather than per-product for the same structural reason the platform itself exists (the §5 one-canonical-home rule): nine products and several dozen domains all need the same memory model, the same grounding contract, the same persona-safety gate, and the same disclosure registry, and each of those is written once here and composed everywhere. A domain library is allowed to depend on the two trees below it — it imports @oshun/contracts for its shapes and @oshun/event-bus, @oshun/types, @oshun/persistence, and the rest of libs/shared for its plumbing — and is never depended on by them. This page is one slab of the stack described in the platform overview; read that first for the layered model this fills in.

What ships, honestly#

The dependency direction is real and verifiable, not aspirational. A grep for any libs/shared/ or libs/contracts/ source importing a libs/oshun/ domain package (@oshun/domain-*, @oshun/memory-iris, @oshun/persona-policy-lilith, the shells, …) returns nothing — the substrate genuinely does not reach up into the domains. The versioning machinery the domain adapters use, buildOshunContractVersionDescriptor, lives below them in libs/shared/types/src/contracts.ts:105; the domains compose it, they do not own it.

Maturity is uneven, and this page labels it rather than implying uniform depth. The deep, load-bearing packages are unambiguous: persona-registry (137 files), analytics (151), domain-veritas (103), domain-nyx (83), persona-policy-lilith (76), memory-iris (71), payments-bridge (66), domain-tara (54), embodiment-psyche (51), shell-core (49), and generation-control-isis (41) are real services with domain-specific algorithms and substantial test suites. At the other end, several packages are honestly thin by design: domain-metis (3 files) is a re-export façade over the @metis/api-client package whose depth lives in the dedicated polyglot libs/metis/* tree; embodiment-aja (5 files) is an adapter-contract-plus-canonical-adapter bridge, not a runtime; design-tokens (4 files) and ui (8 files) are small foundational surfaces. One package is explicitly retired from V1 scope: @oshun/search-discovery's ranker is not adopted by the live /v1/search and /v1/recommendations paths (audit E5, 2026-06-11, documented at the top of libs/oshun/search-discovery/src/index.ts), because it scores DiscoveryObject features the live candidates do not carry — though its offline-eval release gate does ship live (see below). Where a thing is gated on an injected external dependency, the library fails loud rather than fabricating success; that posture is the spine of the section that follows.

The dependency contract#

The split that makes this tree reusable is the same one the overview names, seen from inside the domain layer:

flowchart TB BFF["apps/*/bff · binds app.domainAdapters.<domain>"] subgraph O["libs/oshun · 49 packages (this page)"] DOM["domain-tara · arete · veritas · nyx · nisaba · metis"] SUB["memory-iris · evidence-sophia · embodiment-psyche/aja · generation-control-isis · persona-policy-lilith · persona-registry · trust-safety"] SHELL["shell-core · assistant · routines · desktop · wearable"] X["platform-foundations · analytics · privacy · payments-bridge · i18n · offline · navigation · ui · design-*"] end C["libs/contracts · @oshun/contracts (Zod shapes)"] S["libs/shared · @oshun/types · event-bus · persistence · ai · http-client · data-residency"] BFF --> O O --> C O --> S C --> S

Every arrow points down. libs/contracts/ gives the domains their vocabulary (@oshun/contracts/metis, @oshun/contracts/tara, …) with no behaviour; libs/shared/ gives them their plumbingbuildOshunContractVersionDescriptor from @oshun/types, the event bus, the persistence drivers, the @oshun/data-residency routing helpers — with no domain knowledge. Because nothing below imports anything here, a change to domain-veritas cannot ripple down into the event bus, and a contract edit is a single type-checked change every domain sees at once. See Shared Libraries and Contracts for the two trees below.

The canonical adapter pattern#

Almost every substrate and domain library in this tree exposes the same shape: a versioned adapter contract. This is the single most important architectural fact about libs/oshun/, because it is how a surface consumes a domain without coupling to its internals. Each contract is an oshun.<area>.<name>.adapter descriptor built through the shared buildOshunContractVersionDescriptor, pinned to a semantic version with a minimumCompatibleVersion and a compatibilityMode:

Library Contract id Version
memory-iris oshun.memory.iris.adapter 1.0.0 (adapter.ts:63)
evidence-sophia oshun.evidence.sophia.adapter 1.0.0
generation-control-isis oshun.generation.isis.adapter 1.0.0 (adapter.ts:78)
embodiment-psyche oshun.embodiment.psyche.adapter 1.0.0 (adapter.ts:76)
embodiment-aja oshun.embodiment.aja.adapter 1.0.0 (adapter.ts:82)
persona-policy-lilith oshun.persona_policy.lilith.adapter 1.0.0 (adapter.ts:67)
domain-tara oshun.domain.tara.adapter (domain-adapter.ts:152)
domain-arete oshun.domain.arete.adapter (domain-adapter.ts:181)
domain-veritas oshun.domain.veritas.adapter (domain-adapter.ts:159)
domain-nyx oshun.domain.nyx.adapter (domain-adapter.ts:222)
domain-nisaba oshun.domain.nisaba.adapter (domain-adapter.ts:211)

Each contract carries four things beyond its id: a metadata descriptor (the substrate's posture — Iris declares role: 'memory_substrate', productPosition: 'substrate_not_shell_peer', directSurface: 'assistant_and_settings'); a declared consumer set (Iris lists assistant, tara, arete, veritas, nyx, nisaba, metis, admin, studio, supportadapter.ts:275); a capability vocabulary (Iris: assistant_identity, durable_memory, continuity_state, memory_scopes, memory_scope_hierarchy, consent_aware_controls, privacy_review, searchable_recall, memory_export, memory_deletionadapter.ts:287); and an availability probe that reports per-subsystem ok | degraded | unavailable rather than throwing. Capabilities are role-scoped: Isis defines ISIS_GENERATION_CONTROL_ADAPTER_ROLE_CAPABILITIES so a non-admin role sees a narrowed surface, exactly as Sophia's grounding | review | admin read roles do (detailed in the Sophia feature page).

The pattern's payoff is at the BFF boundary. apps/oshun/bff/src/app.ts:565 decorates the app with domainAdapters (typed OshunDomainServiceAdapters), defaulting to createDefaultDomainAdapters() but injectable — tests pass createMockDomainServiceAdapters(). Routes then call app.domainAdapters.arete.getStreakStats(...) or app.domainAdapters.nyx.getEventReminders(...) (server.ts:707, :714) without importing a domain's internals. The registry in apps/oshun/bff/src/adapters/domain-service-adapters.ts imports only the canonical types from each domain (@oshun/domain-arete, @oshun/domain-veritas, @oshun/domain-nisaba, @oshun/domain-metis/adapter) and wraps them in a DomainServiceCircuitBreaker with buildUnavailableDomainHealth and PartialFailureError handling — so an unavailable domain degrades to a typed fail-loud health record instead of a 500. The BFF actually consumes 29 of these packages today; the binding is real, not decorative. See BFF & Gateway.

The six customer domains#

These are the product-facing domains a V1 customer experiences as tabs. Each ships a contract-validated object model, an adapter, a card-model and launch-actions/deep-links surface, and a set of cross-domain relationship modules that let a domain compose its siblings rather than duplicate them.

  • domain-tara (54 files) — contemplative practice: rituals, breathwork cadences, a session state machine, and lineage syncretism gates. Its barrel re-exports arete-relationship, nisaba-relationship, nyx-relationship, and metis-relationship. Covered in depth on the Tara feature page; the contract model is libs/contracts/src/tara.
  • domain-veritas (103 files, the largest) — grounded stories and claims. It is built as composite namespaces (AttestorWorkflow, CounterclaimBalance, RetractionCascadeUx, ContradictionDetection, EditorialStateMachine) and ships the real retraction machinery: planVeritasSourceRetractionCascade, summarizeVeritasRetractionCascade, and buildVeritasRetractionNoticeView (retraction-cascade.ts) plan and surface the downstream invalidation when a source is retracted.
  • domain-nyx (83 files) — sky events and perspective. Its ephemeris.ts is, per its own header, "REAL in-repo astronomical computation": Meeus algorithms for toJulianDay, sunPosition, moonPosition, moonIllumination, greenwichMeanSiderealTime, and computeNightSky. The comment is explicit that the system must not fabricate sky data on backend failure — the ephemeris is computed, not mocked.
  • domain-arete (45 files) — goals, habits, reflection: streak-recovery, friction-taxonomy, coaching-summary-surface, and veritas-habit-grounding among its modules.
  • domain-nisaba (21 files) — scholarly study: the public-domain corpus, concept-graph-linkages, and study-plans. This is the corpus Sophia's live answer path actually retrieves over.
  • domain-metis (3 files) — education and tutoring, and the honest thin one: its entire src re-exports @metis/api-client (export * from '@metis/api-client', plus a createMetisDomainAdapter alias). Metis's real depth — adaptive learning, assessment, course building, tutor agents — lives in the dedicated libs/metis/* tree, the way Tara's audio runtime lives in libs/tara/* and libs/meditation/*. domain-metis is the namespace adapter that lets the rest of Oshun bind Metis through one @oshun/domain-metis import.

The relationship modules are why a Tara sit can offer a Nisaba passage or an Arete next step without Tara owning Nisaba's or Arete's model: the dependency is a typed re-export, composed at the call site.

The substrate-orchestration libraries#

These have no consumer tab of their own; they are the services every domain leans on, each fronted by the canonical adapter above.

memory-iris (71 files)#

The shared memory service. Its model is genuinely tiered and scoped, not a key-value store: scopes run assistant_profile, session, conversation, domain, cross_domain, notebook, tenant and canonical tiers run core, working, archival, episodic, semantic (types.ts). Retention is concrete and per-scope — scope-hierarchy.ts ships IRIS_SESSION_RAW_RETENTION_DAYS = 30, IRIS_SESSION_SUMMARY_RETENTION_DAYS = 90, IRIS_POSE_RAW_RETENTION_DAYS = 7, IRIS_TENANT_MEMORY_RETENTION_DAYS = 365, and an IRIS_SESSION_PROFILE_PROMOTION_THRESHOLD = 3 that governs when a repeated session fact is promoted into the durable profile (evaluateIrisSessionProfilePromotion). Concurrent writes are reconciled by a real conflict resolver: resolveIrisMemoryWriteConflict derives a conflict key from the write or record (deriveConflictKeyFromWrite / deriveConflictKeyFromRecord), classifies the source kind, and appends an audit event (appendIrisMemoryConflictAuditEvent) — memory writes are conflict-aware and audited, not last-write-wins. Consent runs through the model end to end (consent-ledger, data-rights, privacy-suppression, admin-inspection). Iris threads into the persistence and privacy stories on Persistence & Data and Auth & Identity.

evidence-sophia (14 files)#

The canonical evidence contract — SOPHIA_EVIDENCE_ADAPTER_CONTRACT (oshun.evidence.sophia.adapter, 1.0.0), the source-set lifecycle (computeSophiaSourceSetHash, evaluateSophiaSourceSetReadiness), and the planSophiaSourceLifecycleInvalidation primitive. The adapter is deliberately an interface over an injected @sophia/client-shaped dependency: when that binding is absent the BFF routes fail closed with 502 evidence_adapter_unavailable rather than inventing a result. The deeper engines it fronts (BM25, the fact-check and contradiction loops in libs/sophia/*) are real but unwired on the live answer path. This package is the contract; the running behaviour is documented on the Sophia grounding feature page.

embodiment-psyche (51 files) and embodiment-aja (5 files)#

The embodiment substrates, and a clean example of honest maturity variance. embodiment-psyche is the deep real-time avatar runtime: a session-envelope with transport tiers and capabilities, provider-failover for mid-turn provider loss (planPsycheProviderFailoverMidTurn), backpressure, reconnect-behavior, and a real quality gate. PSYCHE_QUALITY_THRESHOLD_DEFAULT_POLICY (quality-thresholds.ts:52) pins floors of lip-sync-alignment 0.86, expression-coherence 0.82, and voice-naturalness 0.80 (per-locale, en-US 0.86), with maxRerenderAttempts: 2 and a degradation ladder of pass → re-render → fallback-to-voice → fallback-to-text — when an avatar frame fails its floor twice it degrades to voice, then text, rather than shipping a bad render. embodiment-aja, by contrast, is a thin embodied_instruction_bridge: its five files are the adapter contract and canonical adapter only, with the same substrate_not_shell_peer posture but no runtime of its own.

generation-control-isis (41 files)#

The generation control plane: buildIsisGenerationControlPlan, buildIsisGenerationExecutionSummary, model/provider/workflow registries, a dispatch-guard (evaluateIsisDispatch admits or refuses a runtime request), and a cryptographic provenance bundle. provenance-bundle-schema.ts pins CANONICAL_PROVENANCE_BUNDLE_SPEC_VERSION = 1, Crockford-base32 claim ids (claim_[0-9a-z]{26}), hash algorithms sha256 | sha512 | blake3, and signature algorithms ed25519 | ecdsa-p256 — generated artifacts carry signed, versioned provenance. Isis also governs ComfyUI workflows and a CivitAI intake/review pipeline (comfyui-governance.ts, civitai-review-pipeline.ts).

persona-policy-lilith (76 files), persona-registry (137 files), trust-safety (20 files)#

The safety and persona triad. persona-policy-lilith is the tone-and-crisis gate: buildLilithToneGuidance, buildLilithVoiceSafetyPolicy, buildLilithSafetyAssessment, and a pluggable crisis analyzer (analyzeLilithSafetyViaCrisisPolicy, createLilithCrisisSafetyAnalyzer, mergeLilithSafetyAnalyses), backed by a wall of real evaluation suites — eval-crisis-handling, eval-tone-quality, eval-unsafe-claim, eval-spiritual-boundary, eval-clone-abuse-resistance, eval-regression-blockers. This is the substrate behind Tara's crisis handoff and contemplative tone review. persona-registry is the canonical, disclosure-first registry of every persona the shell, domains, and admin surfaces look up by id — pure data and pure functions (get/list/filter/validate/fingerprint), with avatar release gates, cloned-voice red-team evals, and impersonation-risk and disclosure-visibility measurement. trust-safety is the cross-cutting policy spine: policy-taxonomy, severity, decisions, crisis, abuse-patterns, operator-surfaces, and evaluation. Together they make safety structural rather than a per-product afterthought.

The shells#

The shell libraries are the cross-domain surfaces a customer moves between domains through. shell-core (49 files) is the consumer shell's entry-point and domain-navigation model — ConsumerShellEntryPointDescriptor, buildConsumerShellEntryPointHref, the canonical domain-navigation context. shell-assistant (48 files) is the voice-first cross-domain assistant: createAssistantEngine orchestrates intent classification, domain action routing, and response formatting across Tara, Veritas, Nyx, Arete, Nisaba, and Metis via an injected AssistantDomainAdapters — the same adapter-injection seam the BFF uses. shell-routines (11 files) composes morning/evening routines that span Tara and Arete steps (TaraBreathworkStepConfig, AreteEveningReflectionConfig). shell-desktop (19 files) is a framework-agnostic desktop surface (windows, tray, global shortcuts, the oshun:// deep-link protocol, auto-update). shell-wearable (17 files) is the watch/wearable companion: complications, streak widgets, smart-timed reminders, haptic patterns. The shells hold orchestration, not domain logic — they call domain adapters, exactly like the BFF does.

Foundations, design, and cross-cutting services#

The remaining libraries are the platform-shaped concerns the domains share.

  • platform-foundations (20 files) — the genuinely domain-agnostic core that even the substrates lean on, and notably it declares zero npm dependencies. It barrels nine submodules: service-discovery, public-api (OAuth), role-model, step-up (step-up auth), secrets, configs, rollback, abuse-controls, and shared-contracts. It is the in-libs/oshun companion to the @oshun/* infrastructure proper.
  • navigation (26 files) — the canonical route, IA, and journey model: customer-ia, admin-ia, tenant-ia, platform-shells, daypart-journeys, and the cross-domain shared-concept-graph.
  • design-language, design-tokens, ui — the design spine. design-language (22 files) encodes the product voice as data (TARA_CENTERED_PRODUCT_LANGUAGE with its prohibited list of "growth pressure / fear-led prompts / unattributed certainty", and OSHUN_UNIFIED_IA with the customer shell Home/Explore/Activity/Library/Assistant/Profile). design-tokens (4 files) and ui (8 files) are the small, foundational token/component/motion/theme surfaces — thin on purpose.
  • analytics (151 files) — a large, real taxonomy and pipeline: typed customer/admin/assistant/flows/release event taxonomies (the customer taxonomy alone declares 77 distinct event names), buffered sinks, a validation layer, KPI dashboards, and a tracing manifest.
  • privacy (18 files) — consent, residency, export-deletion, dsar, compliance, and the privacy surface; the user-facing half of the same residency and deletion machinery on Persistence & Data.
  • payments-bridge (66 files) — a real settlement bridge: an oracle-aggregator that takes the median rate across price-feed sources and captures the pair-wise spread per invoice (price-feed.ts), a receipt-signer, a cold-spend-queue, an entitlement-bus, trust-tier-disclosure, and security-gates.
  • search-discovery (44 files) — the honest split named above: the ranker is retired from V1's live search/recommendations paths (it scores DiscoveryObject features the candidates lack, and inventing them is forbidden), but its offline-eval release gate ships: buildSearchReleaseGateSummary (evals/offline-evals.ts:158) is mounted at POST /v1/search/offline-eval (apps/oshun/bff/src/search/offline-eval-route.ts) and computes real NDCG@10 / MAP@10 / recall@100 / coverage / diversity per slice with a pass/fail gate.
  • i18n (2 files, one a 721-line catalog) — the canonical locale catalog: OSHUN_LAUNCH_LOCALES is en-US, es-US, fr-FR, de-DE, ar, he, ja-JP, pt-BR (eight; ar and he carrying RTL metadata), with the documented fallback chain <requested> → <language-only> → en-US and per-locale text-expansion budgets.
  • offline (8 files) — offline-first plumbing: an OfflineSyncQueue, a connectivity monitor, and computeBackoffDelay over a DEFAULT_RETRY_POLICY.

Two further libs/oshun packages — persistence (137 files) and auth/ auth-client (56 files) — live in this tree but are deep enough to own their own pages: see Persistence & Data and Auth & Identity.

How a domain call actually composes#

A concrete trace, following the Tara feed from the overview down into this layer:

sequenceDiagram participant BFF as BFF route (registerTaraRoutes) participant TA as app.domainAdapters.tara participant IRIS as memory-iris participant LIL as persona-policy-lilith participant C as @oshun/contracts/tara BFF->>TA: getRecommended({ userId, mood }) TA->>C: parse request (Zod, fail loud at boundary) TA->>IRIS: getContinuityState({ userId, activeDomain: 'tara' }) TA->>LIL: buildLilithSafetyAssessment(mood) for crisis handoff IRIS-->>TA: scoped continuity (consent-checked) LIL-->>TA: tone band + crisis route TA-->>BFF: validated slate (or typed unavailable health)

Each hop borrows a shared foundation: the request shape is a Zod contract from libs/contracts, the continuity read is the memory-iris adapter under its consent rules, the crisis gate is the persona-policy-lilith substrate, and an unavailable domain returns a typed buildUnavailableDomainHealth record through the BFF's circuit breaker rather than a fabricated slate. No step re-implements memory, validation, or safety — each is one of these 49 services, composed.

  • The Shared Platform — the layered model this page fills in.
  • Shared Libraries — the @oshun/* infrastructure these domains compose (and buildOshunContractVersionDescriptor's home).
  • Contracts — the Zod vocabulary every adapter validates against.
  • BFF & Gateway — where app.domainAdapters is bound and the domain routes are registered.
  • Persistence & Data — the persistence package, plus the residency/deletion machinery Iris and Privacy ride on.
  • Auth & Identity — the auth package and the identity that threads through every domain call.