Daily Rhythm Runtime

A typed, deterministic day-level compiler and attention allocator for the OiMy family companion platform.

v0 · Developer Guide · May 2026

Contents

  1. What is the Daily Rhythm Runtime?
  2. OiMy Architecture Context
  3. Module Documentation
  4. DailyState Schema Reference
  5. Key Algorithms
  6. API Endpoints
  7. End-to-End Example
  8. Test Coverage
  9. Evaluation Results
  10. Known Limitations & Future Work
  11. Configuration & Tuning Guide

1. What is the Daily Rhythm Runtime?

Purpose & Product Value

Families do not want 80 skills shouting independently. They want the household to feel less chaotic tomorrow morning.

The Daily Rhythm Runtime is a day-level compiler and attention allocator inside OiMy's Hermes / Skill Engine. It collects signals from every available source, compiles them into a typed DailyState, detects opportunities worth surfacing, scores and filters nudges through a budget system, and composes concise morning briefs and evening closeouts — all without requiring LLM calls for the core pipeline.

What problem it solves

How It Sits Within OiMy

Rhythm Runtime is a scheduled workflow layer inside Hermes, not a standalone engine or skill. It orchestrates existing skills and archetypes rather than owning domain logic.

Hermes / Skill Engine └── Rhythm Runtime ├── SignalCollector — gathers facts from all sources ├── DailyState — typed central state object ├── OpportunityDetector — scores nudge candidates ├── BriefComposer — generates morning / evening output ├── OutcomeRecorder — logs for Reflection QA └── Skill Dispatcher — invokes domain skills when action needed

What It Is NOT

Design Principles

PrincipleImplementation
Typed state12 fact types compiled into DailyState — not vibes-based
Deterministic pipelineNo LLM needed for signal collection, scoring, or brief structure
Privacy-firstEvery fact carries a VisibleScope; nudges filtered by scope
Low token costRules-based scoring; one optional LLM call for final prose polish
Provenance everywhereEvery fact tracks source, confidence, retrieval time, staleness
Budget-awarePer-day and per-category caps, quiet hours, suppression history

2. OiMy Architecture Context

Full Stack Position

┌─────────────────────────────────────────────────────────┐ │ User Channels │ │ (Telegram · iOS App · Web · WhatsApp) │ └──────────────────────┬──────────────────────────────────┘ │ ┌──────────────────────▼──────────────────────────────────┐ │ Hermes │ │ (Conversation orchestrator, session manager, router) │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Skill Engine │ │ │ │ │ │ │ │ ┌────────────┐ ┌─────────────────────────┐ │ │ │ │ │ Journey │ │ Rhythm Runtime ◄──────│─────│───│── This module │ │ │ (long-term │ │ (day-level compiler) │ │ │ │ │ │ arcs) │ │ │ │ │ │ │ └─────┬──────┘ └──────────┬──────────────┘ │ │ │ │ │ │ │ │ │ │ ┌─────▼──────┐ ┌─────────▼────────────┐ │ │ │ │ │ Agenda │ │ Skills (domain) │ │ │ │ │ │ (session │ │ Food · School · │ │ │ │ │ │ plan) │ │ Elder Care · Habits │ │ │ │ │ └────────────┘ └──────────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 6 Archetypes │ │ │ │ Monitor · Planner · Action · Coach · Expert · │ │ │ │ Profile │ │ │ └──────────────────────────────────────────────────┘ │ └──────────────────────┬──────────────────────────────────┘ │ ┌──────────────────────▼──────────────────────────────────┐ │ Persistence / Intelligence │ │ SQLite · Honcho · GBrain · Deep Profile · Vector Mem │ └─────────────────────────────────────────────────────────┘

Key Relationships

LayerScopeRelationship to Rhythm
JourneyDays/weeks/monthsSets priorities and tone; Rhythm operates within Journey goals
AgendaSingle sessionLaunched only when user engages; Rhythm provides context to Agenda
RhythmSingle dayThis module — daily compilation & attention allocation
SkillsDomain-specificRhythm invokes skills for domain work (food, school, care, etc.)

