Blog / How-to guides

Memory with a timeline: what was true then, what was known then, and corrections that keep their history

21 October 2026 · Semurg · 3 min read · Markdown

knowledge-graphtimehistorycorrectionspythonhow-to

Agents get things wrong and then learn better. A useful memory keeps both: the corrected value becomes current, and the earlier value stays readable as history, so you can always ask what your agents believed, and when. Semurg keeps both clocks on every value, the model researchers call bitemporal (valid time and transaction time): you can ask what is true now, what was true at an earlier time (valid_at), and what was known at an earlier moment (as_of), alone or together.

Each value carries two clocks: when it was true in the world (the source's date) and when the memory learned it (when it was stored). A read can ask about either, or both.True in the world: thesource's dateKnown to the memory:when it was storedNow: the newest valueas_of: what was knownat that momentvalid_at: what wastrue at that timeEach value carries two clocks: when it was true in the world (the source's date) and when the memory learned it (when it was stored). A read can ask about either, or both.True in the world: thesource's dateKnown to the memory:when it was storedNow: the newest valueas_of: what was knownat that momentvalid_at: what wastrue at that time

Two clocks on every value

When you store something you can say when it was true, with date (the session's date, or the time of the event). The memory also records when it learned it. Those are two different clocks, and the difference matters: an alert sent at 09:30 says what was believed at 09:30; a review call at 10:20 may correct it, and a report written a week later may add something that was true all along.

An incident, corrected

An on-call agent records an outage from the first alert. Fifty minutes later, the review call corrects the severity and the cause. The program below ran as written against a fresh node of release r11.121.20261021.3619d35, with semurg 0.1.1 from PyPI. Save it as timeline.py (with a node and SEMURG_API_TOKEN set, as in the PyPI quickstart):

python
import os, time
from datetime import datetime, timezone
from semurg import Client

with Client("http://127.0.0.1:4000", token=os.environ["SEMURG_API_TOKEN"]) as mem:
    mem.remember("ops", date="2026-10-01T09:30:00Z", source="alert-7731", facts={"entities": [
        {"name": "INC-2041", "type": "incident",
         "attributes": {"severity": "SEV2", "root_cause": "expired TLS certificate"}}]})
    time.sleep(2)
    before_correction = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    time.sleep(2)
    mem.remember("ops", date="2026-10-01T10:20:00Z", source="review-call", facts={"entities": [
        {"name": "INC-2041", "type": "incident",
         "attributes": {"severity": "SEV1", "root_cause": "connection-pool exhaustion"}}]})

    def severity(**when):
        answer = mem.recall("ops", subject="INC-2041", compact=True, **when)
        return next(i for i in answer["items"] if i["kind"] == "entity")["attributes"]["severity"]

    print("now:                                 ", severity())
    print("true at 10:00:                       ", severity(valid_at="2026-10-01T10:00:00Z"))
    print("true at 10:30, as known now:         ", severity(valid_at="2026-10-01T10:30:00Z"))
    print("true at 10:30, as known before the fix:", severity(valid_at="2026-10-01T10:30:00Z", as_of=before_correction))

It prints:

text
now:                                  SEV1
true at 10:00:                        SEV2
true at 10:30, as known now:          SEV1
true at 10:30, as known before the fix: SEV2

Four answers from one memory, and the last two are the point:

  • Now, the review call's value is current: SEV1. The newest value of an attribute wins.
  • True at 10:00, before the review at 10:20, the alert's SEV2 was the value on record. This is the question a post-incident review asks: what did we have at 10:00?
  • True at 10:30, as known now: SEV1, because the review call's value holds from 10:20.
  • True at 10:30, as known before the correction was stored: SEV2. The same moment in the world, a different moment of knowledge, a different answer. This is how you audit what an agent knew when it acted, not only what is true today.

The corrected value was not overwritten. Each value is kept with its source and its date, and a read chooses by both clocks.

Conflicting reports are kept, side by side

Not every disagreement is a correction. Support may report the first customer complaints at 09:05 while monitoring fired at 09:14. Store them as two attributes, first_customer_report_at and started_at, each from its own source, and both stay current: the memory keeps the evidence and lets the reader see both, cited to where each came from.

Things to know

  • as_of and valid_at take an ISO 8601 date or date and time; a bare date means the end of that day, in UTC. Both can be given at once: what was known at as_of about what was true at valid_at.
  • A value sent without date counts from the moment it was stored.
  • recall and context both take as_of and valid_at, on every interface: Python, plain HTTP, the WebSocket, and MCP.

Next in this series: Graph RAG that says when it does not know. Earlier: the automated ontology.

Related