SwitchBoard Routing Architecture¶
SwitchBoard is the platform's execution-lane selector. It accepts a canonical
TaskProposal, evaluates a configured routing policy, and produces a canonical
LaneDecision. It does not execute backends, proxy providers, host models, or
participate in the runtime request path.
What SwitchBoard does¶
- Accept a
TaskProposalfrom ControlPlane - Evaluate routing factors from the proposal against the lane routing policy
- Select a lane (
aider_local,claude_cli,codex_cli) - Select a backend (
direct_local,kodo,archon_then_kodo,openclaw) - Produce a
LaneDecisioncarrying the selection, confidence, and rationale - Explain decisions in a concise, inspectable way (
LaneSelector.explain()) - Validate the routing policy configuration (
LaneSelector.validate_policy())
What SwitchBoard does not do¶
-
Not a provider proxy. SwitchBoard does not forward arbitrary LLM API traffic to external providers. It does not aggregate remote API access. It stops at route selection and handoff.
-
Not a model host. If
aider_localis selected, SwitchBoard is choosing a WorkStation-hosted capability. WorkStation deploys the local model services; SwitchBoard only decides to use them. -
Not an execution layer. Selecting
kodoorarchon_then_kodomeans the lane runner will invoke those backends. SwitchBoard does not know or implement kodo or Archon execution semantics. -
Not a task proposer. SwitchBoard does not generate, prioritise, or filter TaskProposals. That is ControlPlane's responsibility.
-
Not an auth broker. SwitchBoard does not hold or distribute backend credentials.
How a TaskProposal becomes a LaneDecision¶
TaskProposal
│
▼
LaneSelector.select(proposal)
│
├── 1. Flatten proposal into routing signals
│ task_type, risk_level, priority, local_only, ...
│
├── 2. Evaluate policy rules (ascending priority order)
│ First matching rule wins.
│ Rules with excluded backends are skipped.
│ No match → fallback policy.
│
├── 3. Apply backend override rules
│ Fine-tune backend selection within a chosen lane.
│
└── 4. Produce LaneDecision
proposal_id, selected_lane, selected_backend,
confidence, policy_rule_matched, rationale,
alternatives_considered, decided_at
Routing factors¶
The following proposal fields are extracted as routing signals:
| Signal | Source | Description |
|---|---|---|
task_type |
proposal.task_type |
Broad task category (lint_fix, bug_fix, feature, …) |
risk_level |
proposal.risk_level |
ControlPlane's estimate: low / medium / high |
priority |
proposal.priority |
Scheduling priority |
execution_mode |
proposal.execution_mode |
Execution strategy (goal, fix_pr, …) |
local_only |
"local_only" label present |
Forces aider_local regardless of other factors |
Rules can match on any combination of these signals using exact-match or
any-of (list) semantics. The special max_risk_level condition enables
ceiling comparisons (low < medium < high).
Lane and backend universe¶
Lanes¶
| Lane | Description |
|---|---|
aider_local |
WorkStation-hosted local Aider execution. Zero marginal API cost. |
claude_cli |
Claude Code CLI execution. Requires OAuth. |
codex_cli |
Codex CLI execution. Requires subscription. |
Backends¶
| Backend | Description |
|---|---|
direct_local |
Direct invocation without a runner wrapper (aider_local lane) |
kodo |
kodo execution runner |
archon_then_kodo |
Archon workflow wrapper over kodo execution |
openclaw |
OpenClaw backend (selectable when policy permits) |
Backend selection is separate from lane selection. The lane determines the execution environment; the backend determines the runner/orchestration strategy within that environment.
Policy model¶
Lane routing is driven by LaneRoutingPolicy (see src/switchboard/lane/policy.py).
The default policy lives in src/switchboard/lane/defaults.py.
LaneRoutingPolicy
├── rules: list[LaneRule] ← ordered, priority-based
├── backend_rules: list[BackendRule] ← backend override within lane
├── fallback: FallbackPolicy ← when no rule matches
├── thresholds: DecisionThresholds ← confidence and risk ceilings
└── excluded_backends: list[str] ← never selected
LaneRule — matches signals from the proposal, selects a lane and backend:
- name: local_low_risk
priority: 20
select_lane: aider_local
select_backend: direct_local
when:
task_type: [lint_fix, documentation, simple_edit]
max_risk_level: low
confidence: 0.95
BackendRule — overrides backend selection within a named lane:
FallbackPolicy — applied when no rule matches:
Selection is separate from execution¶
This is the core architectural principle:
SwitchBoard tells the lane runner what to use. It does not do the work.
After SwitchBoard returns a LaneDecision, the lane runner (e.g. kodo) uses
LaneDecision.selected_lane and LaneDecision.selected_backend to prepare an
ExecutionRequest and invoke the appropriate adapter. SwitchBoard is not in
that path.
ControlPlane → TaskProposal → SwitchBoard → LaneDecision
│
▼
lane runner (kodo)
│
ExecutionRequest
│
▼
backend adapter
Policy transparency¶
Routing must be explicit and inspectable. This means:
- Every
LaneDecisionnames the policy rule that fired (policy_rule_matched) - Decisions can be explained via
LaneSelector.explain(proposal) - The full policy can be validated via
LaneSelector.validate_policy() - No routing depends on hidden global state or opaque heuristics
The explanation model (DecisionExplanation) captures:
- which rule matched
- which factors influenced the decision
- which alternatives were ruled out and why
- whether the fallback was used
Entry point¶
from switchboard.lane.engine import LaneSelector
selector = LaneSelector() # uses default policy
decision = selector.select(proposal) # TaskProposal → LaneDecision
explanation = selector.explain(proposal) # for logging/audit
issues = selector.validate_policy() # [] = policy is valid
Custom policy: