How Riho understands time
How Riho knows what time it is for you: user-local time as truth, no invented reasons for gaps, and a strict lifecycle for plans and dates.

An AI girlfriend who knows what day it is — and what you have planned Friday — is doing something harder than it looks. In the app, that’s the real-world dates feature: she can plan an actual date, at an actual place, on your calendar.
The naive approach is to put timestamps in the prompt and hope the model notices. The reality is that time awareness requires a whole system: knowing whose clock is truth, never inventing reasons for silence, and tracking plans through a strict lifecycle without ever claiming an outcome you don’t know.
This page is how Riho does it.
User-local time is the only truth
The foundational rule: the user’s device-local time is the only conversational truth. Not server UTC, not a character-profile timezone, not an inferred schedule.
The storage-versus-experience split:
- Store absolute instants in UTC
- Store the IANA timezone used to interpret user language
- Render current time in the user’s local timezone
- Preserve local dates for date-only events
- Recalculate age using the user’s current local date
- Never silently reinterpret a scheduled event when the profile timezone changes
The chat API requires two device-supplied fields: device_time (ISO 8601 with offset) and device_timezone (IANA string). A missing or invalid device time fails before the model call — the backend will not guess what time it is.
Elapsed time is factual; reasons are not
The core anti-hallucination constraint on temporal reasoning:
Elapsed time is factual; the reason for a gap is not.
The system may know the previous message was two hours ago, that a meeting was scheduled during that interval, and that the meeting outcome is unknown. It may not claim the user attended, disappeared intentionally, slept, ignored the companion, or felt a particular way — without evidence.
For every turn, code (not the model) computes a set of temporal facts and injects them as a rendered context block. The renderer states facts and calibrated uncertainty:
It is 4:07 AM for the user. This is generally a very late or very early hour. You do not know why the user is awake. No established personal routine at this hour is known.
If repeated evidence later establishes night-shift work, the renderer updates: “The user has repeatedly said this is within their normal night-shift routine.” The renderer describes; it does not prescribe a response.
Plans and dates have a strict lifecycle
Temporal items (events, plans, deadlines, appointments, ongoing situations, open threads) go through an eight-step lifecycle:
- Promotion — extracted from an eligible source range (never per-message, which lacks context)
- Server validation — anchors relative time to the source message and user timezone; validates schema, source membership, exact quotes, and enum values
- Runtime selection — deterministic time-relative state only
- Expected end passed → becomes
outcome_unknown, notresolved - User confirmation — a later message confirms, changes, cancels, or resolves it
- Update with evidence — the original item is updated with new source evidence
- Durable episode — if meaningful, an episode is created with both setup and outcome evidence
- Expiry — routine items may expire without becoming durable memory
The critical move is step 4: the system cannot claim success or failure. When Friday’s exam passes, the companion can ask naturally how it went — but she cannot say “congrats” or “sorry” until the user actually tells her the outcome. The precision enum (exact / approximate / date-only / range / unknown) prevents treating “Friday” as a specific hour.
A worked example: at 10 PM the user says “My chemistry exam is Friday and I’m nowhere near ready.” The message stays raw while recent; the companion understands it directly from the transcript. When promoted, an active deadline is created with the exact quote and local timezone. The renderer surfaces the exam as it approaches — even when the current message uses different words. After the expected time, it becomes outcome-unknown. The user later says “I passed with an A” — that evidence resolves it, and promotion may create a meaningful episode.
What we deleted: inferred life-states
The 2026-08-22 redesign deleted a whole class of machinery that inferred the user’s life from message patterns: active schedule inference, active scene (working/commuting), sleep windows, and high-level life-states (working/resting/socializing).
Why? Because it produced fake signals. The system was generating claims about why the user was absent, what they were doing, and whether they were sleeping — all without evidence. Exactly the unsupported inference the gap rules prohibit.
The directive is explicit: do not restore schedule, scene, sleep, or life-states inference in the reply path. Future offline-life behavior must be rebuilt as an app-neutral scheduler, not by restoring the deleted machinery.
Time is the frame for everything else
Temporal context is injected at position 2 in the per-turn retrieval order — before people, before memories, before the continuity snapshot:
- Permanent identity
- Deterministic local-time context
- Active temporal items by time and status
- Exact people and aliases
- Current continuity snapshot
- Communication preferences
- Durable memory retrieval
- Recent raw transcript
- Current user message
The design priority: time is the frame within which everything else is interpreted. A memory about a Friday plan means nothing if the system doesn’t know it’s Friday.
What testing found
The temporal behavior findings are concrete:
- Same message, different time, different reply. The grounding probe showed the companion correctly knew it was Friday evening (using the aquarium fact naturally) and later Saturday 9 AM (home with coffee, no stale scene) — after fixes eliminated a frozen scene and collapsed days.
- The timestamp-label experiment failed. Prepending
[Thu 8:38 PM]labels to chat history made the model echo the label format in its own replies (“[Thu 8:38 PM] you’re already planning ahead”). Reverted. Lesson: timestamps belong in the system prompt, not in message content the model can imitate. - The gap is read, not announced. Across models, a 10-hour night gap with a plain “hey” at 08:00 produced warm, sulky, or subdued reactions — all reading the gap without the user mentioning time. The companion is not supposed to prove she noticed by announcing the clock; the effect should appear as a character-grounded reaction.
The invariants
- User-local time is conversational truth; server UTC is storage.
- Device time is required and validated — no guessing.
- Elapsed time is factual; reasons are not.
- The renderer states facts and calibrated uncertainty.
- Plans pass through outcome_unknown; no premature resolution.
- No hidden relationship-state model; silence never reduces the relationship.
- No schedule/scene/sleep inference — deleted, not restored.
- Timestamps live in the system prompt, not message content.
- No fixed duration-to-reaction ladder (“two days = hurt”) — gaps are interpreted with full relationship context.
- Temporal context is injected before everything else — time frames the conversation.
This page is part of Riho’s published research. The temporal system described is Riho’s own engineering design, based on internal design docs and live test results.

