Skip to main content

A framework-agnostic, event-sourced state platform for AI agents.

Project description

statefold

ci license

A framework-agnostic, event-sourced state platform for AI agents — works under LangGraph, CrewAI, Agno, raw tools, or MCP.

statefold landing page

The observability console: KPI bar, per-session cost/error table, and a run waterfall with a time-travel slider — replay any run step by step and watch state and spend rewind.

statefold console — run waterfall with time-travel

State is a fold over an append-only event log, not a mutable blob. That one choice gives you durable resume, time-travel, replay, and cheap branching for free — the things that turn "a memory library" into a platform.

Why

Every agent framework has its own, incompatible state model (LangGraph checkpointers, CrewAI memory, Agno storage) and MCP is stateless by design. statefold is a neutral substrate they all plug into, so state is portable across frameworks and shareable across agents.

Quickstart (no services required)

pip install -e ".[dev]"
python -m statefold.demo      # crash+resume, time-travel, fork — all in-memory
pytest                          # the core guarantees, proven
import asyncio
from statefold import InMemoryStore, Scope, Event

async def main():
    store = InMemoryStore()
    scope = Scope(tenant="acme", agent="researcher", session="sess-1")

    head = await store.append(scope, [
        Event(kind="message",     payload={"role": "user", "content": "book a hotel"}),
        Event(kind="state_delta", payload={"set": {"stage": "searching"}}),
    ], expected_seq=0)

    print(await store.get_state(scope))            # folded current state
    print(await store.get_state(scope, as_of=1))   # time-travel to seq 1

asyncio.run(main())

Concepts

Concept What it is
Scope Addresses state by (tenant, agent, session, thread). A stream = one thread.
Event Append-only unit with a gap-free per-stream seq.
Reducer Pure (state, payload) -> state. Register your own event kinds via @register.
expected_seq Optimistic concurrency — stale writes are rejected, callers retry.
checkpoint A snapshot; a pure optimization, never the source of truth.
fork O(1) branch of a stream for what-if runs and replays.

Backends

  • InMemoryStore — tests, local dev, the example above.
  • PostgresStore — durable production store (pip install statefold[postgres]). Append-only events table with UNIQUE (stream, seq).
from statefold.postgres import PostgresStore
store = await PostgresStore.connect("postgresql://localhost/statefold")

UI — event-log inspector

A zero-build dashboard (pip install statefold[ui], no npm):

statefold-ui                                    # in-memory + demo data
STATEFOLD_DSN=postgresql://... statefold-ui    # inspect a real store
# -> http://127.0.0.1:8787

Browse every stream, click through the event timeline, and drag the time-travel slider to see the folded state and the usage/cost breakdown at any sequence number. All read-only views over the same event log.

SDK instrumentation (OpenAI, Anthropic, Google)

LLM client SDKs aren't frameworks, so they get instrumentation, not adapters: wrap the client once and every completion call is auto-recorded as an llm_call event — model, tokens, latency — feeding cost tracking and the console with no manual calls:

from statefold.instrument import instrument   # or instrument_openai / _anthropic / _google

client = instrument(OpenAI(), session)          # same client, now recorded
client.chat.completions.create(model="gpt-5", messages=[...])

instrument(Anthropic(), session)                # messages.create
instrument(genai.Client(), session)             # models.generate_content

Sync and async clients both work; wrapping is duck-typed (no SDK is a dependency) and idempotent. Responses without a usage block (e.g. streaming without include_usage) are recorded with zero tokens and a no_usage marker — never silently dropped.

capture_content=True additionally logs the last user message and the assistant reply as message events (opt-in, off by default for privacy and payload size; captured messages are marked captured: true and truncated to 4k chars).

Telemetry & cost

The event log is the telemetry — no separate collector, no double-writes. llm_call events fold into a usage view, and everything the core gives you applies to spend: time-travel ("what had this session cost as of step N?"), per-tenant/session isolation, and a replayable billing audit.

from statefold import register_pricing

register_pricing("claude-sonnet-5", input_per_mtok=3.00, output_per_mtok=15.00)

await s.add_llm_call("claude-sonnet-5", input_tokens=1200, output_tokens=450,
                     latency_ms=980)
u = await s.usage()          # {"llm_calls", "cost_usd", "by_model", "tools", ...}
await s.usage(as_of=40)      # spend as of step 40

Pricing is never hardcoded (prices change) — register your own, or pass an explicit cost_usd per call. Registering prices later back-fills cost on the next read, because cost is computed at fold time; the log never needs rewriting. Unknown-model calls are surfaced as uncosted_calls, never a silent $0. Tool latency and errors are aggregated automatically, including for every call through the MCP proxy.

Semantic recall

Bring any embedder ((text) -> list[float], sync or async) and recall becomes semantic; without one, stores fall back to lexical matching:

