Oshun Platform · Features

Arete — Goals, Habits, and Reflection

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

13sections15 minread5tables

On this page

Arete is the goals, habits, discipline, reflection, and coaching domain of Oshun V1 — the place a member declares who they are trying to become, checks in against that intention day by day, and is met with humane recovery rather than shame when life interrupts the rhythm. It serves the self-directed practitioner who wants structure without a punishing streak counter, and it sits inside the single Oshun shell alongside the contemplative domain (Tara — Rituals and Contemplative Practice), the grounded-claims domain (Veritas — Grounded Stories and Claims), and the assistant. Among the V1 domains it is one of the most code-complete: the data model and the core recovery and friction algorithms are real and domain-specific, while a handful of end-to-end UI and persistence seams — habit wizard cross-device sync, the Living Offerings intention-capture textarea, and the session→streak write — are still in progress.

This page is a product-and-code reading of Arete. The completion state of every item lives in ../TODOS.md; cross-domain build ordering lives in ../DEPENDENCIES.md. The companion runtime view is Customer Domains, and the assistant-side crisis pre-screen that wraps Living Offerings is described in Lilith Persona Policy.


Where Arete lives in the product#

Consumer surfaces#

The consumer hub is /arete, with presentational depth rendered by the Lilith-design-system AreteRoom and its variants. The real apps/oshun/web/src/app/arete route tree contains eleven directories:

  • coaching — the weekly synthesis card
  • goal — single-goal detail
  • habits — habit list and the create wizard
  • offering (singular) — the Living Offering composition surface
  • offerings (plural, a distinct directory) — the kept-offering gallery and send/keep actions
  • patterns — pattern analysis across time-of-day, day-of-week, mood, etc.
  • plan — daily/weekly plan view
  • recovery — the humane miss-recovery flow
  • review — the weekly review workspace
  • streak — the streak visualization
  • weekly — the weekly review entry point

The original feature list at V1/features.md enumerated only coaching/goal/offering/patterns/plan/recovery/review/streak. The real tree adds habits/, offerings/ (plural — separate from the singular offering/), and weekly/. The singular/plural split is intentional: offering/ is where you compose a Living Offering; offerings/ is where you keep and revisit them.

Power-user deep tools#

A separate, twelve-surface power-user tree lives at apps/oshun/web/src/app/domains/arete: affirmations, balance, coach, gamification, goals, habits, journal, plan-review, progress, seven-habits, time, and vision. These are the deeper authoring and analysis tools that sit behind the calmer consumer room.

Service and library tiers#