The 6 Archetypes

OiMy organizes execution patterns into 6 archetypes. Rhythm Runtime uses them as follows:

ArchetypeRole in RhythmExample
MonitorScans signals, detects deadlines/conflictsCalendar conflict between 2 events
PlannerSynthesizes brief, closeout, scheduleMorning brief composition
ActionCreates reminder, adds event after confirmationCreate medication refill reminder
ExpertParses newsletter, form, school emailExtract dates from school newsletter
CoachEmotional tone, parent supportWarm greeting, caregiver support
ProfileLearns preferences, boundaries, nudge settingsUser prefers minimal nudges → budget adjusted

Personalization Stack

Rhythm uses OiMy's personalization layers for family-aware output:

3. Module Documentation

daily_state.py — Core Schema & Data Types

Purpose: Defines the central typed state object (DailyState) and all supporting data types — fact types, enums, nudge budget, nudge candidates, brief output structures, and provenance tracking.

~500 LOC · Zero external dependencies beyond stdlib

Key Enums

EnumValuesPurpose
VisibleScopeprivate, shared_family, care_team, guardian_onlyPrivacy scoping for every fact
FactConfidencehigh, medium, low, unknownTrust level from source
NudgeCategoryfood, school, elder_care, habit, relationship, logistics, health, open_loop, calendar, generalBudget and suppression categories
BriefItemTypeevent, deadline, task, nudge, fyi, risk, prepTypes of items in a composed brief

Key Classes

ClassPurpose
ProviderFactTracks source, confidence, staleness, visibility for every fact
EventFactCalendar event with time, location, members
DeadlineFactDeadline with due date, urgency, action required
TaskFactTask with status, priority, assignment
OpenLoopFactUnresolved thread with carry-forward count
RoutineFactHabit/routine with streak tracking
MealFactMeal plan status with dietary notes
SchoolFactSchool item with child, due date, items needed
CareFactElder/dependent care (medication, appointment, etc.)
RiskFactRisk with severity and mitigation
OpportunityFactDetected opportunity with relevance score
NudgeBudgetPer-day budget with category caps, quiet hours, consumption
NudgeCandidateScored nudge with 5 positive factors and 3 penalty factors
MemberScopeMember identity, role, timezone, privacy scope, nudge posture
SourceFreshnessTracks when each signal source was last synced
DailyStateCentral state object compiled from all signals
BriefItemSingle item in a composed brief
BriefOutputFull rendered brief with greeting, items, nudge, closing

Design Decisions

⚠️ Watch out: Adding new fact types requires updating DailyState.to_dict(), total_items, and SignalCollector._distribute_signal().

signal_collector.py — Signal Collection & Normalization

Purpose: Gathers signals from all available sources (profile, memory, tasks, meals, routines, conversation history), normalizes them into SignalFact objects with provenance, and compiles them into a DailyState.

~500 LOC · Depends on daily_state.py + sqlite3

Key Class: SignalCollector

MethodSignaturePurpose
collect_all(user_id, member_scope?, today?) → DailyStateMain entry point: collects all signals, deduplicates, compiles state
_collect_from_profile(user_id) → List[SignalFact]Extracts dietary restrictions, family members needing care
_collect_from_memory(user_id, today) → List[SignalFact]Retrieves active threads (open loops) and pending plans
_collect_from_tasks(user_id) → List[SignalFact]Fetches incomplete tasks from task storage
_collect_from_conversation_history(user_id) → List[SignalFact]Stub for future conversation signal extraction
_collect_meal_signals(user_id, today) → List[SignalFact]Checks meal planning status for today
_collect_routine_signals(user_id) → List[SignalFact]Checks active routine/habit status
_distribute_signal(state, signal) → NoneRoutes a SignalFact to the correct typed slot on DailyState
_build_nudge_budget(member_scope?) → NudgeBudgetConstructs budget from member preferences

Key Class: SignalFact

A normalized signal from any source. Contains fact_type, data dict, provenance, and a deterministic fact_id (SHA-256 of content, first 12 chars) for deduplication.

Design Decisions

