Skip to main content

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

  1. Ingest an episode from a chat turn, a document, or a fact you already know.
  2. zeit pulls out people, things, and claims; two names for the same person become one entity.
  3. A contradicting claim expires the old fact, and history stays.
  4. 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.

Metadata

Release files for zeit-graph 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zeit-graph 0.1.2
File Size Uploaded
zeit_graph-0.1.2.tar.gz 107.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zeit-graph 0.1.2
File Interpreter ABI Platform
zeit_graph-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 124.4 kB

Release files / zeit_graph-0.1.2.tar.gz

Download URL zeit_graph-0.1.2.tar.gz
Size 107.5 kB
Tags Source
SHA-256 checksum
How to use checksums
60131157df6c5ecac9cc0eebfbe450c930c9095993618817cfb54d75ac04aeaf
BLAKE2b-256 checksum
How to use checksums
7b2a6cf04c7f1317f7910a9250e8b442e24b20944ae11e2c5bf9e89c25538216
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / zeit_graph-0.1.2-py3-none-any.whl

Download URL zeit_graph-0.1.2-py3-none-any.whl
Size 16.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a65647d7bbc2fc3f6a5819e6bb9d9a9fe46fc6a7052d63fa3be511261e812818
BLAKE2b-256 checksum
How to use checksums
19aaff6cbedfdaf0784239b33bc3c547dd198e28016162ff88b13cd77d8f2e10
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release 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