Found by the temporal benchmark's --chat probe (#306) on a build with #350. The agent now has as_of, and it uses it — and still cannot get behind a correction, because the one tool that tells it when the correction landed rounds that moment to a day.
The trace
Asked "before the payroll correction arrived, what salary did we have on record for Lin Zhao?", the model does the right thing in the right order:
entity · Lin Zhao · 1 matches
changes · 2020-01-01 → now · 1 changes ← "2026-09-05 corrected: Lin Zhao salary 32000 CNY …"
facts · Lin Zhao · 0 facts at 2023-06-01, as recorded by 2026-09-04
facts · Lin Zhao · 0 facts as recorded by 2026-09-04
It reads the correction's date, subtracts a day, and asks for the base as of the day before — and the base did not exist the day before. The first ingest and the correction are forty seconds apart. Same shape on "before the handover memo arrived, who did we think led Aurora in August 2024": the memo and the charter it corrected were recorded on the same day, and "the day before" is empty.
This is not a benchmark artifact. A memo that corrects the morning's ingest arrives in the afternoon; the question "what did we hold before it" is a same-day question whenever the base is being fed continuously, which is what sources are for.
Where it is
change_line in crates/utopia-server/src/api/tools.rs:
format!("{} {}: {} {} {}{}{}", c.at.format("%Y-%m-%d"), c.kind, …)
c.at is recorded_at, a timestamp. The line prints the date. parse_when (#350) already accepts RFC3339 on as_of for exactly this reason — "a record-axis moment is usually a timestamp, which a date cannot hold" — so the reader can take the precision; the writer throws it away one tool earlier.
What to build
change_line prints recorded_at as an RFC3339 instant (2026-09-05T02:43:39Z), not a date. The world-axis interval after it stays at day precision — that is how the world was recorded, and mixing the two precisions on one line is itself a hint about which is which.
- The
changes description and the system prompt say the one sentence the model needs: to see the base as it stood before a change, pass that change's instant (or a second earlier) as as_of. The model currently invents "the day before" because that is all a date allows.
changes takes since / until at day precision and that is fine — it windows, it does not pin.
One test: a change line carries the time of day, and parse_when reads it back to the same instant.
Not in scope
Time zones in the display. The instant is UTC and says so with the Z; how the UI renders it is the UI's business (#307).
Found by the temporal benchmark's
--chatprobe (#306) on a build with #350. The agent now hasas_of, and it uses it — and still cannot get behind a correction, because the one tool that tells it when the correction landed rounds that moment to a day.The trace
Asked "before the payroll correction arrived, what salary did we have on record for Lin Zhao?", the model does the right thing in the right order:
It reads the correction's date, subtracts a day, and asks for the base as of the day before — and the base did not exist the day before. The first ingest and the correction are forty seconds apart. Same shape on "before the handover memo arrived, who did we think led Aurora in August 2024": the memo and the charter it corrected were recorded on the same day, and "the day before" is empty.
This is not a benchmark artifact. A memo that corrects the morning's ingest arrives in the afternoon; the question "what did we hold before it" is a same-day question whenever the base is being fed continuously, which is what sources are for.
Where it is
change_lineincrates/utopia-server/src/api/tools.rs:c.atisrecorded_at, a timestamp. The line prints the date.parse_when(#350) already accepts RFC3339 onas_offor exactly this reason — "a record-axis moment is usually a timestamp, which a date cannot hold" — so the reader can take the precision; the writer throws it away one tool earlier.What to build
change_lineprintsrecorded_atas an RFC3339 instant (2026-09-05T02:43:39Z), not a date. The world-axis interval after it stays at day precision — that is how the world was recorded, and mixing the two precisions on one line is itself a hint about which is which.changesdescription and the system prompt say the one sentence the model needs: to see the base as it stood before a change, pass that change's instant (or a second earlier) asas_of. The model currently invents "the day before" because that is all a date allows.changestakessince/untilat day precision and that is fine — it windows, it does not pin.One test: a change line carries the time of day, and
parse_whenreads it back to the same instant.Not in scope
Time zones in the display. The instant is UTC and says so with the
Z; how the UI renders it is the UI's business (#307).