- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Fork and reset a thread
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.
Fork a thread at a 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.
- Without at, the fork copies the whole transcript.
- With at: null, the fork copies no messages. It still copies the settings and the plugin config, so the new thread starts empty with the same setup.
The fork copies:
- The transcript, up to and including the message at.
- The stored settings of the thread, and the values that setPluginSetting stored for its plugins.
- The media that the copied messages use. The copies get new ids.
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.
Fork before or through a message#
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
}- { before: id } copies the messages before id. Before the first message, the fork has no messages, but it keeps the settings.
- { through: id } copies the messages up to and including id, as at does.
- The host picks the new thread id. The new index entry has the title of the old one plus (fork).
- members picks who can use the fork. 'self': only the user who forks, as the owner. 'all': the user who forks is the owner, and the other members keep their roles. See Fork a shared thread.
- Without host access and without members, the fork keeps the owner and the members of the old thread.
- host.sessions.fork calls host.fork, so it copies the same things as host.fork.
Reset with a handoff note#
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:
- The note reaches the model as a user message, as you wrote it. Write it as a summary or a handoff. Without a note, the model sees only what comes after the reset.
- transcript() keeps every message. The reset adds a marker message with metadata.harness.reset, so a UI can draw a line there. The session also sends a harness.reset event. The session view shows it as an info notice.
- If a turn runs, the reset waits. It applies when that turn ends, before the next queued turn.
- While the thread waits for interrupts, the reset is refused with pending_interrupts. Answer the interrupts first.
- It is an input, so it runs once per inputId. On a durable host, the reset stays after a restart, and compaction after a reset sees only the new context.
What you have now#
- A branch of a thread at any message, with its settings and media.
- A fork before or through a message, in the session index.
- A fresh model context with a handoff note, and the whole history in the transcript.
- A marker in the transcript that your UI can show as a line.