* feat: add LangGraph integration with tools, nodes, and store patterns Add hindsight-langgraph SDK providing three integration patterns: - Tools: retain/recall/reflect as LangChain tools for ReAct agents - Nodes: automatic memory injection and storage as graph steps - Store: LangGraph BaseStore implementation for checkpoint-based memory Fix: remove `from __future__ import annotations` in nodes.py which prevented LangGraph from passing RunnableConfig to node functions (runtime type inspection saw string annotations instead of actual types). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: register langgraph with independent versioning system - Set version to 0.1.0 (integrations are versioned independently) - Add langgraph to VALID_INTEGRATIONS in release-integration.sh - Add changelog page for langgraph integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove manual cookbook recipe page The sync-cookbook script will auto-generate this from the notebook in hindsight-cookbook once PR #17 is merged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: comprehensive improvements to langgraph integration Code fixes: - Retain node only stores latest messages instead of all history (prevents duplicates) - Handle multimodal msg.content (list type) in nodes - Fix store docstring separator "/" → "." - Apply search filters before pagination in store - Add ttl parameter to store.aput for LangGraph BaseStore compat - Fix _ensure_bank to not cache failed bank creations - Fix falsy value bugs (or → is not None) in tools - Remove from __future__ import annotations from all files - Consistent default budget="mid" across tools/nodes/store - Bump langgraph floor to >=0.3.0, remove duplicate dev deps Docs fixes: - Fix broken Cloud client example (base_url is required) - Complete API reference tables with all parameters - Add Limitations and Notes section (async-only store, etc.) - Add Requirements section - Fix broken cookbook link and Cloud claim in blog post All 61 unit tests pass. E2E tested against Hindsight Cloud: tools, nodes, store, configure(), multimodal content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove blog post (lives in hindsight-marketing-content) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove Hindsight Cloud section from langgraph docs Keep OSS docs self-hosted-first, consistent with other integration docs (crewai, pydantic-ai, agno). Cloud setup details live in the cookbook notebooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: explicitly mention LangChain compatibility in langgraph integration The tools pattern (create_hindsight_tools) only depends on langchain-core and works with plain LangChain via bind_tools() — no LangGraph required. Update docs to make this clear with both LangGraph and LangChain quick start examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review findings 1. Guard manual test files with if __name__ == "__main__" so pytest doesn't collect and execute them during test runs 2. Remove semantic fallback in HindsightStore.aget() — only return exact document_id matches, not unrelated semantic search hits 3. Make langgraph an optional dependency — tools pattern only needs langchain-core. Install with pip install hindsight-langgraph[langgraph] for nodes and store patterns. Lazy imports with clear error messages. 4. Clean up README to be self-hosted-first, consistent with other integration docs 5. Update docs requirements section to reflect optional langgraph dep Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback for langgraph integration - Fix #2: Add per-bank asyncio.Lock to _ensure_bank for concurrency safety - Fix #3: Clamp search score to max(0.0, ...) to prevent negative values - Fix #4: Implement suffix matching in _handle_list_namespaces - Fix #5: Truncate namespaces to max_depth instead of filtering (per BaseStore contract) - Fix #6: Remove list_namespaces/alist_namespaces overrides — let base class handle prefix=/suffix= kwargs - Fix #7: Document ephemeral namespace tracking and get() limitations in class docstring - Fix #8: Add stable ID to recall node SystemMessage, document ordering behavior - Fix #9: Change budget/max_tokens/recall_tags_match defaults to None so global config fallback works - Fix #10: Conditionally populate __all__ so import * works without langgraph installed - Fix #11: Bump langgraph lower bound from >=0.3.0 to >=0.5.0 - Fix #12: Extract _resolve_client to shared _client.py module Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address remaining review gaps for langgraph integration - Add output_key parameter to create_recall_node for prompt ordering control - Add prefix/suffix/combined filter tests for list_namespaces - Add output_key unit tests (memory text, none on empty, none on error) - Remove unused imports and backward-compat alias in tools.py - Update docs with output_key usage example and API reference Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: relax langgraph version constraint to >=0.3.0 Research confirmed all required APIs (BaseStore, SearchItem, Result, GetOp, PutOp, SearchOp, ListNamespacesOp) are available since langgraph-checkpoint 2.0.7, which maps to langgraph >=0.2.63. Using >=0.3.0 as a clean semver boundary — >=0.5.0 was unnecessarily conservative and excluded many compatible versions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
| sidebar_position |
|---|
| 7 |
LangGraph / LangChain
Persistent long-term memory for LangGraph and LangChain agents via Hindsight. Three integration patterns at different abstraction levels — the tools pattern works with both LangChain and LangGraph, while nodes and the BaseStore adapter are LangGraph-specific.
Features
- Memory Tools — retain, recall, and reflect as LangChain
@toolfunctions compatible withbind_tools()andToolNode. Works with both LangChain and LangGraph — no LangGraph dependency required for this pattern. - Graph Nodes (LangGraph) — Pre-built nodes that auto-inject memories before LLM calls and auto-store after responses
- BaseStore Adapter (LangGraph) — Drop-in
BaseStoreimplementation backed by Hindsight, for LangGraph's native memory patterns - Dynamic Banks — Resolve bank IDs per-request from
RunnableConfigfor per-user memory - Async-Native — Uses
aretain,arecall,areflectdirectly — no thread-pool workarounds
Installation
pip install hindsight-langgraph
Quick Start: Tools (LangChain & LangGraph)
The tools pattern creates standard LangChain @tool functions that work with any LangChain-compatible model via bind_tools(). You can use them with a LangGraph agent or with plain LangChain — no LangGraph required.
With LangGraph (recommended):
from hindsight_client import Hindsight
from hindsight_langgraph import create_hindsight_tools
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
client = Hindsight(base_url="http://localhost:8888")
tools = create_hindsight_tools(client=client, bank_id="user-123")
agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools=tools)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "Remember that I prefer dark mode"}]}
)
With plain LangChain:
from hindsight_client import Hindsight
from hindsight_langgraph import create_hindsight_tools
from langchain_openai import ChatOpenAI
client = Hindsight(base_url="http://localhost:8888")
tools = create_hindsight_tools(client=client, bank_id="user-123")
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)
response = await model.ainvoke("Remember that I prefer dark mode")
When using plain LangChain, you handle the tool execution loop yourself — call the model, check for tool_calls, execute them, and feed results back. LangGraph automates this loop for you.
The agent gets three tools it can call:
hindsight_retain— Store information to long-term memoryhindsight_recall— Search long-term memory for relevant factshindsight_reflect— Synthesize a reasoned answer from memories
Quick Start: Memory Nodes (LangGraph)
Add recall and retain nodes to your graph for automatic memory injection and storage.
from hindsight_client import Hindsight
from hindsight_langgraph import create_recall_node, create_retain_node
from langgraph.graph import StateGraph, MessagesState, START, END
client = Hindsight(base_url="http://localhost:8888")
recall = create_recall_node(client=client, bank_id="user-123")
retain = create_retain_node(client=client, bank_id="user-123")
builder = StateGraph(MessagesState)
builder.add_node("recall", recall)
builder.add_node("agent", agent_node) # your LLM node
builder.add_node("retain", retain)
builder.add_edge(START, "recall")
builder.add_edge("recall", "agent")
builder.add_edge("agent", "retain")
builder.add_edge("retain", END)
graph = builder.compile()
The recall node extracts the latest user message, searches Hindsight, and injects matching memories as a SystemMessage. The retain node stores human messages (optionally AI messages too) after the response.
Quick Start: BaseStore (LangGraph)
Use Hindsight as a LangGraph BaseStore for cross-thread persistent memory with semantic search.
from hindsight_client import Hindsight
from hindsight_langgraph import HindsightStore
client = Hindsight(base_url="http://localhost:8888")
store = HindsightStore(client=client)
graph = builder.compile(checkpointer=checkpointer, store=store)
# Store and search via the store API
await store.aput(("user", "123", "prefs"), "theme", {"value": "dark mode"})
results = await store.asearch(("user", "123", "prefs"), query="theme preference")
Namespace tuples are mapped to Hindsight bank IDs with . as separator (e.g., ("user", "123") becomes bank user.123). Banks are auto-created on first access.
Dynamic Bank IDs
Both nodes and the store support per-user bank resolution from RunnableConfig:
recall = create_recall_node(client=client, bank_id_from_config="user_id")
retain = create_retain_node(client=client, bank_id_from_config="user_id")
# Bank ID resolved at runtime from config
result = await graph.ainvoke(
{"messages": [{"role": "user", "content": "hello"}]},
config={"configurable": {"user_id": "user-456"}},
)
Selecting Tools
Include only the tools you need:
tools = create_hindsight_tools(
client=client,
bank_id="user-123",
include_retain=True,
include_recall=True,
include_reflect=False, # Omit reflect
)
Global Configuration
Instead of passing a client to every call, configure once:
from hindsight_langgraph import configure, create_hindsight_tools
configure(
hindsight_api_url="http://localhost:8888",
api_key="your-api-key", # Or set HINDSIGHT_API_KEY env var
budget="mid", # Recall budget: low/mid/high
max_tokens=4096, # Max tokens for recall results
tags=["env:prod"], # Tags for stored memories
recall_tags=["scope:global"], # Tags to filter recall
recall_tags_match="any", # Tag match mode: any/all/any_strict/all_strict
)
# Now create tools without passing client — uses global config
tools = create_hindsight_tools(bank_id="user-123")
Retain Node Options
retain = create_retain_node(
client=client,
bank_id="user-123",
retain_human=True, # Store human messages (default: True)
retain_ai=False, # Store AI responses (default: False)
tags=["source:chat"], # Tags applied to stored memories
)
Recall Node Options
recall = create_recall_node(
client=client,
bank_id="user-123",
budget="low", # Recall budget: low/mid/high
max_results=10, # Max memories injected
max_tokens=4096, # Max tokens for recall
tags=["scope:user"], # Filter by tags
tags_match="all", # Tag match mode
)
Using output_key for Prompt Control
By default, the recall node appends a SystemMessage to messages. Use output_key to write memory text to a custom state field instead, giving you full control over prompt ordering:
from typing import Optional
from langgraph.graph import MessagesState
class AgentState(MessagesState):
memory_context: Optional[str] = None
recall = create_recall_node(
client=client,
bank_id="user-123",
output_key="memory_context",
)
# In your agent node, read state["memory_context"] and prepend it
# to the system prompt before calling the model.
Limitations and Notes
HindsightStore
- Async-only. All sync methods (
batch,get,put,delete,search,list_namespaces) raiseNotImplementedError. Use the async variants (abatch,aget,aput,adelete,asearch,alist_namespaces) instead. get()relies on recall. There is no direct key lookup — the key is used as a recall query and only exactdocument_idmatches are returned. Items that do not rank in the top recall results may appear missing.list_namespacesis session-scoped. It only tracks namespaces that have been written to viaaput()during the current process. After a restart,list_namespacesreturns empty even though data still exists in Hindsight.deleteis a no-op. Callingadelete()logs a debug message but does not remove data from Hindsight. Hindsight's memory model is append-oriented; fact superseding is handled automatically during retain.
Memory Nodes
- SystemMessage ordering. The recall node adds a
SystemMessagewith recalled memories. BecauseMessagesStateusesadd_messages(which appends), this message appears after existing messages rather than at position 0. The message has a stable ID (hindsight_memory_context) so it is updated rather than duplicated across invocations. If your LLM provider requires system messages first, sort or filter messages in your agent node before passing them to the model.
Error Handling
- Tools raise
HindsightErroron failure, which surfaces to the agent as a tool error. - Nodes silently log errors and return empty messages, so a Hindsight outage does not crash your graph.
API Reference
create_hindsight_tools()
| Parameter | Default | Description |
|---|---|---|
bank_id |
required | Hindsight memory bank ID |
client |
None |
Pre-configured Hindsight client |
hindsight_api_url |
None |
API URL (used if no client provided) |
api_key |
None |
API key (used if no client provided) |
budget |
"mid" |
Recall/reflect budget level (low/mid/high) |
max_tokens |
4096 |
Maximum tokens for recall results |
tags |
None |
Tags applied when storing memories |
recall_tags |
None |
Tags to filter when searching |
recall_tags_match |
"any" |
Tag matching mode (any/all/any_strict/all_strict) |
retain_metadata |
None |
Default metadata dict for retain operations |
retain_document_id |
None |
Default document_id for retain (groups/upserts memories) |
recall_types |
None |
Fact types to filter (world, experience, opinion, observation) |
recall_include_entities |
False |
Include entity information in recall results |
reflect_context |
None |
Additional context for reflect operations |
reflect_max_tokens |
None |
Max tokens for reflect results (defaults to max_tokens) |
reflect_response_schema |
None |
JSON schema to constrain reflect output format |
reflect_tags |
None |
Tags to filter memories used in reflect (defaults to recall_tags) |
reflect_tags_match |
None |
Tag matching for reflect (defaults to recall_tags_match) |
include_retain |
True |
Include the retain (store) tool |
include_recall |
True |
Include the recall (search) tool |
include_reflect |
True |
Include the reflect (synthesize) tool |
create_recall_node()
| Parameter | Default | Description |
|---|---|---|
bank_id |
None |
Static bank ID (or use bank_id_from_config) |
client |
None |
Pre-configured Hindsight client |
hindsight_api_url |
None |
API URL (used if no client provided) |
api_key |
None |
API key (used if no client provided) |
budget |
"mid" |
Recall budget level |
max_tokens |
4096 |
Max tokens for recall results |
max_results |
10 |
Max memories to inject |
tags |
None |
Tags to filter recall results |
tags_match |
"any" |
Tag matching mode |
bank_id_from_config |
"user_id" |
Config key to resolve bank ID at runtime |
output_key |
None |
If set, write memory text to this state key instead of appending a SystemMessage to messages |
create_retain_node()
| Parameter | Default | Description |
|---|---|---|
bank_id |
None |
Static bank ID (or use bank_id_from_config) |
client |
None |
Pre-configured Hindsight client |
hindsight_api_url |
None |
API URL (used if no client provided) |
api_key |
None |
API key (used if no client provided) |
tags |
None |
Tags applied to stored memories |
bank_id_from_config |
"user_id" |
Config key to resolve bank ID at runtime |
retain_human |
True |
Store human messages |
retain_ai |
False |
Store AI responses |
HindsightStore()
| Parameter | Default | Description |
|---|---|---|
client |
None |
Pre-configured Hindsight client |
hindsight_api_url |
None |
API URL (used if no client provided) |
api_key |
None |
API key (used if no client provided) |
tags |
None |
Tags applied to all retain operations |
configure()
| Parameter | Default | Description |
|---|---|---|
hindsight_api_url |
Production API | Hindsight API URL |
api_key |
HINDSIGHT_API_KEY env |
API key for authentication |
budget |
"mid" |
Default recall budget level |
max_tokens |
4096 |
Default max tokens for recall |
tags |
None |
Default tags for retain operations |
recall_tags |
None |
Default tags to filter recall |
recall_tags_match |
"any" |
Default tag matching mode |
verbose |
False |
Enable verbose logging |
Requirements
- Python >= 3.10
- langchain-core >= 0.3.0
- hindsight-client >= 0.4.0
- langgraph >= 0.3.0 (only for nodes and store patterns — install with
pip install hindsight-langgraph[langgraph])