Interrupt persistence and checkpoint-based message editing.
Both interrupt persistence and message editing rely on LangGraph's server-side checkpoints. Once you have one wired up, the other is mostly free.
Interrupt persistence
LangGraph supports interrupting the execution flow to request user input or handle specific interactions. These interrupts can be persisted and restored when switching between threads:
- Make sure your thread state type includes the
interruptsfield. - Return the interrupts from the
loadfunction along with the messages. - The runtime automatically restores the interrupt state when switching threads.
const runtime = useLangGraphRuntime({
stream: async (messages, { initialize, ...config }) => {
/* ... */
},
load: async (externalId) => {
const state = await getThreadState(externalId);
return {
messages: state.values.messages,
interrupts: state.tasks[0]?.interrupts,
};
},
});This is particularly useful for applications that require user approval flows, multi-step forms, or other interactive elements that span multiple thread switches.
Message editing and regeneration
LangGraph uses server-side checkpoints for state management. To support message editing (branching) and regeneration, provide a getCheckpointId callback that resolves the appropriate checkpoint for server-side forking.
const runtime = useLangGraphRuntime({
stream: async (messages, { initialize, ...config }) => {
const { externalId } = await initialize();
if (!externalId) throw new Error("Thread not found");
return sendMessage({ threadId: externalId, messages, config });
},
create: async () => {
const { thread_id } = await createThread();
return { externalId: thread_id };
},
load: async (externalId) => {
const state = await getThreadState(externalId);
return {
messages: state.values.messages,
interrupts: state.tasks[0]?.interrupts,
};
},
getCheckpointId: async (threadId, parentMessages) => {
const client = createClient();
const history = await client.threads.getHistory(threadId);
for (const state of history) {
const stateMessages = state.values.messages;
if (!stateMessages || stateMessages.length !== parentMessages.length) {
continue;
}
const hasStableIds =
parentMessages.every((m) => typeof m.id === "string") &&
stateMessages.every((m) => typeof m.id === "string");
if (!hasStableIds) continue;
const isMatch = parentMessages.every(
(m, i) => m.id === stateMessages[i]?.id,
);
if (isMatch) {
return state.checkpoint.checkpoint_id ?? null;
}
}
return null;
},
});When getCheckpointId is provided:
- Edit buttons appear on user messages, allowing users to edit and resend from that point.
- Regenerate buttons appear on assistant messages, allowing users to regenerate the response.
The resolved checkpointId is passed to your stream callback via config.checkpointId. Your sendMessage helper should map it to the LangGraph SDK's checkpoint_id parameter (see quickstart for the helper).
Without getCheckpointId, edit and regenerate buttons will not appear. This is intentional; truncating client-side messages without forking from the correct server-side checkpoint would produce incorrect state.