Forking is expressed as "submit from the parent checkpoint of a specific message". Pick a message, read its parent checkpoint, and submit new input from there.
Learn more: For branching-chat and time-travel UX, see the branching chat and time travel documentation.
Two pieces work together:
injectMessageMetadata(stream, msgId) — returns { parentCheckpointId } for the given message, or undefined until it loads. See Selectors.submit(input, { forkFrom }) — dispatches a new run whose initial checkpoint is forkFrom, replacing anything that happened after it on the thread.You pick a message, read its parent checkpoint, and submit from there with new input. The new turn becomes the canonical continuation of the thread — old messages after the fork point are superseded.
import { injectStream, injectMessageMetadata, type AnyStream } from "@langchain/angular";
import { HumanMessage, type BaseMessage } from "@langchain/core/messages";
function EditButton({
stream,
message,
newContent,
}: {
stream: AnyStream;
message: BaseMessage;
newContent: string;
}) {
const metadata = injectMessageMetadata(stream, message.id);
return (
<button
disabled={!metadata?.parentCheckpointId}
onClick={() => {
const forkFrom = metadata?.parentCheckpointId;
if (!forkFrom) return;
void stream.submit({ messages: [new HumanMessage(newContent)] }, { forkFrom });
}}
>
Save edit
</button>
);
}
To retry the last AI turn, fork from the parent checkpoint of the preceding human message and re-submit the same input:
function Retry({ stream }: { stream: AnyStream }) {
const lastHuman = [...stream.messages].reverse().find((m) => m.type === "human");
const metadata = injectMessageMetadata(stream, lastHuman?.id);
return (
<button
disabled={!metadata?.parentCheckpointId || !lastHuman}
onClick={() => {
const forkFrom = metadata?.parentCheckpointId;
if (!forkFrom || !lastHuman) return;
void stream.submit({ messages: [lastHuman] }, { forkFrom });
}}
>
Retry
</button>
);
}