This page is the one-screen mental model of Oshun V1: how a request travels from a client surface, through the shared shell and the single BFF, into the customer-facing domains, down through the platform substrates and the shared foundations, and finally to the data and runtime stores. It serves engineers and platform leads who need to know which box a thing lives in before opening any single subsystem page, and it sits directly above the per-subsystem pages — Product Surfaces, Customer-Facing Domains, the substrate pages, and Foundations — that zoom into each layer. It is part of the V1 architecture set hubbed at ../ARCHITECTURE.md.
The V1 Stack at a Glance#
V1 is a strict subset of the wider Oshun monorepo: six customer-facing domains (Tara, Veritas, Nyx, Arete, Nisaba, Metis), six platform substrates (the five product substrates Sophia, Iris, Psyche, Lilith, Isis, plus the Aje payment substrate), six cross-domain support subsystems (Aja, Yemaya, Themis, plus the Studio-only Bellona, Hathor, Neith), a set of operator and distribution surfaces, and the foundations they all share. Everything below is wired so that exactly one path exists from any client to any domain: there is a single shell, a single BFF, a single contracts package, and one typed adapter per substrate.
The diagram is layered top-to-bottom, and the layering is load-bearing: each layer talks only to the one directly beneath it. Client surfaces never call the BFF directly — they call it through the shared shell. Domains never reach into data stores directly — they go through substrates and foundations. This is what keeps the six domains swappable and the substrate runtimes hidden behind stable shapes.
The Five Architectural Commitments#
V1 leans on five decisions that the rest of the document elaborates. They are the reason the stack reduces to a single readable diagram instead of a mesh.
1. A single BFF fronts every Oshun-side domain#
There is exactly one Backend-for-Frontend at apps/oshun/bff (package
@oshun/bff), built on Fastify with OpenAPI 3.1, idempotency keys, tenant
scoping, and data-residency enforcement. Every customer-facing domain is reached
through one BFF prefix, /api/oshun/domains/<domain>. These prefixes are not
folklore — they are declared as the single source of truth in
libs/oshun/domain-registry/src/registry.ts, where each domain entry carries a
bff-base-path:
| Domain | BFF base path |
|---|---|
| Tara | /api/oshun/domains/tara |
| Veritas | /api/oshun/domains/veritas |
| Nyx | /api/oshun/domains/nyx |
| Arete | /api/oshun/domains/arete |
| Nisaba | /api/oshun/domains/nisaba |
| Metis | /api/oshun/domains/metis |
The registry order — tara, veritas, nyx, arete, nisaba, metis — is the
canonical OSHUN_DOMAIN_IDS ordering exported from the same file, and it is the
ordering the BFF, the shell, and the navigation layer all honor. Because the BFF
is singular, cross-cutting concerns (auth, idempotency, tenant isolation,
residency routing, rate limits, audit) are implemented and tested once rather
than per surface. See Communication Patterns for
the request envelope and
Data Architecture and Tenancy for residency
and isolation.
2. A single contracts package drives every spec#
@oshun/contracts at libs/contracts/ holds the Zod schemas that define every
request and response shape. Those schemas are not duplicated into hand-written
OpenAPI — they generate the OpenAPI specs under libs/openapi/src/specs/. The
practical consequence is that the BFF, the typed domain adapters, the admin
surfaces, and the assistant all share one definition of truth: change a schema
in one place and the spec, the types, and the contract tests move with it. Each
domain keeps its own contracts under libs/contracts/src/<domain>/ (Zod schemas
plus spec tests), so the single package is internally partitioned by domain.
3. Per-domain adapters present typed read APIs to the shell#
Under libs/oshun/domain-*, each domain ships an adapter that exposes a typed
read API to the shared shell, the admin surfaces, and the assistant. The shell
never imports a domain's internal runtime — it imports the adapter's stable
shape. This is the boundary that lets a domain rework its internals without
forcing a shell change, and it is why the shell can render all six domains with
one composition strategy.
4. Substrate adapters hide the substrate runtimes behind one shape#
Each of the platform substrates is consumed through a thin Oshun-side adapter whose only job is to present one stable shape and hide the underlying runtime:
| Substrate | Adapter package | What it hides |
|---|---|---|
| Sophia | evidence-sophia |
grounding / evidence runtime |
| Iris | memory-iris |
assistant memory + continuation state |
| Psyche | embodiment-psyche |
real-time runtime |
| Lilith | persona-policy-lilith |
contemplative persona policy |
| Isis | generation-control-isis |
generation control |
| Aje | libs/aje/ via payments-bridge |
non-custodial settlement |
The Aje payment substrate (libs/aje/) is consumed specifically through the
Oshun-side bridge libs/oshun/payments-bridge/ so that settlement stays
non-custodial and the rest of the platform sees only an entitlement-shaped
interface. Each substrate has its own deep-dive:
Sophia, Iris,
Psyche, Lilith,
Isis, and Aje.
5. Metis runs as its own stack, integrated through the shell#
Metis is the launch-blocking education product and the only V1 domain with
its own dedicated apps and microservice stack beneath apps/metis/. The Metis
runtime is apps/metis/{web,admin,api-gateway,worker} and is integrated into
the Oshun shell through libs/metis/api-client (renamed under the Oshun adapter
convention per V1/TODOS.md § 1.3). Every other customer-facing domain renders
through apps/oshun/web and apps/oshun/mobile via the shared shell and the
BFF; Metis is the exception that brings its own backend.
A nuance the surface table flattens: Metis is not solely rendered through
apps/oshun/mobile. There is also a separate Metis mobile app atapps/metis/mobile. So while every other domain is mobile-rendered only through the Oshun mobile shell, Metis has both an Oshun-shell presence and its own dedicated Metis mobile surface. Theapps/metis/tree therefore containsweb,admin,api-gateway,worker, andmobile.
How a Request Actually Flows#
The arrows in the diagram describe a concrete journey. Walk through a customer opening a Veritas claim from the web:
- Client → Shell. The customer is in
apps/oshun/web. They click a claim card. The click is handled inside the shared shell, not by a bespoke page — the shell owns navigation, current-domain persistence, and command-surface registration via@oshun/navigation. - Shell → BFF. The shell issues an HTTPS request (and, for live work, a
WebSocket) to the single BFF. It hits the Veritas prefix
/api/oshun/domains/veritas, carrying the tenant, residency, and idempotency context the BFF enforces. - BFF → Domain. The BFF dispatches to the Veritas domain adapter under
libs/oshun/domain-*, validated against the Zod contracts inlibs/contracts/src/veritas/. - Domain → Substrates. The Veritas domain pulls grounding through the
Sophia adapter (
evidence-sophia), assistant context through Iris (memory-iris), and so on — each substrate behind its single stable shape. - Substrates → Foundations → Data. The substrates persist and read through
the foundations (
@oshun/persistence,@oshun/event-bus,@oshun/queue,@oshun/data-residency,@oshun/identity,@oshun/audit-platform,@oshun/inbound-integrations), which finally touch PostgreSQL+pgvector, Redis, MinIO/S3, Elasticsearch, Qdrant, Neo4j, and Kafka, with OTLP/Jaeger/Prometheus/Grafana observing the whole path.
The same five-hop shape holds for every domain; only the prefix and the adapter change. That uniformity is the payoff of the single-BFF, single-contracts design.
Client Surfaces Feeding the Top Layer#
The CLIENTS box in the diagram contains four boxes, but the actual surface
inventory is larger and worth naming precisely, because two real surfaces are
easy to miss.
The named app surfaces (all real, all packaged)#
Every one of these is a real workspace package with its own package.json and a
substantial src/:
| Surface | Path | Package |
|---|---|---|
| Customer Web | apps/oshun/web/ |
@oshun/web |
| Customer Mobile | apps/oshun/mobile/ |
@oshun/mobile |
| Oshun BFF | apps/oshun/bff/ |
@oshun/bff |
| Admin Web | apps/oshun/admin/ |
@oshun/admin |
| Admin Mobile | apps/oshun/admin-mobile/ |
@oshun/admin-mobile |
| Tenant Console | apps/oshun/tenant-admin/ |
@oshun/tenant-admin |
| Telegram Bot | apps/oshun/telegram-bot/ |
@oshun/telegram-bot |
| Telegram Mini App | apps/oshun/telegram-miniapp/ |
@oshun/telegram-miniapp |
Two surfaces the prose has historically under-described#
- Content service —
apps/oshun/content-service/is a real app, package@oshun/content-service-app. The Product Surfaces table has historically omitted this surface; it belongs in the inventory. - Legal —
apps/oshun/legal/is not an app. It is a markdown-only surface containingprivacy-policy.mdandterms-of-service.mdwith nopackage.json. The architecture's references to "the legal surface atapps/oshun/legal/" are correct, but it is a docs folder, not a buildable app, and should be read that way. See Security, Privacy, and Compliance for how these documents are consumed.
For the web app, the PWA layer is real and test-covered, not aspirational. The
service worker lives at apps/oshun/web/public/sw.js, the manifest at
apps/oshun/web/public/manifest.json (name: "OSHUN",
start_url: "/?surface=pwa", display: "standalone",
theme_color: "#f1ebdd"), and the install/update/caching logic at
apps/oshun/web/src/lib/pwa-service-worker.ts with its policy test at
apps/oshun/web/src/lib/pwa-service-worker.test.ts.
The mobile app is a real Expo + Expo Router project: app.json sets
expo.scheme: "oshun", the iOS bundleIdentifier is com.oshun.mobile, and
its associatedDomains include applinks:oshun.app (plus www.oshun.app,
app.oshun.com, and activitycontinuation:/webcredentials: entries). Its tab
IA lives under apps/oshun/mobile/app/(tabs)/ as index, explore,
activity, library, and profile. Admin Mobile (@oshun/admin-mobile) is
likewise a full Expo Router app with an app/(operator)/ group plus login.tsx
and step-up.tsx, and a deep src/ (auth, incidents, offline, review,
urgent-queue, and more). See Product Surfaces for the
full surface walkthrough.
The Shared Shell Is Wider Than the Diagram Label#
The shell subtitle in the diagram reads
@oshun/shell-* · navigation · offline · ui · design-tokens, and the
@oshun/shell-* glob is doing more work than the commonly cited composition
list implies. The shell is genuinely composed from these real libraries:
| Shell library | Role |
|---|---|
@oshun/shell-core |
home / explore / activity / library / notifications / profile / settings / assistant entry points |
@oshun/shell-assistant |
collapsible assistant dock + context handoff |
@oshun/shell-routines |
cross-domain routine and continuation cards |
@oshun/shell-wearable |
watch / widget / Live-Activity-style surface |
@oshun/shell-desktop |
desktop (Electron-class) surface — see below |
@oshun/shell-achievements |
achievements, challenges, social accountability — see below |
@oshun/navigation |
current-domain persistence, deep links, route analytics, command-surface registration |
@oshun/offline |
offline/PWA/mobile sync substrate — see below |
@oshun/design-tokens, @oshun/ui |
design system + component library |
Three of these are real shell libraries that the older composition prose did not name, and they materially change the picture:
@oshun/shell-desktopis a whole desktop surface class — an Electron-class companion. Itssrc/carriesdesktop-engine.ts,window-manager.ts,tray-companion.ts,protocol-handler.ts,update-manager.ts,widget-engine.ts,notification-bridge.ts, andshortcut-manager.ts. This is a surface category beyond web and mobile; it is real code, and it should be understood as part of the shell composition.@oshun/shell-achievementsshipsachievement-engine.ts,achievement-definitions.ts,challenge-templates.ts, andsocial-accountability.ts— the engagement/accountability layer that rides on top of the cross-domain shell.@oshun/offlineis the real PWA/mobile offline substrate that the shell composition list also tends to drop. Itssrc/exports anOfflineSyncQueueclass (queue.ts), anOfflineCacheclass (cache.ts), and theSyncQueueItem,RetryPolicy, andConnectivityStatetypes (types.ts), plusstorage.ts,retry.ts, andconnectivity.ts. It is the substrate behind the web PWA's offline shell and the mobile app's sync behavior.
See the Shared Consumer Shell section of Product Surfaces and the Subsystem Glossary for the per-library detail.
The /domains/* Namespace Covers All Six Domains#
Customer web ships two coordinated namespaces per domain: a presentational
consumer hub at /<domain> and a power-user deep-tools namespace at
/domains/<domain>/*. An earlier reading of the docs claimed Nisaba and Metis
ship no parallel /domains/* namespace and instead bolt their deep tools onto
the consumer hub. The code disagrees, and the code is authoritative.
Under apps/oshun/web/src/app/domains/ there are four static domain
directories — arete, nyx, tara, veritas — plus a [domainId] dynamic
catch-all and a layout.tsx. The catch-all is what extends the namespace to
the remaining two domains:
apps/oshun/web/src/app/domains/[domainId]/page.tsxcarries aDOMAIN_METArecord with entries for bothnisabaandmetis, and gates rendering onisWebNavigableDomainId(domainId)— if a path is not a navigable domain it callsnotFound().apps/oshun/web/src/navigation/routes.ts:52-53definesWEB_DOMAIN_IDS = ['tara', 'veritas', 'nyx', 'arete', 'nisaba']andWEB_NAVIGABLE_DOMAIN_IDS = [...WEB_DOMAIN_IDS, 'metis']. Sonisabais in the static set andmetisis added to the navigable set — both pass theisWebNavigableDomainIdgate.- The shared
@oshun/navigationroute map atlibs/oshun/navigation/src/routes.ts(lines ~98–130) maps all six domains towebPath: '/domains/<domain>'withdeepLinkBase: 'oshun://<domain>', including explicitnisaba(/domains/nisaba,oshun://nisaba) andmetis(/domains/metis,oshun://metis) entries. Its tests assertgetDomainRouteDefinition('metis').webPath === '/domains/metis'.
The practical truth: /domains/nisaba and /domains/metis are reachable
web routes — served by the [domainId] catch-all rather than by static
directories, but reachable all the same. The dual-namespace pattern applies to
every domain, not four-plus-exceptions. The two namespaces are intentional
audience splits, not deprecation candidates for each other; see
Customer-Facing Domains and the companion
Product Surfaces for the per-domain room and deep-tool
inventory.
Why a catch-all instead of two more static dirs? The static directories (
arete,nyx,tara,veritas) host heavier bespoke client workspaces; Nisaba and Metis route through the data-driven[domainId]page, which reads fromDOMAIN_METAand the navigation route map. That keeps newly navigable domains additive — a domain becomes reachable the moment it is added toWEB_NAVIGABLE_DOMAIN_IDSandDOMAIN_META, without scaffolding a new page tree. The consumer hubs themselves are also real and rich:apps/oshun/web/src/app/nisaba/carries subdirs likecompare,daily,graph,lexicon,manuscript,notebook,scholar, andapps/oshun/web/src/app/metis/carriesassessment,byom,courses,ingest,lesson,session,tutor,upload.
One honest caveat on the live shell count#
The web experimentation layer carries a shellDomainCount (and a
shellNavigationDomainCount) in its FlagEvaluationContext
(apps/oshun/web/src/experimentation/feature-flags.ts). Per the 2026-06-23
triage, the live shell's shellDomainCount can drop from five to four
post-hydration when Nisaba evaluates as disconnected and Metis as planned.
So while the /domains/* routes for all six domains exist and resolve, the
runtime-active domain count surfaced to the shell can be smaller than the
route map suggests at a given moment. The routing capability is real; the live
activation is flag- and connection-gated. This is the architecture being honest
about the difference between "reachable route" and "active domain," not a
contradiction.
Reading Order from Here#
This page is deliberately the shallow map. The next layer of detail lives in:
- Surfaces — every client and operator surface in full: Product Surfaces.
- Domains — the six customer-facing domains, their adapters, contracts, and registry: Customer-Facing Domains.
- Substrates — the platform runtimes behind the stable adapters: the Sophia, Iris, Psyche, Lilith, Isis, and Aje pages.
- Foundations — the shared infrastructure libraries: Foundations.
- Cross-cutting flows — Communication Patterns and Data Architecture and Tenancy.