DEV Community

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

Posted on Edited 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.

Need help with your project?

Get personalized advice on your architecture, code, or career in a 45-minute 1-on-1 consultation.

Book a consultation

Top comments (0)