Parallel Environments with Worktrees
The pattern
Ravi resolves the active Identity by scanning for .ravi/config.json starting from the current working directory before falling back to ~/.ravi/config.json. This means worktree-based orchestrators get automatic identity isolation for free — no environment variables, no subprocess flags, no global state mutation.
project-root/
├── worktrees/
│ ├── agent-1/
│ │ └── .ravi/config.json ← agent-1's identity
│ ├── agent-2/
│ │ └── .ravi/config.json ← agent-2's identity
│ └── agent-3/
│ └── .ravi/config.json ← agent-3's identity
Any ravi command invoked from inside agent-1/ automatically uses agent-1’s identity. No ravi identity use. No RAVI_CONFIG_DIR. Ravi reads the nearest config.
This works with: Aizen, vibe-tree, amux, Claude Squad, Aperant, Supacode, and any other tool that runs agents inside git worktrees or isolated directories.
Setup: provision and wire a worktree
#!/usr/bin/env bash
# setup-agent-worktree.sh
# Usage: ./setup-agent-worktree.sh <worktree-path> <agent-name>
WORKTREE="$1"
AGENT_NAME="$2"
# Create the git worktree (adjust to your workflow)
git worktree add "$WORKTREE" -b "agent/$AGENT_NAME"
# Provision a Ravi identity for this worktree
IDENTITY=$(ravi identity create --name "$AGENT_NAME" --json)
IDENTITY_UUID=$(echo "$IDENTITY" | jq -r '.uuid')
IDENTITY_EMAIL=$(echo "$IDENTITY" | jq -r '.inbox')
IDENTITY_PHONE=$(echo "$IDENTITY" | jq -r '.phone')
# Write the config file into the worktree root
mkdir -p "$WORKTREE/.ravi"
echo "{\"identity_uuid\": \"$IDENTITY_UUID\"}" > "$WORKTREE/.ravi/config.json"
echo "Worktree $WORKTREE wired to identity $AGENT_NAME"
echo " email: $IDENTITY_EMAIL"
echo " phone: $IDENTITY_PHONE"
Once this runs, any ravi command invoked from inside $WORKTREE picks up the correct identity automatically — including commands run by an agent subprocess spawned from that directory.
Fleet provisioning in Python
import subprocess
import json
import pathlib
def provision_worktree(worktree_path: str, agent_name: str) -> dict:
"""
Provision a Ravi identity and wire it to a worktree directory.
Returns identity metadata (uuid, email, phone).
"""
result = subprocess.run(
["ravi", "identity", "create", "--name", agent_name, "--json"],
capture_output=True, text=True, check=True
)
identity = json.loads(result.stdout)
config_dir = pathlib.Path(worktree_path) / ".ravi"
config_dir.mkdir(parents=True, exist_ok=True)
(config_dir / "config.json").write_text(
json.dumps({"identity_uuid": identity["uuid"]})
)
return {
"uuid": identity["uuid"],
"email": identity["inbox"],
"phone": identity["phone"],
"config_dir": str(config_dir),
"worktree": worktree_path,
}
# Provision a fleet of 5 parallel agents
import concurrent.futures
agents = [f"worker-{i}" for i in range(1, 6)]
base = "/tmp/fleet"
with concurrent.futures.ThreadPoolExecutor() as pool:
fleet = list(pool.map(
lambda name: provision_worktree(f"{base}/{name}", name),
agents
))
for agent in fleet:
print(f"{agent['email']} → {agent['worktree']}")
Each agent’s subprocess or coding-agent session picks up the right identity from its worktree root — no shared state, no env var juggling.
Fleet provisioning in TypeScript
import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
interface AgentContext {
uuid: string;
email: string;
phone: string;
worktree: string;
}
function provisionWorktree(worktreePath: string, agentName: string): AgentContext {
const result = execSync(
`ravi identity create --name "${agentName}" --json`,
{ encoding: "utf-8" }
);
const identity = JSON.parse(result);
const configDir = path.join(worktreePath, ".ravi");
fs.mkdirSync(configDir, { recursive: true });
fs.writeFileSync(
path.join(configDir, "config.json"),
JSON.stringify({ identity_uuid: identity.uuid })
);
return {
uuid: identity.uuid,
email: identity.inbox,
phone: identity.phone,
worktree: worktreePath,
};
}
// Provision in parallel
const agents = ["worker-1", "worker-2", "worker-3"];
const base = "/tmp/fleet";
const fleet = await Promise.all(
agents.map((name) => provisionWorktree(`${base}/${name}`, name))
);
fleet.forEach(({ email, worktree }) =>
console.log(`${email} → ${worktree}`)
);
How identity resolution works (the detail)
When any ravi command runs, the CLI walks up the directory tree from $CWD:
$CWD/.ravi/config.json ← checked first
$CWD/../.ravi/config.json ← walks up
...
~/.ravi/config.json ← global fallback
The first config.json found wins. This means:
- Placing
.ravi/config.jsonat the worktree root is enough — the CLI finds it regardless of how deep inside the worktree the agent is currently working. - If no config is found, commands that require an identity will error with a clear message.
ravi identity usewrites to~/.ravi/config.jsonand is not safe to call from parallel agents — it mutates global state. The per-worktree config pattern avoids this entirely.
When to use RAVI_CONFIG_DIR instead
RAVI_CONFIG_DIR is useful when:
- You’re launching an agent as a subprocess with no guaranteed CWD (containers, CI jobs, arbitrary process spawners)
- You need to isolate identity at the environment level, not the directory level
- You’re running an orchestrator where CWD is shared across multiple agent invocations
# Per-process isolation via env var (for subprocess launchers)
IDENTITY_UUID=$(ravi identity create --name "task-$TASK_ID" --json | jq -r '.uuid')
RAVI_CONFIG=$(mktemp -d)
echo "{\"identity_uuid\": \"$IDENTITY_UUID\"}" > "$RAVI_CONFIG/config.json"
RAVI_CONFIG_DIR="$RAVI_CONFIG" ./run-agent.sh
For worktree-based tools, the per-directory config pattern is simpler and requires no env var injection.
Cleanup
When an agent finishes, delete its identity and worktree:
# Read the identity uuid from the worktree config
UUID=$(jq -r '.identity_uuid' "$WORKTREE/.ravi/config.json")
# Delete the identity (removes all credentials, inbox, secrets)
ravi identity delete "$UUID" --yes
# Remove the worktree
git worktree remove "$WORKTREE" --force
For bulk cleanup of identities created with a naming prefix:
ravi identity list --json | jq -r '.[] | select(.name | startswith("worker-")) | .uuid' \
| xargs -I{} ravi identity delete {} --yes
Next steps
- Multi-Agent Setup — role-based multi-agent architecture
- Harness Integration — dispatch-time identity injection for orchestrators
- Production Patterns — token injection, retry loops, token refresh