Skip to content

A fact with no start date is held at every moment #352

Closed

Description

@WaylandYang

Found by the temporal benchmark (#306) while checking why a chat answer that the scorer had passed was wrong. Asked "who did Lin Zhao work for in January 2023?" — five months before the offer letter — the agent answered:

Lin Zhao was employed by the Platform Group as a Staff Engineer in January 2023.

Both halves come from documents about events in 2024 and 2025. Neither says anything about January 2023. The ledger holds them at that moment because neither has a start date.

The two facts

Platform Group -[approved]-> Lin Zhao          valid_from NULL  valid_to NULL       (predicate never declared)
Lin Zhao  · job title · Staff Engineer         valid_from NULL  valid_to NULL  precision unknown

The first is "The raise was approved by the Platform Group" from a payroll note dated 2024-02-20. The second is "Lin Zhao no longer holds the job title Staff Engineer. The date of the change was not recorded" from a role note dated 2025-10-15 — the ended-unknown fact of #345, recorded as a fresh row rather than as the closing of the 2023-06-01 one.

Asked for at = 2023-01-01, the neighbourhood returns the first and the entity panel returns both. works for Meridian Systems from 2023-06-01 is correctly absent — it has a start.

The rule

Everywhere the world axis is read, an unknown start is treated as "since always":

(f.valid_from IS NULL OR f.valid_from <= $at) AND (f.valid_to IS NULL OR f.valid_to > $at)

(graph.rs node and edge queries; the same shape in tools.rs::entity_facts.) It is the symmetric twin of the open-ended rule for valid_to, and for the end it is right: a fact recorded as holding and not yet closed does hold today. For the start it says something stronger than the evidence does. "The raise was approved" in a note dated February 2024 places the approval no later than February 2024 — it does not place it in 2023, and nothing places it in January.

So a question about a moment before any evidence for a fact existed is answered with that fact. The benchmark has three questions written for exactly this reading ("把开区间读成「自古如此」") and all three passed the ledger probe — because the facts in play there had starts. Undated facts were not tested; the chat probe found one by accident, and the question is now pinned as a known gap next to #345.

What to build

An undated fact is bounded by its evidence: it is not asserted to hold before the earliest document that states it. Concretely, the cheapest form is at write time — when the extractor records a fact with no valid_from, store the document's doc_time (falling back to created_at) as valid_from with valid_from_precision = 'unknown', the same marker #345 needs on the other end: "it started at some point we do not know, and this is the first moment we can vouch for". The world-axis predicates then need no change, the entity panel shows the bound with its precision rather than a blank, and a later document that does give the start replaces it the normal way (a corrected fact, #311).

The alternative — keep NULL and have every world-axis query join fact_evidence for the earliest doc_time — reads the same but puts a subquery on the hottest path in the store, and leaves the panel showing nothing where the reader most needs a date.

Either way, the reading valid_from IS NULL → held at every moment goes; the benchmark's pinned question (lin-zhao-employer-2023-01) turns green the day it does.

Not in scope

Facts whose start is stated imprecisely ("since 2023", "since the spring") — valid_from_precision already carries year / month for those. This issue is only about facts with no start at all.

Activity

  1. WaylandYang commented on Sep 5, 2026

    @WaylandYang
    ContributorAuthor

    One shape this bound does not cover, found while talking it through: "once worked at X for five years" (a 2024 document). Both ends unknown, the end no later than the document (#345's ended-unknown), and a duration that ties the two unknowns together. The bound stores the first two; the five years survives only in the evidence quote. That is correct for every at question today — nothing is provable for any specific moment — but the day another document pins one end ("left in 2020"), the other end should follow to 2015, and nothing will recompute it from a quote. So a duration column is the one place where a length is independent information, and only there: "for years" / "for a few years" are bounds, not durations. Not adding it in this issue; noting the trigger — the first real corpus where a later document pins one end of such a fact.

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