Skip to main content

StatePact

Transaction management for autonomous AI agents. Optimistic concurrency, MVCC, strict 2PL, serializability checking, retries, idempotent commits and lease/fencing tokens for state that multiple agents read and write.

CI Python Playground License Status

pip install statepact   # 0.1.0 release is pending; see Install below

The problem

An agent reads shared state, thinks, then writes it back:

stock = await session.read("sku-1")   # 1
await think()                         # three other agents also read 1
await session.write("sku-1", stock - 1)

Without a transaction boundary, two reservations both write 0: one unit is sold twice, or a counter loses updates, or two agents both decide they are the one on call. Framework checkpointers persist messages, not conflict detection across concurrent tool calls. StatePact adds the missing concurrency layer:

  • Isolation you choose — optimistic, snapshot, serializable, pessimistic, or none as an explicitly unsafe baseline.
  • Correct retries — a conflict re-runs the whole transaction on a fresh snapshot; an unknown failure is never silently retried.
  • Idempotent commits — the same operation replayed returns the original result instead of applying twice.
  • Leases with fencing tokens — a paused worker that wakes up after its lease expired cannot overwrite the new owner.
  • Backends you can deploy — process-local memory, PostgreSQL, or Redis.

StatePact is framework-independent. LangGraph, CrewAI and AutoGen adapters are thin optional wrappers; the same engine works with a custom agent loop.

Try the live concurrency playground. It is a browser simulation of the same validation rules (free static hosting), not the Python engine; the runnable Python demo is in demo/.

What it is not

  • Not a workflow/checkpoint engine (use LangGraph, Temporal, …). It coordinates shared mutable state, not message history.
  • Not a vector store or a database. PostgreSQL/Redis store the coordination state; your domain data can live anywhere.
  • Not exactly-once delivery to arbitrary external APIs. The effect journal gives at-most-once completion when the external action is idempotent or accepts an idempotency key. See docs/idempotency.md.
  • Not a multi-database distributed transaction protocol.

Install

0.1.0 is prepared and tested but not yet on PyPI (Trusted Publishing is being configured). Install from source in the meantime:

git clone https://github.com/mithun-27/StatePact
cd StatePact
python -m pip install .            # core, in-memory backend only
python -m pip install ".[postgres]" # + asyncpg
python -m pip install ".[redis]"    # + redis
python -m pip install ".[demo]"     # + Gradio playground

Requires Python 3.12+. The core dependency is Pydantic 2. PostgreSQL and Redis are optional extras. uv sync --extra dev sets up the development environment.

Quickstart

import asyncio
from statepact import RetryPolicy, TransactionManager


async def main() -> None:
    manager = TransactionManager()                 # in-memory backend
    inventory = manager.resource("inventory", {"sku-1": 10})
    await inventory.ensure(manager)

    async def reserve(session) -> bool:
        stock = await session.read("sku-1")
        await asyncio.sleep(0)                     # agent thinking between read and write
        if stock <= 0:
            return False
        await session.write("sku-1", stock - 1)
        return True

    ok = await manager.run(
        "agent-1",
        [inventory],
        reserve,
        retry_policy=RetryPolicy(max_attempts=100),
    )
    print(ok, await inventory.read("sku-1"))
    await manager.close()


asyncio.run(main())

manager.run opens a fresh session per attempt and retries only retryable conflicts. For explicit control, open a session yourself:

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()     # explicit: exiting without commit aborts

Runnable examples: examples/ — inventory, idempotency, leases, PostgreSQL counter.

Isolation modes

Mode Reads Commit validation Guarantees for tracked keys Use when
none latest none none — lost updates are expected baseline/demo only
snapshot MVCC snapshot write base versions (first-committer-wins) no lost updates; write skew possible high throughput, skew tolerated
optimistic MVCC snapshot every read + write base version serializable for the tracked key set default
serializable MVCC snapshot optimistic + serialization-graph audit same as optimistic, with cycle diagnostics audit/high assurance
pessimistic locked reads strict 2PL, deadlock detection/timeout serializable for locked keys hot keys, predictable contention

Reads are tracked at key granularity. Predicate/range reads (SELECT … WHERE) are not tracked, so a transaction that depends on "no rows matched" is not protected from a concurrent insert. Read-only commits skip validation and do not consume a version.

The benchmark demonstrates the difference on the classic write-skew anomaly (two on-call doctors): serializable rejects one transaction in 20/20 trials; snapshot commits both and violates the invariant in 20/20 trials.

Failure semantics

Every error answers three questions: did it commit, is retry safe, which resource/key failed. ConflictError always means not committed.

Exception Retryable Meaning
ReadWriteConflict yes a key this transaction read changed before commit
WriteWriteConflict yes another transaction wrote a key first
StaleSnapshotError yes the snapshot is no longer valid
DeadlockError yes wait-for cycle 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 a key was reused with a different payload
StorageError no backend/transport failure; commit state may be unknown

Retryable exceptions can be retried safely because the transaction did not commit. LeaseLostError is not retryable; the caller must re-acquire a lease before retrying. Unknown commit outcomes (e.g., transport failures) are logged as FAILED and are not retried; use idempotency_key to make the operation safe to resubmit.

Idempotency

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

The default fingerprint is value-insensitive: it hashes the agent id and the write footprint (resource, key, op). A retried read-modify-write computes a different new value but is still the same operation, so it correctly replays instead of raising a mismatch. If you need strict payload binding, pass idempotency_fingerprint explicitly.

External side effects use a separate journal (IdempotentExecutor, MemoryEffectStore) with PENDING → EXECUTING → SUCCEEDED|FAILED transitions and reserve/execute phases. This is at-most-once completion, not a distributed transaction.

Leases and fencing

A lease alone is unsafe: an owner can pause, lose the lease, and wake up later. Every write under a lease therefore carries the lease token, and the backend rejects any token older than the highest token it has observed:

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

Tokens are monotonic high-water marks per resource and are never reused, even after release or expiry. See docs/leases.md.

Backends

MemoryBackend PostgresBackend RedisBackend
Scope one process multi-process multi-process
Durable no yes (tables) yes (prefix)
Commit atomicity asyncio lock DB transaction + advisory lock Lua script
MVCC history in-memory, GC'd 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'd
Extra — pip install .[postgres] pip install .[redis]

PostgreSQL creates its schema on connect(). The commit path serializes validation and application behind one pg_advisory_xact_lock, which makes cross-process commits linearizable at the cost of throughput. Redis executes each commit as one Lua script. The memory backend is the semantic reference and is used by the test suite; it is not safe across processes.

from statepact.storage.postgres import PostgresBackend

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

See docs/backends.md for schemas, key layout and trade-offs.

Integrations

Framework adapters live in statepact.integrations and do not import the framework at module load:

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)
  • statepact.integrations.langgraph — wrap graph nodes; merges the returned patch into state after commit.
  • statepact.integrations.crewai — transaction_tool(...) / TransactionTool, a synchronous CrewAI tool over one atomic read-modify-write.
  • statepact.integrations.autogen — AutoGenTransactionMiddleware, a transactional message handler with optional idempotency prefix.
  • statepact.integrations.base — run_transaction, read_modify_write, transactional for any other framework.

See docs/integrations.md.

Benchmarks

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

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), 20 write-skew trials. The workload injects asyncio.sleep(0) between read and write so interleaving is guaranteed.

Throughput (committed operations/second) and retry amplification

Scenario memory PostgreSQL Redis
Independent keys 1,534 (1.00 attempt/op) 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 ✅

How to read these numbers:

  • The correctness rows are the point. none loses 376–390 of 400 updates; transactions prevent that, and the invariant holds at every commit.
  • Hot-key throughput is a worst case: 40 agents hammer one key with immediate retries. Every retry is a full commit attempt, so backends that serialize commits (PostgreSQL) pay a round-trip tax per conflict. Shard hot resources or use pessimistic locking with jitter for production hot keys.
  • The PostgreSQL backend is correctness-first: one advisory lock per commit, several SQL round trips, and Docker port forwarding on Windows. It is not a throughput-optimized commit engine.
  • These are not comparisons with other libraries. They document this engine on one machine. Re-run on yours:
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 engine, not an HTTP server, so Locust is not used. See docs/benchmarks.md for methodology.

Architecture

AgentSession ── staged reads/writes ──▶ Transaction ── CommitPlan ──▶ StorageBackend
      │                                      │                         │
      │                                      ├─ OCC validation         ├─ MemoryBackend
      │                                      ├─ 2PL lock manager       ├─ PostgresBackend
      │                                      ├─ serializability audit  └─ RedisBackend
      │                                      └─ retry / idempotency
      ├─ event bus / transaction log / metrics
      └─ lease + fencing token

The session never applies state. It builds a CommitPlan; the backend validates and applies it atomically, or rejects it with a classified conflict. That split is what makes the same semantics testable against three very different storage engines.

API at a glance

  • TransactionManager(backend=None) — resource, session, run, run_outcome, lease, health_check, close; transactions, stats.
  • AgentSession — read, get, get_entry, version, write, delete, commit, abort; state, read_set, write_set, commit_result.
  • SharedResource — non-transactional diagnostics: read, get, version, versions, history, state, snapshot.
  • RetryPolicy(max_attempts, base_delay, max_delay, multiplier, jitter, retry_on).
  • Observability: EventBus, TransactionLog, Metrics; optional OpenTelemetry bridge in statepact.observability.otel.

Full reference: docs/api.md.

Documentation

Start at docs/index.md, or jump to:

Development

uv sync --extra dev
uv run pytest -q                 # 121 tests; 14 integration tests skip without service URLs
uv run ruff check src tests demo benchmarks
uv run mypy src
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 behavioural contract against PostgreSQL and Redis (tests/integration/backend_suite.py), including conflicts, idempotent replay, leases/fencing, lock timeouts, write skew and a 20-agent concurrent increment.

Status

0.1.0 — alpha. The semantics, tests and benchmarks are real; the API may still change before 1.0. Known limitations are listed in docs/limitations.md — notably: no predicate/range tracking, the PostgreSQL backend is throughput-limited by its global commit lock, the serializability audit is process-local, and external side effects require cooperation for idempotency.

License

Apache-2.0. See LICENSE.

Metadata

Release files for statepact 0.1.0

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.0
File Size Uploaded Provenance Actions
statepact-0.1.0.tar.gz 131.6 kB With provenance Details for statepact-0.1.0.tar.gz

Built distribution (wheel)

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

Total release size: 225.5 kB

Release files / statepact-0.1.0.tar.gz

Download URL statepact-0.1.0.tar.gz
Size 131.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a54acddeab278654a264f152db4cc836aceb16d4b13e9570050e1814205a27b8
BLAKE2b-256 checksum
How to use checksums
02a45237f7f00e232a81cbe193c901650fb752b72878e2af90c6e89174d0236e
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 7, 2026.

Transparency log

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

Download URL statepact-0.1.0-py3-none-any.whl
Size 93.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4c6045bf9b7e566483d708e0d975ea355e167bdf6024db80cf2eb5acee616222
BLAKE2b-256 checksum
How to use checksums
118fbc4f282b4c28ac74cdb8c6283b7e6648782a22d6fff4866a676068cc83a1
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 7, 2026.

Transparency log

Security for statepact 0.1.0

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.0.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 7, 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.0 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