Skip to main content

ooai-persistence

CI Release Docs Python PDM Coverage Async Postgres

ooai-persistence gives LangGraph apps a usable persistence layer without making you hand-build a big settings tree first.

Responsibilities

  • checkpointer configuration and backend resolution
  • store configuration and backend resolution
  • graph cache configuration
  • strict serializer allowlist support
  • sync and async persistence contexts
  • local infrastructure defaults for Postgres, optional Redis, and optional MongoDB

Quick start

pdm install -G :all
pdm run pytest
pdm run ooai-persistence smoke --backend memory

Start here

Most applications should start with one of these helpers:

  • memory_settings() for tests and local no-infra runs
  • sqlite_settings(path) for one-file local persistence
  • postgres_settings(...) for the real async Postgres path
from ooai_persistence import memory_settings, postgres_settings, sqlite_settings

memory = memory_settings()
sqlite = sqlite_settings(".ooai/persistence/dev.sqlite3")
postgres = postgres_settings(database="ooai_persistence")
postgres_via_uri = postgres_settings("postgresql://postgres:postgres@localhost:5442/ooai_persistence?sslmode=disable")

If those cover your case, you do not need to construct AppSettings(...) directly.

If you want the shortest possible path, skip settings entirely and open the bundle directly:

from ooai_persistence import (
    open_memory_persistence,
    open_postgres_persistence,
    open_sqlite_persistence,
)

async with open_postgres_persistence(database="ooai_persistence") as persistence:
    await persistence.store.aput(("profiles", "demo"), "name", {"value": "Will"})

That is the easiest async entrypoint in the package right now.

If you only want the long-term store and not the full persistence bundle:

from ooai_persistence import open_postgres_store

async with open_postgres_store(database="ooai_persistence") as store:
    await store.aput(("profiles", "demo"), "name", {"value": "Will"})
    item = await store.aget(("profiles", "demo"), "name")

Common patterns

1. Use the store directly

from ooai_persistence import open_sync_memory_store

with open_sync_memory_store() as store:
    store.put(("users", "will"), "profile", {"name": "Will"})
    profile = store.get(("users", "will"), "profile")

2. Compile a graph with async persistence attached

from typing import TypedDict

from langgraph.graph import END, START, StateGraph
from ooai_persistence import open_graph, postgres_settings


class State(TypedDict):
    question: str
    answer: str


def respond(state: State) -> State:
    return {"answer": f"Echo: {state['question']}"}


graph = StateGraph(State)
graph.add_node("respond", respond)
graph.add_edge(START, "respond")
graph.add_edge("respond", END)

settings = postgres_settings(
    host="localhost",
    port=5442,
    database="ooai_persistence",
    user="postgres",
    password="postgres",
)

async with open_graph(graph, settings) as runtime:
    await runtime.persistence.store.aput(("profiles", "demo"), "name", {"value": "Will"})
    result = await runtime.graph.ainvoke(
        {"question": "hello", "answer": ""},
        config={"configurable": {"thread_id": "demo-thread"}},
    )

When a graph uses a checkpointer, LangGraph expects a configurable.thread_id or another checkpoint key in the runnable config.

3. Bind persistence onto a compiled graph

from ooai_persistence import bind_graph_with_persistence, memory_settings, open_sync_persistence

compiled = graph.compile()

with open_sync_persistence(memory_settings()) as bundle:
    persistent_graph = bind_graph_with_persistence(compiled, bundle)
    result = persistent_graph.invoke(
        {"question": "hello", "answer": ""},
        config={"configurable": {"thread_id": "demo-thread"}},
    )

Async Postgres, the easy way

If your real target is async Postgres, the shortest path is:

from ooai_persistence import open_postgres_persistence

async with open_postgres_persistence(database="ooai_persistence") as persistence:
    await persistence.store.aput(("profiles", "demo"), "name", {"value": "Will"})

If you are compiling a LangGraph too:

from ooai_persistence import open_graph, postgres_settings

settings = postgres_settings(database="ooai_persistence")

Or use a URI:

from ooai_persistence import open_postgres_persistence

async with open_postgres_persistence(
    "postgresql://postgres:postgres@localhost:5442/ooai_persistence?sslmode=disable"
) as persistence:
    ...

If you call the Postgres helpers without connection arguments, they read the same OOAI_PERSISTENCE_INFRA__POSTGRES_* settings that AppSettings() uses.

Then bring Postgres up locally:

make bootstrap
make infra-up
make infra-test-postgres

That path exercises the real async LangGraph checkpointer and store, not a fake shim around them.

LangGraph wrappers

The top-level graph helpers are:

  • compile_graph_with_persistence(graph, bundle, **compile_kwargs)
  • bind_graph_with_persistence(compiled_graph, bundle)
  • open_sync_graph(graph_or_compiled_graph, settings, **compile_kwargs)
  • open_graph(graph_or_compiled_graph, settings, **compile_kwargs)

