Route, spawn, and track local AI coding agents from one terminal.
Switchboard is a local control plane for people who use multiple AI coding agents. Give it a goal; it classifies the work, chooses the cheapest capable profile, names the pane, launches the matching local client, and records the spawned agent so you can see what is running.
It does not replace Codex, Claude Code, Grok Build, Ollama, or LM Studio. It keeps small jobs off expensive models, escalates when the task deserves it, and gets out of the way.
The selected harness still owns the agent loop, tools, memory, approvals, and retries. Switchboard owns the terminal control layer around it: routing, pane placement, pane titles, and local state for spawned agents.
From the repo:
uv tool install .For development:
uv sync
uv run switchboard --helpOptional short alias:
alias sw='switchboard'# Open the full-screen control center in an interactive terminal.
switchboard
switchboard board
# Classify the goal, pick the cheapest capable profile, launch a tracked native TUI pane,
# then keep Switchboard available as the control center.
switchboard "fix the auth bug and run the tests"
# Pass several quoted goals to run them at once, one routed agent per split pane (a "swarm").
switchboard "review the auth diff" "polish the dashboard layout"
# Use separate terminal tabs only when you ask for them.
switchboard --tabs "fix the failing tests" "draft the release notes"
# Show the route without launching anything.
switchboard --dry-run "review this PR for payment regressions"
# Send a follow-up to a tracked agent pane.
switchboard --to 2 "check the fallback path before you edit"
switchboard send 2 "summarize where you are stuck"
switchboard rename 2 "codex gpt-5.5 review | auth | fallback status"
switchboard respawn 2
switchboard interrupt 2
switchboard status
switchboard broadcast "report status and blockers in one paragraph"
switchboard focus 2
switchboard history 2
switchboard prune --dry-run
switchboard ps
# Print one board snapshot and exit, or use the legacy line prompt.
switchboard board --once
switchboard board --prompt
# Then type a goal, or use commands like: @2 "...", respawn 2, broadcast "...", new "...", quit.
# Open the full-screen board explicitly.
switchboard board --tui
# Show detailed fields instead of the compact dashboard table.
switchboard board --verbose
# Show an auto-refreshing board view.
switchboard board --watch
# Force a fresh route instead of reusing a compatible tracked pane.
switchboard --new "review this PR for payment regressions"
# Force a task class or a specific profile.
switchboard --task ui "polish the dashboard layout"
switchboard --profile grok-fast "summarize this repo"
# Continue the most recent native session for the routed harness.
switchboard --continue "keep going from the last native session"
# Pass native harness flags after --.
switchboard --profile grok-fast "summarize this repo" -- --max-turns 4
# Debug or bypass the control center by running the routed harness here.
switchboard --direct "summarize this repo"
# Probe installed CLIs, then write the default config.
switchboard discover
switchboard initswitchboard: open the full-screen control center in an interactive terminal.switchboard "GOAL"(orswitchboard run "GOAL"): classify, route, launch tracked native TUI panes, and keep the board available in interactive terminals. Pass several quoted goals to run them at once, each in its own split pane.switchboard route "GOAL": print the route without launching.switchboard --direct "GOAL": run the selected native harness directly in the current terminal.switchboard classify "GOAL": print the task class, confidence, and who decided (rules or local model).switchboard swarm "GOAL" "GOAL" ...: run several goals at once, one routed agent per split pane (the explicit form of passing multiple goals above).switchboard discover: probe installed CLIs and their model catalogs.switchboard init: write the default user config.switchboard profiles: list the effective profiles after config merging.switchboard ps: list tracked agent panes spawned by Switchboard.switchboard ps: list active Switchboard-spawned panes only. Use--status allfor stale/history rows.switchboard send ID "PROMPT": send a follow-up prompt to a tracked pane.switchboard rename ID "TITLE": update a tracked pane title without sending a prompt.switchboard respawn ID: relaunch a tracked agent in a fresh split pane.switchboard interrupt ID: send Ctrl-C to a tracked pane.switchboard broadcast "PROMPT": send one prompt to every active tracked pane.switchboard status [NOTE]: ask every active tracked pane for a concise status report.switchboard focus ID: focus a tracked pane or session.switchboard history [ID]: show recent orchestration events.switchboard prune: remove local records already markeddone,stopped, ormissing.switchboard board: open the full-screen control board; use--oncefor a snapshot or--promptfor the line-oriented board prompt.switchboard stats: show recent local run telemetry.
Commands that support machine-readable output expose --json in their help. init has --print for the config path.
Each goal below was classified by the current rules; the arrow shows the task class and the configured profile before preflight fallback. --dry-run shows the effective profile on your machine.
"fix the crash in the payments webhook before we ship to production"
-> high_stakes -> claude-deep
"review this PR for regressions"
-> review -> codex-review
"polish the dashboard layout"
-> ui -> claude-sonnet
"summarize this repo quickly"
-> fast -> grok-fast
"keep iterating until the tests pass"
-> loop -> grok-loop
"refactor the parser offline with ollama"
-> local_coding -> codex-local-coder
"compare the latest agent harness releases"
-> research -> grok-build
"draft a launch email"
-> writing -> claude-sonnet
--dry-run shows the full launch plan:
switchboard --dry-run "review this PR for payment regressions"Switchboard Swarm
-----------------
terminal ghostty
layout splits
panes 1
review this PR for payment regressions
task high_stakes (0.80)
profile claude-deep
harness claude/interactive
model fable
title claude fable high | high_stakes | review this PR for payment regressions
command ... switchboard --direct --no-fallback --profile claude-deep --no-decompose 'review this PR for payment regressions'
Grok routes deliver the prompt through the launched terminal instead of argv:
prompt launched TTY
command grok -m grok-composer-2.5-fast --permission-mode bypassPermissions --always-approve # prompt sent to launched TTY
Default task classes, their default routes, and the kind of language that triggers them:
| Task | Default route | Typical signals |
|---|---|---|
architecture |
claude-deep |
"architecture", "system design", "trade-offs", "ADR" |
coding |
codex-deep |
the default when nothing else matches |
debugging |
codex-deep |
"bug", "error", "crash", "stack trace" |
fast |
grok-fast |
"quick", "cheap", "summarize", "tl;dr" |
goal |
codex-goal |
"end-to-end", "from scratch", "full implementation" |
high_stakes |
claude-deep |
"production", "payments", "auth", "security", "migration" |
local |
ollama-hermes |
"local", "offline", "private", "ollama" |
local_coding |
codex-local-coder |
local signals plus coding words like "fix" or "refactor" |
loop |
grok-loop |
"until", "keep going", "iterate" |
research |
grok-build |
"research", "latest", "compare", "sources" |
review |
codex-review |
"review", "audit", "PR", "diff", "regression" |
terminal |
codex-deep |
"shell", "script", "cron", "command" |
ui |
claude-sonnet |
"ui", "frontend", "css", "layout", "dashboard" |
verification |
grok-checked |
"verify", "validate", "check", "QA" |
workflow |
claude-workflow |
"workflow", "pipeline", "runbook", "orchestrate" |
writing |
claude-sonnet |
"write", "draft", "email", "memo" |
A goal that mixes high-stakes language with review signals, or matches several high-stakes patterns, escalates to high_stakes. Use switchboard classify to see exactly which patterns matched.
Keyword rules are instant but blunt. By default Switchboard runs in hybrid mode: when the rules are confident, they decide immediately; when they are not, the goal is sent to a cheap generation model already installed on your machine, which picks the task class from the same list. With no local generation model installed, the rules decide everything.
The local model has two jobs, each with its own default model choice:
- Classification (which task class): the smallest installed Ollama generation model — a ~500MB model like
qwen3:0.6banswers in a couple of seconds and is accurate enough for picking a category. - Decomposition (splitting a multi-sentence request into separate goals, one per agent): the largest installed Ollama generation model under 6 GB — this decision spawns agents and deserves the best judgment available.
gemma3:4bscored best in our eval (fast, never over-splits); thinking models likeqwen3:4bwork but answer slowly.
Providers: Ollama (localhost:11434) first, then LM Studio (localhost:1234). Nothing else is ever consulted, so routing never spends cloud tokens and the goal never leaves your machine. Unreachable server, timeout, or an unparseable answer all fall back silently to the rules result and a single-goal route.
In an interactive terminal, Switchboard writes short progress status on stderr while it classifies, decomposes, preflights, or plans a swarm. JSON output suppresses this status so stdout stays machine-readable.
Configure it in profiles.toml:
[classifier]
mode = "hybrid" # "hybrid", "llm" (always ask), or "rules" (never ask)
threshold = 0.70 # hybrid: rules confidence below this consults the model
model = "" # classification model; empty = smallest installed generation model
decompose = true # split compound requests into separate per-agent goals
decompose_model = "" # decomposition model; empty = largest installed generation model under 6 GB
timeout = 60.0 # seconds before giving up and using the rules resultswitchboard classify shows who decided: rule matches list the patterns, model decisions list the provider and model in reasons.
Default profiles:
claude-deep: Claude TUI withfable, high effort, and fallback to Opus/Sonnet.claude-default: Claude TUI using the local CLI default model.claude-opus: Claude TUI withopus, high effort, and fallback to Sonnet.claude-sonnet: Claude TUI withsonnet.claude-workflow: Claude TUI for workflow, process, and multi-step planning prompts.codex-deep: Codex TUI withgpt-5.5and high reasoning.codex-default: Codex TUI using the local CLI default model.codex-goal: Codex TUI high-reasoning profile for broad implementation goals.codex-local-coder: Codex TUI OSS mode using Ollama and Qwen3-Coder.codex-local-coder-latest: Codex TUI OSS mode using Ollama andqwen3-coder:latest.codex-review: Codex TUI for review prompts.codex-review-default: Codex TUI review fallback using the local CLI default model.grok-build: Grok TUI using Grok Build.grok-checked: Grok TUI with--check.grok-default: Grok TUI using the local CLI default model.grok-default-checked: Grok TUI with the default model and--check.grok-fast: Grok TUI with the Composer fast model.grok-loop: Grok TUI with--checkand a bounded turn cap.ollama-coder: direct Ollama prompt mode for Qwen3-Coder.ollama-coder-latest: direct Ollama prompt mode for theqwen3-coder:latesttag.ollama-hermes: direct Ollama prompt mode for Hermes.
Create a user config:
switchboard initConfig lives at:
~/.config/switchboard/profiles.toml
Project overrides can live at:
.switchboard.toml
Both layer over the built-in defaults: project config overrides user config, which overrides the defaults. You only need to write the routes and profiles you want to change.
Example:
[routes]
fast = "grok-fast"
review = "codex-review"
ui = "claude-sonnet"
[profiles.grok-fast]
harness = "grok"
mode = "interactive"
model = "grok-composer-2.5-fast"
fallback = "grok-build"Profile fields:
check: enables Grok's native self-check loop.effort: native effort flag for Claude/Grok profiles.env: environment variables added only to the launched child process.fallback: another profile to try if preflight finds this profile unavailable.flags: native harness flags appended before the prompt.harness:codex,claude,grok, orollama.local_provider: Codex OSS provider, such asollama.mode:interactivefor Codex, Claude, and Grok native TUIs;promptfor Ollama.model: native model name or alias for the selected harness.reasoning_effort: Codexmodel_reasoning_effortconfig value.
Switchboard preflights routes before launch by default. It checks whether the selected CLI exists and, when the CLI exposes a model catalog, whether the selected model is available.
Profiles can declare a fallback chain:
[profiles.codex-deep]
harness = "codex"
mode = "interactive"
model = "gpt-5.5"
fallback = "codex-default"
[profiles.codex-default]
harness = "codex"
mode = "interactive"For cloud CLIs, the default config falls back to that CLI's default model where possible. For local/private routes, it stays local: Switchboard tries the configured Ollama tags first, then falls back to an installed Ollama generation model when one is available. If Ollama is missing, or only embedding models are installed, it fails with a clear routing error instead of silently spending cloud tokens.
Example local fallback:
ollama-hermes -> ollama-hermes-installed
The synthesized *-installed profile uses the first installed Ollama generation model reported by discovery, preferring the tool's default model if one is known.
Use --no-fallback to print or run the raw configured profile without preflight fallback.
Switchboard can continue the most recent native session when the chosen harness supports it:
switchboard --continue "keep going with the next piece"This maps to the harness, not to Switchboard-owned session state:
- Claude profiles use
claude --continue. - Codex interactive profiles use
codex resume --last. - Grok profiles use
grok --continue. - Ollama prompt profiles do not support continuation.
Pass native harness flags after --:
switchboard --profile grok-loop "fix until tests pass" -- --todo-gate
switchboard --profile claude-workflow "plan the release workflow" -- --permission-mode planSwitchboard does not parse or validate passthrough flags. They are appended to the selected harness command before the prompt.
A swarm runs several goals at once, each in its own split pane with its own routed agent. Passing multiple quoted goals to the default command is enough:
switchboard \
"review the auth diff for regressions" \
"polish the dashboard layout" \
"summarize this repo quickly"switchboard swarm ... is the explicit form of the same thing. Quoting is one way to trigger it: a swarm fires when every goal argument has more than one word, so unquoted prose like switchboard fix the auth bug still routes as a single goal.
A single quoted request can also become a swarm. Sentence boundaries (. ; newline) are the only places a request may split; the local model then groups sentences that belong together:
switchboard "rewrite the readme. add examples if needed. do a code review"
# -> 2 panes: "rewrite the readme. add examples if needed" + "do a code review"
switchboard "fix the auth bug and run the tests"
# -> 1 pane: one sentence is always one goal, no model consultedThe contract: conjunctions ("and", "then") never split, because dependent steps must stay with one agent. Write separate sentences — or quote separately — when you want separate agents. The model errs toward grouping, an invalid or over-eager answer is discarded, and with no local generation model installed the request routes as a single goal. Set decompose = false under [classifier] to turn it off.
Split panes are the default, so everything stays visible at once. On Ghostty and iTerm2, split mode uses the current tab when a window already exists; it only creates a new window if the terminal has no window. --splits is accepted for explicitness:
switchboard --splits "fix the failing tests" "draft the release notes"Use --tabs only when you want separate terminal tabs:
switchboard --tabs "fix the failing tests" "draft the release notes"Isolate each agent in its own fresh git worktree so parallel agents cannot edit the same files:
switchboard --worktrees "implement phase 1 of HANDOFF.md" "write the migration tests"--worktrees creates one sibling directory per goal (<repo>-<slug> next to the repo root, on a new swarm/<slug> branch) and points that agent's pane or tab at it. Merge the branches you want to keep afterward and remove the rest with git worktree remove.
For configured profiles, each pane or tab re-invokes switchboard --direct --no-fallback --profile <resolved> --no-decompose "<goal>", so launch behavior, YOLO flags, Grok TTY prompt delivery, and telemetry are identical to a native harness run, without recursively opening another tracked pane or changing profiles after the main router already picked one. Synthesized local fallback profiles such as ollama-hermes-installed launch the resolved native command directly because they do not exist in config.
Switchboard names spawned panes before launching the agent:
codex gpt-5.5 xhigh | coding | Fix README examples
codex gpt-5.5 xhigh | review | Review fallback logic
grok grok-composer-2.5-fast fast | fast | Summarize repo
For Ghostty, iTerm2, and tmux, successful spawns are recorded in local orchestration state:
switchboard ps
switchboard ps --jsonThis state stores the goal text, pane title, profile, model, terminal backend, working directory, and terminal/session ID where the backend exposes one. It only tracks panes Switchboard spawned; it does not scan or list unrelated Codex, Claude, Grok, tmux, or shell processes you started yourself. It also records an event timeline for spawns, prompts, focus actions, status changes, liveness updates, and forgotten panes:
switchboard history
switchboard history 2
switchboard history --jsonSend follow-up prompts to a tracked pane:
switchboard --to 2 "check the fallback path before you edit"
switchboard send 2 "check the fallback path before you edit"
switchboard send --dry-run 2 "summarize where you are stuck"
switchboard rename 2 "codex gpt-5.5 review | auth | fallback status"
switchboard rename --dry-run 2 "codex gpt-5.5 review | auth | fallback status"
switchboard respawn 2
switchboard respawn --dry-run 2
switchboard interrupt 2
switchboard interrupt --dry-run 2
switchboard status
switchboard status "include the next command you plan to run"
switchboard broadcast "report status and blockers in one paragraph"
switchboard broadcast --dry-run "pause after your current command and report status"
switchboard focus 2
switchboard focus --dry-run 2
switchboard history 2
switchboard history --json
switchboard prune --dry-run
switchboard prune
switchboard ps
switchboard ps --status all
switchboard board
switchboard board --status allsend, --to, rename, interrupt, broadcast, and focus support tracked Ghostty terminals, iTerm2 sessions, and tmux panes. Switchboard only targets panes it spawned and recorded. Sending a follow-up updates the tracked pane title to the latest prompt. interrupt sends Ctrl-C into the selected agent TUI so you can stop a running turn before steering it. rename updates the tracked title and terminal title without sending text to the agent. respawn relaunches the tracked goal in a fresh split pane, preserving the current tracked title; configured profiles use the current config, while synthesized local fallback rows reuse the saved native command. Broadcast sends only to active tracked panes that have an addressable surface ID and are not marked done, stopped, or missing; real broadcasts run a liveness refresh first.
Open the board when you want a control center instead of one-off commands. In an interactive terminal, switchboard and switchboard board open the full-screen TUI. Use --once for a snapshot that exits immediately or --prompt for the line-oriented board prompt:
switchboard
switchboard board
switchboard board --once
switchboard board --prompt
switchboard board --tuiInside the board:
switchboard> review the fallback logic
switchboard> send 2 check the fallback path before you edit
switchboard> @2 summarize where you are stuck
switchboard> rename 2 codex gpt-5.5 review | auth | fallback status
switchboard> respawn 2
switchboard> interrupt 2
switchboard> status
switchboard> broadcast report status and blockers in one paragraph
switchboard> use 2
switchboard#2> focus
switchboard#2> interrupt
switchboard#2> now run the failing test directly
switchboard#2> history
switchboard#2> done
switchboard#2> clear
switchboard> new review the diff in a fresh pane
switchboard> new --dry-run inspect the launch plan
switchboard> forget 2
switchboard> prune --dry-run
switchboard> prune
switchboard> filter active
switchboard> filter all
switchboard> refresh
switchboard> check
switchboard> quit
The board lists active Switchboard-spawned panes in a compact, color-coded table with state, age, last-updated time, pane/session ID, profile, model, task, and title. Use switchboard board --verbose or the in-board verbose command for detailed per-agent fields; use compact to return to the table. switchboard board --tui opens a full-screen terminal board with selection, live refresh, F to focus the highlighted pane, I to interrupt it, P to respawn it, D to mark the highlighted pane done, X to stop it, R to mark it running, S to ask the highlighted pane for status, A to ask all active panes for status, ? for in-screen help, a selection-following table viewport, a detail panel for the selected pane's recent event history, and the same typed command surface. Lowercase text starts normal command/goal entry, so typing fix ... routes a goal instead of triggering a hotkey.
In route mode, freeform text routes normally, reuses one unambiguous matching pane when possible, and otherwise opens a new split pane so the board stays available. @ID PROMPT sends directly to a pane, while @ID, #ID, or use ID selects a pane; after that, freeform text goes straight to that pane until clear. status [NOTE] or report [NOTE] asks every active tracked pane for a concise status report. broadcast PROMPT or all PROMPT sends one coordination prompt to every active tracked pane. The default scope is filter active, which shows only live spawned/running/prompted Switchboard panes; filter all intentionally shows stale/history rows; filter STATUS narrows to one concrete status. focus [ID] jumps to a tracked pane, or the active pane when no ID is supplied. respawn [ID] relaunches a tracked pane, or the active pane when no ID is supplied. done, stop, and running update tracked status, and forget ID removes one stale record. prune removes all local records marked done, stopped, or missing; it does not kill panes. new always opens a fresh split instead of reusing a pane. refresh, check, switchboard board --watch, and the TUI perform terminal liveness checks for tracked tmux panes, Ghostty terminals, and iTerm sessions, marking reachable surfaces running and missing surfaces missing without overwriting manual done or stopped states. The board remains dependency-free and uses only the Python standard library.
The default command also does conservative pane reuse. After routing a single goal, Switchboard sends the prompt to an existing pane instead of spawning when all of these are true:
- the pane was spawned and recorded by Switchboard;
- it is in the same working directory;
- it uses the same resolved profile;
- exactly one tracked pane matches.
Use --new to force a fresh route. Prompts containing words like "new", "another", "separate", "fresh", "spawn", or "swarm" also bypass automatic reuse.
Backends, selected automatically or forced with --terminal:
tmux: when running inside tmux, goals become tiled split panes (--splitsand--tabschange nothing here).ghostty: native tabs or splits via AppleScript. Requires Ghostty 1.3+ on macOS withmacos-applescriptenabled (the default).iterm: native tabs or splits via AppleScript on macOS.print: prints one paste-able command per goal for unsupported terminals.
Use --dry-run with concrete goals to see the placement plan, including which terminal backend was detected, without opening anything:
switchboard swarm --dry-run "review fallback logic" "fix docs"Warning: a swarm multiplies the YOLO warning. Several agents running unattended in the same working tree will conflict; use --worktrees whenever more than one goal touches the same repository. A swarm places launches and tracks the spawned panes. It does not yet supervise, sequence, retry, or merge the agents' work.
Switchboard's built-in routes launch native TUIs:
- Codex interactive mode launches the Codex TUI and passes the goal as its prompt argument.
- Claude interactive mode launches the Claude TUI and passes the goal as its prompt argument.
- Grok interactive mode launches the TUI and submits the prompt through the launched terminal.
- Ollama prompt mode uses
ollama run MODEL PROMPTbecause Ollama is not an agent TUI.
Codex, Claude, and Grok profiles only support native TUI launch mode.
The default command launches those TUIs in tracked panes and returns the current terminal to the Switchboard board. Use --direct only when you intentionally want the selected harness to take over the current terminal.
Supported harnesses are launched in native YOLO mode:
- Codex uses
--dangerously-bypass-approvals-and-sandbox. - Claude uses
--dangerously-skip-permissions. - Grok uses
--permission-mode bypassPermissions --always-approve. - Ollama has no agent permission mode to bypass.
Warning: YOLO mode means the launched agent can run commands and edit files without asking you first. Run Switchboard in repositories and directories where you accept that. The flags always appear in --dry-run output, so you can inspect any route before launching it.
switchboard discover
switchboard discover --jsonDiscovery probes installed local tools:
claudecodexgrok- LM Studio on
localhost:1234 ollama
Discovery checks binaries and model catalogs where the harness exposes them. It does not prove that you are logged in or that your account has access to every advertised model.
Switchboard stores local run metadata in:
~/.config/switchboard/runs.sqlite
It records task, profile, harness, mode, model, exit code, elapsed time, working directory, confidence, and goal length. It does not store the goal text, and nothing leaves your machine. Use --no-telemetry to skip recording a run, and switchboard stats to review recent runs.
Switchboard stores local orchestration state in:
~/.config/switchboard/state.sqlite
Unlike telemetry, orchestration state stores goal text and event messages because pane reuse, history, and the board need to know what each spawned agent is doing. Nothing leaves your machine.
Switchboard builds argument arrays and does not invoke a shell for launched goals. Built-in profiles launch native TUIs. Grok prompt delivery launches the native CLI under a pseudo-terminal and writes the initial goal into that terminal.
YOLO flags are applied by Switchboard after profile and passthrough flags, so the launched command's permission posture is explicit in dry-run output. For Grok, a conflicting passthrough --permission-mode is replaced with bypassPermissions because the native CLI rejects duplicate permission modes. The selected harness owns the resulting tool, sandbox, approval, MCP, workflow, and session behavior.
Routes launch without Switchboard confirmation prompts. The selected native TUI still owns any prompts it emits itself, but Switchboard passes native YOLO permission flags for supported harnesses by default.
Switchboard is becoming an agent workbench, but it is not a workflow engine yet. The board can list panes, prompt an existing pane, respawn a tracked goal, and spawn a fresh routed agent. It does not run queues, supervise background jobs, parse diffs, merge a swarm's work, or apply patches. Use the native harness features for those jobs: Codex resume/cloud/apply, Claude background agents/worktrees/workflows, Grok sessions/worktrees/check loops, or shell scripts.
Modern agent CLIs burn tokens differently. Codex, Claude Code, Grok Build, and local Ollama models have different costs, context behavior, tools, permissions, and strengths. Switchboard makes that routing decision explicit and editable so routine work uses cheaper tokens and expensive reasoning is saved for work that needs it.