# Contract: RoutingPolicy **layer:** Functional **maturity:** Beta **module:** `llm_connect.routing` **since:** WP-0003 ## Purpose Route logical task types to concrete `LLMAdapter` instances based on a prioritised rule list, with optional per-rule cost-cap fallback. ## Public surface ```python @dataclass class RoutingRule: task_type: str prefer: LLMAdapter max_cost_per_1k: Optional[float] = None # USD per 1 000 tokens fallback: Optional[LLMAdapter] = None @dataclass class RoutingPolicy: rules: List[RoutingRule] = field(default_factory=list) default: Optional[LLMAdapter] = None def resolve( self, task_type: str, estimated_cost_per_1k: Optional[float] = None, ) -> LLMAdapter: ... ``` ## Invariants 1. Rules are evaluated in list order; the first rule whose `task_type` matches wins. 2. When `estimated_cost_per_1k` is supplied and a matching rule has `max_cost_per_1k` set: - If `estimated_cost_per_1k > max_cost_per_1k` **and** `fallback is not None` → return `fallback`. - Otherwise → return `prefer` (no fallback configured or cost within cap). 3. When no rule matches and `default is not None` → return `default`. 4. When no rule matches and `default is None` → raise `LookupError`. 5. `resolve()` never mutates policy state. ## Error contract | Condition | Exception | |-----------|-----------| | No matching rule, no default | `LookupError` | ## Known consumers - `inter-hub` (IHUB-WP-0012 Phase 11): uses `RoutingPolicy` to select federation adapters per task class.