Operate a local autonomous GitHub with jj workspaces
Forget pull requests if you want to unlock maximum productivity with parallel coding agents. In fact, forget pushing changes to the remote. This post describes a fully local workflow that lets you build gigantic features in a short period of time with parallel coding agents.
Here’s the scenario: I got nerd sniped by an idea recently and worked on a proof-of-concept over the last five days in between other tasks. Over 700 million tokens later, 150 commits, and 40k lines of TypeScript, I have a fully functional app that I’m itching to start using daily. I never pushed the code to GitHub. I didn’t even open the project in my IDE! My setup:
- I exclusively used the new Cursor CLI with Opus 4.5 (Thinking), and only delegating to GPT-5.2 Extra High Thinking when Opus got stuck on a tricky problem.
- No Ralph loops or long-running tasks. With the right instructions, I am finding that agents are token-efficient and capable of producing meaty results in 4-10 minutes that require manual testing before taking on the next task. This might just be a skill issue on my behalf, and I’m open to the idea of giving the agents a longer leash with a better codebase harness.
- I used Apple Notes to track longer-term tasks.
- I used Jujutsu (jj) workspaces to run parallel agents and let them automously merge changes from each other.
I posted some thoughts about the jj workspace workflow on X and Thorsten Ball asked me to share more details, so this post is my attempt to elaborate.
If you’re not familiar with Thorsten, he writes an excellent newletter Register Spill.
Separate worktrees are good, actually
Once you fully embrace agentic coding, you quickly start running several agents at the same time. Parallel agents is the killer feature of agentic coding! When all the code is written by hand, you need multiple humans to properly multi-task. When agents are writing all the code, your mind is freed up to more easily context switch between unrelated tasks.
I’ve seen claims that it’s fine to run parallel agents in the same git checkout. I tried this and it didn’t work well for my setup. The problem I hit on is that the dev server kept hot-module reloading changes from concurrent agents so the website was constantly in a broken state making it impossible for me to manually test changes.
My problems with parallel agents were solved by letting each agent operate on a separate copy of the codebase. For each copy, I opened an iTerm tab with three horizontal panes: top pane for Cursor CLI, middle pane for ad-hoc commands, and bottom pane for the dev server running on a separate port number.
┌────────────────────────────────────────────────────────────────────┐
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │Pet Inventory (●)│ │ Shopping Cart │ │ Checkout Flow │ ... │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
├────────────────────────────────────────────────────────────────────┤
│ .../src/components/PetCard.tsx +15 │
│ + import { useCart } from '../hooks/useCart'; │
│ + const { addToCart } = useCart(); │
│ ... │
│ $ npm run typecheck 2>&1 3.2s │
│ Fixed. The issue was that `useInventory` was called after │
│ the early return. I moved it before the early returns. │
│ │
│ Claude 4.5 Opus (Thinking) - 70.5k · 5 files edited │
│ / commands · 0 files · ! shell · ctrl+r to review edits │
│ ▶ Auto-run all commands (shift+tab to turn off) │
├────────────────────────────────────────────────────────────────────┤
│ $ jj log │
│ $ │
├────────────────────────────────────────────────────────────────────┤
│ VITE v6.1.0 ready in 1279 ms │
│ → Local: http://localhost:3000/ │
│ [vite] connected. │
└────────────────────────────────────────────────────────────────────┘
↑ Tab title = automatically generated chat session name
(●) = Notification bubble (agent needs attention)
Next, for each iTerm tab, I had a matching browser tab to manually test the changes for that agent. This setup immediately clicked with me, and felt more productive than my usual setup with the Cursor agent in the sidebar and the text editor in the middle.
Git worktrees are … unintuitive
I’ll admit, I didn’t actually use git worktrees for this project since I fully switched to jj several months ago. However, git worktrees never clicked for me while the jj equivalent, workspaces, felt intuitive to work with from day one.
I think the main reason git worktrees never clicked for me is because git is too
low-level. Manipulating raw git commits feels like writing C with goto
statements, while working on jj changes feels like working with a high-level
programming language such as TypeScript. With jj, you declare the relationships
between changes and jj automatically keeps it all rebased and up-to-date. Git,
on the other hand, requires more discipline to scale up and you’re one bad
command from getting into a bad state that’s difficult to recover from.
I made three failed attempts last year to switch over to jj so I won’t lie that there’s a learning curve involved. However, with Opus 4.5, you don’t have to run jj commands by yourself anymore so the learning curve might be smaller now. With Sonnet 4.5, I had to reference jj docs in every chat session while it feels like Opus 4.5 has enough jj knowledge pre-baked into its weights.
Jujutsu workspaces are (mostly) great
What sounds like a complicated setup is actually easy to get started with because you aren’t running commands yourself anymore. The agent is better at using jj than you are! To start with, initialize jj and a few workspaces. I did this manually, but the agent will comfortably do it for you as well.
❯ jj git init
❯ jj workspace add ../pet-store-1
❯ jj workspace add ../pet-store-2
❯ jj workspace add ../pet-store-3
Then, run agents in each of those directories and use the following skill to ask an agent to merge the work from the different workspaces.
If I’m being honest, I didn’t even read the document in full. It’s just agents all the way down.
Part of the magic is that Opus is proficient at using the jj revset language to select what changes to duplicate (aka. cherry-pick), rebase, or squash. Git has commands with similar names but they don’t operate at the same high level of abstractions as their equivalent commands do in jj.
I still regularly run jj to manually get an overview of what fixes are
available in the different workspaces.
❯ jj
○ wprutuwr [email protected] 2026-01-25 12:01:14 pet-store-agent-4@ cb21bfbf
│ feat: add OrderConfirmation component with order summary
○ wrpruvwm [email protected] 2026-01-25 12:01:14 a8848d47
│ feat(checkout): implement multi-step checkout flow
@ nxqyznsr [email protected] 2026-01-25 12:01:14 main default@ 49b349b7
│ Add order types and API client for order management
│ ○ wmporypo [email protected] 2026-01-25 12:01:14 pet-store-agent-3@ 69af9189
│ │ WIP: style: add CSS animations and responsive styles for modal
│ ○ zzynkuzl [email protected] 2026-01-25 12:01:14 7e984206
├─╯ feat(modal): add pet detail modal with accessibility support
○ slwmplqp [email protected] 2026-01-25 12:01:14 2f9de97a
│ Add utility functions for price formatting and validation
│ ○ xwxyzskp [email protected] 2026-01-25 12:01:14 pet-store-agent-2@ b7ae4706
│ │ fix: add debouncing to search and fix edge cases
│ ○ rnznwpqu [email protected] 2026-01-25 12:01:14 0f539a3a
├─╯ feat(search): implement search and filter functionality
│ ○ vmzsktuk [email protected] 2026-01-25 12:01:14 pet-store-agent-1@ 01ecf059
│ │ WIP: test: add unit tests for AddToCartButton component
│ ○ wzwnqztk [email protected] 2026-01-25 12:01:14 a1a83f07
│ │ refactor: extract AddToCartButton into reusable component
│ ○ upwnnmkz [email protected] 2026-01-25 12:01:14 bcb118b9
├─╯ feat(PetCard): add 'Add to Cart' button with inventory refresh
○ nkpxnqpo [email protected] 2026-01-25 12:01:14 68dabab6
│ Initial pet store setup with React, TypeScript, and basic components
◆ zzzzzzzz root() 00000000
That’s about it. For this project, I didn’t merge any changes by hand, I didn’t even review the merged code. I just tested the final product to make all the changes were working as intended, dedicating my full attention to finding issues in the user-facing product and breaking them down into actionable tasks for the agents.
The bad parts of jj workspaces
Before you rush to setup jj workspaces, be warned that there rough edges:
- Never edit a jj change that is an ancestor of another workspace. The
jjCLI should prevent this by construction because it inevitably results in the cryptic “Stale working copy” error. Runningjj workspace update-staletypically auto-fixes the issue but I don’t fully understand yet that command does. - Several times, I hit on a bug where all jj command would hang for several minutes. I never figured out what caused this, and I didn’t figure out a workaround besides just waiting for several minutes for it to go away.
- The default jj log doesn’t snapshot all workspaces by default so it shows
outdated information in the other workspaces if they haven’t been snapshotted
for a while. I solved this by asking the agent to add an
jj laalias that runsjj snapshotin all the workspaces before running the defaultjjcommand. - I asked Opus to troubleshoot why jj was hanging and it ran
rm -rf .jja few seconds later wiping out my local jj state. No data is lost because a colocated jj repo is always backed by actual git commits. Fortunately, I had just merged all in-flight work into the main branch so I didn’t have any meaningful data in my jj repo anyways. I re-initialized the jj repo and moved on, but will be more careful next time asking agents to help to troubleshoot these kind of issues.
The takeaway for me is that you want your jj state to be small, almost ephemeral
so it’s fine even if the .jj/ gets wiped away. Aggressively jj abandon dead
experiments to keep your log pristine. In an ideal setup, your default jj log
should fit within the viewport of your terminal and all the changes have
single-character aliases.
Conclusion
This style of coding is an entirely new world for me, and it’s far from the ideal state. What I describe above is already going to be outdated next week.
My past week felt productive since it was the first time I unlocked true multi-tasking with parallel agents thanks to jj workspaces. It’s still to be determined if my week actually was productive, or if I just burnt a metric ton of tokens to produce slop. The real test will be if the app will actually get used or not…