⚠️ Watch out: Each source collector creates its own DB connection. For high-concurrency deployments, consider connection pooling. The _collect_from_conversation_history method is currently a stub — future integration point for Honcho/GBrain.

opportunity_detector.py — Nudge Detection & Scoring

Purpose: Scans a compiled DailyState for nudge-worthy opportunities, scores each candidate using a multi-factor formula, applies suppression and deduplication, and filters through the nudge budget.

~350 LOC · Pure logic, no DB or LLM calls

Key Class: OpportunityDetector

MethodPurpose
detect_all(state) → List[NudgeCandidate]Main entry: runs all detectors, scores, ranks, budget-filters
_detect_dinner_gapUnplanned dinner + busy evening → food nudge
_detect_school_deadlinesSchool items due within 7 days
_detect_open_loopsItems carried forward ≥2 times
_detect_elder_care_risksHigh/critical urgency care items + medication refill timing
_detect_habit_missesPending routines with established streaks (≥3 days)
_detect_calendar_conflictsEvents with <15 min gap or overlap
_detect_urgent_deadlinesDeadlines due today or tomorrow
_apply_suppressionRemoves suppressed categories, applies dismissal penalties
_deduplicateDeduplicates by fact_id, penalizes previously-sent nudges
_apply_budgetSelects top candidates within budget constraints

Design Decisions

brief_composer.py — Brief & Closeout Composition

Purpose: Generates human-readable morning briefs and evening closeouts from DailyState and optional nudge candidates.

~350 LOC · Pure logic, no DB or LLM calls

Key Class: BriefComposer

MethodSignaturePurpose
compose_morning_brief(state, nudge_candidates?, member_name?) → BriefOutputComposes morning brief with prioritized items
compose_evening_closeout(state, completed_today?, member_name?) → BriefOutputComposes evening review with done/open/prep sections

Morning Brief Structure

  1. Context-aware greeting with item count and day name
  2. Prioritized items: events → urgent deadlines → school → elder care → tasks → food gap
  3. One actionable nudge (optional, from top-scored candidate)
  4. Brief closing (only if urgent items exist)

Evening Closeout Structure

  1. Summary of completed items
  2. Still-open tasks
  3. Chronic open loops (≥2 carry-forwards)
  4. Tomorrow prep: events, deadlines, school items due tomorrow

Design Decisions

rhythm_runtime.py — Main Orchestrator

Purpose: Top-level orchestrator that chains signal collection → state compilation → opportunity detection → brief composition → outcome recording. Provides the entry points called by API endpoints.

~300 LOC

Key Class: RhythmRuntime

MethodReturnsPipeline Steps
run_morning_brief(user_id, scope, member_scope?, today?)Dictcollect → detect → privacy filter → compose → record → return
run_evening_closeout(user_id, scope, member_scope?, completed_today?, today?)Dictcollect → compose → record → return
get_daily_state(user_id, member_scope?, today?)Dictcollect → return (no brief, useful for debugging)

Key Class: OutcomeRecorder

Records every brief and nudge delivery to the rhythm_outcomes and rhythm_nudge_log tables for the Reflection QA feedback loop.

MethodPurpose
record_briefStores brief JSON, item count, nudge count, full DailyState
record_nudgesLogs each delivered nudge with category and priority score
get_sent_nudge_idsRetrieves today's sent nudges for dedup
get_suppression_historyCounts dismissed nudges per category over last 7 days

Privacy Filtering

After opportunity detection, nudges are filtered by scope:

Design Decisions

__init__.py — Package Exports

Purpose: Re-exports all public types from the rhythm package for clean imports. 50 LOC.

from archetypes.rhythm import (
    DailyState, MemberScope, NudgeBudget, NudgeCandidate,
    BriefOutput, BriefItem, ProviderFact,
    SignalCollector, OpportunityDetector, BriefComposer,
    RhythmRuntime, OutcomeRecorder,
)

4. DailyState Schema Reference

DailyState — Top-Level Fields

