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.

Download files

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

Source Distribution

zeit_graph-0.1.1.tar.gz (107.5 kB view details)

Uploaded Source

Built Distribution

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

zeit_graph-0.1.1-py3-none-any.whl (16.9 kB view details)

Uploaded Python 3

File details

Details for the file zeit_graph-0.1.1.tar.gz.

File metadata

  • Download URL: zeit_graph-0.1.1.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

Hashes for zeit_graph-0.1.1.tar.gz
Algorithm Hash digest
SHA256 339aa8aff417f02c17d9683c9595d738235eb8decd78dc73ff1a78dd0686da88
MD5 9aa4c5d3ef6a6dcf057174804c98c628
BLAKE2b-256 9caea540e541feb9c16e376566f77f87b06cfdea62f4b6068e0880002562fc7a

See more details on using hashes here.

File details

Details for the file zeit_graph-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: zeit_graph-0.1.1-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

Hashes for zeit_graph-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fc863fce2de6af8515c6ba149b5ce59b30d2bfbe599b52bb67dca383249cdf8e
MD5 14d36031ca65de202e8e37ee3ed534f5
BLAKE2b-256 1f976fd1343350ba0c534e1a372453a87d38889f472332e267a3735c5da7734c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page