Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Creates a JJ workspace at /var/lib/forage/workspaces/<name>
  2. Bind mounts this workspace to /workspace in the container
  3. Bind mounts the source repo’s .jj directory 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:

  1. Runs jj workspace forget <name>
  2. Removes the workspace directory
  3. 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:

ModeConditionBehavior
Direct--direct flag usedMounts directory directly at /workspace
JJ workspacePath contains .jj/Creates isolated JJ workspace
Git worktreePath contains .git/Creates git worktree with branch forage-<name>

Comparison

AspectDirect (--direct)JJ workspaceGit worktree
Working directoryDirect bind mountJJ workspaceGit worktree
Multiple sandboxesNeed separate directoriesShare same repoShare same repo
IsolationFile-level (same files)Change-level (JJ)Branch-level (git)
VCSAny (git, jj, etc.)JJ onlyGit only
CleanupRemoves skill filesForgets JJ workspaceRemoves 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 the beads-sync branch

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