Running multiple AI agents on Juju without isolation is like letting five people
share one clipboard. It works until someone overwrites the wrong bootstrap, and
then you spend your afternoon debugging whose JUJU_DATA is whose instead of
actually fixing anything.
The solution is well-known: each agent needs its own JUJU_DATA, its own
GOBIN, its own view of the world. Joseph Phillips wrote a great post on how
he approaches this,
using four full clones and direnv. If you haven’t read it, stop and do that
now. This post builds directly on it and automates the parts you do by hand.
The plumbing problem
Joseph’s setup works. But it carries a manual tax:
- Copy
.envrcinto each directory by hand - Remember to run
direnv allow(you won’t, at least once — the universe guarantees it) - Delete directories and hope you killed all the controllers first (you didn’t) None of this is intellectually hard. It’s the kind of friction that accumulates like dust — invisible until you’re breathing it.
Worktrees, not clones
Joseph uses four full clones. Honest simplicity, and I respect it. But cloning the same repo four times is like renting four apartments because you keep losing your keys. You have four copies of the object store, four times the disk, and four times the “which branch was I on?” confusion at 9am.
Git worktrees give you the same isolation: each worktree has its own checkout, its own branch, its own working state — but they share one object store. One repo, N agents, and your disk stays within the bounds of reason.
The catch is setup: create the branch, create the worktree, copy .envrc,
run direnv allow, symlink your deps. Every. Time. That’s what wt-helper
is for.
One-time setup
Point wt-helper at your Juju repo once:
wt-helper setup \
--envrc-file .envrc \
--target ~/wd/goland/github.com/gfouillet/juju \
--wrapup-cmd "juju-kill-all" \
--dep-dir _deps \
--dep-dir .idea \
--force
What each flag does:
--envrc-file .envrc— yourdirenvconfig (the one Joseph describes, withJUJU_DATA,GOBIN, andPATH) gets stored and auto-deployed into every future worktree, automatically allowed--wrapup-cmd "juju-kill-all"— when you delete a worktree, this runs first. A Juju controller that nobody kills is just a bootstrap with self-esteem issues, quietly eating cloud credits.--dep-dir _deps --dep-dir .idea— symlinks_depsand your IDE config into every new worktree. Set it up once, inherit everywhere.--force— you’re not asking for permission to update your own hooks.
This installs a post-checkout hook and two git aliases: git wt-add and
git wt-remove. Run once per repo, never again.
Day-to-day
# Spawn an agent for a new task
git wt-add ../juju-wt/5494-fix-leader-election -b 5494-fix-leader-election
# → .envrc installed and direnv-allowed
# → _deps and .idea symlinked in
# → agent starts in a clean, fully wired environment
# Work...
# Done — clean up
git wt-remove ../juju-wt/5494-fix-leader-election
# → juju-kill-all runs first, controllers gone
# → worktree removed
No direnv allow to remember. No controller graveyard. No orphaned bootstraps
quietly accruing on a cloud account you’ve already forgotten about.
Making agents wire themselves up
The last piece is telling your agents to use this. Here’s the relevant section
from my OpenCode AGENTS.md — the file that gives agents standing operating
instructions for the entire workflow:
Summary
# Agent Worktree Rules
You MUST create a dedicated git worktree for EVERY task. You MUST NEVER work
directly on the main branch or in the main repo checkout.
## Detection
Before creating a worktree, determine which mode to use:
1. Run: `git config alias.wt-add`
2. If it returns a value → use **wt-helper mode**
3. If no alias: check if `.envrc` exists in the repo root
- If yes → use **manual copy mode**
- If no → use **plain mode**
## wt-helper mode
- CREATE: `mkdir -p ../<project>-wt && git wt-add ../<project>-wt/<name> -b <branch>`
- DELETE: `git wt-remove <path>` (add `--force` if dirty)
- LIST: `git worktree list`
## Cleanup
- MUST remove the worktree when the task is complete
- MUST run `git worktree prune` after removal
- MUST NOT leave orphaned worktrees
wt-helper runs it via bash -c and sources ~/.profile first, so shell
functions work out of the box. No wrapper scripts, no indirection — just a
function that does exactly what its name threatens.
Get it
go install github.com/gfouillet/wt-helper@latest
wt-helper setup --help
Source and docs: github.com/gfouillet/wt-helper