{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/langchain-agent-memory-nodejs/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-06-17 19:00:48 +0200"}},"id":"c5f118f1-42eb-5ba3-a2f1-e93e19d80eb0","excerpt":"This post extends the  support triage agent  from  Building AI agents with LangChain  into a  multi-turn flow : turn 1 looks up the customer…","frontmatter":{"title":"Conversation memory for LangChain agents","date":"2026-06-18 00:01:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["langchain","ai","llm","node","openai","agents","memory"],"cover":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAPoAAAD6AG1e1JrAAABfklEQVQoz22SibKbMAxF3///YpuQQLPRBPCGd5+OnSbhdaoZARqPjnUv+iqlMM+Zrls4nxwx0sI7GAbNbjcjlsy6Fg6doNsvCJGwK/S9pusEUuTWU1lfFFjmzH438WswOFsPM0Yn+qPk548HsgFyg+1387O2heNBsN/PSLkB1keFBl+QUvF4PBBCcL//RgpDCoVXxFCIIb/rFCEn3rCKegKbxMTr+/WOIaHVijEarRXXy8g43nHeEWJASUPwcdNTtsCMcx7vn+mcxa6+AadpahOLReFdJJdMzhlnIynmzYQbYD1clqVllTzPE9NjaVDnHNbapoKPA38ll/8Dg/9484qSabK01iglGW/3dklMzymdDcRvE249tIkQQmuuP6Z6Vj2sEmOsjZHVOIxemyUhPO1IIX/zvQGVzFzOhvFWpSVSChgdW329WIwpeFcYb77lWmsP4+i5Xesl/+xhXcxhEK05hs9in0+K42FGyYSzhaEX9P3yrs8nzdBLtPoA/wAIJwniSKcwuAAAAABJRU5ErkJggg==","aspectRatio":2.0869565217391304,"src":"/static/665c86ec5d6d5a4bc6259e1ad385d88e/c4ecb/cover.png","srcSet":"/static/665c86ec5d6d5a4bc6259e1ad385d88e/57ab0/cover.png 192w,\n/static/665c86ec5d6d5a4bc6259e1ad385d88e/f4739/cover.png 384w,\n/static/665c86ec5d6d5a4bc6259e1ad385d88e/c4ecb/cover.png 768w","srcWebp":"/static/665c86ec5d6d5a4bc6259e1ad385d88e/dd090/cover.webp","srcSetWebp":"/static/665c86ec5d6d5a4bc6259e1ad385d88e/ae504/cover.webp 192w,\n/static/665c86ec5d6d5a4bc6259e1ad385d88e/fef30/cover.webp 384w,\n/static/665c86ec5d6d5a4bc6259e1ad385d88e/dd090/cover.webp 768w","sizes":"(max-width: 768px) 100vw, 768px","presentationWidth":768,"presentationHeight":366},"resize":{"src":"/static/665c86ec5d6d5a4bc6259e1ad385d88e/c4ecb/cover.png"}}}},"fields":{"slug":"/notes/langchain-agent-memory-nodejs/","readingTime":{"text":"4 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Conversation memory for LangChain agents\",\n  \"date\": \"2026-06-18 00:01:00 UTC\",\n  \"cover\": \"./cover.png\",\n  \"tags\": [\"langchain\", \"ai\", \"llm\", \"node\", \"openai\", \"agents\", \"memory\"],\n  \"canonical_url\": \"https://sevic.dev/notes/langchain-agent-memory-nodejs/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This post extends the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"support triage agent\"), \" from \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/ai-agents-langchain-nodejs/\"\n  }), \"Building AI agents with LangChain\"), \" into a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"multi-turn flow\"), \": turn 1 looks up the customer and invoice; turn 2 creates the ticket without the user repeating IDs. It is post \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"#5\"), \" in the LangChain series, following the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/langchain-overview-nodejs/\"\n  }), \"overview\"), \", \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/langchain-loaders-chunking-nodejs/\"\n  }), \"loaders/chunking\"), \", \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/rag-openai-embeddings-pgvector-langchain/\"\n  }), \"RAG\"), \", and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/ai-agents-langchain-nodejs/\"\n  }), \"agents\"), \" posts.\"), mdx(\"h3\", {\n    \"id\": \"prerequisites\"\n  }, \"Prerequisites\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"OpenAI account\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Generated API key\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Enabled billing\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Node.js version 26\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Packages from the agents post, plus the checkpoint package:\")), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"npm i langchain @langchain/openai @langchain/core @langchain/langgraph-checkpoint zod\\n\")), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"OPENAI_API_KEY\"), \" set in the environment\")), mdx(\"h3\", {\n    \"id\": \"mental-model\"\n  }, \"Mental model\"), mdx(\"p\", null, \"Three related concepts:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Checkpointer\"), \" - short-term session memory. Saves messages and graph state after each step so the next \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"invoke\"), \" on the same thread can resume.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"thread_id\")), \" - conversation key passed in \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"configurable\"), \". Same ID = same history; different ID = isolated session.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Store\"), \" - long-term memory across threads (user preferences, facts learned over time). LangGraph \", mdx(\"a\", _extends({\n    parentName: \"li\"\n  }, {\n    \"href\": \"https://docs.langchain.com/oss/javascript/langgraph/memory#memory-storage\"\n  }), \"stores\"), \" are separate from checkpointers; this post focuses on checkpointers only.\")), mdx(\"p\", null, \"Typical support flow with memory:\"), mdx(\"ol\", null, mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Turn 1 - rep asks to look up \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"cus_1042\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"inv_8891\"), \"; agent calls lookup tools and summarizes findings.\"), mdx(\"li\", {\n    parentName: \"ol\"\n  }, \"Turn 2 - rep says \\\"create the ticket we discussed\\\"; agent recalls prior tool results and calls \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"create_support_ticket\"), \".\")), mdx(\"h3\", {\n    \"id\": \"memorysaver\"\n  }, \"MemorySaver\"), mdx(\"p\", null, \"For demos and tests, use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MemorySaver\"), \" - an in-memory checkpointer that persists state for the lifetime of the process:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { MemorySaver } from '@langchain/langgraph-checkpoint';\\n\\nconst checkpointer = new MemorySaver();\\n\")), mdx(\"p\", null, \"State is lost when the Node process exits. That is fine for local scripts; production apps need a durable backend (see below).\"), mdx(\"h3\", {\n    \"id\": \"attach-a-checkpointer-to-createagent\"\n  }, \"Attach a checkpointer to createAgent\"), mdx(\"p\", null, \"Pass the checkpointer when creating the agent. Reuse the same triage tools and instructions from the agents post:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { createAgent } from 'langchain';\\nimport { MemorySaver } from '@langchain/langgraph-checkpoint';\\n\\nconst agent = createAgent({\\n  model: 'gpt-5.5',\\n  tools: supportTools,\\n  systemPrompt: TRIAGE_INSTRUCTIONS,\\n  checkpointer: new MemorySaver(),\\n});\\n\")), mdx(\"p\", null, \"The agent loop is unchanged - the checkpointer hooks into LangGraph beneath \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"createAgent\"), \".\"), mdx(\"h3\", {\n    \"id\": \"first-turn---lookup\"\n  }, \"First turn - lookup\"), mdx(\"p\", null, \"Pass a stable \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thread_id\"), \" in the invoke config:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const threadConfig = { configurable: { thread_id: 'support-cus-1042' } };\\n\\nconst turn1 = await agent.invoke(\\n  {\\n    messages: [\\n      {\\n        role: 'user',\\n        content:\\n          'Look up customer cus_1042 and invoice inv_8891 for a possible duplicate charge. Summarize what you find. Do not create a ticket yet.',\\n      },\\n    ],\\n  },\\n  threadConfig,\\n);\\n\\nconsole.log(turn1.messages.at(-1)?.content);\\n\")), mdx(\"p\", null, \"The agent calls \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"get_customer\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"get_invoice\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"search_knowledge_base\"), \". LangGraph saves the full message history (including tool results) to the checkpointer.\"), mdx(\"h3\", {\n    \"id\": \"second-turn---follow-up-without-ids\"\n  }, \"Second turn - follow-up without IDs\"), mdx(\"p\", null, \"Send only the new user message on the \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"same\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thread_id\"), \". Prior context is restored automatically:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const turn2 = await agent.invoke(\\n  {\\n    messages: [\\n      {\\n        role: 'user',\\n        content: 'Create the support ticket we discussed.',\\n      },\\n    ],\\n  },\\n  threadConfig,\\n);\\n\\nconsole.log(turn2.messages.at(-1)?.content);\\n\")), mdx(\"p\", null, \"The agent should call \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"create_support_ticket\"), \" using customer and invoice details from turn 1 - the user does not repeat \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"cus_1042\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"inv_8891\"), \".\"), mdx(\"p\", null, \"Read the final answer from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"result.messages\"), \" as in the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/ai-agents-langchain-nodejs/\"\n  }), \"agents post\"), \":\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const lastAi = [...turn2.messages]\\n  .reverse()\\n  .find((message) => message.type === 'ai');\\n\\nconsole.log(lastAi?.content);\\n\")), mdx(\"h3\", {\n    \"id\": \"thread-isolation\"\n  }, \"Thread isolation\"), mdx(\"p\", null, \"Different \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thread_id\"), \" values do not share history. Two support reps working different cases should use separate thread IDs:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await agent.invoke(\\n  { messages: [{ role: 'user', content: 'Look up cus_1042.' }] },\\n  { configurable: { thread_id: 'rep-alice-case-1' } },\\n);\\n\\nawait agent.invoke(\\n  { messages: [{ role: 'user', content: 'Create the ticket we discussed.' }] },\\n  { configurable: { thread_id: 'rep-bob-case-2' } },\\n);\\n\")), mdx(\"p\", null, \"The second invoke on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"rep-bob-case-2\"), \" has no knowledge of Alice's lookup - Bob's thread starts empty.\"), mdx(\"h3\", {\n    \"id\": \"production-checkpointers\"\n  }, \"Production checkpointers\"), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"MemorySaver\"), \" is process-local and not suitable for production. LangGraph supports durable checkpointers backed by Postgres, SQLite, and other stores via \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@langchain/langgraph-checkpoint\"), \" integrations. Swap the checkpointer implementation; the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"thread_id\"), \" API stays the same.\"), mdx(\"p\", null, \"Pick a backend that matches your deployment: Postgres for multi-instance apps, SQLite for single-node services.\"), mdx(\"h3\", {\n    \"id\": \"demo\"\n  }, \"Demo\"), mdx(\"p\", null, \"See the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/delimitertech/demos/tree/main/ai/langchain-agent-memory-nodejs-demo\"\n  }), \"langchain-agent-memory-nodejs-demo\"), \" folder for multi-turn triage and thread-isolation scripts.\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"c5f118f1-42eb-5ba3-a2f1-e93e19d80eb0"}},"staticQueryHashes":["1961101537","2542493696"]}