diff --git a/docs/core_docs/docs/integrations/memory/file.mdx b/docs/core_docs/docs/integrations/memory/file.mdx
new file mode 100644
index 000000000000..d485be9d29c6
--- /dev/null
+++ b/docs/core_docs/docs/integrations/memory/file.mdx
@@ -0,0 +1,31 @@
+---
+hide_table_of_contents: true
+---
+
+import CodeBlock from "@theme/CodeBlock";
+
+# File Chat Message History
+
+The `FileChatMessageHistory` uses a JSON file to store chat message history. For longer-term persistence across chat sessions, you can swap out the default in-memory `chatHistory` that backs chat memory classes like `BufferMemory`.
+
+## Setup
+
+You'll first need to install the [`@langchain/community`](https://www.npmjs.com/package/@langchain/community) package:
+
+```bash npm2yarn
+npm install @langchain/community @langchain/core
+```
+
+import IntegrationInstallTooltip from "@mdx_components/integration_install_tooltip.mdx";
+
+
+
+```bash npm2yarn
+npm install @langchain/openai @langchain/community @langchain/core
+```
+
+## Usage
+
+import Example from "@examples/memory/file.ts";
+
+{Example}
diff --git a/examples/src/memory/file.ts b/examples/src/memory/file.ts
new file mode 100644
index 000000000000..5728ec3af26d
--- /dev/null
+++ b/examples/src/memory/file.ts
@@ -0,0 +1,71 @@
+import { ChatOpenAI } from "@langchain/openai";
+import { FileSystemChatMessageHistory } from "@langchain/community/stores/message/file_system";
+import { RunnableWithMessageHistory } from "@langchain/core/runnables";
+import { StringOutputParser } from "@langchain/core/output_parsers";
+import {
+ ChatPromptTemplate,
+ MessagesPlaceholder,
+} from "@langchain/core/prompts";
+
+const model = new ChatOpenAI({
+ model: "gpt-3.5-turbo",
+ temperature: 0,
+});
+
+const prompt = ChatPromptTemplate.fromMessages([
+ [
+ "system",
+ "You are a helpful assistant. Answer all questions to the best of your ability.",
+ ],
+ new MessagesPlaceholder("chat_history"),
+ ["human", "{input}"],
+]);
+
+const chain = prompt.pipe(model).pipe(new StringOutputParser());
+
+const chainWithHistory = new RunnableWithMessageHistory({
+ runnable: chain,
+ inputMessagesKey: "input",
+ historyMessagesKey: "chat_history",
+ getMessageHistory: async (sessionId) => {
+ const chatHistory = new FileSystemChatMessageHistory({
+ sessionId,
+ userId: "user-id",
+ });
+ return chatHistory;
+ },
+});
+
+const res1 = await chainWithHistory.invoke(
+ { input: "Hi! I'm Jim." },
+ { configurable: { sessionId: "langchain-test-session" } }
+);
+console.log({ res1 });
+/*
+ { res1: 'Hi Jim! How can I assist you today?' }
+ */
+
+const res2 = await chainWithHistory.invoke(
+ { input: "What did I just say my name was?" },
+ { configurable: { sessionId: "langchain-test-session" } }
+);
+console.log({ res2 });
+/*
+ { res2: { response: 'You said your name was Jim.' }
+ */
+
+// Give this session a title
+const chatHistory = (await chainWithHistory.getMessageHistory(
+ "langchain-test-session"
+)) as FileSystemChatMessageHistory;
+
+await chatHistory.setContext({ title: "Introducing Jim" });
+
+// List all session for the user
+const sessions = await chatHistory.getAllSessions();
+console.log(sessions);
+/*
+ [
+ { sessionId: 'langchain-test-session', context: { title: "Introducing Jim" } }
+ ]
+ */
diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore
index e6ae5fa54a4f..b40fdcd61a74 100644
--- a/libs/langchain-community/.gitignore
+++ b/libs/langchain-community/.gitignore
@@ -770,6 +770,10 @@ stores/message/firestore.cjs
stores/message/firestore.js
stores/message/firestore.d.ts
stores/message/firestore.d.cts
+stores/message/file_system.cjs
+stores/message/file_system.js
+stores/message/file_system.d.ts
+stores/message/file_system.d.cts
stores/message/in_memory.cjs
stores/message/in_memory.js
stores/message/in_memory.d.ts
diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js
index b5cc03c53360..b0207b8612ab 100644
--- a/libs/langchain-community/langchain.config.js
+++ b/libs/langchain-community/langchain.config.js
@@ -243,6 +243,7 @@ export const config = {
"stores/message/convex": "stores/message/convex",
"stores/message/dynamodb": "stores/message/dynamodb",
"stores/message/firestore": "stores/message/firestore",
+ "stores/message/file_system": "stores/message/file_system",
"stores/message/in_memory": "stores/message/in_memory",
"stores/message/ipfs_datastore": "stores/message/ipfs_datastore",
"stores/message/ioredis": "stores/message/ioredis",
diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json
index 118d11f80b41..ed9efe073426 100644
--- a/libs/langchain-community/package.json
+++ b/libs/langchain-community/package.json
@@ -2449,6 +2449,15 @@
"import": "./stores/message/firestore.js",
"require": "./stores/message/firestore.cjs"
},
+ "./stores/message/file_system": {
+ "types": {
+ "import": "./stores/message/file_system.d.ts",
+ "require": "./stores/message/file_system.d.cts",
+ "default": "./stores/message/file_system.d.ts"
+ },
+ "import": "./stores/message/file_system.js",
+ "require": "./stores/message/file_system.cjs"
+ },
"./stores/message/in_memory": {
"types": {
"import": "./stores/message/in_memory.d.ts",
@@ -3873,6 +3882,10 @@
"stores/message/firestore.js",
"stores/message/firestore.d.ts",
"stores/message/firestore.d.cts",
+ "stores/message/file_system.cjs",
+ "stores/message/file_system.js",
+ "stores/message/file_system.d.ts",
+ "stores/message/file_system.d.cts",
"stores/message/in_memory.cjs",
"stores/message/in_memory.js",
"stores/message/in_memory.d.ts",
diff --git a/libs/langchain-community/src/load/import_map.ts b/libs/langchain-community/src/load/import_map.ts
index 8b3b734a82c1..0e3192033848 100644
--- a/libs/langchain-community/src/load/import_map.ts
+++ b/libs/langchain-community/src/load/import_map.ts
@@ -67,6 +67,7 @@ export * as caches__upstash_redis from "../caches/upstash_redis.js";
export * as stores__doc__base from "../stores/doc/base.js";
export * as stores__doc__gcs from "../stores/doc/gcs.js";
export * as stores__doc__in_memory from "../stores/doc/in_memory.js";
+export * as stores__message__file_system from "../stores/message/file_system.js";
export * as stores__message__in_memory from "../stores/message/in_memory.js";
export * as memory__chat_memory from "../memory/chat_memory.js";
export * as indexes__base from "../indexes/base.js";
diff --git a/libs/langchain-community/src/stores/message/file_system.ts b/libs/langchain-community/src/stores/message/file_system.ts
new file mode 100644
index 000000000000..f81af5f7a4ef
--- /dev/null
+++ b/libs/langchain-community/src/stores/message/file_system.ts
@@ -0,0 +1,199 @@
+import { promises as fs } from "node:fs";
+import { dirname } from "node:path";
+
+import { BaseListChatMessageHistory } from "@langchain/core/chat_history";
+import {
+ BaseMessage,
+ StoredMessage,
+ mapChatMessagesToStoredMessages,
+ mapStoredMessagesToChatMessages,
+} from "@langchain/core/messages";
+
+export const FILE_HISTORY_DEFAULT_FILE_PATH = ".history/history.json";
+
+/**
+ * Represents a lightweight file chat session.
+ */
+export type FileChatSession = {
+ id: string;
+ context: Record;
+};
+
+/**
+ * Represents a stored chat session.
+ */
+export type StoredFileChatSession = FileChatSession & {
+ messages: StoredMessage[];
+};
+
+/**
+ * Type for the store of chat sessions.
+ */
+export type FileChatStore = {
+ [userId: string]: Record;
+};
+
+/**
+ * Type for the input to the `FileSystemChatMessageHistory` constructor.
+ */
+export interface FileSystemChatMessageHistoryInput {
+ sessionId: string;
+ userId?: string;
+ filePath?: string;
+}
+
+let store: FileChatStore;
+
+/**
+ * Store chat message history using a local JSON file.
+ * For demo and development purposes only.
+ *
+ * @example
+ * ```typescript
+ * const model = new ChatOpenAI({
+ * model: "gpt-3.5-turbo",
+ * temperature: 0,
+ * });
+ * const prompt = ChatPromptTemplate.fromMessages([
+ * [
+ * "system",
+ * "You are a helpful assistant. Answer all questions to the best of your ability.",
+ * ],
+ * ["placeholder", "chat_history"],
+ * ["human", "{input}"],
+ * ]);
+ *
+ * const chain = prompt.pipe(model).pipe(new StringOutputParser());
+ * const chainWithHistory = new RunnableWithMessageHistory({
+ * runnable: chain,
+ * inputMessagesKey: "input",
+ * historyMessagesKey: "chat_history",
+ * getMessageHistory: async (sessionId) => {
+ * const chatHistory = new FileSystemChatMessageHistory({
+ * sessionId: sessionId,
+ * userId: "userId", // Optional
+ * })
+ * return chatHistory;
+ * },
+ * });
+ * await chainWithHistory.invoke(
+ * { input: "What did I just say my name was?" },
+ * { configurable: { sessionId: "session-id" } }
+ * );
+ * ```
+ */
+export class FileSystemChatMessageHistory extends BaseListChatMessageHistory {
+ lc_namespace = ["langchain", "stores", "message", "file"];
+
+ private sessionId: string;
+
+ private userId: string;
+
+ private filePath: string;
+
+ constructor(chatHistoryInput: FileSystemChatMessageHistoryInput) {
+ super();
+
+ this.sessionId = chatHistoryInput.sessionId;
+ this.userId = chatHistoryInput.userId ?? "";
+ this.filePath = chatHistoryInput.filePath ?? FILE_HISTORY_DEFAULT_FILE_PATH;
+ }
+
+ private async init(): Promise {
+ if (store) {
+ return;
+ }
+ try {
+ store = await this.loadStore();
+ } catch (error) {
+ console.error("Error initializing FileSystemChatMessageHistory:", error);
+ throw error;
+ }
+ }
+
+ protected async loadStore(): Promise {
+ try {
+ await fs.access(this.filePath, fs.constants.F_OK);
+ const store = await fs.readFile(this.filePath, "utf-8");
+ return JSON.parse(store) as FileChatStore;
+ } catch (_error) {
+ const error = _error as NodeJS.ErrnoException;
+ if (error.code === "ENOENT") {
+ return {};
+ }
+ throw new Error(
+ `Error loading FileSystemChatMessageHistory store: ${error}`
+ );
+ }
+ }
+
+ protected async saveStore(): Promise {
+ try {
+ await fs.mkdir(dirname(this.filePath), { recursive: true });
+ await fs.writeFile(this.filePath, JSON.stringify(store));
+ } catch (error) {
+ throw new Error(
+ `Error saving FileSystemChatMessageHistory store: ${error}`
+ );
+ }
+ }
+
+ async getMessages(): Promise {
+ await this.init();
+ const messages = store[this.userId]?.[this.sessionId]?.messages ?? [];
+ return mapStoredMessagesToChatMessages(messages);
+ }
+
+ async addMessage(message: BaseMessage): Promise {
+ await this.init();
+ const messages = await this.getMessages();
+ messages.push(message);
+ const storedMessages = mapChatMessagesToStoredMessages(messages);
+ store[this.userId] ??= {};
+ store[this.userId][this.sessionId] = {
+ ...store[this.userId][this.sessionId],
+ messages: storedMessages,
+ };
+ await this.saveStore();
+ }
+
+ async clear(): Promise {
+ await this.init();
+ if (store[this.userId]) {
+ delete store[this.userId][this.sessionId];
+ }
+ await this.saveStore();
+ }
+
+ async getContext(): Promise> {
+ await this.init();
+ return store[this.userId]?.[this.sessionId]?.context ?? {};
+ }
+
+ async setContext(context: Record): Promise {
+ await this.init();
+ store[this.userId] ??= {};
+ store[this.userId][this.sessionId] = {
+ ...store[this.userId][this.sessionId],
+ context,
+ };
+ await this.saveStore();
+ }
+
+ async clearAllSessions() {
+ await this.init();
+ delete store[this.userId];
+ await this.saveStore();
+ }
+
+ async getAllSessions(): Promise {
+ await this.init();
+ const userSessions = store[this.userId]
+ ? Object.values(store[this.userId]).map((session) => ({
+ id: session.id,
+ context: session.context,
+ }))
+ : [];
+ return userSessions;
+ }
+}
diff --git a/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts b/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts
new file mode 100644
index 000000000000..1610bcbee0e1
--- /dev/null
+++ b/libs/langchain-community/src/stores/tests/file_chat_history.int.test.ts
@@ -0,0 +1,147 @@
+/* eslint-disable no-promise-executor-return */
+
+import { expect } from "@jest/globals";
+import { promises as fs } from "node:fs";
+import { HumanMessage, AIMessage } from "@langchain/core/messages";
+import { v4 as uuid } from "uuid";
+import {
+ FILE_HISTORY_DEFAULT_FILE_PATH,
+ FileSystemChatMessageHistory,
+} from "../message/file_system.js";
+
+afterAll(async () => {
+ try {
+ await fs.unlink(FILE_HISTORY_DEFAULT_FILE_PATH);
+ } catch {
+ // Ignore error if the file does not exist
+ }
+});
+
+test("FileSystemChatMessageHistory works", async () => {
+ const input = {
+ sessionId: uuid(),
+ };
+ const chatHistory = new FileSystemChatMessageHistory(input);
+ const blankResult = await chatHistory.getMessages();
+ expect(blankResult).toStrictEqual([]);
+
+ await chatHistory.addUserMessage("Who is the best vocalist?");
+ await chatHistory.addAIMessage("Ozzy Osbourne");
+
+ const expectedMessages = [
+ new HumanMessage("Who is the best vocalist?"),
+ new AIMessage("Ozzy Osbourne"),
+ ];
+ const resultWithHistory = await chatHistory.getMessages();
+ expect(resultWithHistory).toEqual(expectedMessages);
+});
+
+test("FileSystemChatMessageHistory persist sessions", async () => {
+ const input = {
+ sessionId: uuid(),
+ };
+ const chatHistory1 = new FileSystemChatMessageHistory(input);
+ const blankResult = await chatHistory1.getMessages();
+ expect(blankResult).toStrictEqual([]);
+
+ await chatHistory1.addUserMessage("Who is the best vocalist?");
+ await chatHistory1.addAIMessage("Ozzy Osbourne");
+
+ const chatHistory2 = new FileSystemChatMessageHistory(input);
+ const expectedMessages = [
+ new HumanMessage("Who is the best vocalist?"),
+ new AIMessage("Ozzy Osbourne"),
+ ];
+ const resultWithHistory = await chatHistory2.getMessages();
+ expect(resultWithHistory).toEqual(expectedMessages);
+});
+
+test("FileSystemChatMessageHistory clear session", async () => {
+ const input = {
+ sessionId: uuid(),
+ userId: uuid(),
+ };
+ const chatHistory = new FileSystemChatMessageHistory(input);
+
+ await chatHistory.addUserMessage("Who is the best vocalist?");
+ await chatHistory.addAIMessage("Ozzy Osbourne");
+
+ const expectedMessages = [
+ new HumanMessage("Who is the best vocalist?"),
+ new AIMessage("Ozzy Osbourne"),
+ ];
+ const resultWithHistory = await chatHistory.getMessages();
+ expect(resultWithHistory).toEqual(expectedMessages);
+
+ await chatHistory.clear();
+
+ const blankResult = await chatHistory.getMessages();
+ expect(blankResult).toStrictEqual([]);
+});
+
+test("FileSystemChatMessageHistory clear all sessions", async () => {
+ const input1 = {
+ sessionId: uuid(),
+ userId: "user1",
+ };
+ const chatHistory1 = new FileSystemChatMessageHistory(input1);
+
+ await chatHistory1.addUserMessage("Who is the best vocalist?");
+ await chatHistory1.addAIMessage("Ozzy Osbourne");
+
+ const input2 = {
+ sessionId: uuid(),
+ userId: "user1",
+ };
+ const chatHistory2 = new FileSystemChatMessageHistory(input2);
+
+ await chatHistory2.addUserMessage("Who is the best vocalist?");
+ await chatHistory2.addAIMessage("Ozzy Osbourne");
+
+ const expectedMessages = [
+ new HumanMessage("Who is the best vocalist?"),
+ new AIMessage("Ozzy Osbourne"),
+ ];
+
+ const result1 = await chatHistory1.getMessages();
+ expect(result1).toEqual(expectedMessages);
+
+ const result2 = await chatHistory2.getMessages();
+ expect(result2).toEqual(expectedMessages);
+
+ await chatHistory1.clearAllSessions();
+
+ const deletedResult1 = await chatHistory1.getMessages();
+ const deletedResult2 = await chatHistory2.getMessages();
+ expect(deletedResult1).toStrictEqual([]);
+ expect(deletedResult2).toStrictEqual([]);
+});
+
+test("FileSystemChatMessageHistory set context and get all sessions", async () => {
+ const session1 = {
+ sessionId: uuid(),
+ userId: "user1",
+ };
+ const context1 = { title: "Best vocalist" };
+ const chatHistory1 = new FileSystemChatMessageHistory(session1);
+
+ await chatHistory1.setContext(context1);
+ await chatHistory1.addUserMessage("Who is the best vocalist?");
+ await chatHistory1.addAIMessage("Ozzy Osbourne");
+
+ const chatHistory2 = new FileSystemChatMessageHistory({
+ sessionId: uuid(),
+ userId: "user1",
+ });
+ const context2 = { title: "Best guitarist" };
+
+ await chatHistory2.addUserMessage("Who is the best guitarist?");
+ await chatHistory2.addAIMessage("Jimi Hendrix");
+ await chatHistory2.setContext(context2);
+
+ const sessions = await chatHistory1.getAllSessions();
+
+ expect(sessions.length).toBe(2);
+ expect(sessions[0].context).toEqual(context1);
+ expect(sessions[1].context).toEqual(context2);
+});