DEV Community

Cover image for Conversation memory for LangChain agents
Željko Šević
Željko Šević

Posted on • Originally published at sevic.dev

Conversation memory for LangChain agents

This post extends the support triage agent from Building AI agents with LangChain into a multi-turn flow: turn 1 looks up the customer and invoice; turn 2 creates the ticket without the user repeating IDs. It is post #5 in the LangChain series, following the overview, loaders/chunking, RAG, and agents posts.

Prerequisites

  • OpenAI account
  • Generated API key
  • Enabled billing
  • Node.js version 26
  • Packages from the agents post, plus the checkpoint package:
npm i langchain @langchain/openai @langchain/core @langchain/langgraph-checkpoint zod
Enter fullscreen mode Exit fullscreen mode
  • OPENAI_API_KEY set in the environment

Mental model

Three related concepts:

  • Checkpointer - short-term session memory. Saves messages and graph state after each step so the next invoke on the same thread can resume.
  • thread_id - conversation key passed in configurable. Same ID = same history; different ID = isolated session.
  • Store - long-term memory across threads (user preferences, facts learned over time). LangGraph stores are separate from checkpointers; this post focuses on checkpointers only.

Typical support flow with memory:

  1. Turn 1 - rep asks to look up cus_1042 and inv_8891; agent calls lookup tools and summarizes findings.
  2. Turn 2 - rep says "create the ticket we discussed"; agent recalls prior tool results and calls create_support_ticket.

MemorySaver

For demos and tests, use MemorySaver - an in-memory checkpointer that persists state for the lifetime of the process:

import { MemorySaver } from '@langchain/langgraph-checkpoint';

const checkpointer = new MemorySaver();
Enter fullscreen mode Exit fullscreen mode

State is lost when the Node process exits. That is fine for local scripts; production apps need a durable backend (see below).

Attach a checkpointer to createAgent

Pass the checkpointer when creating the agent. Reuse the same triage tools and instructions from the agents post:

import { createAgent } from 'langchain';
import { MemorySaver } from '@langchain/langgraph-checkpoint';

const agent = createAgent({
  model: 'gpt-5.5',
  tools: supportTools,
  systemPrompt: TRIAGE_INSTRUCTIONS,
  checkpointer: new MemorySaver(),
});
Enter fullscreen mode Exit fullscreen mode

The agent loop is unchanged - the checkpointer hooks into LangGraph beneath createAgent.

First turn - lookup

Pass a stable thread_id in the invoke config:

const threadConfig = { configurable: { thread_id: 'support-cus-1042' } };

const turn1 = await agent.invoke(
  {
    messages: [
      {
        role: 'user',
        content:
          'Look up customer cus_1042 and invoice inv_8891 for a possible duplicate charge. Summarize what you find. Do not create a ticket yet.',
      },
    ],
  },
  threadConfig,
);

console.log(turn1.messages.at(-1)?.content);
Enter fullscreen mode Exit fullscreen mode

The agent calls get_customer, get_invoice, and search_knowledge_base. LangGraph saves the full message history (including tool results) to the checkpointer.

Second turn - follow-up without IDs

Send only the new user message on the same thread_id. Prior context is restored automatically:

const turn2 = await agent.invoke(
  {
    messages: [
      {
        role: 'user',
        content: 'Create the support ticket we discussed.',
      },
    ],
  },
  threadConfig,
);

console.log(turn2.messages.at(-1)?.content);
Enter fullscreen mode Exit fullscreen mode

The agent should call create_support_ticket using customer and invoice details from turn 1 - the user does not repeat cus_1042 or inv_8891.

Read the final answer from result.messages as in the agents post:

const lastAi = [...turn2.messages]
  .reverse()
  .find((message) => message.type === 'ai');

console.log(lastAi?.content);
Enter fullscreen mode Exit fullscreen mode

Thread isolation

Different thread_id values do not share history. Two support reps working different cases should use separate thread IDs:

await agent.invoke(
  { messages: [{ role: 'user', content: 'Look up cus_1042.' }] },
  { configurable: { thread_id: 'rep-alice-case-1' } },
);

await agent.invoke(
  { messages: [{ role: 'user', content: 'Create the ticket we discussed.' }] },
  { configurable: { thread_id: 'rep-bob-case-2' } },
);
Enter fullscreen mode Exit fullscreen mode

The second invoke on rep-bob-case-2 has no knowledge of Alice's lookup - Bob's thread starts empty.

Production checkpointers

MemorySaver is process-local and not suitable for production. LangGraph supports durable checkpointers backed by Postgres, SQLite, and other stores via @langchain/langgraph-checkpoint integrations. Swap the checkpointer implementation; the thread_id API stays the same.

Pick a backend that matches your deployment: Postgres for multi-instance apps, SQLite for single-node services.

Demo

See the langchain-agent-memory-nodejs-demo folder for multi-turn triage and thread-isolation scripts.

Top comments (0)