Welcome back! In Chapter 1: CLI Command Registration, we learned how to add the /branch command to our application's "menu".
Now, we are going to cook the meal. We will explore the logic that runs when the user actually orders that command. We call this Conversation Forking.
Imagine you are playing a difficult video game. You are about to fight a boss. What do you do? You create a Save Point. If you lose, you reload the save. If you win, you continue.
Coding with an AI is similar.
The Solution: You type /branch. This acts like "Save As..." in a text editor. It takes your current conversation history, copies it, and moves you into a new timeline. The original conversation stays safe and untouched.
Before we look at the code, let's understand the three ingredients needed to fork a conversation:
When you run /branch, the application performs a specific sequence of events.
Let's look at the code inside branch.ts. We will break the createFork function down into small, digestible pieces.
First, we need to figure out who we are (Current ID) and who we want to be (New ID).
// Inside createFork function...
const forkSessionId = randomUUID() // Generate a brand new ID
const originalSessionId = getSessionId() // Get the ID we are copying from
// Calculate file paths
const forkSessionPath = getTranscriptPathForSession(forkSessionId)
const currentTranscriptPath = getTranscriptPath()
Explanation:
randomUUID() creates a unique string like a1b2-c3d4.... This determines the identity of our new timeline.Next, we read the existing conversation into memory.
// Read the raw text of the current conversation
const transcriptContent = await readFile(currentTranscriptPath)
// Parse the text into a list of objects (messages)
const entries = parseJSONL(transcriptContent)
Explanation:
entries. Now we have the entire history in memory.
This is the most critical part. We don't just copy the messages; we modify them slightly. We want to update the sessionId on every message to match our new fork, and we want to add a forkedFrom property.
for (const entry of entries) {
// Create a copy of the message (forkedEntry)
const forkedEntry = {
...entry,
sessionId: forkSessionId, // Switch to the NEW ID
forkedFrom: { // The "Passport Stamp"
sessionId: originalSessionId,
messageUuid: entry.uuid,
},
}
// Add to our list of lines to write
lines.push(jsonStringify(forkedEntry))
}
Explanation:
...entry: We copy all existing data (text, author, timestamp).sessionId: We overwrite the old ID with the new forkSessionId. Now this message belongs to the new timeline.forkedFrom: We add metadata linking back to the parent. This allows us to trace the family tree of conversations later.Finally, we save this new timeline to the disk.
// Write the new list of lines to a new file
await writeFile(forkSessionPath, lines.join('\n') + '\n', {
encoding: 'utf8',
mode: 0o600, // Secure read/write permissions
})
return { sessionId: forkSessionId, forkPath: forkSessionPath }
Explanation:
forkSessionPath.What if you branch a conversation that is already a branch? We want the names to be friendly, like "My Project (Branch)" and "My Project (Branch 2)".
We use a helper function getUniqueForkName to handle this logic.
async function getUniqueForkName(baseName: string): Promise<string> {
const candidate = `${baseName} (Branch)`
// Check if "My Project (Branch)" already exists
const exists = await searchSessionsByCustomTitle(candidate, { exact: true })
if (!exists) return candidate
// If it exists, we start counting: Branch 2, Branch 3...
// (Logic continues to loop until a free number is found)
}
Explanation:
Now that we understand the logic, here is how the main command handler uses it to solve our "Save Point" problem.
export async function call(onDone, context, args) {
// 1. Run the heavy logic we defined above
const result = await createFork(args) // args is the optional custom name
// 2. Generate a unique name for the user to see
const title = await getUniqueForkName(result.title)
// 3. Switch the active application context to the new session
await context.resume(result.sessionId, /* ... */)
// 4. Tell the user
onDone(`Branched conversation. You are now in "${title}".`)
}
What happens for the user:
/branch Experiment AIn this chapter, we explored the Conversation Forking Logic. We learned:
However, we simply glossed over how exactly these files are structured and saved. We mentioned "JSONL" and "Transcript Paths," but we didn't explain the underlying system that manages these files on your hard drive.
In the next chapter, we will dive deep into how the application permanently stores your conversations.
Next Chapter: Transcript Persistence Model
Generated by Code IQ