Skip to main content

StatePact

Transaction management for autonomous AI agents

Concurrency control for shared agent state — with optimistic validation, MVCC, strict two-phase locking, serializability checks, retries, idempotency, and lease fencing.

CI Docs Python 3.12+ Live Playground Apache 2.0 License Alpha

Live Concurrency Playground ↗ · Documentation · Report an issue


The problem: Multiple autonomous agents can read the same state, make decisions independently, and overwrite one another's updates. StatePact brings transaction-style concurrency control to that shared state.

Table of contents

Why StatePact?

Imagine three AI agents managing the last item in an online store:

  1. Agent A reads stock = 1.
  2. Agent B reads stock = 1.
  3. Both decide the item is available.
  4. Both write stock = 0.

Two reservations have now claimed one item. This is a classic race condition, and it becomes increasingly important as agents execute tools and modify shared state concurrently.

Framework checkpointers persist messages and workflow state, but they do not automatically provide conflict detection across concurrent tool calls. StatePact adds that missing coordination layer.

What you get

Capability What it does
Isolation modes Choose optimistic, snapshot, serializable, pessimistic, or explicitly unsafe none
Conflict-aware retries Re-run the entire transaction against a fresh snapshot after a retryable conflict
Idempotent commits Replay an operation without applying its writes twice
Leases and fencing Prevent an expired worker from writing after a newer lease owner takes over
Multiple backends Use process-local memory, PostgreSQL, or Redis
Framework integrations Connect LangGraph, CrewAI, AutoGen, or a custom agent loop

StatePact is framework-independent. Framework adapters are optional wrappers around the same transaction engine.

Quick start

Installation

Python 3.12+ is required.

pip install statepact                 # core package — in-memory backend
pip install "statepact[postgres]"     # + PostgreSQL backend
pip install "statepact[redis]"        # + Redis backend
pip install "statepact[demo]"         # + the Gradio playground

The core dependency is Pydantic 2. PostgreSQL and Redis support are optional extras. For development from a checkout:

git clone https://github.com/mithun-27/StatePact.git
cd StatePact
uv sync --extra dev

Your first transaction

import asyncio

from statepact import RetryPolicy, TransactionManager


async def main() -> None:
    manager = TransactionManager()

    inventory = manager.resource("inventory", {"sku-1": 10})
    await inventory.ensure(manager)

    async def reserve(session) -> bool:
        stock = await session.read("sku-1")

        # Simulate work between reading and writing.
        await asyncio.sleep(0)

        if stock <= 0:
            return False

        await session.write("sku-1", stock - 1)
        return True

    reserved = await manager.run(
        "agent-1",
        [inventory],
        reserve,
        retry_policy=RetryPolicy(max_attempts=100),
    )

    print("Reserved:", reserved)
    print("Remaining stock:", await inventory.read("sku-1"))

    await manager.close()


asyncio.run(main())

manager.run() opens a fresh session for each attempt and retries only retryable conflicts.

For explicit transaction control:

async with manager.session(
    agent_id="planner",
    resources=[inventory],
) as session:
    stock = await session.read("sku-1")
    await session.write("sku-1", stock - 1)

    result = await session.commit()

# Exiting a session without committing aborts the transaction.

More runnable examples are available in examples/: inventory, idempotency, leases, and a PostgreSQL counter.

How it works

A session stages reads and writes. It does not apply state directly. At commit time, StatePact builds a CommitPlan; the selected backend validates the plan and applies it atomically, or rejects it with a classified conflict.

 AgentSession
      │
      │ staged reads / writes
      ▼
 Transaction
      │
      ▼
   CommitPlan
      │
      ├── OCC validation
      ├── 2PL lock manager
      ├── serializability audit
      └── retry / idempotency
      │
      ▼
 StorageBackend
      ├── MemoryBackend
      ├── PostgresBackend
      └── RedisBackend

 Supporting components:
   • event bus / transaction log / metrics
   • lease + fencing token

This separation allows the same transaction semantics to be tested against three different storage engines.

Isolation modes

StatePact offers five isolation modes:

