Bi-Temporal Knowledge Graph
zeit is a Python library for building LLM applications. It turns notes, chats, and events into a knowledge graph your model can search.
Those sources contradict each other. Most stores overwrite — the model only remembers the latest version. zeit expires the old fact instead of deleting it. Your app can retrieve what's true now, and still ask what was true last spring. Every fact keeps two clocks: when it was true in the world, and when you wrote it down.
Two clocks
Say you ingest this today:
Ada works at Acme.
Next month you ingest:
Ada left Acme for Birch in March.
Most graphs overwrite. You only remember Birch.
zeit keeps both:
| Fact | True in the world | Written down |
|---|---|---|
| Ada works at Acme | until March | January |
| Ada works at Birch | from March | April |
Ask “where does Ada work?” and you get Birch. Ask what was true in February and you still get Acme.
What you do with it
- Ingest an episode from a chat turn, a document, or a fact you already know.
- zeit pulls out people, things, and claims; two names for the same person become one entity.
- A contradicting claim expires the old fact, and history stays.
- Search before the next model call: hits mix meaning, keywords, and nearby graph links; by default the model sees what’s valid now.
Intended shape
Install the PyPI package zeit-graph; the import stays zeit.
Drop a Graph into your LLM app.
The public API is async.
A Graph is one SurrealDB namespace + database.
from zeit import Graph
graph = Graph(
url="ws://localhost:8000/rpc",
namespace="app",
database="memory",
credentials=credentials,
)
await graph.add_episode("Ada left Acme for Birch in March 2026.")
await graph.add_triplet("Ada", "works_at", "Birch", "Ada works at Birch.")
hits = await graph.search("where does Ada work?")
Extract, resolve, and invalidate default to google:gemini-3.7-flash.
The embedder defaults to google:gemini-embedding-2.
Pass a ModelStack to override any of those.
Skip extraction and write a known fact with add_triplet.
Look up a stored entity or fact with get_entity and get_fact.
Run from this repo
Python 3.14 or newer, uv, and GNU Make 3.82 or newer.
macOS ships Make 3.81, so use Homebrew gmake: brew install make.
gmake check
That creates .venv from uv.lock and runs lint plus tests.
gmake lint is check-only (ruff check, ruff format --check, basedpyright).
gmake format applies ruff.
gmake test starts local SurrealDB on 127.0.0.1:8000, then runs pytest excluding e2e.
gmake e2e starts local SurrealDB on 127.0.0.1:8000, then runs pytest -m e2e verbosely against live Gemini and process-start Logfire.
Copy .env.example to .env and fill in GEMINI_API_KEY plus LOGFIRE_TOKEN.
The e2e harness reads repo .env before start.
SURREAL_URL defaults to ws://127.0.0.1:8000/rpc.
Leave it empty to start a brew SurrealDB instead.
Install SurrealDB with brew install surrealdb/tap/surreal.
pytest asserts the graph only.
After a run, follow the Logfire MCP recipe in AGENTS.md.
gmake help lists the rest.
Release
Human release notes live in CHANGELOG.md (Keep a Changelog).
During development, append user-facing work under ## Unreleased in ### Added / ### Changed / ### Fixed as appropriate.
Empty Unreleased (no bullets) hard-fails the release.
gmake release patch
gmake release is the sole release path.
It runs gmake check, refuses an empty Unreleased, bumps the version, promotes Unreleased to ## [vX.Y.Z] - YYYY-MM-DD, then commits, tags, and pushes.
Do not run gh release create or uv publish locally.
GitHub Actions on tag v* re-runs CI, builds sdist and wheel, creates a GitHub Release whose notes are that promoted section, and publishes zeit-graph to PyPI.
Publishing uses Trusted Publishing against the GitHub pypi environment.
Do not store a PyPI token in the repo.
Before the first upload, add a pending trusted publisher on PyPI for project zeit-graph, owner kborovik, repository zeit, workflow release.yml, environment pypi.
How it runs
zeit is the ingest then resolve then expire then search algorithm. The current implementation uses:
- SurrealDB as the only store
- PydanticAI for every LLM call (
pydantic-ai-slim[google]) - Logfire for traces — you configure Logfire in your process; zeit does not take a token
Configure Logfire before you construct a Graph.
import logfire
logfire.configure()
zeit instruments PydanticAI after that, so you do not call logfire.instrument_pydantic_ai.
Default Gemini models read GEMINI_API_KEY.
Swap the embedder if you want.
The graph API stays the same.
Status
The package is early: it ships Graph.add_episode, add_triplet, search, get_entity, and get_fact plus closed types, a swappable embedder, a SurrealDB store, first-party extract, resolve, and invalidate agents, a last-N episode context window, and Logfire traces on PydanticAI calls.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zeit_graph-0.1.2.tar.gz.
File metadata
- Download URL: zeit_graph-0.1.2.tar.gz
- Upload date:
- Size: 107.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60131157df6c5ecac9cc0eebfbe450c930c9095993618817cfb54d75ac04aeaf
|
|
| MD5 |
c1f5ed4bfe3c922d676ec0013d004735
|
|
| BLAKE2b-256 |
7b2a6cf04c7f1317f7910a9250e8b442e24b20944ae11e2c5bf9e89c25538216
|
File details
Details for the file zeit_graph-0.1.2-py3-none-any.whl.
File metadata
- Download URL: zeit_graph-0.1.2-py3-none-any.whl
- Upload date:
- Size: 16.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a65647d7bbc2fc3f6a5819e6bb9d9a9fe46fc6a7052d63fa3be511261e812818
|
|
| MD5 |
dfb652ca2f35a1cf9d10b42c3e9d1f21
|
|
| BLAKE2b-256 |
19aaff6cbedfdaf0784239b33bc3c547dd198e28016162ff88b13cd77d8f2e10
|