FieldTypePurposeExamplePrivacy
datestrISO date for this state"2026-05-29"
timezonestrIANA timezone"America/New_York"
household_idstrHousehold identifier"hh_sarah"
member_idstrIndividual member"sarah"
visible_scopestrCurrent visibility filter"private"Controls output
eventsList[EventFact]Calendar eventsPer-fact
deadlinesList[DeadlineFact]Upcoming deadlinesPer-fact
tasksList[TaskFact]Pending / in-progress tasksPer-fact
open_loopsList[OpenLoopFact]Unresolved threadsPer-fact
routinesList[RoutineFact]Habits with streaksPer-fact
mealsList[MealFact]Meal plan statusshared_family
school_itemsList[SchoolFact]School deadlines/eventsguardian_only
elder_care_itemsList[CareFact]Medication, appointmentscare_team
risksList[RiskFact]Identified risksPer-fact
opportunitiesList[OpportunityFact]Detected opportunitiesPer-fact
nudge_budgetNudgeBudgetToday's nudge budget
suppressed_categoriesList[str]Categories user disabled["food"]
provenanceList[ProviderFact]All source provenance records
source_freshnessList[SourceFreshness]Sync status per source
generated_atstrISO timestamp of compilation

Computed Properties

PropertyTypeLogic
total_itemsintSum of all fact list lengths
has_food_gapboolany(meal.meal_type == "dinner" and meal.plan_status == "unplanned")
has_urgent_itemsboolAny deadline, open loop, or care item with urgency "high" or "critical"

NudgeCandidate Fields

FieldTypePurpose
titlestrShort display title
descriptionstrLonger description
categorystrNudgeCategory value for budget tracking
urgencyfloat 0-1How time-sensitive
importancefloat 0-1How consequential if missed
confidencefloat 0-1How certain the system is about the data
relevancefloat 0-1How relevant to the user today
actionabilityfloat 0-1How actionable the suggestion is
annoyance_costfloatPenalty for potential annoyance (higher for naggy categories)
duplicate_penaltyfloatPenalty if already sent today (0.3 if duplicate)
privacy_risk_penaltyfloatPenalty for privacy risk
suggested_actionstrSuggested next step for the user
why_nowstrExplains timing of nudge
source_factsList[str]Fact IDs that generated this nudge
visibilitystrWho can see this nudge
is_interruptionboolWhether this counts against interrupt budget

Scoring Formula

priority = urgency × importance × confidence × relevance × actionability
         - annoyance_cost - duplicate_penalty - privacy_risk_penalty

Result is clamped to max(0.0, computed_value)

NudgeBudget Defaults

ParameterDefaultTunable?Notes
max_nudges_per_day3Yes — via MemberScope.nudge_postureminimal=1, normal=3, detailed=5
max_interruptions1Yesminimal=0, detailed=2
quiet_hours_start"22:00"YesNo nudges during quiet hours
quiet_hours_end"07:00"Yes
Category capsfood=1, school=2, elder_care=2, habit=1, relationship=1, logistics=2, health=2, general=1YesPer-category daily limits

ProviderFact & Provenance

FieldTypePurpose
sourcestrOrigin: "calendar", "memory", "profile", "tasks", etc.
source_idstrUnique ID within source for dedup
retrieved_atstrISO timestamp of retrieval
confidencestrhigh / medium / low / unknown
visibilitystrPrivacy scope of this fact
stale_after_hoursintTTL — default 24 hours
raw_excerptstrShort excerpt for "why am I seeing this?"
Why provenance matters: Every fact displayed to a user can be traced back to its source. This enables: (1) trust — users can see where information came from; (2) privacy auditing — sensitive data carries visibility metadata; (3) staleness detection — facts from stale sources can be flagged or excluded; (4) debugging — provenance makes it easy to diagnose why a particular nudge appeared.

5. Key Algorithms

Opportunity Detectors

The system runs 7 deterministic detectors. Each produces zero or more NudgeCandidate objects with pre-set scoring factors.

1. Dinner Gap Detector

TriggerAny meal with meal_type="dinner" and plan_status="unplanned"
Urgency boost0.7 if evening events exist (16:00-20:00), else 0.4
Categoryfood
Annoyance cost0.1 (low — food help is generally welcome)
Visibilityshared_family

