khora.integrations.langgraph.KhoraStore implements LangGraph’s
BaseStore interface so a StateGraph can use khora as its long-term
semantic memory in one line:
Khora.remember / Khora.recall / Khora.forget and
maps LangGraph’s (tuple[str, ...], str, dict) item shape onto khora
documents. Each (namespace_root, user_id) pair gets a deterministic
khora namespace_id (UUID5), so a second KhoraStore over the same
user sees the same memory.
Scope (v0.13)
KhoraStore: semantic long-term memory store. Shipped.KhoraCheckpointer: NOT shipped. LangGraph’sPostgresSaver(inlanggraph-postgres) already covers the opaque-blob checkpoint surface and khora offers no differentiator there. Revisit only if a single-DB-dependency story matters to a real user.
Install
langgraph>=1.0,<2.0. The adapter is also registered under
the khora.integrations entry-point group, so discover() returns it
without any explicit registration.
Constructor
| arg | default | notes |
|---|---|---|
kb | - | A connected Khora instance. Adapter does NOT own the lifecycle. |
user_id | - | Required, ≥ 8 chars, not in {"", "default", "anon", "anonymous", "user", "test"}. Disaster-mode prevention. |
namespace_root | "user_id" | Bucket key under which this app’s LangGraph namespaces live. |
app_id | "langgraph" | Free-form app identifier stamped into stored metadata. |
namespace_sep | "/" | Single-character separator used to flatten tuple namespaces. Must not appear in any tuple segment. |
index_config | None | Optional LangGraph IndexConfig. Only dims is consulted, and must match khora’s embedder dim or construction raises. |
skill_name | "general_entities" | khora extraction skill name forwarded to remember. |
entity_types | [] | Extraction whitelist. Empty list disables extraction (pure KV blob mode). |
relationship_types | [] | Same: empty disables. |
Method semantics
All 6 async methods are first-class. Sync variants (put, get,
search, delete, list_namespaces, batch) bridge through
khora.integrations._sync.run_sync, which rejects calls made from
inside a running event loop. From inside a graph node, use the async
methods. From a notebook or sync script, use the sync ones.
aput→Khora.rememberwithexternal_idderived from(flat_namespace, key). Overwriting an existing item deletes the previous document first so chunks don’t accumulate.aget→Khora.storage.get_document_by_external_idthen project metadata back to a LangGraphItem. ReturnsNonefor foreign documents (nolg_namespacein metadata).asearch(query=...)→Khora.recallthen map chunks toSearchItem. Withoutquery, falls back to alist_documentsscan.filteris applied client-side (exact match only in v1).adelete→Khora.forget. Missing keys are a silent no-op, matchingInMemoryStoresemantics.alist_namespaces: list documents in the bound khora namespace and aggregate distinctlg_namespacetuples. O(N_documents) scan, acceptable for bounded LangGraph workloads. Track a dedicated table at >= O(10⁴) docs.abatch→ serial dispatch over the per-op methods.
Ignored kwargs
ttl(per-item): khora has no per-item TTL. The adapter accepts it to satisfy the interface and emits oneRuntimeWarningperKhoraStoreinstance. UseKhora.forget_sessionfor bulk cleanup.index=False: khora always embeds. The adapter accepts the kwarg and emits oneRuntimeWarningperKhoraStoreinstance. Items remain retrievable.
Quickstart
example.py
examples/integrations/langgraph/example.py by
tools/check_examples_drift.py (CI gate).
Limits and future work
- Filter operators (
$gt,$lt, …): v1 supports exact match only. Operator support is a clean addition behind a feature flag. alist_namespacesSQL pushdown - the current O(N) scan is fine for typical workloads but not for hot multi-tenant deployments. ASELECT DISTINCT metadata->'lg_namespace'helper on the storage layer would fix it.- Checkpointer: explicit non-goal (see “Scope” above).