Skip to main content

Tenant isolation and per-tenant usage metering for LangGraph checkpointers and stores

Project description

langgraph-tenancy

CI PyPI - Version License: MIT

Tenant isolation for LangGraph persistence — as a drop-in wrapper.

LangGraph's own threat model says it plainly:

Checkpoint savers index by thread_id. Without application-level auth, any caller with a valid thread_id can access that thread's state. [...] Users embedding LangGraph directly must implement their own access controls.

If you run a multi-tenant product on open-source LangGraph, the only thing between Customer A's agent state and Customer B's is a query filter in your application code. This package replaces that convention with enforcement.

Install

pip install langgraph-tenancy

Usage

Wrap your existing checkpointer and store. Nothing else changes.

from langgraph_tenancy import (
    TenantScopedCheckpointer,
    TenantScopedStore,
    InMemoryUsageLedger,
)

ledger = InMemoryUsageLedger()
checkpointer = TenantScopedCheckpointer(PostgresSaver(...), usage_ledger=ledger)
store = TenantScopedStore(InMemoryStore())

graph = builder.compile(checkpointer=checkpointer, store=store)

# tenant_id is now REQUIRED on every invocation
graph.invoke(
    {"messages": ["hello"]},
    config={"configurable": {"thread_id": "t1", "tenant_id": "acme"}},
)

# free per-tenant token metering, extracted from checkpointed messages
ledger.totals("acme")  # TenantUsage(input_tokens=..., output_tokens=..., by_model={...})

What it enforces

Raw LangGraph behavior With langgraph-tenancy
Any caller with a thread_id reads that thread Threads are physically keyed tenant::thread; wrong-thread_id bugs cannot cross tenants
Missing filter → silent unscoped query Missing tenant_idTenantRequiredError, nothing read or written
checkpointer.list(None) enumerates every tenant's threads Refused with UnscopedAccessError
Store namespaces are convention; any node can read any namespace Every op is rooted at the tenant segment, resolved from the run config automatically
delete_thread("t1") deletes whoever owns t1 Requires an explicit for_tenant("acme").delete_thread("t1") handle
usage_metadata buried in checkpoint blobs, unqueryable Aggregated per tenant (and per model), deduped by message id

No magic

The entire mechanism is key prefixing plus mandatory-context checks, in two small files you can audit in ten minutes:

  • thread ids become "{tenant_id}::{thread_id}" before reaching your database; the prefix is stripped from everything returned.
  • store namespaces ("memories",) become ("{tenant_id}", "memories").
  • tenant ids containing the separator are rejected, so acme can never craft a key that collides with another tenant's space.

It composes with any BaseCheckpointSaver / BaseStore implementation — Postgres, SQLite, Redis, MongoDB, in-memory — because it never touches storage itself.

What it is not

  • Not authentication. You decide which tenant a request belongs to; this package guarantees that decision is enforced everywhere downstream.
  • Not encryption. Combine with EncryptedSerializer for at-rest encryption.
  • Not a replacement for database-level controls in high-assurance setups (RLS, schema-per-tenant) — it's the layer that makes your application unable to leak, whatever the database allows.

Tested

The adversarial test suite — every test attempts a cross-tenant access the raw LangGraph API allows — runs against InMemorySaver and a real PostgresSaver in CI. The isolation guarantees are proven on actual SQL storage, not just the in-memory reference.

Development

uv venv && uv pip install -e ".[test]"
uv run pytest                 # postgres tests skip if no server is reachable

# to run the postgres leg locally:
export LG_TENANCY_PG_URI=postgresql://user@localhost:5432/langgraph_tenancy_test
uv run pytest

Status

Early (0.1.x). Covered today: sync + async checkpointer paths, sync store paths, in-memory and Postgres backends. Not yet covered: subgraph checkpoint_ns edge cases, AsyncPostgresSaver, PostgresStore, store TTL ops. Issues and PRs welcome.

License

MIT

Project details


Download files

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

Source Distribution

langgraph_tenancy-0.1.1.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

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

langgraph_tenancy-0.1.1-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: langgraph_tenancy-0.1.1.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for langgraph_tenancy-0.1.1.tar.gz
Algorithm Hash digest
SHA256 90d06d1be6dde7879de17b96ddeaeed3de9972b3cf04afade01ed1184041a3c2
MD5 e06363d54924c09f54a35802c15af913
BLAKE2b-256 b77aa44ec94dc1af39ba945883ef34ee9f46cbf8bb9e09cd90ab753493c81a61

See more details on using hashes here.

Provenance

The following attestation bundles were made for langgraph_tenancy-0.1.1.tar.gz:

Publisher: release.yml on ac12644/langgraph-tenancy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

File hashes

Hashes for langgraph_tenancy-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d16ce5fbca11b79eddc91a3f91ebc1e98cabb749777c19de6142a0d925230035
MD5 134b794db074b8c0b60e86f579c31b73
BLAKE2b-256 412faa0d623df63da9ec3ef43918ab894e8df368705d5ef3c7a2653ef64fd071

See more details on using hashes here.

Provenance

The following attestation bundles were made for langgraph_tenancy-0.1.1-py3-none-any.whl:

Publisher: release.yml on ac12644/langgraph-tenancy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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