Metadata-Version: 2.4
Name: statefold
Version: 0.1.0
Summary: A framework-agnostic, event-sourced state platform for AI agents.
Author: Joshua
License: Apache-2.0
License-File: LICENSE
Keywords: agents,agno,crewai,event-sourcing,langgraph,mcp,state
Requires-Python: >=3.10
Provides-Extra: agno
Requires-Dist: agno>=2; extra == 'agno'
Provides-Extra: crewai
Requires-Dist: crewai>=1; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: agno>=2; extra == 'dev'
Requires-Dist: asyncpg>=0.29; extra == 'dev'
Requires-Dist: crewai>=1; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: langgraph>=0.2; extra == 'dev'
Requires-Dist: mcp>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: starlette>=0.40; extra == 'dev'
Requires-Dist: uvicorn>=0.30; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: postgres
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
Provides-Extra: ui
Requires-Dist: starlette>=0.40; extra == 'ui'
Requires-Dist: uvicorn>=0.30; extra == 'ui'
Description-Content-Type: text/markdown

# statefold

[![ci](https://github.com/Joshuaakaspace/statefold/actions/workflows/ci.yml/badge.svg)](https://github.com/Joshuaakaspace/statefold/actions/workflows/ci.yml)
[![license](https://img.shields.io/badge/license-Apache--2.0-e3b341)](LICENSE)

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

![statefold landing page](docs/landing.png)

*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](docs/console.png)

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)

```bash
pip install -e ".[dev]"
python -m statefold.demo      # crash+resume, time-travel, fork — all in-memory
pytest                          # the core guarantees, proven
```

```python
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)`.

```python
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):

```bash
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:

```python
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.

```python
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:

```python
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:

```python
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:

```python
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:

```python
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):

```python
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:

```bash
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.

```bash
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

- [x] Event-sourced core (in-memory + Postgres)
- [x] LangGraph `BaseCheckpointSaver` adapter
- [x] MCP state server (any agent gets state by adding one server)
- [x] Stateful MCP proxy (wrap any third-party MCP server, unmodified)
- [x] CrewAI 1.x StorageBackend + Agno v2 Db adapter + generic façade
- [x] 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
