# LangGraph checkpointer and store

> Persist LangGraph agents in HotKV. A checkpointer that keeps graph state between runs, and a long-term memory store with filters, semantic search and expiry. No HotKV license needed.

URL: https://docs.hotkv.com/ai/langgraph/


`langgraph-checkpoint-hotkv` gives [LangGraph](https://langchain-ai.github.io/langgraph/)
agents two kinds of persistence in HotKV:

- **Checkpointers** (`HotKVSaver`, `AsyncHotKVSaver`): a graph keeps its state
  between runs, can be interrupted and resumed, and can go back to any earlier
  step and branch from it.
- **A store** (`HotKVStore`, `AsyncHotKVStore`): long-term memory shared across
  threads and runs. Items are found by namespace, by filter or, with an
  embedding model, by meaning, and they can expire.

It works on a HotKV server with or without an enterprise license.

It is open source (Apache-2.0):
[github.com/hotkv-ltd/hotkv-langgraph](https://github.com/hotkv-ltd/hotkv-langgraph).

## Install

```bash
pip install langgraph-checkpoint-hotkv
```

It needs Python 3.10 or newer and `langgraph-checkpoint` 4.2 or newer.

## Checkpointer

```python
from langgraph.checkpoint.hotkv import HotKVSaver

with HotKVSaver.from_conn_string("hotkv://localhost:6379") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    graph.invoke({"messages": [("user", "hi")]}, {"configurable": {"thread_id": "1"}})
```

For `ainvoke` and `astream`, use the async saver:

```python
from langgraph.checkpoint.hotkv import AsyncHotKVSaver

async with AsyncHotKVSaver.from_conn_string("hotkv://localhost:6379") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    await graph.ainvoke(...)
```

`from_conn_string` accepts `hotkv://`, `hotkvs://` (TLS), `redis://` and
`rediss://` URLs. Extra keyword arguments go to the HotKV client. To share a
connection pool, build the saver yourself: `HotKVSaver(hotkv.from_url(url))`.

Everything is stored under a key prefix (`langgraph` by default; set
`prefix=`), so several applications or environments can share one server.

### Housekeeping

```python
checkpointer.delete_thread("1")                     # forget a conversation
checkpointer.prune(["1"], strategy="keep_latest")   # keep only the newest checkpoint of a thread
checkpointer.copy_thread("1", "2")                  # branch a whole thread
```

`prune` understands LangGraph's `DeltaChannel`: when a thread uses one, it keeps
the older checkpoints the channel needs to rebuild its state, so the state is
never silently emptied. Checkpoints do not expire by themselves. Prune or
delete old threads on a schedule.

## Store

```python
from langgraph.store.hotkv import HotKVStore

with HotKVStore.from_conn_string("hotkv://localhost:6379") as store:
    store.put(("users", "ada"), "prefs", {"theme": "dark"})
    store.get(("users", "ada"), "prefs")
    store.search(("users",), filter={"theme": "dark"})
    store.list_namespaces(prefix=("users",))
    graph = builder.compile(checkpointer=checkpointer, store=store)
```

The filter operators are `$eq`, `$ne`, `$gt`, `$gte`, `$lt` and `$lte`. Results
come newest first.

### Semantic search

Give the store an embedding model and it indexes what you put, using
[HotKV's vector search](https://docs.hotkv.com/ai-features/index.md#vector-search-vsim):

```python
from langchain.embeddings import init_embeddings

with HotKVStore.from_conn_string(
    "hotkv://localhost:6379",
    index={"dims": 1536, "embed": init_embeddings("openai:text-embedding-3-small")},
) as s:
    s.put(("docs",), "a", {"text": "cats purr and chase mice"})
    s.search(("docs",), query="a pet that hunts", limit=3)    # results carry .score
```

`fields` (default the whole item) and a per-`put` `index=[...]` choose what is
embedded. `index=False` keeps an item out of the index. A filter is applied to
the nearest neighbours the index returns, so a very selective filter on a large
namespace can return fewer results than `limit`.

### Expiry

```python
HotKVStore.from_conn_string(url, ttl={"default_ttl": 60})   # minutes, for every new item
store.put(ns, key, value, ttl=5)                             # or per item
```

A read or a search refreshes the clock unless you pass `refresh_ttl=False`.
Expiry is kept in whole seconds on the server, so a `ttl` of 0.02 minutes lasts
2 seconds.

## Things to know

- Use a standalone or replicated HotKV server. The savers and the store touch
  several keys in one transaction or script, which a cluster rejects unless
  every key falls in one slot. Cluster mode is not supported.
- The async classes also answer the synchronous methods when they are called
  from another thread than the event loop that owns the connection. That is how
  LangGraph runs a sync node of an async graph. A blocking call on the event
  loop's own thread raises `asyncio.InvalidStateError` instead of deadlocking.
- Search without a `query` reads every item of the matching namespaces, which
  suits namespaces of up to a few thousand items, such as one user's memories.
  For larger collections use a query.
- Thread ids, checkpoint namespaces, store keys and namespace labels must not
  contain the control characters U+001E and U+001F. The store also rejects `.`
  in a label, as LangGraph's other stores do.
- `list_namespaces` can show a namespace whose items all expired until
  something searches it.

> **Note**
> This is separate from HotKV's own [agent memory](https://docs.hotkv.com/ai-features/index.md#agent-memory-amem)
> commands, which need a license. The LangGraph store uses the core data types and
> vector search only.

## Next steps

- [AI features guide](https://docs.hotkv.com/ai-features/index.md)
- [MCP server](https://docs.hotkv.com/ai/mcp-server/index.md)
- [Caching gateway](https://docs.hotkv.com/ai/gateway/index.md)