Give each AI agent worktree its own local HTTP runtime: ports, URLs, logs, and agent-readable status.
Switchyard lets you run multiple AI coding agents against one repo without port fights, mystery processes, or lost terminal state. Each task gets an isolated git worktree plus its own services, branch-scoped URLs, logs, and runtime summary.
Git worktrees isolate code, but they do not isolate your local runtime. If two
worktrees both want localhost:3000, localhost:8080, .env.local, and the
same terminal scrollback, you are back to manual bookkeeping.
Switchyard is useful when you want to:
- Let several agents work on branches at the same time without port clashes.
- Give each branch stable local URLs that humans and agents can both open.
- Keep logs discoverable after the terminal that started a service is gone.
- Let an agent ask for runtime state through CLI JSON or MCP instead of guessing.
- Use shared local infra for one branch and isolated services for another.
- Stay local-first, with no cloud control plane or public tunnel.
Example branch runtime:
web.feature-login.entropic.localhost:7331 -> 127.0.0.1:41000
api.feature-login.entropic.localhost:7331 -> 127.0.0.1:41001Agents can ask for the current state directly:
switchyard brief --json
switchyard where web feature/login --json
switchyard logs web --branch feature/login --json
switchyard mcpExample output:
{
"configured_services": ["api", "web"],
"services": [
{
"service": "web",
"status": "running",
"url": "http://web.feature-login.entropic.localhost:7331",
"port": 41000
}
],
"checkouts": [],
"env_warnings": [],
"recent_errors": []
}Switchyard is intentionally local-first:
- No cloud account or hosted control plane.
- No Docker required for the default path.
- No UI lock-in.
- No public tunnels by default.
- One small
switchyard.toml. - No telemetry.
- Binds to loopback by default and rejects non-loopback service/proxy hosts.
- Does not expose public tunnels, LAN sharing, ngrok, or Tailscale endpoints.
- Treats
switchyard.tomlservice commands as executable local project code. - Executes service commands without a shell; put pipes, redirects, or
&&in a script. - Links or copies only configured env paths, and rejects env paths outside the project/worktree.
Reference docs:
To smoke path-free MCP setup from a project that has switchyard.toml:
switchyard mcp smoke --jsonRecommended for CLI use: install with pipx. It gives Switchyard its own
small environment and puts the switchyard command on your shell path.
On macOS with Homebrew:
brew install pipx
pipx ensurepathRestart your terminal, or reload your shell:
source ~/.zshrcThen install Switchyard from PyPI:
pipx install switchyard-dev
switchyard --versionIf you do not use Homebrew:
python3 -m pip install --user pipx
python3 -m pipx ensurepathThen restart your terminal and run:
pipx --version
pipx install switchyard-dev
switchyard --versionIf switchyard says command not found, your shell probably has not loaded
pipx's bin directory yet. Run pipx ensurepath, restart the terminal, and
check that ~/.local/bin is on PATH:
echo "$PATH"
pipx listYou can also install with pip inside an active virtualenv:
python3 -m pip install switchyard-devThe package distribution name is switchyard-dev; the installed commands are
switchyard and sy.
For source development:
git clone https://github.com/hwennnn/switchyard.git
cd switchyard
python3 -m venv .venv
. .venv/bin/activate
pip install -e .Or run from source:
PYTHONPATH=src python3 -m switchyard --helpInside a git repository:
switchyard init --dry-run
switchyard init
switchyard create feature/login
switchyard up feature/login
switchyard up feature/login --profile shared
switchyard open web feature/loginTypical up output:
started proxy on 127.0.0.1:7331
started web on :41000 -> http://web.feature-login.entropic.localhost:7331View state:
switchyard doctor --json
switchyard status
switchyard brief
switchyard logs web --branch feature/login --jsonbrief --json and switchyard://project/brief include
configured_services, so agents can discover valid service names before
starting or querying runtime state. They also include env_warnings for
missing configured env link/copy sources, so agents can catch setup gaps before
creating a worktree.
Use profiles when a project has more than one local runtime shape, such as isolated backing services vs shared local infra:
[profiles.shared]
services = ["api", "web"]
[profiles.shared.env]
POSTGRES_PORT = "5432"
REDIS_PORT = "6379"Then start that mode explicitly:
switchyard up feature/login --profile sharedStop it:
switchyard down --branch feature/login
switchyard proxy stopSwitchyard ships a stdio MCP server for AI agents:
switchyard mcpWhen launched from a project or any child directory, switchyard mcp pins
itself to the nearest switchyard.toml. You should not need to hand-write a
project path argument for normal setup.
When launched from a Switchyard-registered worktree, it keeps the parent
project as the server boundary while defaulting requests to that worktree's
branch.
For Codex, run the installer from inside the project. It detects the real project root once, stores it as a local Switchyard alias, and writes MCP config without making you type or maintain a path:
switchyard mcp installThe generated Codex block uses a stable project alias:
args = ["mcp", "--project", "name"]If the switchyard executable is not visible to the current shell, the helper
prints a commented fallback that launches the current Python interpreter with
args = ["-m", "switchyard", "mcp", "--project", "name"]. Either way, the
project is resolved through the local alias, and the generated block does not
emit cwd, --cwd, or an absolute project path.
If SWITCHYARD_HOME is set during setup, the generated block includes an
[mcp_servers.name.env] table so the MCP server can find the same local alias
state when Codex launches it later.
To inspect the config first, use the setup helper. It registers the same local alias and prints ready-to-paste TOML:
switchyard mcp config
switchyard mcp config --json
switchyard mcp install --dry-run --jsonUse --json when an agent or script needs the generated TOML, launch command,
alias state, or setup error envelope without scraping prose.
To see registered aliases:
switchyard mcp projects --jsonThe JSON includes home and state_path so agents can tell which local
Switchyard registry they are inspecting.
Use --name if you want a different MCP server name. If an alias already
points at another project, Switchyard refuses to repoint it unless you pass
--force. Start from the checkout you are configuring so setup stays path-free:
switchyard mcp install --name switchyard-entropic
switchyard mcp config --name switchyard-entropicThe MCP server exposes agent-friendly tools:
switchyard_doctor
switchyard_create
switchyard_list
switchyard_status
switchyard_brief
switchyard_where
switchyard_logs
switchyard_up
switchyard_checkout
switchyard_uncheckout
switchyard_downAgents should usually read switchyard://project/brief first, or call
switchyard_brief when resources are unavailable. Then use switchyard_where
or switchyard_logs for focused follow-up. switchyard_create,
switchyard_up, switchyard_checkout, switchyard_uncheckout, and
switchyard_down change local state, so MCP clients should keep user approval
enabled for them.
Switchyard marks read-only discovery tools and local mutation tools with MCP
tool annotations so clients can present safer approval UI.
MCP clients that prefer resources can read stable, read-only context first:
switchyard://project/brief
switchyard://project/doctor
switchyard://agent/guideThese MCP resources do not initialize Switchyard state.
MCP clients that expose prompts can offer ready-made agent workflows:
switchyard_runtime_handoff
switchyard_branch_runtimeShell-only agents can run switchyard brief --json from a registered worktree;
Switchyard resolves the parent project and current branch automatically.
Switchyard ships a Codex skill for agents that prefer skill-guided workflows:
switchyard skill installUse it with prompts like:
Use $switchyard to inspect this project's local agent runtime.[project]
name = "entropic"
# Optional: keep Switchyard-created worktrees inside the repo.
# worktree_root = ".worktrees/switchyard"
[env]
link = [".env", ".env.local"]
copy = []
[proxy]
host = "127.0.0.1"
port = 7331
tld = "localhost"
[ports]
start = 41000
end = 49999
[services.web]
command = "npm run dev -- --port {port}"
port = 3000
[services.api]
command = "npm run api -- --port {port}"
port = 8080See the examples directory for fuller configs, including a multi-service app with Docker backing services and peer placeholders.
Desired ports are preferences. If 3000 is busy, Switchyard allocates a free port and injects:
PORT
HOST
CANONICAL_PORT
SWITCHYARD_URL
SWITCHYARD_BRANCH
SWITCHYARD_SERVICE
SWITCHYARD_WEB_URL
SWITCHYARD_WEB_PORT
SWITCHYARD_API_URL
SWITCHYARD_API_PORTService commands should either honor PORT/HOST or include placeholders such
as {port} and {host}. Otherwise the process may ignore Switchyard's assigned
port and bind its own default.
Commands may use tokens:
{port}
{host}
{url}
{service}
{branch}
{branch_slug}
{project}
{project_slug}
{web_url}
{web_port}
{api_url}
{api_port}Peer tokens are based on service names. Hyphens are available as underscores,
so a db-main service can be referenced as {db_main_port} and
{db_main_url}.
Some dev tools refuse dynamic ports. Checkout maps one branch back onto the canonical port while the rest stay isolated:
switchyard checkout feature/login webExample:
localhost:3000 -> web.feature-login.entropic.localhost:7331 -> 127.0.0.1:41000Undo:
switchyard uncheckout --branch feature/login webToday, checkout is HTTP-focused. Raw TCP services such as Postgres and Redis are not yet managed by the built-in forwarder.
switchyard init [--dry-run] [--json]
switchyard doctor [--json]
switchyard create <branch> [--json]
switchyard list [--json]
switchyard up [branch] [services...] [--json]
switchyard down [--branch branch] [services...] [--json]
switchyard checkout <branch> [services...] [--json]
switchyard uncheckout [--branch branch] [services...] [--json]
switchyard status [--json]
switchyard logs [service] [--branch branch] [--json]
switchyard open <service> [branch]
switchyard where <service> [branch] [--json]
switchyard brief [branch] [--json]
switchyard mcp [--project name]
switchyard mcp config [--name name] [--force] [--json]
switchyard mcp install [--name name] [--dry-run] [--force] [--json]
switchyard mcp projects [--json]
switchyard mcp smoke [project] [--nested path] [--name name] [--json]
switchyard skill show
switchyard skill install [--target dir] [--force]
switchyard proxy stopBy default, Switchyard writes machine state to:
~/.switchyard/state.json
~/.switchyard/logs/
~/.switchyard/worktrees/Override with:
SWITCHYARD_HOME=/tmp/switchyard switchyard statusSwitchyard is alpha, but usable for local agent runtime coordination.
What works today:
- Git worktree creation with env link/copy preflight.
- Dynamic loopback ports and branch-scoped
.localhostURLs. - Agent-readable JSON for setup, logs, runtime state, and checkout mappings.
- Stdio MCP tools, resources, and prompts with schemas, annotations, and local mutation boundaries.
- One-command Codex MCP setup using local project aliases, not path args.
- Bundled Codex skill for agent workflow guidance.
Current benchmark guardrails:
| Check | Gate |
|---|---|
| MCP initialize + doctor | median under 2500 ms |
| service startup smoke | median under 5000 ms |
brief --json payload |
under 12000 bytes / 3000 estimated tokens |
| source tree | under 250 KB |
The full release gate also builds and install-smokes the wheel, and keeps the wheel artifact under 350 KB.
From a repository checkout:
python3 scripts/benchmark.py --runs 3
python3 scripts/release_check.py- Binds to
127.0.0.1by default. - Rejects proxy and service hosts outside loopback addresses.
- Stops only recorded service PIDs whose command still matches the registry.
- Scopes stop actions to the current registered worktree branch when run inside one.
- Does not replace existing env targets by default;
--force-envis explicit. - Rejects env paths outside the project/worktree.
- Keeps generated state outside the repo by default.
- No public sharing, ngrok, Tailscale, or LAN exposure in v0.
- Built-in proxy is HTTP, not TLS.
- Built-in proxy is not a full WebSocket/HMR replacement yet.
- Canonical checkout forwards HTTP only.
- Docker Compose, Caddy, Portless, and Worktrunk adapters are planned, not shipped.