Mode Read behavior Commit validation Guarantees for tracked keys Best suited for
none Latest None No guarantees; lost updates are expected Baselines and demos only
snapshot MVCC snapshot Write base versions (first-committer-wins) Prevents lost updates; write skew is possible High throughput when skew is acceptable
optimistic MVCC snapshot Every read and write base version Serializable for the tracked key set General default
serializable MVCC snapshot Optimistic validation plus serialization-graph audit Same guarantee as optimistic, with cycle diagnostics Audits and higher assurance
pessimistic Locked reads Strict 2PL, deadlock detection, and timeout Serializable for locked keys Hot keys and predictable contention

Read-only commits skip validation and do not consume a version.

The included write-skew benchmark uses the classic two-on-call-doctors scenario:

  • serializable rejects one transaction in 20/20 trials.
  • snapshot commits both transactions and violates the invariant in 20/20 trials.

Failure and retry semantics

Every transaction error should answer three questions:

  • Did the transaction commit?
  • Is retrying safe?
  • Which resource or key failed?

A ConflictError always means the transaction did not commit.

Exception Retryable? Meaning
ReadWriteConflict Yes A key read by this transaction changed before commit
WriteWriteConflict Yes Another transaction wrote the key first
StaleSnapshotError Yes The snapshot is no longer valid
DeadlockError Yes A wait-for cycle was broken by aborting this transaction
LockTimeoutError Yes Lock acquisition timed out
LeaseLostError No The fencing token is stale; re-acquire the lease
SerializationError Yes A serialization cycle was detected
RetryExhaustedError No The retry policy gave up
IdempotencyMismatchError No An idempotency key was reused with a different payload
StorageError No Backend or transport failure; commit state may be unknown

Retryable conflicts are safe to retry because the transaction did not commit. LeaseLostError requires acquiring a new lease first.

Unknown commit outcomes, such as transport failures, are recorded as FAILED and are not automatically retried. Use an idempotency_key to make resubmission safe.

Idempotency

Pass an idempotency_key to a session or to manager.run(). Replaying the same key returns the original CommitResult with replayed=True; the writes are not applied again.

The default fingerprint is value-insensitive. It hashes the agent ID and write footprint (resource, key, op). This is useful for read-modify-write retries: the newly computed value may differ, while the logical operation remains the same.

For strict payload binding, provide idempotency_fingerprint explicitly.

External side effects

External effects use a separate journal through IdempotentExecutor and MemoryEffectStore, with these transitions:

PENDING → EXECUTING → SUCCEEDED | FAILED

The journal separates reservation from execution. It provides at-most-once completion, not a distributed transaction. For details, see Idempotency and side effects.

Leases and fencing tokens

A lease by itself is not enough. A worker can pause, lose its lease, and wake up after another worker has taken ownership.

StatePact attaches a fencing token to writes made under a lease. The backend rejects any token older than the highest token it has observed for that resource.

lease = await manager.lease(
    "job",
    owner="worker-a",
    ttl_seconds=30,
)

async with manager.session(
    agent_id="worker-a",
    resources=[job],
    fence=lease.token,
) as session:
    ...

Tokens are monotonic high-water marks per resource. They are never reused, even after release or expiry.

See Leases and fencing.

Storage backends

Feature MemoryBackend PostgresBackend RedisBackend
Scope One process Multi-process Multi-process
Durable No Yes — tables Yes — key prefix
Commit atomicity asyncio lock DB transaction + advisory lock Lua script
MVCC history In-memory, garbage-collected statepact_history Capped Redis lists
Pessimistic locks Wait-for graph Polling + timeout Lua + TTL
Leases / fencing In-memory Durable Durable
Idempotency In-memory Durable TTL-based

PostgreSQL

PostgreSQL creates its schema on connect(). The commit path serializes validation and application behind one pg_advisory_xact_lock, making cross-process commits linearizable at the cost of throughput.

from statepact import TransactionManager
from statepact.storage.postgres import PostgresBackend

backend = await PostgresBackend.connect(
    "postgresql://user:pass@host/db"
)
manager = TransactionManager(backend)

Install the optional dependency with:

pip install "statepact[postgres]"

Redis

Redis executes each commit as a single Lua script. Install its optional dependency with:

pip install "statepact[redis]"

The memory backend is the semantic reference used by the test suite. It is not safe for coordination across processes.

See Backend details for schemas, key layouts, and trade-offs.

Agent framework integrations

Framework adapters live in statepact.integrations. They do not import their optional framework dependencies at module load.

LangGraph example

from statepact.integrations.langgraph import LangGraphCoordinator

coordinator = LangGraphCoordinator(
    manager,
    agent_id="researcher",
    resources=[inventory],
)


@coordinator.node
async def reserve(state, session):
    stock = await session.read("sku-1")

    if stock == 0:
        return {"status": "sold_out"}

    await session.write("sku-1", stock - 1)
    return {"status": "reserved"}


graph.add_node("reserve", reserve)

Available adapters

Integration Adapter Purpose
LangGraph LangGraphCoordinator Wrap graph nodes and merge returned patches after commit
CrewAI transaction_tool(...) / TransactionTool Synchronous tool for an atomic read-modify-write
AutoGen AutoGenTransactionMiddleware Transactional message handler with optional idempotency prefix
Custom agents run_transaction, read_modify_write, transactional Reusable helpers for any framework

See Integrations.

Benchmarks

Measured on 2026-11-07 using the benchmark runner in this repository. Raw artifact: run-20261107T051143Z.json (latest.json is identical).

Test environment

  • Windows 11 10.0.26300
  • Python 3.12.13, AMD64, 12-logical-CPU VM
  • PostgreSQL 16 and Redis 7 in Docker Desktop on the same host
  • 40 agents, 10 operations each (400 commits), and 20 write-skew trials
  • asyncio.sleep(0) inserted between read and write to guarantee interleaving

Throughput and retry amplification

Throughput is measured in committed operations per second. The value in parentheses is average attempts per operation.

Scenario Memory PostgreSQL Redis
Independent keys 1,534 (1.00) 53.7 (1.00) 229.4 (1.00)
Single hot key, zero backoff 344 (10.75) 7.7 (13.56) 18.8 (20.59)
Single hot key, jittered backoff 241 (3.17) 7.8 (12.90) 26.8 (14.15)
Read-only commits 6,619 133.4 427.4
Lease + commit (sequential) 3,013 18.3 88.9

Correctness under the same load

Check Memory PostgreSQL Redis
none: counter after 400 increments 10 ❌ 24 ❌ 10 ❌
optimistic: counter after 400 increments 400 ✅ 400 ✅ 400 ✅
snapshot: write-skew violations 20/20 ❌ 20/20 ❌ 20/20 ❌
serializable: write-skew violations 0/20 ✅ 0/20 ✅ 0/20 ✅

Interpreting the results

  • Correctness is the key result. With none, 376–390 of 400 updates are lost. With optimistic, all 400 increments are preserved.
  • Hot-key throughput is a worst-case workload. Forty agents contend on one key with immediate retries. Each retry is a full commit attempt, so serialized backends such as PostgreSQL pay a round-trip cost per conflict.
  • For production hot keys, consider sharding hot resources or using pessimistic locking with jitter.
  • PostgreSQL is correctness-first in this implementation. A global advisory lock per commit and several SQL round trips limit throughput. It is not a throughput-optimized commit engine.
  • These are not comparisons against other libraries. They document this engine's behavior on one machine.

Re-run the benchmark on your machine:

docker compose up -d postgres redis

STATEPACT_TEST_POSTGRES_URL=postgresql://statepact:statepact@localhost:5432/statepact \
STATEPACT_TEST_REDIS_URL=redis://localhost:6379/0 \
python benchmarks/run.py \
  --backends memory,postgres,redis \
  --agents 40 \
  --ops 10 \
  --trials 20

The runner is in-process by design: it measures the transaction engine rather than an HTTP server, so Locust is not used.

See Benchmark methodology.

API overview

Component Main responsibilities
TransactionManager resource, session, run, run_outcome, lease, health_check, close; exposes transactions and stats
AgentSession read, get, get_entry, version, write, delete, commit, abort; exposes state, read_set, write_set, commit_result
SharedResource Diagnostic reads: read, get, version, versions, history, state, snapshot
RetryPolicy max_attempts, base_delay, max_delay, multiplier, jitter, retry_on
Observability EventBus, TransactionLog, Metrics, and optional OpenTelemetry bridge

Full reference: API documentation.

Documentation

Start with the documentation site or the documentation index.

Guide Description
Quickstart Get a transaction running
Concepts Core StatePact concepts
Isolation and anomalies Isolation modes and concurrency anomalies
Backends Storage schemas and backend trade-offs
Leases and fencing Lease ownership and stale-worker protection
Idempotency and side effects Safe replays and external effects
Retries Retry behavior and policies
Integrations Agent framework adapters
Benchmarks Methodology and results
Operations Operational guidance
Limitations Known limitations

Development

Set up the development environment:

uv sync --extra dev

Run the unit tests and static checks:

uv run pytest -q
uv run ruff check src tests demo benchmarks
uv run mypy src

The test suite currently contains 121 tests. Fourteen integration tests are skipped unless service URLs are configured.

Run PostgreSQL and Redis integration tests:

docker compose up -d postgres redis

STATEPACT_TEST_POSTGRES_URL=postgresql://statepact:statepact@localhost:5432/statepact \
STATEPACT_TEST_REDIS_URL=redis://localhost:6379/0 \
uv run pytest tests/integration -q

The integration suite runs the same behavioral contract against PostgreSQL and Redis, including conflicts, idempotent replay, leases and fencing, lock timeouts, write skew, and a 20-agent concurrent increment.

Project status and limitations

Current release: 0.1.1 — Alpha

0.1.1 is a documentation and release-hardening patch with no API or behaviour changes, so upgrading from 0.1.0 requires no code changes. The transaction semantics, tests, and benchmarks are implemented. The API may change before 1.0.

Known limitations include:

  • Predicate and range reads are not tracked.
  • The PostgreSQL backend's global commit lock limits throughput.
  • The serializability audit is process-local.
  • External side effects require cooperation from the external system for idempotency.

See Known limitations before using StatePact in production.

What StatePact is not

  • Not a workflow or checkpoint engine. Use tools such as LangGraph or Temporal for workflow orchestration. StatePact coordinates shared mutable state, not message history.
  • Not a vector store or a database. PostgreSQL and Redis store coordination state; your domain data can live elsewhere.
  • Not exactly-once delivery to arbitrary external APIs. The effect journal provides at-most-once completion when the external action is idempotent or accepts an idempotency key.
  • Not a multi-database distributed transaction protocol.

License

StatePact is released under the Apache-2.0 License.


StatePact — safer shared state for concurrent AI agents.

Explore the repository · Try the live playground

Metadata

Release files for statepact 0.1.1

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

Source distribution (sdist)

Source distribution for statepact 0.1.1
File Size Uploaded Provenance Actions
statepact-0.1.1.tar.gz 132.8 kB With provenance Details for statepact-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for statepact 0.1.1
File Interpreter ABI Platform Actions
statepact-0.1.1-py3-none-any.whl Python 3 none any Details for statepact-0.1.1-py3-none-any.whl

Total release size: 227.6 kB

Release files / statepact-0.1.1.tar.gz

Download URL statepact-0.1.1.tar.gz
Size 132.8 kB
Tags Source
SHA-256 checksum
How to use checksums
8e0c3b2cf5467b0491c410fa3a164c0b681fe6aba702cbb0cd15dc3cef3edc28
BLAKE2b-256 checksum
How to use checksums
f05923c65012e6995ff428d7908427a07169158fdaa4f467858e46e5ace184e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 9, 2026.

Transparency log

Release files / statepact-0.1.1-py3-none-any.whl

Download URL statepact-0.1.1-py3-none-any.whl
Size 94.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9635cb98126639266bd626f2409389a40ffe2ec46e71a3794c16d9c860def2e6
BLAKE2b-256 checksum
How to use checksums
863d40c6c29055a63876838f62f0e86d2b479d953f294c6df81f893537dfb44f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 9, 2026.

Transparency log

Security for statepact 0.1.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for statepact-0.1.1.tar.gz

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 Nov 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

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