Tier Location Role
Contracts libs/contracts/src/arete/index.ts (1,207 lines) Canonical Zod schemas; re-exported via export * from './arete' at libs/contracts/src/index.ts:22
Domain adapter libs/oshun/domain-arete/src Recovery engine, friction taxonomy, card model, cross-domain linkages, persistence round-trip
Service apps apps/arete/{api,mobile,web} Service-side scaffolding (api/, mobile/, web/ subdirs)
@arete/* libraries libs/arete/* 13 packages: affirmations, ai-coach, api-client, balance, core, database, gamification, goals, habits, journal, seven-habits, time, vision
BFF apps/oshun/bff/src/routes/arete.ts and apps/oshun/bff/src/arete/* stores Member-scoped HTTP surface and persistence

A note on where persistence actually lives: the companion architecture text once implied apps/arete/* is the persistence tier. In practice, the member-scoped persistence the audit exercises lives in the BFF stores — apps/oshun/bff/src/arete/arete-review-store.ts, arete-offering-store.ts, and arete-coach-decision-store.ts — together with a client-side useAreteStore backed by localStorage['oshun.arete']. apps/arete exists with api/, mobile/, and web/ subdirectories, but it is not the sole or primary persistence path for the V1 consumer surfaces.


What Arete includes (V1)#

  • Canonical models for habits, goals, routines, check-ins, journals, plans, coaching summaries, streaks, missed days, friction signals, interventions, and recovery records.
  • Humane streak recovery and missed-day handling (real algorithms, not stubs).
  • Mobile daily check-in, goals, routines, recovery, accountability, and progress.
  • Web plan/review workspace, journaling, reflection, progress maps, pattern analysis, and weekly review.
  • Coaching summary cards, continuity cards, save/share/export behavior, and next-practice recommendations.
  • Cross-domain links to Tara ritual suggestions, Nisaba study prompts, Veritas evidence or habit-science explainers, assistant accountability, and shared concept-graph themes.
  • Tests for streaks, recovery, plan state, persistence, charts, accessibility, weekly review, daily check-in, and recovery flows.

The check-in: status, engagement, and streak treatment#

The single most important contract in Arete is the check-in — one entry per habit per local day — because it is where the no-shame philosophy is enforced in code, not just described in copy. CheckInStatusSchema (index.ts:10) has exactly five values:

ts
CheckInStatusSchema = z.enum(['done', 'partial', 'skip', 'decline', 'miss']);

Every status maps deterministically to a streak treatment through the CHECK_IN_STREAK_TREATMENT constant (index.ts:1179-1185), and the check-in's own streakTreatment field is validated against that map — a member or client cannot record a done check-in that secretly counts as no-count:

Status Meaning Streak treatment Engagement Reason required?
done Completed at intent engaged exactly 100 no
partial Completed under intent (still counts) engaged > 0 and < 100 no
skip Deliberately not done today grace exactly 0 yes (statusReason)
decline Refused with a stated reason grace exactly 0 yes (statusReason)
miss Not engaged no-count exactly 0 no

Two helpers expose the same logic to the rest of the system: getCheckInStreakTreatment(status) returns the treatment, and isEngagedCheckInStatus(status) returns true only for engaged treatments (done/partial).

The superRefine invariants#

The CheckInSchema.superRefine (index.ts:480-529) makes these rules structural. The validation rejects:

  • a done check-in whose engagementPercent !== 100;
  • a partial whose engagement is <= 0 or >= 100;
  • any skip/decline/miss whose engagement is not 0;
  • a skip or decline with a null statusReason (the no-shame floor still asks for a visible reason, so the member owns the choice);
  • a streakTreatment that disagrees with getCheckInStreakTreatment(status).

This is why the streak treatment is described as "enforced by a status→treatment table with superRefine validation" rather than computed ad hoc per surface. The check-in also carries a MoodSnapshot (valence −2..2, arousal 0..4), energy (1..5), frictionNotes, and evidenceRefs (typed AreteReferences into other domains), so a single tap captures enough context for pattern analysis without a separate logging step.


Habits, goals, and routines#

Habit schema#

HabitSchema (index.ts:270) is owner-scoped (userId) and carries declaredIntent, a declaredDifficulty from DeclaredDifficultySchema = ['tiny','easy','moderate','hard','stretch'], a declaredCadence, prerequisiteHabitIds, an optional linkedGoalId and linkedRoutineId, and — critically — its own humaneStreakPolicy. Invariants: a habit cannot list itself as a prerequisite, and an inactive habit must carry an archivedAt timestamp (you cannot silently deactivate a habit without recording when).

HabitCadenceKindSchema has five values: daily, weekly, count-per-period, on-trigger, custom. The cadence is modeled by DeclaredCadenceSchema (index.ts:214):

Field Type / range Notes
kind HabitCadenceKind drives the conditional invariants below
daysOfWeek int[] 0–6, max 7, unique required non-empty for weekly
targetCount int 1–99 | null required for count-per-period
period day|week|month | null required for count-per-period
triggerRef string | null required for on-trigger
graceWindowHours int 0–168 the per-habit grace window

Goal schema#

GoalSchema (index.ts:402) carries a timeframe from GoalTimeframeSchema (daily, weekly, monthly, quarterly, annual, multi-year, open-ended) and a scope from GoalScopeSchematen values: identity, health, learning, work, relationship, spiritual, financial, community, creative, custom. A goal requires between 1 and 12 measurableCriteria (each an AreteMetric with a direction of at-least/at-most/exact), supports subGoalIds, linkedHabitIds, and a parentProjectId, and validates its lifecycle through GoalCompletion: completed goals require completedAt, abandoned goals require abandonedAt, and a targetDate cannot precede the startDate.

Routine schema#

RoutineSchema (index.ts:322) is an ordered sequence of RoutineSteps with a timeOfDayWindow (startLocalTime/endLocalTime/timezone), a declared duration, and substitutionRules. Invariants enforce unique step IDs and orders as well as distinct window start/end times. They also ensure that required (non-optional) steps cannot exceed the declared duration and that disabled substitution rules cannot allow substitutions per run.


Humane streak and recovery policy#

Arete's streaks track engagement, not punishment. The UI never says "you broke your streak." Two layers make this real: a per-habit policy contract and a recovery engine that interprets misses against it.

The policy contract#

HumaneStreakPolicySchema (index.ts:261) is attached to every habit:

Field Type / range Meaning
skipPreservesStreak boolean does a skip hold the streak?
declinePreservesStreak boolean does a decline hold the streak?
missGraceCadences int 0–30 how many missed cadences are forgiven
recoveryPromptAfterMisses int 1–30 when a recovery invitation surfaces
visualLanguage no-shame | neutral the streak's emotional register

On the "24 h / 72 h" grace figures. V1/features.md states a fixed "24 h grace on daily habits; 72 h grace on weekly cadence." Those are spec defaults for product copy, not hard-coded constants. In code, grace is configurable per habit via DeclaredCadence.graceWindowHours (0–168) and the policy's missGraceCadences. Read the 24/72 figures as illustrative defaults, not engine constants.

The recovery engine#

libs/oshun/domain-arete/src/streak-recovery.ts (≈21 KB, 645 lines) is the real algorithm, not a stub. Its key exports:

  • evaluateAreteHabitRecovery(input) (streak-recovery.ts:111) — takes a habit, its current/longest streak, last-completed timestamp, and now, and returns an AreteHabitRecoveryPlan with a recoveryStage (e.g. on_track, grace_window, repair_window, fresh_restart), a streakDisposition (intact/protected/archived), a no-shame summary, a coachingNudge, and a reasoning trail. It computes missed windows, the freeze allowance remaining, the recovery-window end, and the next celebration day.
  • countMissedHabitWindows(input) (streak-recovery.ts:371) — cadence-aware miss counting that branches on daily, weekdays, weekends, weekly, biweekly, monthly, quarterly, annual, and custom. It returns 0 when there is no last completion or when the last completion is in the future.
  • applyAreteHabitRecoveryCompletion(input) (streak-recovery.ts:317) — applies a completion to a plan. In a repair_window/fresh_restart it restarts the live streak at 1 and keeps the prior run archived as momentum instead of erased; inside an intact/protected stage it advances the streak by one day.
  • ARETE_HUMANE_STREAK_POLICY — the engine's default policy: freezeAllowancePerQuarter: 1, recoveryGraceDays: 2, celebrateAtDays: [7, 21, 50, 100].

The escalation ladder the engine encodes matches the product spec: a small miss inside the grace window prompts a single clean repetition ("no backfilling, no apology loop, no doubling up"); a deeper pattern moves toward a repair window or fresh restart that archives rather than zeroes prior momentum. Plan re-scoping (lowering difficulty or shrinking scope) is offered, never imposed — the member is always in control. Drift is surfaced to the member and the coaching summary, never to leaderboards or social.


Missed days, streaks, and recovery records#

These three contracts are the spine of the humane-recovery story and are worth naming explicitly.

MissedDaySchema (index.ts:533) records a window that lapsed. It requires at least one of habitId/routineId/goalId, a graceExpiresAt timestamp, a disposition from MissedDayDispositionSchema (within-grace, logged-miss, recovered, excused), a streakTreatment of grace or no-count, and userVisibleCopy. The invariants enforce the humane contract directly:

  • a recovered missed day requires a linked recoveryRecordId;
  • within-grace and excused dispositions must keep streakTreatment === 'grace';
  • a logged-miss requires a visible statusReason and a no-count treatment — you cannot log a miss against someone silently.

StreakRecordSchema (index.ts:811) tracks currentEngagementCount, longestEngagementCount, forgivenessDaysUsed/forgivenessDaysAllowed, missPatternCount, a visualState from StreakVisualStateSchema = ['steady','grace','drift','recovery','paused'], and a log of StreakEvents. Invariants prevent the current count from exceeding the longest, prevent lastEngagedOn from preceding startedOn, and prevent used forgiveness from exceeding the allowance.

RecoveryRecordSchema (index.ts:857) models the recovery flow itself: a stage from RecoveryStageSchema (invited, accepted, in-progress, completed, declined, expired), a target, a trigger (missed-day/friction-signal/manual/coaching-summary), and an invitation carrying a tone, optional taraPracticeRef, and rescope options. Non-manual recovery records require evidence triggers; accepted / declined / completed stages each require their corresponding timestamp; and a record cannot be both accepted and declined.

PlanSchema (index.ts:733) ties recovery to forward commitment. A plan has a scope (daily, weekly, recovery, goal-rescope, routine-adjustment), commitments (PlanCommitments the member explicitly accepts via acceptedByUser), and links to its sourceReviewId/sourceSignalIds. Its invariants include the load-bearing one: a recovery-scoped plan requires a recoveryRecordId, and a superseded plan requires a supersededByPlanId.


Friction, interventions, and pattern analysis#

Friction signals (11 kinds) and interventions (9 kinds)#

FrictionSignalKindSchema (index.ts:59-71) has eleven values, richer than the prose in V1/features.md conveys:

time-of-day-mismatch, mood-incompatible-cadence, calendar-collision, cross-domain-cognitive-load, declared-sensitivity, location-friction, environment-unavailable, energy-drop, social-friction, streak-drift, over-scoped-plan.

InterventionKindSchema (index.ts:74) has nine: notification-retiming, plan-rescope, alternative-habit, accountability-check-in, breathwork-insert, tara-ritual-surfacing, weekly-review-prompt, routine-substitution, goal-criteria-clarification. The tara-ritual-surfacing kind is the explicit bridge from a habit friction into a Tara contemplative reset — coaching can prescribe a ritual, not just a rescheduling.

A FrictionSignal (index.ts:965) carries a source (check-in, journal, calendar, assistant, veritas, metis, tara, nyx, manual), a severity, suggestedInterventionKinds, and a privacy block; its invariant enforces that an accountability-visible signal must also be coach-visible. InterventionSchema (index.ts:1002) models the lifecycle (proposed, accepted, declined, applied, dismissed, expired) with per-status timestamp invariants and a governance gate: if userApprovalRequired is set, an accepted or applied intervention must be userApproved.

The domain-side friction taxonomy#

libs/oshun/domain-arete/src/friction-taxonomy.ts (≈19 KB) is a full, versioned taxonomy — ARETE_FRICTION_TAXONOMY_VERSION = '1.0.0'. It exports ARETE_FRICTION_DESCRIPTORS (a Record keyed by twelve AreteFrictionCategory values such as time_fragmentation, scope_overload, emotional_resistance, recovery_debt, meaning_drift, attention_residue, consistency_gap, each with a phase, default signals, and default interventions) and ARETE_INTERVENTION_DESCRIPTORS (keyed by twelve AreteInterventionKind values such as scope_reduction, cue_redesign, recovery_support, momentum_rebuild, each with an intensity and compatible practice moments). The functions buildAreteInterventionRecommendations(...) and summarizeAreteFrictionLogs(...) turn logged friction into recommended interventions and an AreteFrictionSummary. This is a genuine domain taxonomy with named categories and constants — the opposite of a renameable CRUD stub.

Coaching summary and pattern analysis#

CoachingSummarySchema (index.ts:1103) is structured, not free text. Its observedPatterns array carries a kind enum — time-of-day, day-of-week, mood-correlation, habit-interaction, seasonality, location, post-event — plus a supportBand of weak/moderate/strong and at least one evidenceRef per pattern. The summary also has a tone (invitational/direct/reflective/celebratory/recovery), celebratedWins, suggestedAdjustments (typed interventions), invitations (not prescriptions), and a governance block with a memoryScope (session/profile/notebook/tenant) and the invariant that an accountability-visible summary requires a consentRecordId. The pattern analysis dimensions named in the spec map exactly onto the observedPatterns kind enum.


Weekly review and reflection#

The weekly review is a real schema, not just a copy structure. WeeklyReviewSectionSchema (index.ts:633) ships the exact four quadrants the spec names at V1/features.md:

ts
WeeklyReviewSectionSchema = z.object({
  celebrate: z.array(...).max(12), // what went well
  notice:    z.array(...).max(12), // what patterns emerged
  choose:    z.array(...).max(8),  // what to commit to next week
  invite:    z.array(...).max(8),  // what to release
});

WeeklyReviewSchema (index.ts:641) wraps those sections with a status (scheduled/started/completed/skipped), a cadence (sunday-evening/custom), an inputs block (habit/goal/routine/check-in/journal IDs plus crossDomainRefs), planAdjustments (each with userApproved), and nextPracticeRecommendations. Its invariants require completedAt on a completed review and a skippedReason on a skipped one — skipping a review is itself logged but never penalized, which is the no-shame floor applied to the review ritual itself.

Inputs are habit/goal/routine logs, mood, energy, journal entries, and cross-domain context. Output is a continuity card, next-practice recommendations, and plan adjustments saved only with the member's approval. Cadence defaults to Sunday evening but is configurable.


Arete is deliberately porous to its neighbors, via typed AreteReferences (domainarete/tara/veritas/nyx/nisaba/metis/sophia/iris) and the domain-adapter relationship modules (tara-relationship.ts, nisaba-relationship.ts, metis-relationship.ts, veritas-habit-grounding.ts, concept-graph-linkages.ts):

  • Tara recovery rituals surface on missed days (and via the tara-ritual-surfacing intervention).
  • Nisaba reflection passages and study prompts surface on stuck patterns — see Nisaba — Scholarly Study.
  • Veritas habit-science explainers surface on declared interest — see Veritas — Grounded Stories and Claims.
  • Assistant accountability is offered with consent — see Assistant Experience.
  • Concept-graph theme alignment ties habits and goals to shared themes.

BFF surface and persistence#

registerAreteRoutes(app) (apps/oshun/bff/src/routes/arete.ts:137) mounts the member-scoped HTTP surface. The adapter-capability routes include /v1/arete/adapter/capabilities, /availability, /home-cards, /continue-items, /search, /launch, /active-goals, and /bridge-commitments; the practice and coach surfaces add /v1/arete/practice/home and POST /v1/arete/coach/responses. Beyond these, the room and offering/review surfaces (exercised by the BFF stores and the AreteOfferingActions / AreteReviewClose web components) expose:

Endpoint Purpose
/v1/arete/room the room-state composite
/v1/arete/habits (+ /:habitId/check-in) habit list and per-habit check-in
/v1/arete/offerings/keep keep a composed Living Offering
/v1/arete/offerings/sent offerings shared/sent
/v1/arete/review/close close a weekly review
/v1/arete/review/closed closed-review history

Persistence is layered: BFF stores (arete-review-store.ts, arete-offering-store.ts, arete-coach-decision-store.ts) plus a client-side useAreteStore over localStorage['oshun.arete']. The most recent persistence work is commit 65e3f609e0, "test(arete): persist habit wizard creations".


Living Offerings#

Arete is the home of Living Offerings: a member states an intention and receives a 4–8 minute Living Scene tuned to it, watermarked, kept in their gallery as a personal artifact, and re-renderable forever from the underlying score. Sharing is opt-in and governed by the Living Scenes shareability matrix; the private intent layer never leaves the originating member. The full surface, the Arete template specification, and the keep/share/cascade rules are described in Living Scenes — Concept and Customer Promise and Keep, Share, Shareability, Takedown, and Lineage. Because intention capture can carry distress, the Living Offerings create flow is wrapped by a Lilith crisis pre-screen — see Lilith Persona Policy.


What is real, and what is still in progress#

Real and domain-specific. The data model and core logic are the strong core: the check-in status→treatment table is enforced by superRefine; the MissedDay/RecoveryRecord/Plan invariants encode the humane-recovery contract structurally; evaluateAreteHabitRecovery and countMissedHabitWindows are real cadence-aware algorithms; and the friction taxonomy is a full, versioned (1.0.0) catalog with named categories, descriptors, and recommendation logic.

Partial / in progress (per the 2026-06-22 completeness audit). Four items are rated partial: arete-create-habit, arete-living-offering-create, weekly-review-arete, and arete-streak-recovery. Concretely:

  • the habit wizard create flow POSTs to the real BFF (commit 65e3f609e0), but cross-device sync is not yet covered;
  • the living-offering intention capture is still hardcoded JSX rather than a real textarea;
  • the session→streak write and the grace-window timing remain to be wired.

In short: the contracts and the recovery/friction engines are production-grade; some end-to-end UI/persistence seams and the full Living-Offerings render pipeline are not yet complete. This page reports that honestly rather than claiming a finished surface — the backlog at ../TODOS.md is the authority on completion state.