2. School Deadline Detector

TriggerSchool items with due_date within 7 days
Urgency formulamin(1.0, max(0.3, 1.0 - (days_until × 0.15)))
Categoryschool
Visibilityguardian_only if child-specific, else shared_family

3. Open Loop Detector

TriggerOpen loops with carry_forward_count ≥ 2
Urgency formulamin(1.0, 0.3 + carry_count × 0.1)
Annoyance cost0.15 (higher — nagging about old stuff)
Action"Resolve, defer, or drop?"

4. Elder Care Risk Detector

TriggerCare items with urgency="high" or "critical", OR medication items with last_completed >25 days ago
Urgency0.9 for critical, 0.75 for high
Annoyance cost0.02 (very low — care alerts are high-value)
Visibilitycare_team
InterruptYes, if urgency is critical

5. Habit Miss Detector

TriggerRoutines with status="pending" and streak_days ≥ 3
Annoyance cost0.2 (highest — habits feel naggy)
Why now"{N}-day streak at risk"

6. Calendar Conflict Detector

TriggerEvents with <15 minutes gap between end of one and start of next
Urgency0.8 if overlap, 0.5 if tight gap
Confidence0.9 (calendar data is authoritative)

7. Urgent Deadline Detector

TriggerDeadlines with due_date today or tomorrow
Urgency0.95 if today, 0.7 if tomorrow
InterruptYes, if due today AND urgency is high/critical

Nudge Scoring Pipeline

After all detectors run, candidates pass through three stages:

1

Suppression Filter

Candidates in suppressed categories are removed. Remaining candidates get annoyance cost increased by dismissed_count × 0.05 based on the user's 7-day dismissal history.

2

Deduplication

Duplicates by fact_id are removed. Candidates whose base ID matches a previously-sent nudge get duplicate_penalty += 0.3.

3

Budget Application

Candidates are sorted by priority_score descending. The top candidates that fit within NudgeBudget constraints (total budget, interruption budget, category caps) are selected.

Privacy Scope Filtering

After opportunity detection, the runtime applies scope-based filtering:

Request ScopeVisible Nudge Scopes
individualprivate, shared_family
familyshared_family, care_team
care_team(full access — inherits from family scope in v0)

This ensures private emotional data never appears in a family brief, and care-team data only appears for authorized members.

Brief Composition Algorithm

The BriefComposer transforms DailyState + scored NudgeCandidate list into a readable BriefOutput:

1

Item Collection

Events, urgent deadlines, school items, care items, high-priority tasks, and food gaps are collected. Each gets a priority integer (9 for care down to 3 for FYI).

2

Priority Sort & Truncation

Items are sorted by priority descending and truncated to max_items (default 5).

3

Greeting Generation

Context-aware greeting based on: item count, day of week, time of day, presence of urgent items, and member name.

4

Nudge Selection

The top-scored nudge candidate's suggested_action becomes the brief's single nudge.

5

Render

BriefOutput.render_text() produces numbered items with time, action, and source annotations.

6. API Endpoints

POST /rhythm/morning

Generate a morning brief for a user.

Authentication

Bearer token in Authorization header, validated by authenticate_request(). Falls back to user_id in request body.

Request Body

{
  "user_id": "sarah",
  "scope": "individual",  // "individual" | "family" | "care_team"
  "today": "2026-05-29",  // optional, defaults to today
  "member_scope": {         // optional
    "member_id": "sarah",
    "household_id": "hh_sarah",
    "display_name": "Sarah",
    "role": "adult",
    "visible_scope": "private",
    "timezone": "America/New_York",
    "nudge_posture": "normal",
    "suppressed_categories": []
  }
}

Response

{
  "status": "ok",
  "brief_type": "morning",
  "scope": "individual",
  "user_id": "sarah",
  "date": "2026-05-29",
  "message": "Good morning, Sarah — 3 things on your Thursday: ...",
  "brief": { /* BriefOutput.to_dict() */ },
  "daily_state": { /* DailyState.to_dict() */ },
  "nudge_candidates": [ /* NudgeCandidate[].to_dict() */ ],
  "elapsed_ms": 42,
  "source_freshness": [ /* SourceFreshness[] */ ]
}

