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 rather than a chore I dread. I went through three tools chasing that. This is what happened with each.

git worktrees: the boring default that didn't fit

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 execution isolation, they're great. For the workflow 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. The isolation was real but the version-control story underneath was still just… git, with all its branch-switching ceremony.

GitButler: exactly the right idea

GitButler's virtual branches were precisely my mental model. Multiple branches applied to one working tree at the same time, changes assigned between them freely, reorder and restack without the switch-stash-switch dance. Conflicts and rebasing handled fluidly. For parallel work it felt purpose-built.

I liked it enough that I built a Neovim plugin for it — gitbutler.nvim — a buffer-based UI driving the but CLI entirely through but <command> --json, so I could manage virtual branches, assign files, commit, absorb, squash and push without leaving my editor. It even surfaced the operations log for snapshot-restore. I put real effort in.

Two things took me back off it.

The smaller one: it's GUI-centric by design, and I live in the terminal and Neovim. Building the plugin was partly an attempt to drag the model into my world — and the fact that I had to build a whole plugin to make it fit was, in hindsight, a signal.

The bigger one: instability. A version control tool is the one piece of software that has to be trustworthy above all else — it's holding your work. When the tool managing my history isn't something I fully trust, that's not a papercut, it's a dealbreaker. No feature set compensates for that.

There's also a structural wrinkle worth naming: with virtual branches, one working tree holds the union of everything applied at once. That's convenient for editing, but it means your tests can see a merged state that doesn't correspond to any single branch — which is exactly the wrong property when you're trying to get a trustworthy test signal from one agent's change in isolation.

Jujutsu: the model, without the instability

Jujutsu (jj) ended up being the answer, and the interesting thing is it doesn't sit as a third option on the worktrees-versus-GitButler spectrum — it collapses both halves into one tool. The "nice branch/commit layer" I wanted from GitButler and the "physical isolation" I wanted from worktrees are both native to jj. In the terminal. No GUI. And it's Rust.

A distinction that matters, because conflating these two is the trap GitButler fell into:

So the rule is simple: heads for organising versions, workspaces for isolating execution. Want trustworthy parallel test signal? Spin up a workspace per agent — same as worktrees — but now with jj's model on top.

Where jj actually wins for AI work

The killer property is the auto-snapshotting working copy. The working copy is always a commit, and jj captures the current state automatically. An agent can generate dozens of files and you never lose work and never stage anything. Combine that with the operation log and jj undo, and you get the best agent-mistake-recovery primitive of the three: every state the agent passed through is in the op log, and undo rewinds it losslessly. For a human-in-the-loop flow where you're reviewing and occasionally reverting what an agent did, that beats both raw worktrees (nothing but the reflog) and GitButler's undo (which I'd be leaving the terminal to reach).

It also delivers the first-class conflicts GitButler markets: rebase always succeeds, conflicts are recorded in the commit and resolved later in any order, so restacking agent branches never blocks. And the commit surgery — split, squash, move changes between revisions, edit any commit and auto-restack its descendants — matches GitButler's drag-and-drop while staying in commands that fit tmux and Neovim natively. That was the whole thing I'd wanted: address conflicts now, rebase seamlessly, without trusting an unstable layer or leaving the terminal.

Two honest caveats

It isn't free of sharp edges, and pretending otherwise would be dishonest.

First, the snapshot model has no background daemon — jj snapshots when you run a jj command. If an agent makes changes and crashes before any jj command runs, those changes aren't yet in history. The fix is a hook: a preexec that runs jj status before each command, or a Claude Code PreToolUse hook running jj status (about 0.01s) so it snapshots before each tool call. If you're wiring jj into an agent flow, that hook is non-optional — bake it in from the start.

Second, the agent and editor ecosystem is younger than worktrees. Worktrees have native support across the major agents; jj support is catching up. The practical fix is teaching the agent the jj model explicitly — a jj section in your CLAUDE.md (or equivalent) so the agent doesn't reflexively reach for git add and git commit, and knows to use the working-copy-as-commit model.

The quiet team advantage: colocated mode

The thing that sealed it: run jj in colocated mode and it keeps a real .git repo underneath. GitHub, your CI, any teammate still on plain git — none of them see anything unusual. You're adopting jj unilaterally, on top of the existing git repo, transparently. No GUI to standardise on, no buy-in required, no licensing question (jj is Apache-2.0). You get the better model without imposing anything on anyone.

The ranking, for my use case

For "AI agents plus me, developing, testing and reviewing in parallel":

Jujutsu is the best fit. It matches worktrees on test isolation (via workspaces), beats raw git on the develop-and-review loop (auto-snapshot, op-log undo, painless commit surgery, first-class conflicts), is terminal-native and Rust, and adopts transparently over an existing git repo. The price is a small-but-real mental-model shift, the snapshot hook for agents, and a younger ecosystem you paper over with a jj skill in your agent config.

Worktrees remain the boring-safe default — most native agent support, nothing new to learn. The right call if you want zero adoption cost or a single obvious primitive for a team to share.

GitButler drops to third for this specific goal, because of the shared-tree test-signal problem and, for me, the stability. Its real edge is the GUI review experience — which, if you live in the terminal, jj's op log and commit surgery largely neutralise anyway.

On gitbutler.nvim

The plugin still works and it's still up. I'm glad I built it — it's some of my better Lua, the --json-only architecture is clean, and it taught me a lot about GitButler's model. But I've moved my own workflow to jj, so I'll likely archive it before long. If you're a GitButler user in Neovim, it's there and MIT-licensed; just know the author rides a different horse now.

That's the honest arc: the model GitButler pitched was right, but the tool I could actually trust, in the environment I actually work in, turned out to be Jujutsu.

Rebuilding the GitButler gestures in jjui

Moving to jj didn't mean giving up the ergonomics that drew me to GitButler — it meant rebuilding them as terminal-native gestures. I use jjui as my interactive front-end and wired a set of custom actions into its config that reconstruct the GitButler moves I missed, plus a couple jj makes possible that GitButler never could.

The one that matters most is toggle simultaneous edit — my jj-native replacement for virtual branches. It manages a merge commit sitting over several branches at once, so I can edit them all in a single working copy, exactly like GitButler's applied-branches model. Toggling it on a selected set creates the simultaneous-edit merge; toggling a commit that's already in the merge removes it (dissolving the merge when it's the last one); toggling a commit outside the merge detaches it to trunk and adds it as a parent. The working copy stays parked on the merge throughout. That's GitButler's headline feature, reconstructed in jj, opt-in per moment rather than the permanent default — so when I don't want the merged-union test-signal problem, I just don't engage it.

The rest are quality-of-life gestures that lean on jj's auto-rebase:

Behind the fetch-and-cleanup gesture is a small standalone script that forgets local bookmarks whose PR has merged upstream. Rather than guessing from empty commits, it asks GitHub directly (gh pr list --state merged) for the head branches of merged PRs — ground truth across squash, rebase and merge merges alike — intersects that with my local bookmarks minus trunk, and forgets each match. It only abandons the underlying commit when it's a clean mutable leaf, failing closed on anything divergent, conflicted, or carrying stacked work. It supports a dry-run flag and a built-in self-test.

This is the part I'd point any GitButler refugee to: the model you liked isn't locked inside GitButler. With jj's auto-rebase and first-class conflicts doing the hard part underneath, a few hundred lines of config and one small script gets you the gestures back — in the terminal, on a foundation you can trust.