Automatic LLM provider switching with Git isolation. Keep coding even when rate limits hit by automatically rotating through configured providers (Claude, OpenRouter, Gemini, Groq, Ollama), preserving work through nested git branches.
- Automatic Provider Switching: Detects 429/quota errors and rotates through configured providers
- Git Isolation: Creates nested branches (
dev→dev-openrouter→dev-gemini) to isolate work per provider - Test Integration: Optionally runs tests before switching to ensure code quality
- Smart Notifications: Discord webhooks and handoff callbacks to resume sessions on new providers
- Offline Fallback: Supports local Ollama models when network providers are down
- Session Management: HTTP server for managing sessions, rollback, and merge operations
- Python 3.9+ - Required for orchestrator
- Node.js 16+ - Required for UI and Electron app
- Git 2.20+ - Required for branch management
- API Keys - At least one LLM provider:
- Claude (Anthropic) - Recommended
- OpenRouter
- Google Gemini
- Groq
- Ollama - Local/offline option
# Clone repository
git clone https://github.com/your-org/switch-ex.git
cd switch-ex
# Install Python orchestrator
pip install -e .
pip install -e ".[dev]" # Include development tools
# Install UI dependencies (optional)
cd ui
npm install
cd ..
# Install Electron app dependencies (optional)
cd electron
npm install
cd ..# Copy sample configuration
cp orchestrator/config/config.sample.yaml orchestrator/config/config.yaml
# Edit configuration (or use environment variables)
# Set your provider API keys and preferencesCreate a .env.local file (gitignored) or set environment variables:
# Required for notifications
export DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/..."
# Provider API keys (at least one required)
export ANTHROPIC_API_KEY="sk-ant-..." # Claude
export OPENROUTER_API_KEY="sk-or-..." # OpenRouter
export GOOGLE_API_KEY="..." # Gemini
export GROQ_API_KEY="gsk_..." # Groq
export OLLAMA_HOST="http://localhost:11434" # Ollama (optional)
# Optional webhooks
export ATTENTION_ISSUE_URL="..." # Attention callback
export RESPONSE_ROUTER_URL="..." # Handoff callbackWindows PowerShell:
$env:ANTHROPIC_API_KEY = "sk-ant-..."
$env:DISCORD_WEBHOOK_URL = "https://discord.com/api/webhooks/..."Edit orchestrator/config/config.yaml:
providers:
# Ordered tier list - will try in this order
- name: claude
enabled: true
models:
- id: claude-3-sonnet
notes: primary
- name: openrouter
enabled: true
models:
- id: openrouter/mistralai/mistral-7b-instruct
notes: free tier fallback
- name: gemini
enabled: true
models:
- id: gemini-2.5-flash
notes: free tier
- name: ollama
enabled: true
models:
- id: gemma:2b
notes: offline/local fallback
notifications:
discord_webhook: "${DISCORD_WEBHOOK_URL}"
callback_url: "${RESPONSE_ROUTER_URL:-}"
attention_issue: false
include_diffs: false
git:
base_branch: dev
branch_prefix: dev-
commit_message: "auto: before switch to {provider}"
push: true
tests:
command: "" # e.g., "pytest" or "npm test"
block_on_fail: true
behavior:
wait_mode: false
offline_allowed: true# Start HTTP server (default: localhost:8080)
python -m orchestrator.src.server
# Or with custom host/port
python -m orchestrator.src.server --host 0.0.0.0 --port 8080
# Or use the installed command
switch-ex-server --host 0.0.0.0 --port 8080cd ui
npm run dev # Development server on http://localhost:3000
npm run build # Production buildcd electron
npm run dev # Development mode
npm run build # Build UI and orchestrator
npm run dist # Create distributable packagesOnce the orchestrator server is running:
- Start a coding session - The orchestrator monitors Claude Code or other LLM sessions
- Hit rate limit - When a provider hits a rate limit (HTTP 429)
- Auto-switch - Orchestrator:
- Commits current work
- Creates new branch:
dev-<next-provider> - Switches to next enabled provider
- Sends notifications
- Resumes session
- Continue coding - Work continues on new provider/branch
# Python orchestrator tests
pytest # All tests
pytest -v orchestrator/tests/test_switch_controller.py # Specific file
pytest -m unit # Unit tests only
pytest -m integration # Integration tests only
pytest --cov # With coverage
# UI tests
cd ui
npm run test # Vitest unit tests
npm run test:e2e # Playwright E2E tests# Python
ruff check orchestrator/
ruff format orchestrator/
mypy orchestrator/
# UI
cd ui
npm run lint# Build orchestrator executable (PyInstaller)
cd orchestrator
pyinstaller switch-ex-orchestrator.spec
# Build Electron app
cd electron
npm run build # Build UI and orchestrator
npm run dist # Create platform packages
npm run dist:win # Windows only
npm run dist:mac # macOS only
npm run dist:linux # Linux only- Python Orchestrator (
orchestrator/) - Core switching logic, HTTP server, provider adapters - Next.js UI (
ui/) - Dashboard for monitoring sessions and configuring providers - Electron App (
electron/) - Desktop application wrapper (optional)
switch-ex/
├── orchestrator/ # Python orchestrator backend
│ ├── src/
│ │ ├── adapters/ # Provider adapters (Claude, OpenRouter, etc.)
│ │ ├── controllers/ # Core logic (switching, sessions, git, notifications)
│ │ ├── monitor/ # Claude CLI monitor
│ │ ├── webhooks/ # Inbound webhook handlers
│ │ └── server.py # HTTP server
│ ├── config/ # Configuration files
│ └── tests/ # Unit and integration tests
│
├── ui/ # Next.js dashboard
│ ├── app/ # Next.js app router pages
│ ├── components/ # React components
│ ├── lib/ # Utilities and API client
│ └── tests/ # Vitest and Playwright tests
│
├── electron/ # Electron desktop app
│ ├── main.js # Electron main process
│ ├── preload.js # Preload script
│ ├── orchestrator-manager.js # Orchestrator subprocess
│ └── first-run.js # First-run setup wizard
│
├── scripts/ # Installation and utility scripts
│ ├── install.sh # Unix installation
│ ├── install.ps1 # Windows installation
│ └── run-integration-tests.sh # Test runner
│
└── docs/ # Documentation
- Provider Tier System: Providers are ordered by priority (configured in
config.yaml) - Rate Limit Detection: Adapters detect HTTP 429 errors from provider APIs
- Nested Branching: Each provider switch creates a new branch:
- Start:
dev - First switch:
dev → dev-openrouter - Second switch:
dev-openrouter → dev-openrouter-gemini
- Start:
- Session State: Persisted to
.switch-ex-sessions/for recovery - Rollback/Merge: Manual operations available via API to manage branch hierarchy
Each provider in config.yaml can be configured with:
name: Provider identifier (claude, openrouter, gemini, groq, ollama)enabled: Whether to use this provider in rotationmodels: List of models to useid: Model identifier for the provider APInotes: Optional notes
base_branch: Starting branch (default:dev)branch_prefix: Prefix for provider branches (default:dev-)commit_message: Template for auto-commits (can use{provider})push: Auto-push branches to remote (default:true)
discord_webhook: Discord webhook URL for notificationscallback_url: Handoff callback URL for resuming sessionsattention_issue: Enable attention issue creation (default:false)attention_issue_url: URL for attention webhookinclude_diffs: Include code diffs in notifications (default:false, not recommended for security)
command: Test command to run before switching (e.g.,pytest,npm test)block_on_fail: Block switch if tests fail (default:true)
The orchestrator exposes an HTTP API (default port 8080):
GET /health- Health checkGET /sessions- List all sessionsGET /session/{id}- Get session detailsPOST /session/{id}/restart- Restart session with specified provider
POST /session/{id}/rollback- Rollback to parent branchPOST /session/{id}/merge- Merge current branch to parent
GET /decisions- List pending decisionsPOST /decision/request- Create decision requestPOST /decision/{id}/respond- Respond to decision
POST /webhook/callback- Receive session callbacksPOST /webhook/human-decision- Receive human decision responses
GET /events- Subscribe to real-time events (session start, provider switch, etc.)
Sends notifications on:
- Provider switch (with reason, e.g., "rate limit")
- Session start/stop
- Errors
Payload includes:
session_idprovider(current and next)branch(current and next)event(switch, start, stop, error)severity(info, warning, error)
Security Note: Does NOT include code diffs or secrets by default.
POST to callback_url when provider switches, allowing external systems to resume sessions:
{
"session_id": "abc123",
"provider": "openrouter",
"branch": "dev-openrouter",
"event": "switch_complete"
}from orchestrator.src.main import run
# Run orchestrator programmatically
result = run(
messages=[{"role": "user", "content": "Hello"}],
session_id="my-session",
provider="claude"
)Create custom provider adapters by extending BaseAdapter:
from orchestrator.src.adapters.base import BaseAdapter, ProviderLimitError
class MyAdapter(BaseAdapter):
def send_message(self, messages, model):
# Implementation
# Raise ProviderLimitError on rate limits
passRegister in orchestrator/src/adapters/__init__.py.
Pre-commit and pre-push hooks are available:
# Pre-commit: Compile check
python -m compileall orchestrator
# Pre-push: Compile and test
python -m compileall orchestrator && pytest# Check port availability
lsof -i :8080 # macOS/Linux
netstat -ano | findstr :8080 # Windows
# Check logs
tail -f logs/orchestrator.log # If logging configured- Check API keys: Verify environment variables are set
- Check provider config: Ensure providers are enabled in
config.yaml - Check rate limits: Verify you have quota with the provider
- Check git: Ensure git is configured (
git config user.name,git config user.email)
# Run specific test
pytest -v orchestrator/tests/test_switch_controller.py::test_name
# Check test output
pytest -s # Show print statements
# Debug test
pytest --pdb # Drop into debugger on failure# Check verification
cd electron
node verify-install.js
# Check logs
# Windows: %APPDATA%\switch-ex\logs
# macOS: ~/Library/Logs/Switch-Ex
# Linux: ~/.config/switch-ex/logs- CLAUDE.md - Development guide for Claude Code
- TESTING.md - Comprehensive testing guide
- DOCUMENT-ASSESSMENT.md - Documentation audit results
- electron/BUILD.md - Electron build instructions
- electron/IPC-API-REFERENCE.md - Electron IPC API
- scripts/README.md - Installation scripts guide
- docs/ - Technical specifications and ADRs
Platform-specific installation scripts are available in scripts/:
Windows:
.\scripts\install.ps1 -CreateShortcut -RunAfterInstallmacOS/Linux:
chmod +x scripts/install.sh
./scripts/install.shSee scripts/README.md for detailed usage.
- Fork the repository
- Create a feature branch
- Make changes with tests
- Run linting and tests
- Submit pull request
MIT License - see LICENSE file
- GitHub Issues: Bug reports and feature requests
- Documentation: See
docs/directory - Claude Code: See
CLAUDE.mdfor AI-assisted development
See FURTHER-DEVELOPMENT.md for planned features and enhancements.