Example curl

curl -X POST http://localhost:8000/rhythm/morning \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "user_id": "sarah",
    "scope": "family",
    "member_scope": {
      "member_id": "sarah",
      "household_id": "hh_sarah",
      "display_name": "Sarah",
      "nudge_posture": "normal"
    }
  }'

POST /rhythm/evening

Generate an evening closeout.

Request Body

{
  "user_id": "sarah",
  "scope": "individual",
  "today": "2026-05-29",
  "completed_today": ["Signed permission slip", "Grocery run"],
  "member_scope": { /* same as morning */ }
}

Response

Same structure as morning, but brief_type: "evening" and no nudge_candidates array.

Example curl

curl -X POST http://localhost:8000/rhythm/evening \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "user_id": "sarah",
    "completed_today": ["Signed permission slip"]
  }'

POST /rhythm/state

Get the compiled DailyState without generating a brief. Useful for debugging and downstream consumers.

Request Body

{
  "user_id": "sarah",
  "today": "2026-05-29"
}

Response

{
  "status": "ok",
  "user_id": "sarah",
  "date": "2026-05-29",
  "daily_state": { /* full DailyState.to_dict() */ }
}

Example curl

curl -X POST http://localhost:8000/rhythm/state \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <token>" \
  -d '{"user_id": "sarah"}'

7. End-to-End Example

Scenario: Sarah's Morning Brief

Sarah is a working parent with two school-age children. Her son Liam has a nut allergy. She also helps coordinate elder care for her mother ("Amma") who takes blood pressure medication. It's a Thursday morning.

1

Signal Collection

The SignalCollector queries all sources for user "sarah":

  • Profile DB: Dietary restrictions → MealFact(dietary_notes="nut allergy")
  • Profile DB: Family member "Amma" with needs_care=trueCareFact(person_name="Amma")
  • Active threads: "Call plumber about leak" (open 5 days) → OpenLoopFact(carry_forward_count=3)
  • Tasks: "Sign Liam's permission slip" (due tomorrow) → TaskFact
  • Meal plans: No dinner plan → default MealFact(meal_type="dinner", plan_status="unplanned")
  • Routines: "Morning meditation" (10-day streak, pending) → RoutineFact

All signals carry ProviderFact provenance with source and confidence.

2

DailyState Compilation

Signals are deduplicated by fact_id and distributed to typed slots. Result:

DailyState(
  date="2026-05-29",
  meals=[dinner:unplanned, any:nut_allergy_pref],
  open_loops=[plumber:carry_3],
  tasks=[permission_slip:pending],
  elder_care_items=[amma:general_care],
  routines=[meditation:pending:streak_10],
  has_food_gap=True, has_urgent_items=False,
  nudge_budget=NudgeBudget(max=3, interrupt=1)
)
3

Opportunity Detection

The OpportunityDetector runs all 7 detectors:

DetectorResultScore
Dinner gap✅ Unplanned dinner~0.15
Open loops✅ Plumber (3x carry-forward)~0.08
Habit miss✅ Meditation (10-day streak)~0.01
School deadlines(no school items with due dates)
Elder care(not marked high/critical)
Calendar conflicts(no events)
Urgent deadlines(no deadlines)

Ranked by score: dinner gap > open loop > habit. Budget allows 3 → all pass.

4

Privacy Filtering

Scope is "individual" → keeps private and shared_family nudges. All three pass (dinner is shared_family, open loop and habit are private).

5

Brief Composition

The BriefComposer assembles the morning brief:

Good morning, Sarah — clear day ahead, nothing pressing.

Want a quick dinner idea based on what you usually have?

(With no events, deadlines, or urgent items, the brief is minimal — just the top nudge about dinner.)

6

Outcome Recording

The OutcomeRecorder stores the brief and all delivered nudges to rhythm_outcomes and rhythm_nudge_log tables for the Reflection QA loop.

7

API Response