store = InMemoryStore(embedder=my_embedder)
# Postgres: uses pgvector (HNSW, cosine) when the extension is available,
# otherwise ranks in Python over stored embeddings — same API either way.
store = await PostgresStore.connect(dsn, embedder=my_embedder, embedding_dim=1536)

Pre-computed vectors are also supported (remember(..., embedding=...), recall_vec(...)) for frameworks that embed upstream, like CrewAI.

Adapters

Point an existing framework at the store — no changes to your agent code:

from statefold import InMemoryStore
from statefold.adapters.langgraph import PlatformCheckpointer

store = InMemoryStore()  # or PostgresStore.connect(...)
graph = builder.compile(checkpointer=PlatformCheckpointer(store, tenant="acme", agent="researcher"))
# crash mid-run -> re-invoke with the same thread_id -> resumes from the event log

Every LangGraph checkpoint and pending-write becomes an event in the durable log, so the log is the graph's memory — durable resume, history, and replay come for free. See tests/test_langgraph_adapter.py for a crash-and-resume proof.

CrewAI (>= 1.x) — a StorageBackend for CrewAI's unified memory; every MemoryRecord lives in the durable store and survives restarts:

from statefold.adapters.crewai import AgentStateBackend

backend = AgentStateBackend(store, tenant="acme", agent="crew1")
# plug into CrewAI's memory as its storage backend

(A legacy AgentStateStorage for CrewAI 0.x ExternalMemory is also included.)

Agno (v2) — a Db whose sessions and user memories write through to the event log and rehydrate on restart:

from statefold.adapters.agno import AgentStateDb

db = AgentStateDb(store, tenant="acme", agent="assistant")
agent = Agent(db=db, add_history_to_context=True, ...)
# restart the process -> sessions and memories come back from the log

Anything else — the SessionState façade (no framework required):

from statefold.adapters.generic import SessionState

s = SessionState(store, tenant="acme", agent="bot", session="sess-1")
await s.add_message("user", "book a hotel")
await s.set(stage="searching")
await s.remember("user prefers window seats")

Because every adapter writes to the same store, state is shared across frameworks: a memory saved by an Agno agent is recallable by a CrewAI crew or a raw MCP agent on the same scope.

MCP state server

Any MCP-capable agent (raw, CrewAI, Agno, a Claude/GPT tool loop) gets durable state by adding one server — no framework integration:

pip install -e ".[mcp]"
statefold-mcp                                   # in-memory (ephemeral)
STATEFOLD_DSN=postgresql://localhost/statefold statefold-mcp   # durable

Tools: state_append, state_get (with as_of time-travel), state_head, state_checkpoint, memory_remember, memory_recall. tenant/agent come from env so a client can't spoof another tenant's scope.

Stateful MCP proxy

MCP servers are stateless per call by design. The proxy wraps any third-party MCP server, unmodified: it mirrors the downstream's tools 1:1 and records every call + result as events in the durable log.

statefold-mcp-proxy -- npx -y @modelcontextprotocol/server-filesystem C:/data
STATEFOLD_REPLAY=1 statefold-mcp-proxy -- <cmd...>   # deterministic replay

You get an auditable, time-travelable history of every MCP interaction per session — and with STATEFOLD_REPLAY=1, identical calls return the recorded result without re-executing side effects (reproduce a run exactly, or resume one safely).

Roadmap

  • Event-sourced core (in-memory + Postgres)
  • LangGraph BaseCheckpointSaver adapter
  • MCP state server (any agent gets state by adding one server)
  • Stateful MCP proxy (wrap any third-party MCP server, unmodified)
  • CrewAI 1.x StorageBackend + Agno v2 Db adapter + generic façade
  • Semantic recall: pluggable embedders, pgvector with automatic fallback
  • SSE event tail for live subscriptions
  • TypeScript SDK over the same HTTP/MCP surface

License

Apache-2.0

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

statefold-0.1.0.tar.gz (277.7 kB view details)

Uploaded Source

Built Distribution

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

statefold-0.1.0-py3-none-any.whl (54.2 kB view details)

Uploaded Python 3

File details

Details for the file statefold-0.1.0.tar.gz.

File metadata

  • Download URL: statefold-0.1.0.tar.gz
  • Upload date:
  • Size: 277.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for statefold-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c0fed65b9a0f16650de7dfd4a06be109c7f1d6b3fd605bf39ef057277120a62e
MD5 0effc0ab49820858bfb95a62f2ed0898
BLAKE2b-256 c566ce55945980cc32f80075813c20cf237e1af8c9e0c7ae2eb71950adb2e490

See more details on using hashes here.

File details

Details for the file statefold-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: statefold-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 54.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for statefold-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ce8cec08df9add4834a6bf628c6b6ad7d45af6853381ec5a7564d1d1403fea88
MD5 1b031ae33a25df8429819564f77ebd78
BLAKE2b-256 a0757587a39caa7892a9342825b25a04bb3dd313f6a31c2a391402b12578ef63

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 Pingdom Monitoring Sentry Error logging StatusPage Status page