Skip to main content

tempora

Deterministic temporal operators for AI agent memory.

LLMs understand. Code keeps the books.

python license


Agent memory breaks when facts change. Given

#0    "User lives in Delhi"
#41   "User lives in Bangalore"
#306  "User lives in Mumbai"

ask "Where did I live before Mumbai?" and production memory systems guess: on the public conflict benchmark (MemoryAgentBench, ICLR 2026) Mem0 scores 18% and Zep/Graphiti 7% — because every version of the fact lands in the prompt and one model call must untangle ordering, meaning, and answer at once.

Don't Ask the LLM to Track Freshness (May 2026) showed the fix for the simplest case: let the LLM only find relevant facts, then pick the newest with Python max() — jumping single-hop accuracy to ~95%. But max() answers exactly one question shape. Real questions about changing facts have four:

question operator resolver
"Where do I live?" current timeline[-1]
"Where did I live before?" previous timeline[-2]
"How many cities have I lived in?" count len(timeline)
"List everywhere I've lived" history ", ".join(timeline)

tempora implements the missing three, plus the one-call router that picks the right one.

Architecture

question ─────────────────────────────────────────────┐
   │                                                  │
   ▼                                                  ▼
route()                 LLM sees only the question:   extract()   LLM sees facts + question:
"what KIND of           returns one label             "which facts are about this
question is this?"      current|previous|             subject?" Told explicitly NOT to
                        count|history                 judge recency. Returns ALL versions.
   │                                                  │
   └────────────────────┬─────────────────────────────┘
                        ▼
                   resolve()          pure Python: sort candidates by serial,
                                      dedupe, apply the one-line operator.
                                      No LLM. Cannot hallucinate ordering,
                                      cannot miscount, fully auditable.

Two constrained LLM calls for understanding; zero LLM involvement in the temporal logic.

Results

Both arms below share identical BM25 retrieval and identical candidate extraction — the only difference is whether the deterministic step knows more operations than max(). Questions are generated mechanically from MemoryAgentBench's own fact conflicts, so gold answers are computed from data, never by an LLM.

operator n newest-only baseline tempora router accuracy
current 12 83% 83% 100%
previous 12 8% 83% 92%
count 12 0% 83% 100%
history 12 0% 83% 100%
total 48 23% 83%

The residual gap is candidate-extraction misses on a small open model, not resolver error — the resolvers are pure functions with unit tests.

We also reproduce the freshness paper's single-hop pipeline at 91% (openai/gpt-oss-120b, n=100, 6k context), against published memory-system baselines of 54% (HippoRAG-v2), 18% (Mem0), and 7% (Zep/Graphiti).

Install

pip install git+https://github.com/nitininhouse/tempora.git

Core has a single dependency (rank-bm25, used only by the benchmarks). No LLM SDK: you pass any callable (system: str, user: str) -> str.

Quickstart

import os
from tempora import answer
from tempora.llm_helpers import openai_compatible   # optional convenience

llm = openai_compatible(api_key=os.environ["GROQ_API_KEY"])   # any OpenAI-compatible endpoint

facts = [                     # (serial, text) — higher serial = recorded later
    (0,   "User lives in Delhi."),
    (41,  "User lives in Bangalore."),
    (306, "User lives in Mumbai."),
]

answer("Where did the user live before Mumbai?", facts, llm)
# {'answer': 'Bangalore', 'operator': 'previous', 'candidates': [...]}

Run python examples/quickstart.py for all four operators against the same facts.

API

function does
answer(question, facts, llm) full pipeline; returns {answer, operator, candidates}
route(question, llm) classify the question into an operator name
extract(question, facts, llm) LLM candidate extraction, all fact versions
resolve(operator, candidates) pure-Python resolution; no LLM
build_timeline(candidates) serial-ordered, deduplicated value list

Reproduce the numbers

pip install -e ".[bench]"
export GROQ_API_KEY=...                  # or any OpenAI-compatible endpoint via base_url
cd benchmarks
python generate_operator_set.py          # 160 questions from real MAB conflicts
python run_operators.py                  # baseline vs tempora, per-operator table
python run_freshness.py                  # single-hop reproduction

Prior art, honestly

  • Don't Ask the LLM to Track Freshness proved deterministic-newest beats LLM judgment and named non-freshness operators as open future work. tempora is that future-work section, built.
  • Temporal KGQA (TEQUILA, CronKGQA) used operator decomposition — before/after/first/last — over structured temporal knowledge graphs years ago. Agent memory forgot this; tempora re-applies the idea to serial-ordered conversational facts.
  • Zep/Graphiti stores bitemporal history and Mem0 exposes a history() API — storage without question routing, which is how a system that stores history still scores 7% answering questions about it.

Limitations

  • Operator set derives from one benchmark's synthetic conflicts; phrasing diversity is limited.
  • Serial order stands in for time. Inferring order from language ("after I graduated…") is the next milestone.
  • Four operators. Real usage will want as-of(date), still-valid, duration.
  • Multi-hop conflict chains remain open here as everywhere (published best ≤7%; the freshness paper's chain extension reaches ~30%).

License

MIT © Nitin Gupta

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tempora_memory-0.1.0.tar.gz (11.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tempora_memory-0.1.0-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file tempora_memory-0.1.0.tar.gz.

File metadata

  • Download URL: tempora_memory-0.1.0.tar.gz
  • Upload date:
  • Size: 11.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for tempora_memory-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cfc5e22b693ece06cb87a340dada1cd34be6c4f66e5b92409b67934c05fe2570
MD5 98d54c3033e9cd1fd802576174ec21e3
BLAKE2b-256 64d75043a0fbacb21a1c74919bd3ab734f640fdd3ea0a52727351fae608cebea

See more details on using hashes here.

File details

Details for the file tempora_memory-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tempora_memory-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.13

File hashes

Hashes for tempora_memory-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0b7b36fa1c4f97a5d90d43d61f829bf114ac41fc5c17349b0841dd7c0eae11aa
MD5 20f13be1b832bdaf23158f906a0f8b4b
BLAKE2b-256 e9fdb1a193d479407e276d019dc7faa303af2c92367bc8cc255b22a407ba4a70

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page