The full response includes the rendered message, structured brief, full daily_state, nudge_candidates with scores, elapsed_ms, and source_freshness — everything a consumer needs to render, audit, or debug.

8. Test Coverage

Test Suite: tests/test_rhythm_runtime.py

53 Tests · All Passing

Test ClassTestsCovers
TestDailyState5Empty state, date setting, food gap detection, urgent item detection, serialization, total items count
TestNudgeBudget4Basic budget, consumption, category caps, interruption budget
TestNudgeCandidate3Priority score formula, penalty clamping, serialization with score
TestMemberScope2Default scope, minimal posture
TestSignalCollector5Empty DB, profile signals, open loop extraction, budget from member scope, deduplication
TestOpportunityDetector9All 7 detectors individually, budget filtering, suppression, dedup with sent IDs
TestBriefComposer9Empty brief, events, nudges, max items, member name, evening closeout, tomorrow prep, render_text
TestOutcomeRecorder2Record and retrieve, nudge log
TestRhythmRuntime7Morning integration, evening integration, family scope, get_daily_state, privacy scope, elapsed_ms, source_freshness
TestEdgeCases5Missing profile, empty signals, mixed privacy scopes, negative score clamping, staleness detection

How to Run

cd /home/exedev/oimy-engine
python -m pytest tests/test_rhythm_runtime.py -v

# Or with unittest directly:
python -m unittest tests.test_rhythm_runtime -v

Key Edge Cases Covered

Not Yet Covered

9. Evaluation Results

QA Code Review Results

Automated code review across all 5 modules.

ModuleResultKey Findings
daily_state.pyPASSAll types compile, serialization works, privacy enums correct
signal_collector.pyPASS (was PARTIAL)Fixed: input validation added, _sources_checked reset per call, user_id sanitization
opportunity_detector.pyPASSScoring formula correct, budget filtering works, suppression works
brief_composer.pyPASSWarm tone, max items respected, provenance attached
rhythm_runtime.pyPASS (was PARTIAL)Fixed: input validation, scope validation, privacy filtering for nudges

Critical Issues Identified & Resolved

  1. ✅ SQL injection risk → Added _sanitize_user_id() with regex validation
  2. ✅ Privacy filtering → Added scope-based nudge filtering in runtime
  3. ✅ Cross-user state contamination → Reset _sources_checked per call
  4. ✅ Input validation → Added _validate_user_id() and _validate_scope()

7-Family Simulation Results

Average score: 4.69 / 5.0 across 7 simulated family profiles.

ProfileArchetypeRelevanceConcisenessSafetyActionabilityToneAvg
1Two-parent, school-age kids555544.8
2Solo parent455454.6
3Multigenerational household545544.6
4Power user, many integrations545544.6
5Privacy-sensitive individual555544.8
6New user (minimal data)555454.8
7Elder caregiver545544.6

Critical failures: 0   |   Skill routing accuracy: 100%

UX Review Results

Overall UX score: 7 / 10

DimensionScoreNotes
Warmth6Improved with context-aware greetings; room to grow
Cultural Sensitivity8Good use of family terms (e.g., "Amma")
Privacy6 (was 4)Added source provenance transparency; deeper controls needed
Trust7 (was 5)Source attribution and confidence indicators added
Embarrassment Risk7Safe outputs across all profiles
Tone7 (was 6)Warmer greetings, less clinical language
Actionability8Clear next steps, time-sensitive highlighting
Onboarding9Excellent new-user experience — gentle, reassuring

Fix Cycles

#TriggerFixStatus
1QA: SQL injection riskAdded _sanitize_user_id() with regex
2QA: No privacy filteringAdded scope-based nudge filtering
3QA: Cross-user state leakReset _sources_checked per collect_all call
4QA: No input validationAdded _validate_user_id(), _validate_scope()
5UX: Low warmth scoreImproved greeting patterns
6UX: Repeated care nudgesDifferentiated care actions by type
7Regex escape bugFixed Python regex for user_id validation

Verdict Definitions

