Worktrees, GitButler, Jujutsu: finding a version control model for parallel AI agents

I run AI coding agents in parallel. Several at once, on the same repo, each chewing on a different change while I review, test and steer. It's a genuinely different workflow from one-human-one-branch, and it broke my git habits fast.

The core need isn't just "isolate some throwaway branches." It's this: I want several long-lived lines of change that I keep working on and testing at the same time, I want to deal with conflicts there and then rather than discovering them at some upstream merge weeks later, and I want rebasing to be seamless.

I went through three tools chasing that. I thought I'd settled on Jujutsu. I was wrong. I've gone full circle back to GitButler. Here is why.

git worktrees: the boring default

Worktrees are the obvious first answer, and they're what Claude Code, Codex and Cursor all support natively. Each worktree is a separate directory backed by the same repo, so each agent gets true filesystem isolation — its own checkout, its own node_modules, no stepping on each other.

For the version-control story I actually wanted, they fell short. Worktrees isolate where code runs; they do nothing for how versions relate. I was still rebasing by hand, conflicts still surfaced late at merge time instead of when I wanted to deal with them, and keeping several long-lived changes coherent across N directories was bookkeeping, not a model.

But their biggest selling point—execution isolation—turned out to be partially an illusion. If you spin up three agents doing local development, you immediately hit port clashes, database locks, and dev server collisions. Worktrees don't solve this. You still have to orchestrate unique ports and environments for each worker anyway.

Jujutsu: the detour

Jujutsu (jj) was my next stop. It collapses the worktree and branch layers into one tool (workspaces and anonymous heads). It's a brilliant piece of software, terminal-native, and written in Rust.

I spent time wiring up custom interactive UI layers (jjui) to recreate the gestures I missed. But the reality was that I was trying to force jj to behave exactly like the tool I actually wanted to use: GitButler.

GitButler: the right idea, finally in the terminal

GitButler's virtual branches are precisely my mental model for parallel AI work. Multiple branches applied to one working tree at the same time. You assign changes between them freely, reorder, restack, and resolve conflicts fluidly. Each AI worker simply gets a virtual branch.

I had initially bounced off GitButler for two reasons. Both turned out to be wrong.

First, I thought having all changes in a single shared working tree would break test signals (since the tree is a union of all active branches). In practice, it doesn't matter. The real bottleneck for parallel testing isn't the file system—it's the port clashes and local dev instances, which you have to manage regardless of your version control tool.

Second, the stability. Previously, I found GitButler too unstable to trust with my history. But the instability wasn't in the core version control model—it was in the Tauri GUI.

The fix was moving to a full CLI flow. GitButler's underlying but CLI is solid. I took my Neovim plugin—gitbutler.nvim—and completely rewrote it. Instead of trying to drag the GUI model into Neovim, it is now built to match the but CLI TUI directly. I get GitButler's exact workflow, inside my editor, without the GUI instability.

The Workflow Now

For "AI agents plus me, developing, testing and reviewing in parallel", the setup is now remarkably simple.

One repository. One working directory. GitButler manages a virtual branch for each active agent.

When an agent writes code, the changes appear in the working directory. I can assign those file modifications to the agent's virtual branch from inside Neovim using gitbutler.nvim. Conflicts between agents are handled natively by the virtual branch system. The agents don't need to know about GitButler—they just edit files.

If I need to test a specific agent's work in strict isolation, I can unapply the other virtual branches in one keystroke, run the tests, and reapply them. No branch switching ceremony, no multiple directories.

The model GitButler pitched was right all along. It just needed to live in the terminal.