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.
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):
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:
now: SEV1
true at 10:00: SEV2
true at 10:30, as known now: SEV1
true at 10:30, as known before the fix: SEV2Four 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_ofandvalid_attake 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 atas_ofabout what was true atvalid_at.- A value sent without
datecounts from the moment it was stored. recallandcontextboth takeas_ofandvalid_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.