I am encountering an issue where, an interrupt is causing my message history to be forgotten, as well as other odd state behaviour.
The following is a simplified setup to show the issue. The issue is occurring in a browser based application using "deepagents/browser"
Setup
- A state back end, and a checkpointer using MemorySaver
- A parent agent, with two subagents, “hello” and “goodbye”
- The “hello” agent uses an interrupt to check it is allowed to say “hello”,
- The “goodbye” agent simply says goodbye.
Steps
- I ask the root agent to say hello, it performs the request, and routes to the hello agent. The hello agent initiates an approval (using interrupt) and then says hello..
- I ask the root agent what I have asked it to do. It has forgotten its message history and is unable to say.
- Restart the app
- If I ask the root agent to say goodbye, it performs the request.
- I then ask the root agent what I have asked it to do, and it reports back my previous requests.
The checkpoint state / message history has become corrupted or lost when I have used a subagent tool interrupt. This then affects other workflows and follow up questions.
Sample code
Note: For the sample code to work in the browser, it is necessary to register an AsyncLocalStorage. This file provides an AsyncLocalStorage polyfill
https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/src/tests/browser.ts
Agent setup
import { z } from "zod";
import { tool } from "langchain";
import { Command, interrupt, MemorySaver } from "@langchain/langgraph";
import { createDeepAgent, StateBackend, type AnySubAgent, type CreateDeepAgentParams } from "deepagents/browser";
import { createChatModel } from "./modelProvider.ts";
function createThreadId(prefix: string): string {
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).slice(2, 10);
return `${prefix}-${timestamp}-${randomPart}`;
}
const sayHello = tool(
async ({ name }: { name: string }) => {
const approval = interrupt({
type: "approval_request",
action: `say hello to ${name}`,
message: `Please approve or reject: say hello to ${name}`,
}) as { approved?: boolean; reason?: string };
if (!approval.approved) {
return `Greeting for ${name} was REJECTED. Reason: ${approval.reason || "No reason provided"}`;
}
return `Greeting for ${name} was APPROVED. Hello, ${name}!`;
},
{
name: "sayHello",
description: "Ask for approval and then return a greeting.",
schema: z.object({
name: z.string().default("friend").describe("Who should be greeted"),
}),
},
);
const greeterSubagent: AnySubAgent = {
name: "greeter",
description: "Handles greeting requests using the sayHello tool.",
systemPrompt: "If asked to say hello, call the sayHello tool.",
tools: [sayHello],
};
const sayGoodbyeTool = tool(
async ({ name }: { name: string }) => {
return `Goodbye, ${name}!`;
},
{
name: "sayGoodbye",
description: "Return a goodbye message without requiring approval.",
schema: z.object({
name: z.string().default("friend").describe("Who should receive the goodbye"),
}),
},
);
const sayGoodbyeSubagent: AnySubAgent = {
name: "sayGoodbye",
description: "Handles goodbye requests using the sayGoodbye tool.",
systemPrompt: "If asked to say goodbye, call the sayGoodbye tool.",
tools: [sayGoodbyeTool],
};
async function createRootAgent() {
const model = (await createChatModel({ modelTier: "fast" })) as DeepAgentModel;
const checkpointer = new MemorySaver();
return createDeepAgent({
model,
checkpointer,
backend: (runtime) => new StateBackend(runtime),
subagents: [greeterSubagent, sayGoodbyeSubagent],
systemPrompt:
"You are a root assistant. Delegate hello requests to greeter and goodbye requests to sayGoodbye. Keep answers concise.",
});
}
Hello conversation
thread_id: hello-thread-mqqrutpv-nae4v4r3
Step 1 input: say hello
Interrupt count: 1
Interrupt type: approval_request
Interrupt action: say hello to friend
Interrupt message: Please approve or reject: say hello to friend
Interrupt decision: approved
Step 1 output: Hello, friend!
Step 2 input: tell me the last few requests that I have asked you to do. I am trying to see if you remember my requests or have lost your memory
Step 2 output: I don’t have access to your request history. If you paste the recent conversation/log here (or tell me which app/session), I can summarize the last few requests from that text.
async function runHelloFlow(): Promise<string> {
const threadId = createThreadId("hello-thread");
const agent = await createRootAgent();
const config: InvokeConfig = { configurable: { thread_id: threadId } };
const lines: string[] = [];
lines.push(`thread_id: ${threadId}`);
const firstResult = await agent.invoke(
{
messages: [{ role: "user", content: "say hello" }],
},
config,
);
lines.push("Step 1 input: say hello");
const interrupts = (firstResult as { __interrupt__?: Array<{ value?: unknown }> }).__interrupt__;
lines.push(`Interrupt count: ${interrupts?.length ?? 0}`);
let helloResponse: unknown = firstResult;
if (interrupts && interrupts.length > 0) {
const interruptValue = (interrupts[0].value ?? {}) as {
type?: string;
action?: string;
message?: string;
};
lines.push(`Interrupt type: ${interruptValue.type ?? "(unknown)"}`);
lines.push(`Interrupt action: ${interruptValue.action ?? "(unknown)"}`);
lines.push(`Interrupt message: ${interruptValue.message ?? "(no message)"}`);
const approved = window.confirm(interruptValue.message ?? "Please approve this action.");
lines.push(`Interrupt decision: ${approved ? "approved" : "rejected"}`);
helloResponse = await agent.invoke(
new Command({
resume: approved
? { approved: true }
: {
approved: false,
reason: "Rejected by user in browser confirmation dialog.",
},
}),
config,
);
}
lines.push(`Step 1 output: ${getLastAssistantMessage(helloResponse) ?? "(no assistant output found)"}`);
const secondResult = await agent.invoke(
{
messages: [{ role: "user", content: "tell me the last few requests that I have made" }],
},
config,
);
lines.push("Step 2 input: tell me the last few requests that I have asked you to do. I am trying to see if you remember my requests or have lost your memory");
lines.push(`Step 2 output: ${getLastAssistantMessage(secondResult) ?? "(no assistant output found)"}`);
return lines.join("\n");
}
Goodbye conversation
thread_id: goodbye-thread-mqqrx7ib-72m3notm
Step 1 input: say goodbye
Step 1 output: Goodbye, friend!
Step 2 input: tell me the last few requests that I have asked you to do. I am trying to see if you remember my requests or have lost your memory
Step 2 output: I can only see the requests in this chat so far:
1) “say goodbye”
2) “tell me the last few requests that I have made”
async function runGoodbyeFlow(): Promise<string> {
const threadId = createThreadId("goodbye-thread");
const agent = await createRootAgent();
const config: InvokeConfig = { configurable: { thread_id: threadId } };
const lines: string[] = [];
lines.push(`thread_id: ${threadId}`);
const firstResult = await agent.invoke(
{
messages: [{ role: "user", content: "say goodbye" }],
},
config,
);
lines.push("Step 1 input: say goodbye");
lines.push(`Step 1 output: ${getLastAssistantMessage(firstResult) ?? "(no assistant output found)"}`);
const secondResult = await agent.invoke(
{
messages: [{ role: "user", content: "tell me the last few requests that I have made" }],
},
config,
);
lines.push("Step 2 input: tell me the last few requests that I have asked you to do. I am trying to see if you remember my requests or have lost your memory");
lines.push(`Step 2 output: ${getLastAssistantMessage(secondResult) ?? "(no assistant output found)"}`);
return lines.join("\n");
}
I am encountering an issue where, an interrupt is causing my message history to be forgotten, as well as other odd state behaviour.
The following is a simplified setup to show the issue. The issue is occurring in a browser based application using "deepagents/browser"
Setup
Steps
The checkpoint state / message history has become corrupted or lost when I have used a subagent tool interrupt. This then affects other workflows and follow up questions.
Sample code
Note: For the sample code to work in the browser, it is necessary to register an AsyncLocalStorage. This file provides an AsyncLocalStorage polyfill
https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/src/tests/browser.ts
Agent setup
Hello conversation
Goodbye conversation