Workspace-based coordination CLI for multi-agent work on codebases using jj (Jujutsu).
Bacchus helps AI agents coordinate when working on the same codebase by:
- Workspace isolation - each agent works in its own jj workspace
- Task management - SQLite-based task tracking with dependencies and footprints
- Session management - stop hooks keep agents working until tasks complete
- Orchestrator-driven release - agents mark work ready, orchestrator handles merging
- Non-blocking conflicts - jj allows conflict detection without blocking work
curl -fsSL https://raw.githubusercontent.com/vu1n/bacchus/main/scripts/install.sh | bashThis installs the bacchus binary to ~/.local/bin/ and cleans up any legacy global hooks/skills from previous versions.
cd your-project
bacchus initThis sets up everything project-local:
.bacchus/- task database, workspaces, archetypes.claude/settings.json- project-level stop hook.claude/skills/bacchus/SKILL.md- Claude Code skill (teaches Claude how to use bacchus)
- jj (Jujutsu) v0.20+
- git (jj uses git backend)
git clone https://github.com/vu1n/bacchus.git
cd bacchus
cargo build --release
cp target/release/bacchus ~/.local/bin/curl -fsSL https://raw.githubusercontent.com/vu1n/bacchus/main/scripts/uninstall.sh | bashBacchus coordinates multi-agent work through a plan → orchestrate → execute flow:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. PLAN │
│ User request → planner/architect workflow │
│ Breaks down work into tasks with dependencies │
│ Outputs: .bacchus/tasks.yaml │
├─────────────────────────────────────────────────────────────────────┤
│ 2. IMPORT │
│ bacchus task import --epic-id <EPIC> │
│ Loads tasks from YAML into SQLite │
│ Calculates ready tasks (no blockers, no footprint conflicts) │
├─────────────────────────────────────────────────────────────────────┤
│ 3. ORCHESTRATE │
│ session start orchestrator + session spawn-workers │
│ Monitors progress, handles merges, manages conflicts │
├─────────────────────────────────────────────────────────────────────┤
│ 4. EXECUTE │
│ Each agent: claim → work in jj workspace → release │
│ Orchestrator: rebase → merge to main → close task │
└─────────────────────────────────────────────────────────────────────┘
Bacchus runs a deterministic swarm loop:
- Plan - break goal into atomic tasks with dependencies and footprints
- Admit - only tasks that are dependency-clear and non-overlapping become
ready - Execute - workers claim one task each in isolated jj workspaces
- Reconcile - orchestrator merges ready work, reopens failed/stale work, resolves conflicts via
needs_resolution - Measure -
bacchus evalreports throughput plus worker-reliability signals
Swarm safety comes from hard invariants:
- Single-task ownership via task claims (
claimed_by, heartbeat) - Leader fencing via orchestrator lease (one orchestrator run controls scheduling)
- Workspace isolation (one jj workspace per task)
- Failure recovery (stale worker detection, task reopen, optional stale PID kill)
Ralph mode is the default recommendation (permissioned, auditable runs):
claudeYolo mode is optional for trusted local repos:
claude --dangerously-skip-permissionsIn both modes, keep bacchus stop hooks enabled so sessions enforce task completion and orchestration safety.
Tell Claude what you want and mention bacchus:
"I want to add user authentication with login, logout, and password reset.
Use bacchus to parallelize this work across multiple agents."
With the bacchus skill workflow, Claude can plan tasks, import them, and orchestrate agents with archetype prompts.
Autonomous worker spawning requires BACCHUS_WORKER_CMD (see Session Management).
| What you want | What to say |
|---|---|
| Full automation | "Use bacchus to implement X with N agents" |
| Plan only | "Break down X into tasks for bacchus" |
| Single task | "Work on task TASK-001 with bacchus" |
| Check status | "Show bacchus status" |
Key roles:
- Planner: Breaks down requests into atomic tasks with dependencies
- Orchestrator: Spawns agents, monitors progress, handles merges
- Agent: Works on a single task in an isolated workspace (with type-specific archetype)
Copy/paste this and replace <GOAL>:
Use bacchus to implement: <GOAL>
Operating mode:
- Use Ralph mode by default (no --dangerously-skip-permissions).
- Keep sessions active; do not exit while bacchus session check blocks.
Execution contract:
1) Ensure jj is initialized for this repo (colocated with git if needed).
2) Create/update an epic and task plan in .bacchus/tasks.yaml (atomic tasks, dependencies, footprints, archetypes).
3) Import tasks into SQLite and verify ready queue.
4) Start orchestrator session with max concurrency 3.
5) Use bacchus session spawn-workers --dry-run first, then launch workers.
6) Continuously process releases and recover stale/failed workers.
7) Finish when all tasks are closed or explicitly blocked, then summarize outcomes and risks.
Required commands to use:
- bacchus task import --epic-id <EPIC>
- bacchus task list --ready
- bacchus session start orchestrator --max-concurrent 3
- bacchus session spawn-workers --count 3
- bacchus process-releases
- bacchus eval --days 7
This guide walks through setting up Bacchus for a multi-agent workflow.
cd your-project
# One-shot bootstrap (recommended)
bacchus init --epic-id AUTH --epic-title "Authentication"
# Initialize jj once (if repo is git-only today)
if ! jj root >/dev/null 2>&1; then
jj git init --colocate
jj config set --repo user.name "Your Name"
jj config set --repo user.email "you@example.com"
jj bookmark create main -r @
jj describe -m "Initialize jj for bacchus"
jj new
fibacchus init is idempotent: it creates .bacchus/tasks.yaml if missing, bootstraps jj unless --skip-jj, and can create an initial epic.
Bacchus uses jj (Jujutsu) for version control. If your project uses git, initialize jj in colocated mode:
cd your-project
jj git init --colocate
# Set up user config for jj
jj config set --repo user.name "Your Name"
jj config set --repo user.email "you@example.com"
# Create main bookmark (like a branch)
jj bookmark create main -r @
jj describe -m "Initial commit"
jj newYou can create tasks manually or ask Claude to help plan:
Option A: Ask Claude to plan (recommended for complex work)
"Break down user authentication into tasks for bacchus"
Claude will analyze your request and create tasks with proper dependencies.
Option B: Create tasks manually
bacchus task initOption C: Generate recursively with Bun (experimental)
bun scripts/recursive-planner.ts \
--goal "Implement user authentication with login, logout, and password reset" \
--epic-id AUTH \
--llm-provider claude \
--task-granularity small \
--import \
--validateThe recursive planner writes .bacchus/tasks.yaml, can split broad tasks into smaller subtasks, and can optionally run task import + task validate.
When your prompt is goal-only (no PRD/spec), it generates explicit spec + scaffolding + architecture tasks before implementation slices.
Use --llm-provider codex to decompose with Codex headless, --llm-provider openai for API mode, or --llm-provider off for heuristic-only planning.
Use --task-granularity small to force finer agent-sized tasks.
Edit .bacchus/tasks.yaml:
version: 1
tasks:
- id: AUTH-001
title: "Add user login endpoint"
description: |
Create POST /api/login that:
- Validates credentials
- Returns JWT token
- Handles errors appropriately
priority: 1
status: open
depends_on: []
footprint:
creates:
- "src/routes/login.rs"
modifies:
- "src/routes/mod.rs::*"
- id: AUTH-002
title: "Add authentication middleware"
description: "Protect routes with JWT validation"
priority: 2
status: open
depends_on: [AUTH-001] # Must complete login first
footprint:
creates:
- "src/middleware/auth.rs"
- id: AUTH-003
title: "Add logout endpoint"
description: "POST /api/logout to invalidate tokens"
priority: 2
status: open
depends_on: [AUTH-001] # Can run parallel with AUTH-002
footprint:
creates:
- "src/routes/logout.rs"Import tasks to SQLite:
bacchus task import --epic-id AUTH
# Verify import
bacchus task list
bacchus task list --ready # Shows AUTH-001 (others blocked)Claim your first task:
bacchus claim AUTH-001 agent-1Output:
{
"success": true,
"task_id": "AUTH-001",
"title": "Add user login endpoint",
"workspace_path": ".bacchus/workspaces/AUTH-001"
}Work in the isolated workspace:
# Check workspace status
jj -R .bacchus/workspaces/AUTH-001 status
# Make your changes (files are auto-tracked by jj)
# Create src/routes/login.rs, etc.
# Describe your change (like a commit message)
jj -R .bacchus/workspaces/AUTH-001 describe -m "Implement login endpoint with JWT"
# View what you've done
jj -R .bacchus/workspaces/AUTH-001 log
jj -R .bacchus/workspaces/AUTH-001 diffWarning: Never
cdinto the workspace! Usejj -R <path>instead. Workspaces are deleted on release.
When done, mark the task ready for release:
bacchus release AUTH-001 --status doneOutput:
{
"success": true,
"task_id": "AUTH-001",
"status": "ready_for_release",
"commit_id": "abc123...",
"message": "Task AUTH-001 marked ready for release. Orchestrator will merge."
}Check what's now available:
bacchus task list --ready
# Now shows AUTH-002 and AUTH-003 (AUTH-001 unblocked them)If the orchestrator detects conflicts during merge:
# Task will be marked needs_resolution
bacchus task show AUTH-001
# Resolve conflicts in your workspace
jj -R .bacchus/workspaces/AUTH-001 resolve
# Mark resolved and ready again
bacchus resolve AUTH-001Abandoning work:
# Discard changes and reset task to open
bacchus release AUTH-001 --status failedBlocked on something:
# Keep workspace but mark task blocked
bacchus release AUTH-001 --status blockedFinding stale work:
# List claims older than 30 minutes
bacchus stale --minutes 30
# Clean them up automatically
bacchus stale --minutes 30 --cleanupFull workflow with Claude:
# Tell Claude what you want:
"Implement user authentication with bacchus using 3 agents"
# Claude will:
# 1. Plan: break down into tasks.yaml
# 2. Import: bacchus task import --epic-id AUTH
# 3. Orchestrate: spawn agents and monitor progress
Manual orchestration:
# After planning and importing, start orchestrator session
bacchus session start orchestrator --max-concurrent 3
# Optional: enable autonomous worker spawning from orchestrator checks
export BACCHUS_WORKER_CMD='claude'
# Spawn workers on demand
bacchus session spawn-workers --count 3
# Then run `bacchus session check` in your stop-hook loop| Command | Description |
|---|---|
task init |
Create tasks.yaml template |
task list [--status X] [--ready] |
List tasks |
task show <task_id> |
Show task details |
task import [--epic-id X] |
Import tasks from YAML to SQLite |
task validate |
Validate task definitions |
| Command | Description |
|---|---|
init [--skip-jj] [--force-tasks] [--epic-id X --epic-title Y] |
Bootstrap bacchus in the current repo (jj + tasks template + optional epic) |
next <agent_id> |
Get next ready task, create workspace, claim it |
claim <task_id> <agent_id> [--force] |
Claim specific task (must be ready unless --force) |
heartbeat <task_id> <agent_id> |
Refresh task claim lease heartbeat |
release <task_id> --status done|blocked|failed |
Mark task ready for release |
process-releases [--limit N] |
Orchestrator: merge tasks in ready_for_release |
stale [--minutes N] [--cleanup] |
Find/cleanup abandoned claims |
list |
List all active claims |
resolve <task_id> |
Mark task ready after resolving conflicts |
abort <task_id> |
Reset from needs_resolution to in_progress |
| Command | Description |
|---|---|
review <task_id> [--build-cmd X] [--test-cmd Y] |
Review task before release (includes symbol-aware footprint checks) |
eval [--epic X] [--days N] |
Show completion metrics plus worker reliability counters (timeouts, stale recovery, kill attempts/successes) |
| Command | Description |
|---|---|
session start agent --task-id <id> [--agent-id <agent>] |
Start agent session (enables stop hook and background heartbeat when owner known) |
session start orchestrator [--max-concurrent N] |
Start orchestrator session |
session start architect --agent-id <id> |
Start architect session |
session stop |
Clear session, allow exit |
session status |
Show current session state |
session check |
Check if exit should be blocked (for hooks) |
session spawn-workers [--count N] [--dry-run] |
Launch ready workers once for active orchestrator session |
session prune [--minutes N] |
Remove stale scoped sessions and orphaned expired leases |
| Command | Description |
|---|---|
epic list [--status X] |
List epics (open, planning, active, closed) |
epic show <epic_id> |
Show epic details with task counts |
epic create --id X --title Y [--description Z] |
Create a new epic |
epic assign <epic_id> <agent_id> |
Assign epic to architect for breakdown |
epic set-status <epic_id> <status> |
Update epic status (open/planning/active/closed) |
| Command | Description |
|---|---|
archetype list |
List available agent archetypes |
archetype show <name> |
Show archetype details and keywords |
archetype prompt <name> |
Get the specialized prompt for an archetype |
archetype select <task_id> |
Select best archetype for a task |
| Command | Description |
|---|---|
message list [--agent X] [--status Y] |
List agent messages |
message send <agent> <type> <payload> |
Send message to agent |
message claim <agent> [--limit N] |
Claim pending messages for processing |
message ack <message_id> <agent> |
Mark claimed message as processed |
message fail <message_id> <agent> [--reason X] |
Mark claimed message as failed |
message reclaim-stale |
Requeue/fail stale processing messages |
| Command | Description |
|---|---|
index <path> |
Index files for symbol search |
symbols [--pattern X] [--kind Y] |
Search for symbols |
symbols [--file X] [--lang Y] |
Filter by file path or language |
symbols [--search X] [--fuzzy] |
Full-text search with fuzzy matching |
symbols --handle |
Return handle instead of full results (token-saving) |
Handles provide compact pointers to query results, reducing token overhead by 90%+.
| Command | Description |
|---|---|
handle expand <handle> [-n N] [--offset M] |
Retrieve data from handle with pagination |
handle filter <handle> [--kind X] [--file Y] |
Filter handle creating new handle |
handle list |
List all active handles |
handle clear |
Clear all handles |
handle info <handle> |
Get metadata about a handle |
Example workflow:
# Search returns a handle instead of full data
$ bacchus symbols --search "auth" --handle
{
"handle": "$sym1",
"count": 47,
"preview": ["auth::login", "auth::logout", "auth::verify"]
}
# Expand to get actual data (on demand)
$ bacchus handle expand $sym1 --limit 5
# Filter to narrow results
$ bacchus handle filter $sym1 --kind function
# Returns: $sym2 with 32 functions
# Clean up when done
$ bacchus handle clearHandles are session-scoped and automatically cleared on bacchus session stop.
| Command | Description |
|---|---|
status |
Show claims, orphaned workspaces, broken claims |
context [--task-id X] |
Generate markdown context for agent |
workflow |
Print protocol documentation |
events [--limit N] |
Show recent orchestration events |
self-update |
Update bacchus to latest version |
check-update |
Check if newer version is available |
Bacchus integrates with Claude Code through a skill and stop hooks:
The bacchus skill (~/.claude/skills/bacchus/SKILL.md) provides:
- Workflow guidance for planning, importing, and orchestrating tasks
- Agent archetype prompts for specialized task execution
- Command reference for the bacchus CLI
The skill is automatically loaded when you mention "bacchus" or multi-agent coordination.
Stop hooks in ~/.claude/settings.json prevent premature exit:
- Agent mode: Blocks exit until assigned task is closed
- Orchestrator mode: Blocks exit while work remains
Tasks have two separate classifications:
Task Type (PM workflow - what kind of work):
bug_fix | feature | refactor | test | docs | infra | generic
Archetype (Agent specialization - what expertise):
Default archetypes for reference:
| Archetype | Focus |
|---|---|
frontend |
UI/UX, components, styling |
backend |
APIs, auth, validation |
data |
Pipelines, SQL, schemas |
test |
Coverage, fixtures, e2e |
infra |
CI/CD, containers, cloud |
review |
Quality, patterns |
security |
Vulnerabilities, OWASP |
generic |
General development |
Archetypes are explicitly set by the planner. Bacchus uses archetypes.yaml as the source of truth - customize by copying to .bacchus/archetypes.yaml in your project.
claim/next → work in workspace → release (mark ready) → orchestrator merges
# Option A: Next ready task
bacchus next agent-1
# Option B: Specific task
bacchus claim TASK-42 agent-1Output:
{
"success": true,
"task_id": "TASK-42",
"title": "Implement auth",
"workspace_path": ".bacchus/workspaces/TASK-42"
}Work in the jj workspace. Changes are auto-snapshotted - no explicit add/commit needed.
Warning: Never
cdinto a workspace. Usejj -Rinstead - workspaces are ephemeral and get deleted on release.
# Check status
jj -R .bacchus/workspaces/TASK-42 status
# Describe your change (like a commit message)
jj -R .bacchus/workspaces/TASK-42 describe -m "Implement auth"
# View your changes
jj -R .bacchus/workspaces/TASK-42 diff# Success - mark ready for release (orchestrator will merge)
bacchus release TASK-42 --status done
# Blocked - keep workspace, mark task blocked
bacchus release TASK-42 --status blocked
# Failed - discard workspace, reset task to open
bacchus release TASK-42 --status failedWhen you mark a task ready, the orchestrator:
- Rebases your commit onto current main
- If conflicts: marks task
needs_resolution(you fix withjj resolve) - If clean: advances main bookmark and closes task
Manual trigger (outside orchestrator stop-hook loops):
bacchus process-releasesSessions enable stop hooks that prevent premature exit:
# Start agent session (blocks until task closed; pass agent id for immediate heartbeat loop)
bacchus session start agent --task-id TASK-42 --agent-id agent-1
# Start orchestrator session (blocks while work remains)
bacchus session start orchestrator --max-concurrent 3
# Launch ready workers once (optional manual trigger)
bacchus session spawn-workers --count 3
# Check session state
bacchus session status
# Clear session to exit
bacchus session stop
# Cleanup stale scoped session files
bacchus session prune --minutes 240Session state is scoped per CLI/session identity in .bacchus/sessions/<scope>.json.
Set BACCHUS_SESSION_ID explicitly when running multiple concurrent sessions from the same repo root.
Only one orchestrator can hold the leader lease at a time; secondary orchestrator starts are rejected.
Autonomous worker spawning controls:
BACCHUS_ORCHESTRATOR_AUTO_SPAWN=1(default) enables auto-spawn attempts duringsession check.BACCHUS_WORKER_CMDsets the worker shell command (required for auto-spawn).BACCHUS_WORKER_MAX_RETRIESlimits retries per task (default:3).BACCHUS_WORKER_RETRY_BACKOFF_MSsets retry backoff (default:60000).BACCHUS_WORKER_STALE_GRACE_MSextends stale-worker recovery grace beyond claim timeout (default:60000).BACCHUS_WORKER_MAX_RUNTIME_MSoptionally fails/reopens long-running workers after this runtime (default: disabled).BACCHUS_WORKER_KILL_STALE=1enables best-effort PID termination before failing/reopening stale workers (default:0). Usebacchus session spawn-workers --dry-runto preview ready candidates and slot usage without launching workers. When a worker is stale (task heartbeat expired), orchestrator recovers it by marking worker failed and reopening the task.
Find and cleanup abandoned claims:
# List stale claims (>30 min old)
bacchus stale --minutes 30
# Auto-cleanup
bacchus stale --minutes 30 --cleanupTasks are defined in YAML and imported to SQLite:
# .bacchus/tasks.yaml
version: 1
tasks:
- id: AUTH-001
title: "Implement user authentication"
description: "Add JWT-based auth"
type: feature # PM workflow: bug_fix | feature | refactor | test | docs | infra | generic
archetype: backend # Agent expertise: frontend | backend | data | test | infra | review | security | generic
priority: 1
status: open
depends_on: []
footprint:
modifies:
- "src/auth/handler.rs::AuthHandler"
creates:
- "src/auth/middleware.rs"
- id: API-002
title: "Add rate limiting"
type: feature
archetype: backend
priority: 2
depends_on: [AUTH-001]
footprint:
modifies: ["src/middleware/mod.rs::*"]
- id: AUTH-001-SEC
title: "Security review of auth"
type: feature # The review is a feature task
archetype: security # But needs security expertise
priority: 3
depends_on: [AUTH-001]Import with: bacchus task import --epic-id MY-EPIC
project/
├── .jj/ # jj repository data
├── .bacchus/
│ ├── bacchus.db # SQLite database (tasks, claims, metrics)
│ ├── tasks.yaml # Task definitions (import source)
│ ├── sessions/ # Scoped session state files
│ │ ├── default.json
│ │ └── <scope>.json
│ └── workspaces/
│ ├── TASK-42/ # Agent 1's jj workspace
│ └── TASK-43/ # Agent 2's jj workspace
┌─────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR MODE │
│ Spawns agents for ready tasks │
│ Blocks while: ready tasks exist OR agents active │
│ Approves when: all work done or blocked │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │
│ │ TASK-A │ │ TASK-B │ │ TASK-C │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ AGENT MODE │
│ Blocks while: assigned task not closed │
│ Approves when: task status == "closed" │
└─────────────────────────────────────────────────────────────┘
- TypeScript / JavaScript
- Python
- Go
- Rust
MIT