A command-line tool for managing AI agent workspaces using git worktrees and terminal automation.
Foundry automates the full lifecycle of parallel development workspaces: creating feature branches, setting up worktrees, opening terminal layouts with your tools, and cleaning up when you're done.
cargo install --path .Or build manually:
cargo build --release
# Binary is at target/release/foundry# From inside a git repo:
foundry start my-featureThis will:
- Create a branch (
my-feature, oryourprefix/my-featureif configured) - Create a worktree at
~/.foundry/worktrees/<project>/my-feature - Run any configured setup scripts
- Open a new terminal tab with your configured pane layout
When you're done:
# Merge to main and clean up:
foundry finish
# Or push and create a GitHub PR first:
foundry pr
# Then finish when CI passes (automatically merges the PR):
foundry finish
# Or discard without merging:
foundry discard| Command | Description |
|---|---|
foundry start <name> |
Create branch, worktree, run setup, open workspace |
foundry start --issue <number> |
Start from a GitHub issue (auto-generates name and prompt) |
foundry start <name> --prompt "..." |
Start with a prompt passed to the AI agent |
foundry start <name> --fetch |
Fetch and fast-forward main before branching |
foundry open [name] |
Reopen workspace (resumes agent conversation if available) |
foundry open --all |
Reopen all active workspaces for the project |
foundry pr [name] |
Push branch and create a GitHub PR |
foundry pr [name] --title "..." |
Create PR with a custom title |
foundry finish [name] |
Finish workspace: merge PR (if created) or merge locally |
foundry finish [name] --local |
Force local merge, ignoring any associated PR |
foundry discard [name] |
Teardown and clean up without merging |
foundry discard [name] --force |
Discard even if the branch has unmerged commits |
foundry switch [name] |
Switch to a workspace's terminal tab |
foundry restore [branch] |
Restore workspace from an archived branch |
foundry status |
Show status dashboard of all workspaces (use --watch/-w to continuously refresh) |
foundry diff [name] |
Show changes in a workspace vs main |
foundry diff [name] --stat |
Show file change summary vs main |
foundry history |
Show workspace activity history |
foundry list |
List all active workspaces across all projects |
foundry trust [path] |
Review and approve a project's .foundry.toml |
foundry trust [path] --revoke |
Withdraw a previous approval |
foundry projects list |
List registered projects |
foundry projects add <name> <path> |
Register a project |
foundry projects remove <name> |
Unregister a project |
foundry completions <shell> |
Generate shell completions (bash, zsh, fish) |
| Flag | Description |
|---|---|
--project <name> |
Specify project explicitly (otherwise inferred from cwd) |
--verbose |
Show detailed output for each step |
--yes |
Skip confirmation prompts |
--version |
Show version |
foundry start <name> is idempotent — if the worktree already exists, it skips creation and opens the workspace.
foundry pr [name] pushes the feature branch to the remote and creates a GitHub PR via the gh CLI. If a PR already exists for the branch (created manually on GitHub), foundry links it instead of creating a duplicate. The workspace stays open so you can fix issues if CI fails. The PR title is auto-generated from the branch name unless --title is provided.
foundry finish [name] checks whether a PR was created (via foundry pr). If so, it merges the PR on GitHub, fetches to sync local refs, and cleans up. If not, it merges locally using the configured strategy (fast-forward only by default). Branches with commits are archived (e.g., archive/my-feature-20260321); branches with no commits are simply deleted. foundry merge is an alias for foundry finish.
If the associated PR was closed or merged outside of foundry, finish will report an error with instructions to either reopen the PR or run foundry finish --local to merge locally instead.
foundry finish [name] and foundry discard [name] can infer the workspace name from your current directory if you're inside a worktree.
foundry restore [branch] accepts a full branch name (archive/my-feature-20260321) or just the branch name without the archive prefix. Run with no arguments to see available archived branches.
foundry open resumes the agent's previous conversation (e.g., claude --continue) when reopening a workspace that has conversation history. If a new --prompt is provided, it starts a fresh conversation instead. --all reopens all active workspaces for the project, skipping any that are already open.
foundry discard requires --force (or -f) if the branch has unmerged commits, similar to git branch -D. Workspaces with no commits can be discarded freely.
foundry history shows recent workspace lifecycle events (started, finished, discarded, restored) with timestamps and metadata. Use --limit to control how many events are shown (default: 20).
foundry status displays a dashboard of all active workspaces with their current state. The dashboard includes git status (clean/dirty), commit count vs main, and agent activity. Use --watch / -w to continuously refresh the display every 2 seconds, useful for monitoring progress while agents work. For Claude workspaces, the dashboard shows rich activity info: the current tool being used while working, the last message from the agent when idle, and error details (rate limit, auth failure, etc.) when an error occurs.
Foundry uses two levels of TOML configuration:
- Global config at
~/.foundry/config.toml— defaults for all projects - Project config at
.foundry.tomlin each repo root — project-specific overrides
Everything foundry keeps under ~/.foundry (config, project registry, workspace state, history, hooks) moves to another directory if you set FOUNDRY_HOME to it. worktree_dir still defaults to ~/.foundry/worktrees; set it explicitly to move worktrees too.
# ~/.foundry/config.toml
# Optional prefix for branch names (omit for no prefix)
# "my-feature" becomes "xiphux/my-feature"
branch_prefix = "xiphux"
# Default AI agent: "claude" (default), "codex", "every-code", "gemini",
# "aider", "copilot", "kiro", "opencode", "crush", "nanocoder", "pi", or "custom"
agent = "claude"
# Starting port for dynamic port allocation (default: 10000)
# port_range_start = 20000
# Automatically fetch and fast-forward main before branching (default: false)
# auto_fetch = true
# Remote to fetch from (default: "origin")
# fetch_remote = "upstream"
# Remote to push to for PR commands (default: auto-detect)
# If there's one remote, uses it. If multiple, defaults to "origin".
# pr_remote = "origin"
# Bypass all permission checks and sandbox constraints (default: false).
# Agents use their most permissive mode (YOLO/auto-approve-all).
# Use with caution — this grants agents unrestricted system access.
# unrestricted_permissions = true
# Prefix for archived branches (default: "archive")
archive_prefix = "archive"
# Merge strategy: "ff-only" (default) or "merge"
merge_strategy = "ff-only"
# Base directory for worktrees (default: "~/.foundry/worktrees")
worktree_dir = "~/.foundry/worktrees"If no [[panes]] are configured, foundry uses a simple default layout — the agent on the left and a plain shell on the right:
┌──────────────┬──────────────┐
│ │ │
│ agent │ shell │
│ │ │
└──────────────┴──────────────┘
You can define your own layout in the global config. The first pane becomes the initial tab. Subsequent panes split from a named parent pane in the given direction.
# ~/.foundry/config.toml
[[panes]]
name = "agent"
agent = "claude"
# Per-pane environment variables (optional)
[panes.env]
SOME_VAR = "value"
[[panes]]
name = "git"
command = "lazygit"
split_from = "agent"
direction = "right"
[[panes]]
name = "shell"
split_from = "git"
direction = "down"
# Optional panes are only included if the project opts in
[[panes]]
name = "server"
split_from = "shell"
direction = "right"
optional = trueYou can run multiple different agents in a single workspace by setting agent on different panes. Each agent type can only appear once per workspace.
[[panes]]
name = "developer"
agent = "claude"
[[panes]]
name = "reviewer"
agent = "codex"
split_from = "developer"
direction = "right"
[[panes]]
name = "shell"
split_from = "developer"
direction = "down"This produces the following layout (with server pane only if the project opts in):
┌──────────────┬──────────────┐
│ │ lazygit │
│ ├───────┬──────┤
│ agent │ shell │ dev │
│ │ │server│
└──────────────┴───────┴──────┘
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique name for this pane |
agent |
No | Agent to run in this pane (auto-generates command with permissions and prompt passthrough) |
command |
No | Command to run (empty = shell prompt, ignored if agent is set) |
split_from |
No* | Name of the pane to split from |
direction |
No* | Split direction: "right" or "down" |
optional |
No | If true, only included when the project opts in (default: false) |
[panes.env] |
No | Environment variables to set for this pane |
* Required for all panes except the first (which becomes the tab).
Important: Always use agent instead of command for AI coding agents. Using agent = "claude" ensures foundry sets up permissions, status tracking, and prompt passthrough. If you use command = "claude" directly, foundry will warn you to switch to the agent field.
# .foundry.toml (in repo root)
# Setup scripts run after worktree creation (in order)
[[scripts.setup]]
name = "Copy env file"
command = "cp {source}/.env {worktree}/.env"
[[scripts.setup]]
name = "Install dependencies"
command = "npm install"
working_dir = "{worktree}"
deferred = true # runs in the shell pane after the workspace opens
# Teardown scripts run before worktree deletion (in order)
[[scripts.teardown]]
name = "Stop containers"
command = "docker compose down"
working_dir = "{worktree}"
# Allocate unique ports per workspace (available as env vars in all panes)
ports = ["VITE_PORT", "API_PORT"]
# Additional context injected into the agent's session (supports port variables)
# context = "The dev server runs at http://localhost:{VITE_PORT}"
# Opt in to optional panes and/or override pane commands
[panes.server]
command = "npm run dev"Project config can also override global scalar values:
# Override the default agent for this project
agent = "codex"
# Override the merge strategy for this project
merge_strategy = "merge"Setup scripts marked with deferred = true run in the shell pane after the workspace opens, rather than blocking before it opens. This lets you start working while slower scripts (like npm install) run in the background.
Deferred scripts are chained together with && and sent to the first pane that has no command configured (typically the "shell" pane). Non-deferred scripts run in order before the workspace opens, as usual.
When running multiple workspaces in parallel, dev servers compete for the same ports. Foundry can allocate unique ports per workspace and expose them as environment variables.
Add a ports array to your project config listing the environment variable names:
# .foundry.toml
ports = ["VITE_PORT", "API_PORT", "DYNAMODB_PORT"]Each workspace gets a contiguous block of ports (starting from 10000 by default). The variables $VITE_PORT, $API_PORT, $DYNAMODB_PORT are available in all panes and setup scripts. Ports are assigned once at foundry start and remain stable for the life of the workspace.
Configure your dev servers to use these variables instead of hardcoded ports (e.g., vite --port $VITE_PORT).
Foundry automatically injects workspace context into agents that support it (currently Claude via a SessionStart hook). This context tells the agent:
- That it's working in an isolated worktree (safe to make changes freely)
- What panes the user started the workspace with (e.g., "server": npm run dev)
- Allocated port values
You can append project-specific context with the context field in .foundry.toml. Port variables are expanded automatically:
# .foundry.toml
context = """
The Vite dev server runs at http://localhost:{VITE_PORT}.
The API server runs at http://localhost:{API_PORT}.
Run tests with: npm test
"""This is useful for giving agents information they can't easily discover on their own, such as where the dev server is running (since it runs in a separate pane with a dynamically allocated port).
To customize the starting port, set port_range_start in your global config:
# ~/.foundry/config.toml
port_range_start = 20000The following variables can be used in command and working_dir fields in scripts and pane commands:
| Variable | Description |
|---|---|
{source} |
Absolute path to the main repo checkout |
{worktree} |
Absolute path to the worktree |
{branch} |
Full branch name (with prefix if configured) |
{name} |
Short name (without prefix) |
{project} |
Project name |
- Scalar values (branch_prefix, agent_command, etc.): project overrides global
- Pane layout: global defines the structure; project can override
commandandenvfor existing panes by name, and opt in to optional panes - Scripts: project-only (no global scripts)
.foundry.toml lives in the repo root and is checked into version control, so
its contents come from whoever wrote the repository. Several keys are
executable: setup and teardown scripts run through sh -c, pane command
overrides are typed into a shell, and agent_command runs verbatim.
The first time foundry uses a project config containing any of those, it shows exactly what would run and asks for approval:
/path/to/repo/.foundry.toml contains commands that foundry will run:
- setup script 'install': pnpm install
This file comes from the repository, not from your own configuration.
Only approve it if you trust this repo.
Trust this project config? [y/N]
Approval is recorded per project in ~/.foundry/trust.toml, keyed to a hash of
the file, and is requested again whenever the file changes. Configs with no
executable content never prompt.
Approve ahead of time — or in a script, where foundry refuses to prompt and
fails instead — with foundry trust:
foundry trust # approve the current repo
foundry trust /path/to/repo # approve a specific repo
foundry trust --revoke # withdraw approvalFoundry automatically registers projects the first time you run a command inside a git repo. The project name is derived from the directory name.
Projects are stored in ~/.foundry/projects.toml. You can manage them manually:
foundry projects list
foundry projects add myapp /path/to/myapp
foundry projects remove myappFoundry supports multiple AI coding agents. The agent is configured via agent in your global or project config.
| Agent | Config value | Default | With unrestricted_permissions |
Prompt | Resume |
|---|---|---|---|---|---|
| Claude | claude |
Auto mode (worktree-scoped) | Bypass all prompts | Positional | --continue |
| Codex | codex |
Sandbox (built-in) | Same | Positional | --resume |
| Every Code | every-code |
Sandbox (built-in) | Same | Positional | --resume |
| Gemini CLI | gemini |
Sandbox (worktree-scoped) | No sandbox, YOLO | -p flag |
--resume |
| Aider | aider |
Ask for permission | --yes (auto-approve) |
Interactive | No |
| GitHub Copilot | copilot |
Ask for permission | --yolo (auto-approve) |
-p flag |
No |
| Kiro | kiro |
Ask for permission | --trust-all-tools |
Positional | --resume |
| OpenCode | opencode |
Ask for permission | --auto (auto-approve) |
--prompt flag |
--continue |
| Crush | crush |
Ask for permission | --yolo (auto-approve) |
No | --continue |
| Nanocoder | nanocoder |
Ask for permission | No | Positional | No |
| Pi | pi |
No permission prompts | Same (no flag) | Positional | --continue |
| Custom | custom |
N/A | N/A | N/A | N/A |
By default, foundry uses the safest available permission level for each agent:
- Auto mode (Claude): Claude launches with
--permission-mode auto, where a model analyzes each permission request and auto-approves the ones it judges safe, falling back to a prompt otherwise. Worktree-scoped allow/deny rules still apply on top. - Worktree-scoped sandbox (Codex, Every Code, Gemini): The agent can read/write freely within the worktree, but OS-level sandboxing prevents modifications outside it. Commands are auto-approved within sandbox boundaries.
- Ask for permission (Aider, Copilot, Kiro, OpenCode, Crush, Nanocoder): The agent launches with standard permissions and prompts the user before taking actions.
- No permission prompts (Pi): Pi executes tools without approval prompts by design and offers no flag to change this. Foundry runs Pi inside the isolated worktree; for stronger isolation, Pi's docs recommend running it in a container.
Setting unrestricted_permissions = true in your config bumps agents to their most permissive mode — auto-approving all actions without sandbox constraints. Use with caution. This key is read from the global config only: a project's .foundry.toml can turn it off, but cannot turn it on.
For Claude, foundry writes worktree-scoped rules into the workspace's
.claude/settings.local.json — allowing reads and writes under the worktree,
and denying git push and git checkout main.
Treat these as guardrails against accident, not as a security boundary.
They stop an agent from wandering out of its workspace by mistake. They are not
a sandbox, and they do not contain an agent that is actively trying to get out:
a deny rule like Bash(git push*) is pattern matching on the command string, so
git -C <path> push, extra whitespace, a shell alias, or writing to .git
directly all sidestep it.
For actual isolation, use an agent with OS-level sandboxing (Codex, Every Code, Gemini) or run the agent in a container.
Claude gets the richest integration: foundry copies your source repo's .claude/settings.local.json into the worktree and merges in status-tracking hooks and worktree-scoped permissions (auto-approve file operations within the worktree, deny git push and checkout main). Claude launches in auto permission mode, which uses model analysis to auto-approve most permission requests.
Codex and Every Code use --full-auto for autonomous operation with a built-in OS sandbox scoped to the workspace.
Gemini CLI launches with --sandbox --approval-mode=yolo — the sandbox restricts writes to the project directory while YOLO mode auto-approves within those boundaries. Note: Gemini may prompt to trust the worktree directory on first use. To avoid this for every workspace, trust the parent worktree directory (e.g., ~/.foundry/worktrees/).
Aider launches as an interactive REPL. Since Aider auto-exits after processing --message, foundry launches it interactively and lets you type prompts directly.
GitHub Copilot launches with standard permissions by default. Note: Copilot may prompt to trust the worktree directory on first use. You can choose "Remember this folder" when prompted.
Kiro (formerly Amazon Q Developer CLI) launches with kiro-cli chat. Prompts are passed as positional arguments and sessions can be resumed with --resume.
OpenCode launches as an interactive TUI. Prompts are passed via --prompt and sessions can be resumed with --continue. With unrestricted_permissions, it launches with --auto, which approves every permission not explicitly denied in opencode.json. Auto-approval can also be configured persistently there ("permission": "allow").
Crush (by Charmbracelet, the original project OpenCode was forked from) launches as an interactive TUI. Sessions can be resumed with --continue. Use --yolo for auto-approve mode. No interactive prompt passthrough.
Nanocoder is a local-first coding agent. Prompts are passed as positional arguments. No session resume or auto-approve CLI flags are currently available.
Pi (pi.dev) is a minimal terminal coding agent. Prompts are passed as positional arguments and sessions can be resumed with --continue. Pi has no permission popups by design — it executes tools directly — and provides no auto-approve flag, so unrestricted_permissions has no effect.
Custom agents use whatever command you specify in agent_command. Foundry runs it as-is without additional configuration.
Foundry detects the terminal automatically from the TERM_PROGRAM environment variable.
| Terminal | Platform | Mechanism |
|---|---|---|
| Ghostty | macOS | AppleScript |
| iTerm2 | macOS | AppleScript |
| WezTerm | macOS, Linux, Windows | wezterm cli |
| Windows Terminal | Windows | wt.exe |
| Zellij (fallback) | macOS, Linux | zellij CLI |
| tmux (fallback) | macOS, Linux | tmux CLI |
| Bare (fallback) | any | single pane, no splits |
Native terminal backends open a new tab with splits. If no native backend is detected, foundry falls back to Zellij or tmux (whichever is available), which take over the current terminal with a multiplexer session. If neither is available, bare mode runs the first agent command in the current terminal with no splits.
To skip detection, set FOUNDRY_TERMINAL to one of ghostty, iterm2, wezterm, windows-terminal, zellij, tmux or bare (for example, to use tmux from inside Ghostty).
Windows Terminal does not support run_in_pane (deferred pane commands) or focus_tab due to wt.exe limitations.
On Windows Terminal, you can specify which shell to use in panes:
# ~/.foundry/config.toml
shell = "C:/Program Files/Git/bin/bash.exe"Supported values: "powershell", "pwsh", or a path to bash.exe. If you specify git-bash.exe, foundry automatically resolves it to the embeddable bin/bash.exe in the same Git installation. Other terminal backends use their default shell and ignore this setting.
Generate completions for your shell:
# Zsh
foundry completions zsh > ~/.zfunc/_foundry
# Bash
foundry completions bash > /etc/bash_completion.d/foundry
# Fish
foundry completions fish > ~/.config/fish/completions/foundry.fish