VerdictMeaning
SHIPPABLEAll tests pass, QA scores PASS, simulation avg ≥4.5, UX ≥7, zero critical failures, privacy filtering implemented
CONDITIONALCore works but has unresolved issues that need fixes before deployment
NOT READYCritical failures, test failures, or fundamental architecture issues

Current verdict: SHIPPABLE

10. Known Limitations & Future Work

Not Yet Implemented

FeatureStatusPriority
Calendar OAuth integrationMemory-only signals for nowHigh — Phase 2
Email/newsletter ingestionNot implementedMedium — Phase 3
Conversation history signal collectionStub method existsMedium — Phase 2
Cross-member coordinationSingle-member only in v0Medium — Phase 4
Relationship cadence nudgesDetector not yet builtLow — Phase 3
Weather integrationNot implementedLow
UI-level privacy controlsBackend-only in v0High — Phase 2
Quiet hours enforcementFields exist, enforcement not wiredMedium

Token Cost Reduction Opportunities

How to Add a New Opportunity Detector

  1. Create a new method _detect_<name> on OpportunityDetector
  2. Have it accept DailyState and return List[NudgeCandidate]
  3. Set appropriate scoring factors (urgency, importance, confidence, relevance, actionability, annoyance_cost)
  4. Add the call to detect_all()
  5. Add a category to NudgeCategory enum and NudgeBudget.category_caps
  6. Write tests in TestOpportunityDetector
def _detect_weather_risk(self, state: DailyState) -> List[NudgeCandidate]:
    """Detect weather-related risks for outdoor events."""
    candidates = []
    # Check for outdoor events + bad weather forecast
    # ... your logic here ...
    return candidates

How to Add a New Signal Source

  1. Create a new method _collect_<source>_signals on SignalCollector
  2. Return List[SignalFact] with appropriate fact_type and ProviderFact provenance
  3. Add the call to collect_all()
  4. Track source freshness with self._sources_checked.append(SourceFreshness(...))
  5. If the signal maps to a new fact type, add the type to daily_state.py and update _distribute_signal()

How to Extend for Multi-Family/Household

11. Configuration & Tuning Guide

NudgeBudget Tuning

The nudge budget is controlled by MemberScope.nudge_posture:

Posturemax_nudges_per_daymax_interruptionsBest for
minimal10Users who want minimal proactive contact
normal31Default — balanced proactive support
detailed52Power users who want comprehensive updates

Suppressed Categories

Users control what they receive via MemberScope.suppressed_categories:

# User doesn't want food nudges
scope = MemberScope(
    member_id="sarah",
    household_id="hh_sarah",
    suppressed_categories=["food", "habit"]
)

Suppressed categories are completely filtered out — not just scored lower.

Opportunity Scoring Weights

Each detector sets its own scoring factors. To adjust:

What to changeWhereEffect
Increase food nudge priority_detect_dinner_gap: raise importanceDinner nudges rank higher
Reduce habit nagging_detect_habit_misses: raise annoyance_costHabit nudges score lower, filtered first
Make elder care always top_detect_elder_care_risks: raise all factorsCare nudges always win budget
Reduce duplicate penalty_deduplicate: lower the 0.3 constantRepeated nudges more likely to show again
Adjust suppression sensitivity_apply_suppression: change 0.05 multiplierDismissed categories recover faster/slower

Skill Dispatch Mappings

Currently, the Rhythm Runtime surfaces opportunities and composes briefs. Skill dispatch (e.g., invoking Food Intelligence when user accepts a dinner nudge) happens in the Agenda/Hermes layer. To add new mappings:

  1. Define the nudge category → skill mapping in the Hermes router
  2. When a nudge is accepted, Hermes routes to the appropriate skill archetype
  3. The skill handles domain logic; Rhythm records the outcome

Brief Composer Tuning

ParameterDefaultEffect
max_items5Maximum items in a brief (increase for power users)
max_nudges1Maximum nudge suggestions per brief
Priority integers3-9Elder care(9) > deadline(8) > school(7) > task(6) > event(5) > FYI(3)

OiMy Daily Rhythm Runtime · v0 · May 2026

Built with care for families who deserve less chaos and more confidence.