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.
Every message assembled by the stream carries metadata describing the checkpoint before it was produced. Forking from that checkpoint rewinds the thread to the state immediately prior to the message; any new submission from there produces an alternate branch without mutating the original run history on the server.
import { Component, Input } from "@angular/core";
import { HumanMessage, type BaseMessage } from "@langchain/core/messages";
import { injectMessageMetadata, injectStream } from "@langchain/angular";
@Component({
standalone: true,
template: `
<button [disabled]="!metadata()?.parentCheckpointId" (click)="saveEdit()">Save edit</button>
`,
})
export class EditButtonComponent {
@Input({ required: true }) message!: BaseMessage;
@Input({ required: true }) newContent!: string;
readonly stream = injectStream();
readonly metadata = injectMessageMetadata(this.stream, () => this.message.id);
saveEdit() {
const forkFrom = this.metadata()?.parentCheckpointId;
if (!forkFrom) return;
void this.stream.submit({ messages: [new HumanMessage(this.newContent)] }, { forkFrom });
}
}
To retry the last AI turn, fork from the parent checkpoint of the preceding human message and re-submit the same input:
import { Component, computed } from "@angular/core";
import { injectMessageMetadata, injectStream } from "@langchain/angular";
@Component({
standalone: true,
template: `
<button [disabled]="!metadata()?.parentCheckpointId || !lastHuman()" (click)="retry()">
Retry
</button>
`,
})
export class RetryButtonComponent {
readonly stream = injectStream();
readonly lastHuman = computed(() =>
[...this.stream.messages()].reverse().find((m) => m.type === "human"),
);
readonly metadata = injectMessageMetadata(this.stream, () => this.lastHuman()?.id);
retry() {
const forkFrom = this.metadata()?.parentCheckpointId;
const lastHuman = this.lastHuman();
if (!forkFrom || !lastHuman) return;
void this.stream.submit({ messages: [lastHuman] }, { forkFrom });
}
}