Repository navigation
A pushed statement cannot carry a stated world time without a chat model #1089
Description
Problem
A statements source exists so a system that already holds
{thing, relation, value, when} can push observations without a model in the loop
(ADR 0054). Today such a caller cannot give a statement a stated world time:
when and ended are stored verbatim as time mentions, and the only path from a
mention to valid_from/valid_to is resolve_time, which requires a configured
chat model. A caller that already holds 2026-09-23T08:14:03Z must spell it into
words and pay a model call to read it back — the round trip the contract removed
for the statement itself. On a deployment with no chat model the words never
resolve, and each pushed document additionally leaves a resolve_time job that
fails permanently after its retries.
Current behavior
Observed on main at 0197bd8f (an earlier version of this was also exercised live
against v0.1.0-rc7):
when/endedin thestuple are free strings; extra contract keys refuse at
the door, but the time strings themselves are recorded verbatim astime_mentions
(utopia-extract/open.rs,api/sources_routes.rs,extraction_open.rs).- Every pushed document enqueues
resolve_time(extraction_open.rs), and
resolve_documentreturns "Chat model not configured" before reading mentions
(time_resolution.rs); jobs retry three times by default, so a model-free
deployment accumulates one failed job per push. doc_timelands on attestation: the open row'sattested_fromtakes it when
doc_time_sourceissource/content, and world-axis reads order an undated row
byCOALESCE(valid_from, attested_from)— an observation anchor, not a stated
interval.- With a chat model configured, an RFC 3339-looking
whenresolves as an absolute
date correctly today — the model does in prose what the caller already had in
structure.
Why doc_time is not equivalent
doc_time says when the source dated the observation; when/ended say when the
relation held. For live telemetry they coincide and attestation orders the row
correctly. For backfilled history, effective-dated records, event time that differs
from ingest time, or an explicit end, only when/ended can carry it — and today
that means words a model must re-read.
Generic use cases
- An ERP or event-bus connector replaying postings whose effective dates precede
ingest. - A robot or sensor log pushed after collection, where event time is a timestamp,
not a phrase. - A migration or import tool whose source records carry their own validity dates.
- A security or monitoring pipeline stating "the session was open between T1 and T2".
Design question
This is the open question ADR 0054 left: "Whether when should accept an RFC 3339
instant directly rather than time words, once the instant precision on the roadmap
exists." It is worth deciding on its own terms: may a pushed item carry its world
time in a form code resolves — e.g. an RFC 3339 timestamp at the ladder's finest
current precision (second, ADR 0024) — while everything else in the item remains
time words on the model path?
Smallest useful first cut
A shape to react to, not a prescription: when when/ended parses as a complete
RFC 3339 timestamp, the server resolves it in code — an absolute reference at
second precision, grade A since the payload states it — and still records the
literal string as the mention for evidence. parse_time
(utopia-extract/src/lib.rs) already reads exactly this shape at
hour/minute/second precision for date-typed values and tool arguments, and 0024's
third decision already blesses "a timestamp a machine wrote" at sub-day precision —
the reader exists; the question is only whether the mentions path may call it. Every other value keeps today's model
interpretation. Questions the cut would have to answer:
- Is a bare instant a point or a since? For a
stateproperty, do the binding's
marksdecide (as for any statement dated to one moment), or is another shape
needed givenempty_state_spanrefuses a zero-length state span? - Does
endedtake the same form, and how would a caller express "ended, date
unknown"? - If
resolve_timere-runs over a mention the caller resolved in code, does the
code resolution stand or defer to the model? - Polymorphic
when/ended, or a sibling structured slot ins?
Non-goals
- No typed writes — no
predicate_id, noclass, no promise that a pushed
statement binds before alignment reads it; 0054's "not doing" stands. - No batch endpoint and no new envelope fields — the response stays
{"action": …}and caller identity staysexternal_id. - No code matching natural-language time words — 0045's eighth decision stands;
this only removes a model round trip for input that was never a word. - Correctly dated open statements is the whole deliverable. Typed
materialization still flows through alignment and a model or a person's binding
decision; nothing here makes pushed statements reach the typed graph on their own.
Suggested acceptance criteria
If the direction is accepted, a reasonable bar for a first cut:
- A
statementspush whosewhen/endedis a complete RFC 3339 timestamp lands as
an open statement withvalid_from/valid_toatsecond(or coarser, per the
ladder) precision and grade A — with no chat model configured. - The literal string stays recorded as the mention for evidence; re-running
resolve_timedoes not silently regress it. - Any
when/endedthat is not a timestamp takes today's model path unchanged. - Keys outside the contract still refuse at the door; the
e/n/deletedshape is
untouched.
Questions for maintainers
- Polymorphic
when/ended, a separate structured slot ins, or another form? - Is a caller-stated instant correctly read as an absolute reference with grade A?
- How should a bare instant interact with a
stateproperty'smarksand
empty_state_span? - Should "ended, date unknown" be expressible, or is that words-only by design?
- On
resolve_timere-runs, does a code-resolved mention stand as resolved or
defer to the model?
Related
- Let a source push statements in the open contract #884 (Statements source), [Architecture exploration] Utopia + Jev for embodied intelligence: temporal knowledge and closed-loop decisions #875 (observation-style sources), Typed materialization leaves successive functional values open until explicit reconciliation #899/Per-object document updates lose observation-time ordering during reconciliation #900
(functional-timeline reconciliation, document-version dates), Materialization reconciles the rows it writes, and a version keeps the date it was pushed with #911
(engine rewrites carry statement links). - ADR 0054 (open questions), ADR 0045 (time interpretation contract), ADR 0024
(precision ladder), ADR 0022 (ended-unknown).
Activity
WaylandYang commentedon Oct 5, 2026
Thanks @ansonnmm. This is the question 0054 left open, and the failing
resolve_timejob on a deployment with no chat model is a defect on its own. The direction is accepted; a PR is welcome.Your five questions:
when/ended, no new slot. The contract's shape stays. For a pushed item the payload is the evidence, so a timestamp there is what the source said.markssays whether it is the start or the end of a state; with neither, the statement stays in the open graph with its date, as today (A statement dated at one moment, bound to a state property, becomes a state that holds at no moment #966). A state row whose ends are equal is still never written (A binding to a state property says what a single date marks, and a state row whose ends are equal is never written #975).whenwithendedis an interval.resolve_time. A mention the code can read is resolved there and never sent to the model, so a re-run gives the same answer.Three things about the cut:
parse_timealready reads at day precision or finer:YYYY-MM-DD, andYYYY-MM-DDTHH[:MM[:SS]]withZor an offset, each at the precision it is written to. A date matters as much as an instant: an effective-dated record knows the day, and making its caller writeT00:00:00Zwould store a precision the source never had (0024). A bare year or year-month is not in this cut.resolve_timedoes not fail for want of a chat model. It resolves what code can, leaves the rest unresolved at grade C, and ends. That removes the permanently failed job per push.The rest as you wrote it: the literal string stays as the mention, anything else takes today's path, and nothing about typed writes changes. Two notes on timing: #1087 has just changed how
resolve_documentorders its work, so please build on current dev; and #994 is about to change how an end that names a period is stored, so say in the PR how anendedgiven as a timestamp sits with it. Please add a dated note under 0054's open question and a line under 0045's decision 8 that a timestamp is not a time word.WaylandYang commentedon Oct 6, 2026
Landed in #1090 (b29a60a on dev): a complete date or a zoned timestamp from a pushed statement is resolved in code at grade A, and
resolve_timefinishes for a statements source with no chat model configured. Checked end to end on a server without a chat model; the numbers are in the review on #1090.