JJ Workspaces
Forage integrates with Jujutsu (jj) to enable multiple agents working on the same repository simultaneously, each with an isolated working copy.
Overview
When you use --repo with a JJ repository (without the --direct flag), Forage:
- Creates a JJ workspace at
/var/lib/forage/workspaces/<name> - Bind mounts this workspace to
/workspacein the container - Bind mounts the source repo’s
.jjdirectory so the workspace symlink resolves
Each sandbox gets its own working copy of the files, but they all share the repository’s operation log and history.
┌─────────────────────────────────────────────────────────────────────┐
│ Host │
│ │
│ ~/projects/myrepo/ │
│ ├── .jj/ ◄─────────────────────────┐ │
│ ├── src/ │ shared │
│ └── ... │ │
│ │ │
│ /var/lib/forage/workspaces/ │ │
│ ├── agent-a/ ◄── jj workspace ───────────┤ │
│ │ ├── src/ (separate working copy) │ │
│ │ └── ... │ │
│ └── agent-b/ ◄── jj workspace ───────────┘ │
│ ├── src/ (separate working copy) │
│ └── ... │
│ │
└─────────────────────────────────────────────────────────────────────┘
Creating JJ Sandboxes
Prerequisites
Your project must be a JJ repository:
cd ~/projects/myrepo
jj git init --colocate # or jj init
Create Multiple Sandboxes
# First agent
forage-ctl up agent-a --template claude --repo ~/projects/myrepo
# Second agent on the same repo
forage-ctl up agent-b --template claude --repo ~/projects/myrepo
# Third agent with a different template
forage-ctl up agent-c --template multi --repo ~/projects/myrepo
Each sandbox appears as a JJ workspace:
jj workspace list -R ~/projects/myrepo
Output:
default: abc123 (no description set)
agent-a: def456 (empty) (no description set)
agent-b: ghi789 (empty) (no description set)
agent-c: jkl012 (empty) (no description set)
Working with JJ Inside Sandboxes
When you connect to a JJ sandbox, the skill injection includes JJ-specific instructions:
forage-ctl ssh agent-a
Inside the sandbox, use JJ commands:
# Show status
jj status
# Show changes
jj diff
# Create a new change
jj new
# Describe your change
jj describe -m "Add feature X"
# See all changes
jj log
Isolation Benefits
Parallel Work
Each agent works on a separate JJ change:
agent-a: Working on feature-auth
agent-b: Working on feature-api
agent-c: Reviewing and testing
Changes don’t interfere—each workspace has its own working copy.
Easy Coordination
From the host, you can see all work:
# See all changes from all workspaces
jj log -R ~/projects/myrepo
# Squash agent work into main
jj squash --from agent-a -R ~/projects/myrepo
Safe Experimentation
If an agent makes a mess:
# Reset just that sandbox
forage-ctl reset agent-a
# Or abandon the change in JJ
jj abandon agent-a -R ~/projects/myrepo
Cleanup
When you remove a JJ sandbox, Forage:
- Runs
jj workspace forget <name> - Removes the workspace directory
- Cleans up container and metadata
forage-ctl down agent-a
The changes made in that workspace remain in the repository history—only the workspace is removed.
Workspace Modes
Forage automatically detects the workspace mode based on the repository type:
| Mode | Condition | Behavior |
|---|---|---|
| Direct | --direct flag used | Mounts directory directly at /workspace |
| JJ workspace | Path contains .jj/ | Creates isolated JJ workspace |
| Git worktree | Path contains .git/ | Creates git worktree with branch forage-<name> |
Comparison
| Aspect | Direct (--direct) | JJ workspace | Git worktree |
|---|---|---|---|
| Working directory | Direct bind mount | JJ workspace | Git worktree |
| Multiple sandboxes | Need separate directories | Share same repo | Share same repo |
| Isolation | File-level (same files) | Change-level (JJ) | Branch-level (git) |
| VCS | Any (git, jj, etc.) | JJ only | Git only |
| Cleanup | Removes skill files | Forgets JJ workspace | Removes git worktree |
Use --direct when:
- Simple single-agent workflow
- Project doesn’t use JJ or git
- You want direct file access without VCS isolation
Use JJ repos (auto-detected) when:
- Multiple agents on same codebase
- You want change isolation
- Project uses JJ for version control
Use Git repos (auto-detected) when:
- Multiple agents on same git repository
- Each agent works on a separate branch (auto-created as
forage-<name>)
Composable JJ Mounts
With workspace mounts, you can create multiple JJ workspaces within a single sandbox. A common pattern is overlaying a beads branch alongside the main workspace:
templates.claude-beads = {
agents.claude = { ... };
workspace.mounts.main = {
containerPath = "/workspace";
mode = "jj";
};
workspace.useBeads = {
enable = true;
package = pkgs.beads;
};
};
forage-ctl up agent-a -t claude-beads --repo ~/projects/myrepo
This creates two JJ workspaces from the same repository:
/workspace— the main working copy/workspace/.beads— checking out thebeads-syncbranch
Each mount gets its own managed workspace directory under /var/lib/firefly-forage/workspaces/<sandbox>/<mount-name>/.
You can also mount JJ workspaces from different repositories using named repos:
forage-ctl up dev -t multi-repo \
--repo ~/projects/frontend \
--repo backend=~/projects/backend
See Workspace Mounts for the full guide.
Troubleshooting
“Not a jj repository”
The path doesn’t contain a .jj/repo directory:
# Initialize JJ
cd ~/projects/myrepo
jj git init --colocate
“JJ workspace already exists”
A workspace with that name already exists in the repo:
# Check existing workspaces
jj workspace list -R ~/projects/myrepo
# Use a different sandbox name, or remove the existing workspace
jj workspace forget existingname -R ~/projects/myrepo
JJ commands fail inside sandbox
Ensure the source repo’s .jj directory is accessible. The sandbox needs the bind mount to resolve the workspace symlink. This should be automatic—if it’s not working, check:
# Inside sandbox
ls -la /workspace/.jj/
# Should show a symlink to the repo's .jj directory