The top-level persistence helpers are:

  • open_sync_memory_persistence()
  • open_sync_sqlite_persistence(path)
  • open_sync_postgres_persistence(...)
  • open_memory_persistence()
  • open_sqlite_persistence(path)
  • open_postgres_persistence(...)

The top-level store-only helpers are:

  • open_sync_store(settings)
  • open_sync_memory_store()
  • open_sync_sqlite_store(path)
  • open_sync_postgres_store(...)
  • open_store(settings)
  • open_memory_store()
  • open_sqlite_store(path)
  • open_postgres_store(...)

open_sync_graph and open_graph yield a PersistentGraph with:

  • runtime.graph: the compiled or rebound LangGraph
  • runtime.persistence: the managed PersistenceBundle

CLI

The package ships a small diagnostics and smoke-test CLI:

ooai-persistence doctor --backend postgres --json
ooai-persistence smoke --backend memory
ooai-persistence smoke --backend sqlite --sqlite-path .ooai/persistence/smoke.sqlite3
ooai-persistence smoke --backend postgres --async
ooai-persistence env --output .env
ooai-persistence ensure-postgres

The async Postgres smoke command exercises both the LangGraph checkpointer and store through the public context API.

Local Postgres

make bootstrap
make infra-up
make infra-up-docker
make infra-test-postgres
make infra-down

make bootstrap creates .env from .env.example when needed and installs all PDM extras. See infra/README.md for Compose details.

make infra-test-postgres remains as a compatibility alias for the same Postgres E2E and CLI smoke flow.

If you want the shortest test commands, use:

make test-e2e-memory
make test-e2e-sqlite
make test-e2e-local
make test-e2e-postgres

The Postgres target brings up the configured database, runs the public wrapper E2E suite for open_postgres_persistence(...) and open_postgres_store(...), and then runs the async CLI smoke check.

make infra-up is the ergonomic default: it uses the Postgres server from .env, ensures the configured database exists, and avoids depending on Docker just to run the async store and persistence tests locally.

If you want the Compose-backed service explicitly, use make infra-up-docker.

The matching .env path is already laid out in .env.example.

Default backend behavior

By default, checkpointer and store use backend="auto".

auto resolves in this order:

  1. async Postgres when configured
  2. async SQLite when configured
  3. async MongoDB when configured
  4. async Redis when configured
  5. in-memory fallback

Serializer allowlist registry

The package includes a reusable strict-msgpack registry:

from ooai_persistence.serde.registry import MsgpackAllowlistRegistry

registry = MsgpackAllowlistRegistry()
registry.register_symbol("my_app.models", "WorkflowState")
registry.register_type(MyPersistedModel)
registry.register_import_string("my_app.models:AnotherPersistedModel")

That registry can be passed into the persistence context:

from ooai_persistence import AppSettings, open_persistence

settings = AppSettings()

async with open_persistence(settings, registry=registry) as bundle:
    ...

The same registry also flows through open_graph(...) and open_sync_graph(...). It also flows through the direct-open helpers like open_postgres_persistence(...).

When to use AppSettings directly

Reach for AppSettings(...) only when you want to:

  • override checkpointer and store backends separately
  • drive config from .env
  • customize serializer allowlists, cache settings, or infra defaults
  • compose persistence settings into a larger application settings object

Documentation and release checks

pdm run sphinx-build -W -b html docs docs/_build/html
pdm build

CI runs formatting, linting, typing, tests with coverage, and the Sphinx build. Docs also publish from main to GitHub Pages.

Releasing

Releases are tag-driven:

pdm lock --check
make check
git tag vX.Y.Z
git push origin vX.Y.Z

The release workflow verifies that the tag matches pyproject.toml, runs the async Postgres E2E checks, builds the wheel/sdist, publishes through PyPI Trusted Publishing, and creates a GitHub Release with artifacts attached.

Metadata

Release files for ooai-persistence 0.2.5

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

Source distribution (sdist)

Source distribution for ooai-persistence 0.2.5
File Size Uploaded
ooai_persistence-0.2.5.tar.gz 27.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ooai-persistence 0.2.5
File Interpreter ABI Platform
ooai_persistence-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 50.6 kB

Release files / ooai_persistence-0.2.5.tar.gz

Download URL ooai_persistence-0.2.5.tar.gz
Size 27.0 kB
Tags Source
SHA-256 checksum
How to use checksums
97775071ee47f324b0a2fc2051c4c16c08f3711d4c27c91bbc7a43d3c926eac4
BLAKE2b-256 checksum
How to use checksums
76279d6b96e3408f697592add7688d29ba1dc25555ebb784f4072ecee37b3cd8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 5, 2026.

Transparency log

Release files / ooai_persistence-0.2.5-py3-none-any.whl

Download URL ooai_persistence-0.2.5-py3-none-any.whl
Size 23.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6c74ed97e94ba230477c8d240df9364fee48c48af9bab3df29689503f1528c32
BLAKE2b-256 checksum
How to use checksums
c054c271d12ac365bb83e8a97e8a7d892ec77d6f79dcb35277817cad090566fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

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