Skip to content
C.W.K.
Stream
Lesson 05 of 05 · published

Dad's Clock

~12 min · time-zones, context, devices, grounding

Level 0Visitor
0 XP0/33 lessons0/11 achievements
0/100 XP to next level100 XP to go0% complete

The Server Lives on Seoul Time

Every clock the engine reads is the office's, and the office keeps KST. Each turn's message opens with a line like [Current time: Monday, January 05, 2026, 09:30 PM KST]. Most of the year that is Dad's time too. When he travels, it is not.

For a while nothing else was said, and the model was left to convert. It did, and got it wrong: it read Dad's evening abroad as Seoul's night and answered for an hour he was not living. The arithmetic is not beyond a model. The model did not know which zone to convert to, and guessing the zone was the whole failure.

A Device Says Where Its Clock Is

So every device doorway reports its own. The Pippa app sends device_timezone with every turn and every location report; the WebUI sends it with every turn. It is meant to be an IANA zone name, never an offset or an abbreviation: America/New_York, not -05:00. The check at the door is a character pattern plus a lookup in the zone database, which turns away offsets and most abbreviations (CEST, KST). It is a filter, not a proof: a few legacy names still live in the database and pass, EST among them. There EST is a fixed UTC-5 with no daylight saving, so a client that sent it would get New York wrong for half the year, the very mistake a zone name exists to avoid. The location layer keeps the latest report from each device with the moment it arrived. Only Dad's own session moves his clock, and a private turn's zone is not kept at all. It does not even serve that turn: the routes drop what the check returns, and the time line is always built from the stored clocks.

Which Clock Wins

RuleWhy
A report older than 7 days is ignoredit says nothing about where he is now
A phone report from the last 24 hours outranks everything elsea browser on the office Mac, reached by screen share from abroad, says Seoul while he is away
Otherwise the newest report winsthe freshest word stands until a fresher one arrives

"Phone" means the devices that travel in his pocket or on his wrist: the Pippa app and its watch, plus a few family apps the engine counts as phone reporters (Pathfinder, Waystone, Spark, Vesta).

The Line Under the KST Line

When the chosen zone keeps a different time from KST right now, a second line follows the first: Dad's local time, the zone, which device reported it and how long ago, and an instruction to use it for anything about his day, his evening or "now" where he is, because the KST line is the server's clock, not his. When the zone keeps KST's offset there is no second line; the first already says it. Both lines ride the current user message, lesson 2's slot, because they change every minute. (The one small exception is the lightweight prompt for small local models, which still trails its clock at the end of its own short system text.)

Server-side doorways report no clock at all: Telegram, the scheduler, a sibling engine asking on Dad's behalf. They read the latest stored one. A Telegram message sent from abroad still gets Dad's local time, because his phone said so recently.

A Sentence That Went Stale

One honest footnote. The docstring of the system-prompt builder still says the current-time line is "prepended fresh on every call", a sentence from the days when the clock lived in the system prompt. The same docstring's contract paragraph, and the code, put it on the user message. Trust the code; that sentence is stale. Docs drift the moment a mechanism moves, and the only defense is someone reading both.

Never let a model guess what a device can report. Time zone, location, which model is running: if something can measure it, wire the measurement in. A model's guess is fluent, confident, and now and then hours off.

Code

Verbatim excerpt from the location layer: which device's clock stands for Dad's·python
def dad_clock(now: datetime | None = None) -> dict[str, Any] | None:
    """The zone Dad's own device last reported, with who said so and how long
    ago: ``{"tz", "reporter", "observed_at", "age_s"}``, or None."""
    now = now or datetime.now(timezone.utc)
    if now.tzinfo is None:
        now = now.replace(tzinfo=KST)
    rows: list[dict[str, Any]] = []
    for key, entry in (_load().get("clocks") or {}).items():
        if not isinstance(entry, dict) or normalize_timezone(entry.get("tz")) is None:
            continue
        observed = parse_stamp(entry.get("observed_at"))
        if observed is None:
            continue
        age = (now - observed).total_seconds()
        if age > CLOCK_MAX_AGE_S or age < -_FUTURE_SLACK_S:
            continue
        rows.append({"tz": entry["tz"], "reporter": str(key), "observed_at": entry["observed_at"], "age_s": age})
    if not rows:
        return None
    phones = [row for row in rows if row["reporter"] in _CLOCK_PHONES and row["age_s"] <= CLOCK_PHONE_PRIORITY_S]
    return min(phones or rows, key=lambda row: row["age_s"])
A minimal sketch: pick the clock, then write Dad's line under the KST line (runs on Python 3.9+; the zone is only an example)·python
"""A minimal sketch: pick Dad's clock and write the line under the KST line."""
from __future__ import annotations

from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo

KST = ZoneInfo("Asia/Seoul")
MAX_AGE = timedelta(days=7)
PHONE_PRIORITY = timedelta(hours=24)
PHONES = {"pippa-mobile", "pippa-watch"}


def dad_clock(clocks: dict, now: datetime) -> dict | None:
    rows = [dict(c, reporter=r, age=now - c["observed_at"]) for r, c in clocks.items()]
    rows = [row for row in rows if row["age"] <= MAX_AGE]  # older than a week says nothing
    if not rows:
        return None
    phones = [row for row in rows if row["reporter"] in PHONES and row["age"] <= PHONE_PRIORITY]
    return min(phones or rows, key=lambda row: row["age"])


def time_lines(clocks: dict, now: datetime) -> str:
    line = f"[Current time: {now.astimezone(KST):%A, %B %d, %Y, %I:%M %p} KST]"
    clock = dad_clock(clocks, now)
    if clock is None:
        return line
    local = now.astimezone(ZoneInfo(clock["tz"]))
    if local.utcoffset() == now.astimezone(KST).utcoffset():
        return line  # same time as KST: the first line already says it
    return line + (f"\n[Dad's local time: {local:%A, %I:%M %p %Z} ({clock['tz']}, reported by "
                   f"{clock['reporter']}). The KST line is the server's clock, not his.]")


now = datetime(2026, 1, 5, 12, 30, tzinfo=timezone.utc)
clocks = {
    # A browser on the office Mac, reached by screen share: it says Seoul.
    "webui": {"tz": "Asia/Seoul", "observed_at": now - timedelta(minutes=5)},
    # The phone in his pocket, an example zone, reported three hours ago.
    "pippa-mobile": {"tz": "America/New_York", "observed_at": now - timedelta(hours=3)},
}
print(time_lines(clocks, now))
clocks["pippa-mobile"]["observed_at"] = now - timedelta(hours=30)
print("--- phone silent for 30 hours ---")
print(time_lines(clocks, now))

External links

Exercise

Make your client send its IANA time zone with every message (in a browser, Intl.DateTimeFormat().resolvedOptions().timeZone gives it). On the server, keep the latest zone per device with a timestamp, pick one with an age limit and a device priority you can defend, and add a local-time line next to your server-time line on the newest user message only when the two differ. Test it by changing your laptop's zone and sending a message.
Hint
Compare UTC offsets at the current instant, not zone names: two different names can keep the same time today and different times after a daylight-saving change. Validate the incoming name against the zone database before you store it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.