Local HTTP runtimes for parallel AI agent worktrees.
Project description
Switchyard
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.
It is intentionally local-first:
- No cloud account.
- No Docker required for the default path.
- No UI lock-in.
- No public tunnels by default.
- One small
switchyard.toml.
Local Trust Model
- No telemetry.
- No cloud account or hosted control plane.
- 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. - Links or copies only configured env paths, and rejects env paths outside the project/worktree.
Status
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.
Release readiness is enforced with unit, e2e, concurrency, MCP, benchmark, and package gates. Current benchmark guardrails include:
| 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
Reference docs:
To smoke path-free MCP setup from a project that has switchyard.toml:
switchyard mcp smoke --json
Why
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.
Without Switchyard, parallel agent work means babysitting ports, terminals, env files, and logs. With Switchyard, agents can ask the runtime where everything is.
Switchyard gives each branch a named runtime:
web.feature-login.entropic.localhost:7331 -> 127.0.0.1:41000
api.feature-login.entropic.localhost:7331 -> 127.0.0.1:41001
Agents can ask for the current state directly:
switchyard brief --json
switchyard where web feature/login --json
switchyard logs web --branch feature/login --json
switchyard mcp
Example 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": []
}
Install
Install the CLI from PyPI:
pipx install switchyard-dev
Or with pip:
python3 -m pip install switchyard-dev
The 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 --help
Quick Start
Inside a git repository:
switchyard init --dry-run
switchyard init
switchyard create feature/login
switchyard up feature/login
switchyard open web feature/login
Typical up output:
started proxy on 127.0.0.1:7331
started web on :41000 -> http://web.feature-login.entropic.localhost:7331
View state:
switchyard doctor --json
switchyard status
switchyard brief
switchyard logs web --branch feature/login --json
brief --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.
Stop it:
switchyard down --branch feature/login
switchyard proxy stop
MCP Setup
Switchyard ships a stdio MCP server for AI agents:
switchyard mcp
When 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 install
The 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 --json
Use --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 --json
The 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-entropic
The 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_down
Agents 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/guide
These MCP resources do not initialize Switchyard state.
MCP clients that expose prompts can offer ready-made agent workflows:
switchyard_runtime_handoff
switchyard_branch_runtime
Shell-only agents can run switchyard brief --json from a registered worktree;
Switchyard resolves the parent project and current branch automatically.
Agent Skill
Switchyard ships a Codex skill for agents that prefer skill-guided workflows:
switchyard skill install
Use it with prompts like:
Use $switchyard to inspect this project's local agent runtime.
Config
[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 = 8080
See 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_PORT
Service 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}.
When A Tool Requires localhost:3000
Some dev tools refuse dynamic ports. Checkout maps one branch back onto the canonical port while the rest stay isolated:
switchyard checkout feature/login web
Example:
localhost:3000 -> web.feature-login.entropic.localhost:7331 -> 127.0.0.1:41000
Undo:
switchyard uncheckout --branch feature/login web
Today, checkout is HTTP-focused. Raw TCP services such as Postgres and Redis are not yet managed by the built-in forwarder.
Commands
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 stop
State And Logs
By default, Switchyard writes machine state to:
~/.switchyard/state.json
~/.switchyard/logs/
~/.switchyard/worktrees/
Override with:
SWITCHYARD_HOME=/tmp/switchyard switchyard status
Safety Defaults
- 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.
Current Limits
- 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file switchyard_dev-0.1.0.tar.gz.
File metadata
- Download URL: switchyard_dev-0.1.0.tar.gz
- Upload date:
- Size: 38.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b87efc1abd07518f64ad4080c27c9fc6572771f74039114eaa4748b7e850d28
|
|
| MD5 |
2b69a9dfe60cd3734b1c48550e2dd9f1
|
|
| BLAKE2b-256 |
4eb07ed24674dd9e749feff10d4bc8e625957f2a29d430de79232a3fa1c43d99
|
Provenance
The following attestation bundles were made for switchyard_dev-0.1.0.tar.gz:
Publisher:
release.yml on hwennnn/switchyard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
switchyard_dev-0.1.0.tar.gz -
Subject digest:
2b87efc1abd07518f64ad4080c27c9fc6572771f74039114eaa4748b7e850d28 - Sigstore transparency entry: 1930644800
- Sigstore integration time:
-
Permalink:
hwennnn/switchyard@f8d8898c4bb0e44213f7f2fbf6ed4e671d536687 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hwennnn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f8d8898c4bb0e44213f7f2fbf6ed4e671d536687 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file switchyard_dev-0.1.0-py3-none-any.whl.
File metadata
- Download URL: switchyard_dev-0.1.0-py3-none-any.whl
- Upload date:
- Size: 44.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a31a29365e8cf800651c0aa9c3584dc1a3225b307bc53ef658a3959abf960961
|
|
| MD5 |
1901a1d3c077b8874426bde380cc7e07
|
|
| BLAKE2b-256 |
66ab7b319928fcb89e2b5a68b20647dd20d944563866d3a3f08760a00b85ea35
|
Provenance
The following attestation bundles were made for switchyard_dev-0.1.0-py3-none-any.whl:
Publisher:
release.yml on hwennnn/switchyard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
switchyard_dev-0.1.0-py3-none-any.whl -
Subject digest:
a31a29365e8cf800651c0aa9c3584dc1a3225b307bc53ef658a3959abf960961 - Sigstore transparency entry: 1930645508
- Sigstore integration time:
-
Permalink:
hwennnn/switchyard@f8d8898c4bb0e44213f7f2fbf6ed4e671d536687 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hwennnn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f8d8898c4bb0e44213f7f2fbf6ed4e671d536687 -
Trigger Event:
workflow_dispatch
-
Statement type: