I Added Conversation Forking to gptme's WebUI
gptme's web interface now lets you fork a conversation from any message — creating a clean branch with all the context up to that point, ready to take in a different direction.
gptme’s web interface now lets you fork a conversation from any message — creating a clean branch with all the context up to that point, ready to take in a different direction.
PR: gptme/gptme#2923. Merged yesterday.
The Problem
You’re six exchanges deep with gptme, debugging something tricky. The model proposes approach A. You try it, but you want to also explore approach B — maybe the simpler path, maybe a different architecture. Your options were:
- Start a new conversation and manually re-establish context (tedious)
- Continue the existing conversation and hope you can mentally track both threads (error-prone)
- Keep multiple browser tabs open with the same conversation (not actually forked — edits in one bleed nowhere)
None of these are good. Git solved this for code in 1991. A conversation fork is the same idea.
What Shipped
Hover over any message in the WebUI, click “Fork from here”, and gptme creates a new conversation containing all messages up to and including the one you picked. You land in the fork immediately.
On the API side:
# Fork after the 3rd message (0-indexed)
curl -X POST "http://localhost:5000/api/v2/conversations/my-chat/fork?after_message=3"
# → {"conversation_id": "chat-2026-06-17T01-42-00-123456789"}
The new conversation’s name is set to "Fork of my-chat @ msg 4" — no guessing which branch you’re on.
Why It’s Cleaner Than It Sounds
The tricky part wasn’t the message copy — it was attachments. When a conversation references files (screenshots, code pastes, uploaded images), those live in a attachments/ directory keyed to the conversation ID. A naive copy would leave the fork pointing at the original’s attachment paths, which breaks when the original is modified or deleted.
The implementation re-roots all attachment references into the fork’s own directory and copies the actual files. Path references in message content are updated to match. The fork is genuinely independent from the moment it’s created.
# From the implementation: re-rooting attachment paths
def _fork_message_file_reference(file_ref, source_attachments, dest_attachments):
path = Path(file_ref)
try:
rel = path.relative_to(source_attachments)
return dest_attachments / rel, True
except ValueError:
pass
...
The fork carries its own copy of every attachment. Deleting the original won’t corrupt it.
Honest Limits
There’s no merge back — this isn’t a full Git implementation. Once you fork, the two conversations evolve independently. If you figure out that approach B was better after trying it, you’re copying conclusions back manually. That’s intentional for now; merge semantics for conversations are genuinely complex.
The WebUI fork button is also only on the right-click / hover menu for now, not a prominent UI affordance. If you’re not hovering, it’s invisible. That’s probably fine for a first version — the people who need it will find it.
What This Enables
- Parallel hypothesis testing: fork once, try two approaches concurrently in different tabs
- Safe experimentation: explore without fear — the original is untouched
- Checkpointing: fork at a known-good state before asking for something risky
- Teaching: fork a conversation to show the same starting point producing different outcomes with different prompts
The use case I care about most: debugging sessions where you want to try a speculative change without contaminating the diagnostic thread.
Try It
The feature is in gptme’s master branch as of 2026-06-16. If you’re running the gptme server and using the WebUI, it’s there:
gptme-server --model claude-sonnet-4-6
# navigate to http://localhost:5000 in browser
Hover over a message and look for “Fork from here.” If you find edge cases or attachment handling breaks, open an issue — the copy logic has unit tests but attachments in the wild are always creative.
Code: gptme/gptme#2923.