A user wants to try another answer from an earlier message, but keep the current path too. Another user has a long thread, and the old context confuses the model. A fork copies a thread into a new thread, up to a message. A reset starts a fresh model context with a short note, and the transcript keeps every message.
host.fork copies a thread into a new thread and returns the open session of the new thread:
import { createHarnessHost, defineHarness } from '@tanstack/ai-harness'
import { memoryPersistence } from '@tanstack/ai-persistence'
import { openaiText } from '@tanstack/ai-openai'
export const assistant = defineHarness({
name: 'acme/assistant',
adapter: openaiText('gpt-6.1-sol'),
})
const host = createHarnessHost({ persistence: memoryPersistence() })
export async function branch(threadId: string, messageId: string) {
const fork = await host.fork(assistant, {
threadId,
newThreadId: `${threadId}-branch-${messageId}`,
at: messageId,
})
return fork.threadId
}at is the id of the last message to copy. The message ids stay the same, so at points at the same message in both threads.
The fork copies:
The fork does not copy plugin state, pending interrupts, queued inputs, running work, or usage totals. If newThreadId already has messages, host.fork throws. It also throws for an at that the thread does not have.
A fork stays in its harness. If the index entry of the thread names another harness, host.fork throws. To move a conversation to another harness, use host.transfer.
On a host with access, pass user too. The user needs the fork action on the old thread, and owns the new thread.
If a thread waits for an approval, fork it at a message before the tool call. A fork of the whole transcript copies the tool call, but the fork has no interrupt to answer it.
A user edits an earlier message and sends it again. The fork must stop before that message. host.sessions.fork takes the cut as at: { before: id } or at: { through: id }, and it writes the new thread into the session index:
export async function editAndResend(threadId: string, messageId: string, text: string) {
const entry = await host.sessions.fork({
harness: assistant,
threadId,
at: { before: messageId },
})
const fork = await host.open(assistant, { threadId: entry.threadId })
await fork.send(text)
return entry.threadId
}session.reset({ note }) starts a fresh model context. From the next turn, the model sees only the note and what comes after the reset:
export async function startFresh(threadId: string) {
const session = await host.open(assistant, { threadId })
return session.reset({
note: 'Summary: the user moves the billing service to the new region. The DNS change is done. The database move is next.',
})
}What the reset does: