# Launcher Path vs Server Path in Switchyard: Architecture and Responsibilities Explained

> Understand the Switchyard launcher path vs server path distinction. Learn how Python orchestration prepares your environment and Rust services execute LLM routing.

- Repository: [NVIDIA-NeMo/Switchyard](https://github.com/NVIDIA-NeMo/Switchyard)
- Tags: architecture
- Published: 2026-08-15

---

**In Switchyard, the launcher path is the Python orchestration layer that prepares environment variables and starts a native server, while the server path is the compiled Rust service that implements the HTTP API and executes LLM routing algorithms.**

NVIDIA's Switchyard project separates client-side orchestration from high-performance request routing through a deliberate dual-path architecture. Understanding this distinction helps developers extend launchers for new AI clients or modify routing behavior without crossing language boundaries unnecessarily.

## What Is the Launcher Path in Switchyard?

The **launcher path** handles Python-level CLI logic, environment preparation, and native server invocation. It lives entirely in the Python package and serves as the entry point for end users running commands like `switchyard launch claude`.

### Primary Responsibilities

The launcher path performs five core functions:

- **Parses CLI arguments** via [`switchyard/cli/launch_command.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/switchyard/cli/launch_command.py), the central dispatch module
- **Locates LLM binaries** such as Claude Code or Codex CLI on the host system
- **Spins up a `NativeServer`** instance that wraps the Rust service
- **Patches environment variables** to redirect client traffic through the Switchyard proxy
- **Renders the interactive UI** or manages the subprocess lifecycle

### Key Implementation Files

The launcher path centers on three main modules in `switchyard/cli/launchers/`:

| File | Purpose |
|------|---------|
| [`launch_command.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/launch_command.py) | CLI entry point that routes subcommands to specific launcher implementations |
| [`native_server.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/native_server.py) | Python façade (`NativeServer` class) that instantiates and controls the Rust server |
| [`claude_code_launcher.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/claude_code_launcher.py) | Concrete launcher for Claude Code; implements `ClaudeCodeLauncher` |

Additional launchers like [`codex_cli_launcher.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codex_cli_launcher.py) follow the same pattern, enabling reuse of the underlying native server across multiple AI clients.

### Launcher Flow Example

When a user runs `switchyard launch claude`, the execution flow proceeds through launcher path components:

```python

# Simplified excerpt showing launcher path orchestration

# From switchyard/cli/launchers/claude_code_launcher.py

from switchyard.cli.launchers.native_server import NativeServer
from pathlib import Path

def launch_claude_config(config, model, claude_args=None):
    # 1. Start the native server (crosses into server path)

    server = NativeServer(config_path=config, port=0)
    server.start()
    
    # 2. Prepare environment variables for proxy routing

    env = os.environ.copy()
    env["ANTHROPIC_BASE_URL"] = server.base_url
    
    # 3. Launch the actual Claude Code binary

    return subprocess.run(["claude", *claude_args], env=env).returncode

```

The launcher never processes LLM requests directly—it delegates all routing to the native server it started.

## What Is the Server Path in Switchyard?

The **server path** implements the actual LLM routing logic in Rust, exposed to Python via PyO3 bindings. This path handles HTTP request serving, provider-neutral client abstractions, and performance-critical routing algorithms.

### Primary Responsibilities

The server path operates as a compiled service with four key roles:

- **Binds to a TCP port** and runs an Axum-based HTTP service
- **Exposes OpenAI-compatible endpoints** including `/v1/chat/completions` and `/v1/models`
- **Executes routing algorithms** from the `libsy` crate to select providers and manage load
- **Reports health and statistics** to the launcher via the `/v1/stats` endpoint

### Key Implementation Files

| File | Purpose |
|------|---------|
| [`switchyard_rust/server.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/switchyard_rust/server.py) | PyO3 bindings that expose `Server` to Python; entry point from launcher path |
| [`crates/libsy-llm-client/src/lib.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy-llm-client/src/lib.rs) | Provider-neutral LLM client for forwarding requests to upstream services |
| [`crates/libsy/src/algorithms.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/algorithms.rs) | Core routing algorithms (least-loaded, weighted, etc.) |

### Server Initialization Pattern

The Rust `Server` struct loads a TOML deployment configuration and automatically selects a port:

```rust
// From switchyard_rust::server (exposed via PyO3)
// Equivalent Python usage in native_server.py

use switchyard_rust::server::Server;
use std::path::PathBuf;

#[pyfunction]
fn create_server(toml_path: String, port: u16) -> PyResult<ServerWrapper> {
    // port=0 requests any available ephemeral port from the OS
    let server = Server::new(PathBuf::from(toml_path), port)?;
    Ok(ServerWrapper { inner: server })
}

```

Once started, the server runs independently on a Tokio runtime, handling concurrent requests without Python's Global Interpreter Lock (GIL) constraints.

## Critical Distinctions Between Launcher Path and Server Path

| Dimension | Launcher Path | Server Path |
|-----------|-------------|-------------|
| **Language** | Pure Python | Rust with PyO3 Python bindings |
| **Location** | `switchyard/cli/launchers/` | `switchyard_rust/`, `crates/libsy*/` |
| **Primary crate/module** | N/A (Python package) | `libsy`, `libsy-llm-client` |
| **Runtime model** | Synchronous, process-spawning | Async Tokio, multi-threaded |
| **Key classes** | `ClaudeCodeLauncher`, `NativeServer` | `switchyard_rust::server::Server` |
| **HTTP serving** | None (proxies to server) | Axum-based, full HTTP API |
| **GIL dependency** | Holds GIL during execution | Releases GIL; runs native threads |

## Why Switchyard Splits These Responsibilities

The architecture separates launcher and server paths for three engineering reasons:

1. **Performance isolation** — Routing logic benefits from Rust's zero-cost abstractions and Tokio's async runtime, free from Python's GIL contention

2. **Platform flexibility** — The Python launcher adapts easily to different operating systems and client binaries, while the compiled Rust server remains consistent

3. **Launcher reusability** — Multiple CLI clients (Claude, Codex, OpenClaw) share the same `NativeServer` and underlying Rust service, reducing code duplication

## Practical Interaction: Starting a Server from Python

The launcher path instantiates the server path through a thin boundary layer:

```python

# Complete example: launching Claude Code with Switchyard routing

from pathlib import Path
from switchyard.cli.launchers.claude_code_launcher import launch_claude_config

# TOML deployment defines routes, authentication, and upstream providers

config_path = Path("switchyard/cli/defaults/claude_switchyard.toml")

# Launcher path handles CLI, environment, and subprocess management

exit_code = launch_claude_config(
    config=config_path,
    model="anthropic.claude-v2",
    claude_args=["--verbose"]
)

print(f"Claude Code exited with code {exit_code}")

```

Behind this call, `launch_claude_config` delegates to `NativeServer`, which crosses into the server path via [`switchyard_rust/server.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/switchyard_rust/server.py) to create the actual HTTP service.

## Summary

- **Launcher path** (`switchyard/cli/launchers/`) provides Python-based CLI logic, environment setup, and native server orchestration
- **Server path** (`switchyard_rust/`, `crates/libsy/`) delivers compiled Rust HTTP service with LLM routing algorithms
- The split eliminates Python GIL constraints from request handling while keeping client integration flexible
- `NativeServer` in [`switchyard/cli/launchers/native_server.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/switchyard/cli/launchers/native_server.py) serves as the bridge, using PyO3 to instantiate `switchyard_rust::server::Server`
- Multiple launchers can reuse the same Rust server implementation, as demonstrated by [`claude_code_launcher.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/claude_code_launcher.py) and [`codex_cli_launcher.py`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/codex_cli_launcher.py)

## Frequently Asked Questions

### How does the launcher communicate with the running server?

The launcher retrieves `base_url` and `port` from the `NativeServer` instance after construction. It passes these as environment variables to client subprocesses. The server additionally exposes a `/v1/stats` endpoint that the launcher can poll for health and routing information.

### Can the server run without the launcher?

Yes. The Rust `Server` can be instantiated directly from Rust code or through the PyO3 bindings in any Python script. The launcher path adds convenience for CLI integration but is not strictly required for server operation.

### Where are routing algorithms actually implemented?

Routing algorithms live in the server path at [`crates/libsy/src/algorithms.rs`](https://github.com/NVIDIA-NeMo/Switchyard/blob/main/crates/libsy/src/algorithms.rs). These Rust implementations handle provider selection, load balancing, and failover logic. The launcher path only specifies which algorithm to use via the TOML deployment configuration.