Skip to content

docs(ws2): rewrite WS2 as time grounding, add sliced execution plan - #70

Open
roiguri wants to merge 2 commits into
mainfrom
docs/ws2-time-grounding
Open

docs(ws2): rewrite WS2 as time grounding, add sliced execution plan#70
roiguri wants to merge 2 commits into
mainfrom
docs/ws2-time-grounding

Conversation

@roiguri

@roiguri roiguri commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Re-measured WS2 against production and re-read the reference implementation instead of recalling it. Splits the result into a reasoning doc (the roadmap) and an execution doc (four verifiable slices).

What changed in the reasoning

The premise no longer holds. WS2 rested on "64% of heartbeat turns cross a minute boundary mid-turn". Re-measured over 7 days of prod: median heartbeat turn is 12.6s, mean 13.7s, zero turns ≥60s. Crossings are ~23%. That caps the clock's cache cost at ~5% of input ≈ $1/mo.

So WS2's justification moves to correctness. build_system_prompt runs inside _llm_node, so the clock is rebuilt on every LLM call — within one turn, call 1 reads 06:14 and call 4 reads 06:15. The model's "now" moves mid-reasoning, on turns that may be computing a reminder's fire_at. Nothing logs or tests it.

The cost case belongs to a different item. _add_and_trim is the reducer on messages, so it fires on every state update, not once per turn. Both threads sit at MAX_MESSAGES and a tick adds ~11.5 messages, so the window head slides several times inside a turn — the only window where caching can pay. Trim hysteresis is worth ~$6.65/mo of a $22.87/mo bill; the clock is ~$1/mo.

The reference implementation has moved on. OpenClaw at HEAD aa743c9f no longer matches the "timezone only, no clock" position this doc recorded (that was a Jul 13 snapshot). It now carries a day-resolution date plus a session_status pointer, stamps each message at the LLM boundary from that message's own timestamp, and appends Current time:/Reference UTC: to cron bodies from the tick's start time.

New: timezone & day-edge verification (§2.4)

Tested rather than reasoned, against the live gate:

  • Midnight always exists at both 2026 Israel transitions (they happen at 02:00), so every day-boundary computation — _today_israel_start_utc, daily-log filenames, the chat/notification slice filters — is safe.
  • Windows shift by exactly one UTC tick across DST, measured for all four windowed tasks.
  • A 24h task locked on the last in-window tick loses exactly one occurrence at spring-forward, then self-heals. Simulated over every possible lock position through both transitions using the real any_due/stamp path.
  • morning-readiness-check is on the safe side of both transitions — stamped at 06:00Z, in-window in both offsets. Its heartbeat-assert §2b ticks left: 0 flag is a dropped-tick fragility, not an imminent DST loss. Left as-is by owner decision.
  • %Z is unsafe for stamps — Python yields IST for Israel winter, colliding with India Standard Time.

Execution plan

WS2_TIME_GROUNDING_PLAN.md — four independently revertable commits:

Slice Nature
S1 Per-turn stamp in ask_jarvis additive — clock stays, verifies in isolation
S2 Envelope Current timeCurrent date removal
S3 llm_calls.jsonl per-call telemetry read-only, independent
S4 Trim hysteresis gated on S3's reading

S3 exists because per-turn telemetry can't attribute a miss to a call, and two outcomes need different responses — if the head hash is stable and cache_read is still flat, that's Gemini's cache and S4 should not be built at all.

Also

Records that this reverts a deliberate decision: ARCHITECTURE_PLAN.md dropped main.py's time_ctx prepending as problem #4 ("jammed into the user message"), on structural-tidiness grounds — no correctness or caching consideration at the time.

Docs only; no code changes.

https://claude.ai/code/session_01TcKHUrthfLzuzdFDBm5N1s

roiguri added 2 commits August 1, 2026 09:57
Re-measured against prod (7d to 2026-08-01) and re-read the reference
implementation instead of recalling it. Three things changed:

- The "64% of heartbeat turns cross a minute boundary" premise no longer
  holds: median tick is 12.6s, zero turns >=60s, so crossings are ~23%.
  That caps the clock's cache cost at ~5% of input (~$1/mo) and moves WS2's
  justification to correctness — build_system_prompt runs inside _llm_node,
  so a turn's own "now" moves mid-reasoning.
- _add_and_trim is a reducer, so it fires on every state update, not once
  per turn. Both threads sit at MAX_MESSAGES and a tick adds ~11.5 messages,
  so the window head slides several times *inside* a turn. Item 4 (trim
  hysteresis, ~$6.65/mo) owns WS2's cost case; items 1-3 own correctness.
- OpenClaw at HEAD aa743c9f has moved on from the "timezone only, no clock"
  position this doc recorded: it now carries a day-resolution date plus a
  session_status pointer, stamps each message at the LLM boundary from that
  message's own timestamp, and appends Current time/Reference UTC to cron
  bodies from the tick's start time.

Adds 2.4, a verified timezone/day-edge section: midnight always exists at
both 2026 Israel transitions so day-boundary math is safe; windows shift
exactly one UTC tick across DST; a 24h task locked on the last in-window
tick loses exactly one occurrence at spring-forward (simulated over every
lock position through both transitions). morning-readiness-check is stamped
at 06:00Z, in-window in both offsets, so its ticks-left-0 flag is a
dropped-tick fragility and not an imminent DST loss.

Also records that this reverts a deliberate decision: ARCHITECTURE_PLAN
dropped main.py's time_ctx prepending on structural-tidiness grounds, with
no correctness or caching consideration at the time.

Claude-Session: https://claude.ai/code/session_01TcKHUrthfLzuzdFDBm5N1s
Splits WS2 into four sliced, independently revertable commits so each can
be verified on its own rather than shipping the workstream in one piece:

- S1 per-turn stamp (additive; the envelope clock stays, so the model
  briefly sees the time twice and S1 verifies in isolation)
- S2 envelope Current time -> Current date (removal)
- S3 llm_calls.jsonl per-call telemetry (read-only, independent)
- S4 trim hysteresis, gated on S3's reading

Also collapses the parent's design item 3: the stamp carries a UTC
reference for every scope rather than only the tick body, because
manage_reminder's ISO-UTC fire_at is produced mostly in *user* turns, so
that is where the hand conversion actually happens.

Corrects a factual error introduced in 9585f66: ask_jarvis has three call
sites, not four. ask_jarvis_once bypasses the agent loop entirely (no
system prompt, no tools, no history) and is out of scope.

Claude-Session: https://claude.ai/code/session_01TcKHUrthfLzuzdFDBm5N1s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant