# Memory for LangGraph agents: create_agent middleware, with nothing in the graph's state | Niadra

> How to give customer memory to a LangGraph agent: NiadraMiddleware wraps every model call of create_agent, puts the context in the system message, records the turns and brings the history tools, without writing anything to the state the checkpointer stores. SDK code and limits.

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

Integration · LangGraph

# Memory for LangGraph agents.

NiadraMiddleware wraps every model call of the agent built by langchain.agents.create\_agent: the pack goes into the system message, the turns are recorded and the history tools come along. Nothing is written to the graph's state: the checkpointer never stores a pack.

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

LangGraph's checkpointer persists one thread's state: this graph's messages, for this thread. A long-term memory store keeps what this agent decided to remember, in a namespace of your application. Neither knows what the customer told another vendor's voice agent nor what the billing agent did in the ERP.

Niadra stays outside the graph. The middleware appends the customer's context to the system message, with your instructions first, and the turn\_block after the messages, on every model call. The state stays yours alone, and the checkpointer stays small.

The minimal example, as the documentation has it

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

```
"""A LangGraph agent (langchain.agents.create_agent) with the customer's memory as middleware."""

import asyncio

from langchain.agents import create_agent

from niadra import AsyncNiadra, phone
from niadra.integrations.langgraph import NiadraMiddleware

niadra = AsyncNiadra(channel="chat")

async def main() -> None:
    async with niadra.conversation("thread-81", subject=phone("+5511912345678")) as conversation:
        agent = create_agent(
            "openai:gpt-4.1",
            system_prompt="You are Acme's agent.",
            middleware=[NiadraMiddleware(conversation)],
        )
        result = await agent.ainvoke(
            {"messages": [{"role": "user", "content": "Where is my replacement lid?"}]}
        )
        print(result["messages"][-1].content)
    await niadra.close()

asyncio.run(main())
```

-   Python

The same code is in examples/langgraph\_agent.py 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

The pack is appended to the system message, which keeps your instructions first, and the turn\_block goes after the messages. Nothing is written to the graph's state. For the older create\_react\_agent, pre\_model\_hook=pre\_model\_hook(conversation) gives the model the same messages through llm\_input\_messages, again without touching state.

Turns

The customer's messages are recorded before the call, keyed by their position in the conversation, so the history the checkpointer replays is never recorded twice; the model's answer, with its usage\_metadata, after it.

Tools

The middleware brings the history tools, and the agent memory tools, as its own tools, so create\_agent offers them without you listing them.

Verification

conversation.verify() before invoking the agent.

Handoff

transferred\_to\_agent() and transferred\_to\_human() record the transfer; call them where the graph hands the conversation over.

## 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 BaseStore of its own: Niadra is not the graph's state store.
-   No separate node for LangGraph.js: withNiadraContext() in @niadra/sdk/langchain already gives the model node its context without writing it into the graph's checkpointed state.
-   Nothing here fails the agent: with Niadra slow or down, the model call goes on without the pack.
-   Tested against langgraph 1.2 and langchain 1.4 with a fake chat model and Niadra on the emulator.

## Frequently asked questions

### Can I keep the checkpointer and the store I already have?

You can, and should. The middleware touches neither the graph's state nor the store: the context enters the model call and leaves with it. What Niadra keeps is the company's memory of the customer, derived from the events of every channel.

### And in LangGraph.js?

withNiadraContext() from @niadra/sdk/langchain gives the model node its context right before the call, without writing to state; the NiadraCallbackHandler records the answers. The full example is on the LangChain page.

### Can every run of the agent become a turn record?

It can, with turn records switched on in the space: before\_agent opens the turn, wrap\_tool\_call records each tool call and after\_agent closes it with the model calls and their tokens. In a replay in your CI, the graph's tools answer from the record.

### 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)
-   [LangChain](/en/integracoes/langchain)
-   [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)
