Skip to content

A pushed statement cannot carry a stated world time without a chat model #1089

Closed

Description

@ansonnmm

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/ended in the s tuple are free strings; extra contract keys refuse at
    the door, but the time strings themselves are recorded verbatim as time_mentions
    (utopia-extract/open.rs, api/sources_routes.rs, extraction_open.rs).
  • Every pushed document enqueues resolve_time (extraction_open.rs), and
    resolve_document returns "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_time lands on attestation: the open row's attested_from takes it when
    doc_time_source is source/content, and world-axis reads order an undated row
    by COALESCE(valid_from, attested_from) — an observation anchor, not a stated
    interval.
  • With a chat model configured, an RFC 3339-looking when resolves 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 state property, do the binding's
    marks decide (as for any statement dated to one moment), or is another shape
    needed given empty_state_span refuses a zero-length state span?
  • Does ended take the same form, and how would a caller express "ended, date
    unknown"?
  • If resolve_time re-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 in s?

Non-goals

  • No typed writes — no predicate_id, no class, 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 stays external_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 statements push whose when/ended is a complete RFC 3339 timestamp lands as
    an open statement with valid_from/valid_to at second (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_time does not silently regress it.
  • Any when/ended that is not a timestamp takes today's model path unchanged.
  • Keys outside the contract still refuse at the door; the e/n/deleted shape is
    untouched.

Questions for maintainers

  1. Polymorphic when/ended, a separate structured slot in s, or another form?
  2. Is a caller-stated instant correctly read as an absolute reference with grade A?
  3. How should a bare instant interact with a state property's marks and
    empty_state_span?
  4. Should "ended, date unknown" be expressible, or is that words-only by design?
  5. On resolve_time re-runs, does a code-resolved mention stand as resolved or
    defer to the model?

Related

Activity

  1. WaylandYang commented on Oct 5, 2026

    @WaylandYang
    Contributor

    Thanks @ansonnmm. This is the question 0054 left open, and the failing resolve_time job on a deployment with no chat model is a defect on its own. The direction is accepted; a PR is welcome.

    Your five questions:

    1. Polymorphic 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.
    2. Grade A. A is "an absolute date the text writes" (0045), and the payload writes it.
    3. No new shape for a bare instant. It is a single date like any other. At materialisation the binding's marks says 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). when with ended is an interval.
    4. "Ended, date unknown" stays words-only in this cut. A sentinel for it is a separate decision.
    5. The code's resolution stands, because it happens inside 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:

    • A complete timestamp or a complete date. What parse_time already reads at day precision or finer: YYYY-MM-DD, and YYYY-MM-DDTHH[:MM[:SS]] with Z or 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 write T00:00:00Z would store a precision the source never had (0024). A bare year or year-month is not in this cut.
    • Pushed statements only. Mentions read from documents keep the model path, whatever they look like.
    • resolve_time does 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_document orders 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 an ended given 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.

  2. WaylandYang commented on Oct 6, 2026

    @WaylandYang
    Contributor

    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_time finishes 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions