The @atomicmemory/langgraph adapter provides node factories for LangGraph JS graphs and framework-agnostic helpers you can call inside any node body. Rather than coupling to LangGraph’s runtime, the node factories emit plain async (state) => Partial<state> functions that you register with .addNode() — which means the adapter does not import @langchain/langgraph at runtime and is completely insulated from LangGraph version churn. The peer declaration is there to document the intended consumer; pin the version in your application.
Installation
pnpm add @atomicmemory/langgraph @atomicmemory/sdk @langchain/langgraph
Three surfaces at a glance
| Surface | Use when |
|---|
createMemoryRetrieveNode() | You want a graph node that searches AtomicMemory and merges the rendered context into state before the model call. |
createMemoryIngestNode() | You want a graph node that persists the completed turn into AtomicMemory after the model call. |
searchMemory() / ingestTurn() | You want to call AtomicMemory directly inside a node body, an edge condition, or a standalone helper. |
Minimal end-to-end example
The example below wires a three-node graph: memory retrieval → model call → memory ingest. The extractor callbacks (getQuery, applyContext, getMessages, getCompletion) decouple the adapter from your state schema — use whatever channels your graph already exposes.
import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
import { MemoryClient } from '@atomicmemory/sdk';
import {
createMemoryRetrieveNode,
createMemoryIngestNode,
} from '@atomicmemory/langgraph';
const memory = new MemoryClient({
providers: { atomicmemory: { apiUrl: process.env.ATOMICMEMORY_URL!, apiKey: process.env.ATOMICMEMORY_KEY! } },
});
await memory.initialize();
const scope = { user: 'pip', namespace: 'my-graph' };
const retrieve = createMemoryRetrieveNode<
typeof MessagesAnnotation.State,
{ context: string | null }
>(memory, {
scope,
getQuery: (state) => {
const last = [...state.messages].reverse().find((m) => m.getType?.() === 'human');
return typeof last?.content === 'string' ? last.content : '';
},
applyContext: (_state, context) => ({ context }),
});
const ingest = createMemoryIngestNode<
typeof MessagesAnnotation.State,
Record<string, never>
>(memory, {
scope,
getMessages: (state) =>
state.messages.map((m) => ({
role: m.getType?.() === 'human' ? 'user' : 'assistant',
content: typeof m.content === 'string' ? m.content : '',
})),
getCompletion: (state) => {
const last = state.messages.at(-1);
return typeof last?.content === 'string' ? last.content : '';
},
});
const graph = new StateGraph(MessagesAnnotation)
.addNode('retrieve', retrieve)
.addNode('model', async () => ({ /* your model call */ }))
.addNode('ingest', ingest)
.addEdge('__start__', 'retrieve')
.addEdge('retrieve', 'model')
.addEdge('model', 'ingest')
.compile();
Framework-agnostic helpers
If you need to call AtomicMemory from within a node body rather than at the node-factory level, the helpers are available as named exports:
import { searchMemory, ingestTurn } from '@atomicmemory/langgraph';
// Inside any node body:
const { context } = await searchMemory(memory, { query, scope });
await ingestTurn(memory, { messages, completion: text, scope });
ingestTurn() excludes system messages by default. Opt in via includeRoles only when your system content is genuinely user-authored material worth remembering.
Scope binding
Scope is fixed at factory time. Agents and downstream nodes cannot rebind to a different user or namespace by mutating graph state — this is an intentional security boundary.
If you also want memory_search and memory_ingest as agent-callable tools (for use in a LangGraph tool node, for example), use the @atomicmemory/langchain adapter. LangGraph consumes the same tool()-shaped objects that @atomicmemory/langchain produces, so you can combine both adapters in a single graph.
Use @atomicmemory/langgraph for the retrieve/ingest lifecycle nodes and @atomicmemory/langchain for the agent-callable tool pair. They share the same MemoryClient instance and scope — construct MemoryClient once and pass it to both.