Skip to main content
This is a self-hosting guide. The hosted product at https://www.orcapods.ai needs none of this: sign in and go.

Why Multiple Runners?

Running multiple runners lets you:
  1. Scale horizontally: more runners means more concurrent sessions
  2. Specialize by runtime: dedicate runners to specific LLM providers
  3. Isolate workloads: separate runners for different tenants or use cases (future)
  4. Improve resilience: conductor routes away from unhealthy runners

How Session Routing Works

Each runner has a hash derived from its RUNNER_BASE_URL:
Session IDs embed this hash: sess-<hash>-<8hex> When any conductor receives a request targeting a session, it extracts the hash, looks it up in its remote.Pool, and routes directly to the owning runner. Routing is stateless by session ID, but membership changes are currently held in each conductor process; horizontally replicated conductors must therefore reconcile their registries independently.

Basic Two-Runner Setup


Capability-Based Routing

Runners advertise their capabilities. The conductor only routes sessions to runners that support the requested runtime: Set capabilities via RUNNER_CAPABILITIES (comma-separated):

Sidecar Configuration per Runner

Each runner can have different sidecars for different runtimes: Per-runtime sidecars (recommended for production):
Single poly-sidecar (simpler for development):

Checking Topology

The GET /api/topology endpoint shows all runners, their capabilities, health, membership state, process footprint, session breakdown, and the sidecar worker instances observed beneath each runner:
Runner entries are keyed by hash rather than by URL: the runner’s base URL is an internal infrastructure identifier and is stripped before this response is built, so it never reaches a tenant. process, sessionsByRuntime, and sidecars can be null when a runner does not answer its topology probe. sidecars[].observedInstances is a lower bound on worker replicas, not a count, and cold means an idle or scaled-to-zero sidecar rather than a fault. There is currently no dashboard view of this data; use the curl example above, or GET /api/stats/summary for an aggregate healthy/total runner count.

Runner Health & Failover

The conductor probes each runner at startup and during reconcile, and records a membership state. An individual runner that is unreachable during startup no longer prevents the conductor from booting when at least one runner answers; it remains visible as unreachable and is excluded from new session routing until a successful reconcile. A runner that is deliberately draining is also excluded from new sessions, while sessions it already owns remain routed by their session ID. Boot still fails when no runner answers at all.
Sessions are never migrated automatically. Sessions already assigned to an unreachable runner remain pinned to it and can error until it recovers; sessions on a draining runner remain routable so the runner can finish its in-flight work. Removing a runner outright can orphan those sessions.

Multi-Host Deployment

When runners run on different hosts, RUNNER_BASE_URL must be a routable URL that all conductors can reach:
Never use localhost for RUNNER_BASE_URL in multi-host setups. It needs to be a URL that resolves from the conductor’s network.

Runner-Specific Tool Configuration

Different runners can have different tools available. For example:
  • Runner A has TAVILY_API_KEY set, so web_search and web_extract work
  • Runner B does not, so those tools return errors
Sessions are assigned to runners based on capability, not tool availability. If you rely on specific tools, ensure all runners in the pod have the necessary env vars set.
Last modified on September 6, 2026