All integrations

LangGraph integration — CLS++ as a long-term memory BaseStore

clsplusplus.integrations.langgraph.CLSMemoryStore is a native subclass of langgraph.store.base.BaseStore — the long-term-memory store you pass as store= when you compile a LangGraph graph. It routes every store operation through the CLS++ brain, so your agent's long-term memory is persistent, shared across processes/machines, and reusable across AI vendors that point at the same CLS++ namespace.

Implemented against LangGraph 1.x (BaseStore, Op, Item, SearchItem re-exported from langgraph-checkpoint). CLSMemoryStore implements the two abstract methods BaseStore requires — batch(ops) and abatch(ops) — so the inherited get / put / search / delete / list_namespaces (and their async variants) all work and funnel through CLS++.

Install

pip install clsplusplus[langgraph]

langgraph is an optional extra. The base package imports fine without it; only this integration requires langgraph installed.

Environment

Get an API key from https://www.clsplusplus.com/profile#api-keys, then:

export CLS_API_KEY="cls_live_..."
# optional — defaults to https://www.clsplusplus.com
export CLS_BASE_URL="https://www.clsplusplus.com"

CLSMemoryStore(user=..., api_key=..., url=...) also accepts these directly; unset values fall back to the env vars above.

Usage

Pass the store to compile(...); LangGraph injects it into any node that declares a store parameter.

from langgraph.graph import StateGraph, START, MessagesState
from langgraph.store.base import BaseStore
from clsplusplus.integrations.langgraph import CLSMemoryStore

# One CLS++ "brain" namespace for this agent/tenant.
store = CLSMemoryStore(user="my-agent")


def remember(state: MessagesState, *, store: BaseStore):
    user_id = state.get("user_id", "anon")
    last = state["messages"][-1].content
    # Write a long-term memory. The value is a dict; it is JSON-serialized
    # into CLS++ and the (namespace, key) is preserved in metadata.
    store.put(("memories", user_id), key="latest", value={"text": last})
    return state


def recall(state: MessagesState, *, store: BaseStore):
    user_id = state.get("user_id", "anon")
    # Semantic recall — this is CLS++'s primary, best-supported operation.
    hits = store.search(("memories", user_id), query=state["messages"][-1].content, limit=5)
    context = "\n".join(item.value.get("text", "") for item in hits)
    # ... inject `context` into your prompt ...
    return state


builder = StateGraph(MessagesState)
builder.add_node("recall", recall)
builder.add_node("remember", remember)
builder.add_edge(START, "recall")
builder.add_edge("recall", "remember")

# IMPORTANT: pass the store at compile time.
graph = builder.compile(store=store)

Async graphs work too — CLSMemoryStore implements abatch, so astore.aput / asearch / aget are available (they run the sync CLS++ client in a worker thread).

Operation mapping & limitations

LangGraph op CLS++ behavior
put(ns, key, value) Brain.learn(json(value), source="langgraph", langgraph_namespace=ns, langgraph_key=key)
put(ns, key, None) (delete) Brain.forget(key) — best-effort semantic delete
search(prefix, query=..., limit=...) Brain.ask(query, limit)list[SearchItem] with rank-derived descending score
get(ns, key) best-effort: semantic recall seeded by key, returns the top hit
delete(ns, key) Brain.forget(key) — best-effort semantic delete
list_namespaces(...) returns []

CLS++ is a semantic memory (vector recall), not an exact key-value table. That makes search the natural, fully-supported access path. The following are intentional, documented limitations of mapping a KV interface onto semantic recall:

  • get is best-effort. It returns the memory most semantically similar to the key, which is usually — but not guaranteed to be — the exact item written under that key. For exact KV semantics use a KV-backed store (e.g. PostgresStore); use CLSMemoryStore where search is the main path.
  • delete is best-effort. Brain.forget(key) removes the closest semantic match to the key, not a guaranteed exact (namespace, key) row.
  • list_namespaces returns []. The CLS++ SDK does not expose an enumerable index of written (namespace, key) tuples. Namespace identity is still preserved per item in metadata for relate-back; it is just not enumerable.
  • TTL is server-managed. supports_ttl is False; per-item TTL passed to put is ignored — manage retention via CLS++.

The same BaseStore adapter pattern extends to other frameworks (LlamaIndex, AutoGen) that accept a pluggable memory backend.

See also