# Memory for LangChain and LangGraph.js: a context runnable, a callback and the history tools | Niadra

> How to give customer memory to a LangChain chain in Python and to a LangGraph.js graph: context_runnable() places the context in the prompt messages, NiadraCallbackHandler records the turns and history_tools() hands over the kit as StructuredTools; in JavaScript, niadraContext(), withNiadraContext() and niadraTools(). SDK code and limits.

URL: https://niadra.com/en/integracoes/langchain

Integration · LangChain

# Memory for LangChain and LangGraph.js.

In Python, with langchain-core only: context\_runnable() places the context in the prompt's messages, NiadraCallbackHandler records the turns and history\_tools() hands over the kit as StructuredTools. In JavaScript, @niadra/sdk/langchain brings niadraContext() for LCEL chains, withNiadraContext() for LangGraph.js nodes, the callback handler and niadraTools().

[Request early access](/en/enterprise)[The adapter's documentation(opens docs.niadra.com)](https://docs.niadra.com/en/integrations/langchain)

LangChain's conversation memory is this chain's history, in a session of your application. It does not cross vendors, does not cross channels and does not know what a system of record changed. Every agent in the company keeps its own version of the customer.

Niadra comes in as a runnable in the middle of the chain: the prompt's messages go in, and come out with the customer's context as a SystemMessage right after the system messages and the turn\_block at the end. The callback records what the customer said and what the model answered, with the usage\_metadata.

The minimal example, as the documentation has it

```
pip install 'niadra[langchain]'   # langchain-core 1.6 or newer, below 2
```

```
"""An LCEL chain with the customer's context and the history tools."""

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

from niadra import Niadra, phone
from niadra.integrations.langchain import NiadraCallbackHandler, context_runnable, history_tools

niadra = Niadra(channel="chat")
prompt = ChatPromptTemplate.from_messages([("system", "You are Acme's agent."), ("human", "{question}")])

with niadra.conversation("thread-81", subject=phone("+5511912345678")) as conversation:
    model = ChatOpenAI(model="gpt-4.1").bind_tools(history_tools(conversation))
    chain = prompt | context_runnable(conversation) | model
    reply = chain.invoke(
        {"question": "Where is my replacement lid?"},
        config={"callbacks": [NiadraCallbackHandler(conversation)]},
    )
    print(reply.content)
```

```
npm install @niadra/sdk @langchain/core   # @langchain/core 1.x, as an optional peer dependency
```

```
// A LangGraph.js agent: the context goes into the model call inside the node (never into the
// graph's state), the history tools run through ToolNode, and the callback records the answers.
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { END, MessagesAnnotation, START, StateGraph } from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
import { Niadra, handles } from "@niadra/sdk";
import { NiadraCallbackHandler, niadraTools, withNiadraContext } from "@niadra/sdk/langchain";

const niadra = new Niadra();

/** One customer message in; `userId` comes from your session, never from the model. */
export async function reply(userId: string, threadId: string, text: string): Promise<string> {
  const convo = niadra.conversation({ subject: handles.appUserId(userId), channel: "web_chat", conversation_id: threadId });
  const tools = niadraTools(convo);
  const model = new ChatOpenAI({ model: "gpt-4.1" }).bindTools(tools);

  const graph = new StateGraph(MessagesAnnotation)
    .addNode("agent", async (state) => ({ messages: [await model.invoke(await withNiadraContext(convo, state.messages))] }))
    .addNode("tools", new ToolNode(tools))
    .addEdge(START, "agent")
    .addConditionalEdges("agent", toolsCondition, ["tools", END])
    .addEdge("tools", "agent")
    .compile();

  const result = await graph.invoke(
    { messages: [new SystemMessage("You are Acme's support agent. Be brief."), new HumanMessage(text)] },
    { callbacks: [new NiadraCallbackHandler(convo)] },
  );
  return result.messages.at(-1)?.text ?? "";
}
```

-   Python
-   TypeScript

The same code is in examples/langchain\_chain.py, examples/langgraph-js.ts in the SDK repositories, where it runs in CI against the framework's real types and Niadra's emulator. To try it without Niadra's cloud, niadra-mock and NIADRA\_BASE\_URL=http://127.0.0.1:8765.

## How the adapter wires in

The five primitives of every Niadra integration, in this framework's extension points.

Context

In Python, context\_runnable() takes the prompt's messages, a list or a PromptValue, and returns them with the pack as a SystemMessage right after the leading system messages and the turn\_block as a SystemMessage at the end; with\_context() and awith\_context() do the same for a list you build. In JavaScript, niadraContext(session) is a runnable for LCEL chains (niadraContext(convo).pipe(model)); withNiadraContext(session, messages) does the same inside a LangGraph.js node, right before the model call, so nothing lands in the graph's state.

Turns

NiadraCallbackHandler records the customer's messages when a chat model starts, keyed by position, and the model's answer with its usage\_metadata when it ends. In JavaScript, it records the answers with the usage\_metadata LangChain standardizes; answers that only call tools record nothing.

Tools

history\_tools(): StructuredTools with the kit's names, descriptions and schemas, bound to the customer. In JavaScript, niadraTools(session).

Verification

conversation.verify() before the call.

Handoff

conversation.handoff() where the chain transfers.

## What the agent receives

The context is compiled when the memory changes and served ready, with no AI model on the read. What another channel said during the conversation arrives as a delta, at the end of the prompt.

-   Who the customer is, by what the conversation has proven: the verification level decides what goes in
-   Facts, open items and promises, with the date and the channel they came from
-   What other agents did inside the company, confirmed by the system of record
-   Patterns computed by rule, with the evidence and the expiry
-   The three history tools: search, timeline and open an item, bound to the customer in your code
-   A receipt of every read, chained by SHA-256

[See the context from the inside](/en/produtos/contexto)

## What the adapter does not do

-   No BaseChatMessageHistory or BaseStore of its own: Niadra is not the state store of the chain or the graph, and the checkpointer never stores a pack.
-   Nothing here fails the chain: with Niadra slow or down, the messages go to the model as they came.
-   Tested against langchain-core 1.6 and @langchain/core 1.2 with a fake chat model and Niadra on the emulator.

## Frequently asked questions

### Do I need all of langchain or only the core?

In Python, only langchain-core: the runnable, the callback and the tools use the core's interfaces. The example uses langchain-openai for the model, but any chat model works.

### Does LangChain's conversation memory keep working?

It does, and it is yours. The chain's history is what the model saw in this session; Niadra is what the company knows of the person, from every channel and system. The callback records the turns once, by position, so the history replayed on the next call does not go in twice.

### And in a chain that calls tools several times?

In JavaScript, answers that only call tools record nothing; the agent's turn is the answer with text. The history tools run through the ToolNode, like any other, bound to the customer your code opened.

### Do I have to change my model, my prompt or my vendor?

No. The adapter places the context after your instructions and the delta at the end of the prompt, in the extension points the framework already has. Your model, your prompt and your vendor stay the same, and switching any of them later does not erase the memory.

### Where does the data live, and what does it cost?

The data stays in a single region, stated in the contract, encrypted with AES-256-GCM under a key exclusive to your company and protected in a FIPS 140-3 HSM. The price is per conversation or task in which an agent read the memory: US$ 2 to 3 per thousand, by volume, with reads, searches and system events included. Niadra is opening to companies by request, before the public launch.

## Other integrations

-   [LiveKit Agents](/en/integracoes/livekit)
-   [Pipecat](/en/integracoes/pipecat)
-   [Vapi](/en/integracoes/vapi)
-   [Retell AI](/en/integracoes/retell)
-   [ElevenLabs Agents Platform](/en/integracoes/elevenlabs)
-   [Twilio](/en/integracoes/twilio)
-   [WhatsApp Cloud API](/en/integracoes/whatsapp)
-   [OpenAI Agents SDK](/en/integracoes/openai-agents)
-   [LangGraph](/en/integracoes/langgraph)
-   [CrewAI](/en/integracoes/crewai)
-   [Vercel AI SDK](/en/integracoes/ai-sdk)
-   [n8n](/en/integracoes/n8n)
-   [All 36 integrations, in the documentation(opens docs.niadra.com)](https://docs.niadra.com/en/integrations/overview)

## Tell us what you are building.

A work email and two lines about your agents are enough. The people who write the code reply, with an early-access proposal for your case.

[Rather tell us more about your company? Use the full form](/en/enterprise)
