A framework-agnostic, event-sourced state platform for AI agents.
Project description
statefold
A framework-agnostic, event-sourced state platform for AI agents — works under LangGraph, CrewAI, Agno, raw tools, or MCP.
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.
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-onlyeventstable withUNIQUE (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
BaseCheckpointSaveradapter - 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0fed65b9a0f16650de7dfd4a06be109c7f1d6b3fd605bf39ef057277120a62e
|
|
| MD5 |
0effc0ab49820858bfb95a62f2ed0898
|
|
| BLAKE2b-256 |
c566ce55945980cc32f80075813c20cf237e1af8c9e0c7ae2eb71950adb2e490
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ce8cec08df9add4834a6bf628c6b6ad7d45af6853381ec5a7564d1d1403fea88
|
|
| MD5 |
1b031ae33a25df8429819564f77ebd78
|
|
| BLAKE2b-256 |
a0757587a39caa7892a9342825b25a04bb3dd313f6a31c2a391402b12578ef63
|