1"use strict";(self.webpackChunksemantic_router_docs=self.webpackChunksemantic_router_docs||[]).push([[3781],{26562(e,n,i){i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>d,default:()=>h,frontMatter:()=>s,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"proposals/unified-config-contract-v0-3","title":"Unified Config Contract v0.3","description":"Records the implemented configuration contract shared by the router, CLI, dashboard, Helm, operator, and DSL.","source":"@site/docs/proposals/unified-config-contract-v0-3.md","sourceDirName":"proposals","slug":"/proposals/unified-config-contract-v0-3","permalink":"/docs/proposals/unified-config-contract-v0-3","draft":false,"unlisted":false,"editUrl":"https://github.com/vllm-project/semantic-router/edit/main/website/docs/proposals/unified-config-contract-v0-3.md","tags":[],"version":"current","frontMatter":{"title":"Unified Config Contract v0.3","description":"Records the implemented configuration contract shared by the router, CLI, dashboard, Helm, operator, and DSL.","created":"2026-03-17T00:00:00.000Z","status":"Implemented"},"sidebar":"tutorialSidebar","previous":{"title":"Unified Model Catalog and Evaluation Index","permalink":"/docs/proposals/unified-model-catalog-and-evaluation-index"},"next":{"title":"Protocol-Neutral Codec Matrix","permalink":"/docs/proposals/multi-protocol-adaptor"}}');var t=i(74848),o=i(28453);const s={title:"Unified Config Contract v0.3",description:"Records the implemented configuration contract shared by the router, CLI, dashboard, Helm, operator, and DSL.",created:new Date("2026-03-17T00:00:00.000Z"),status:"Implemented"},d=void 0,a={},l=[{value:"Problem",id:"problem",level:2},{value:"Implemented contract",id:"implemented-contract",level:2},{value:"Provider and model boundary",id:"provider-and-model-boundary",level:2},{value:"Routing and DSL boundary",id:"routing-and-dsl-boundary",level:2},{value:"Entrypoints and multi-recipe routing",id:"entrypoints-and-multi-recipe-routing",level:2},{value:"Defaults and configuration source",id:"defaults-and-configuration-source",level:2},{value:"Repository sources",id:"repository-sources",level:2},{value:"Migration",id:"migration",level:2},{value:"Scope and non-goals",id:"scope-and-non-goals",level:2},{value:"References",id:"references",level:2}];function c(e){const n={a:"a",blockquote:"blockquote",code:"code",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Status:"})," Implemented \xb7 ",(0,t.jsx)(n.strong,{children:"Created:"})," 2026-03-17"]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"problem",children:"Problem"}),"\n",(0,t.jsx)(n.p,{children:"The router, CLI, dashboard, Helm chart, operator, and DSL previously interpreted\noverlapping configuration shapes. A file accepted by one surface could require\ntranslation or undocumented defaults in another. Model identity was also mixed with\ndeployment endpoints and credentials."}),"\n",(0,t.jsx)(n.h2,{id:"implemented-contract",children:"Implemented contract"}),"\n",(0,t.jsx)(n.p,{children:"The public configuration has eight top-level sections:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:"version:\nlisteners:\nproviders:\nevaluation:\nrouting:\nentrypoints:\nrecipes:\nglobal:\n"})}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Section"}),(0,t.jsx)(n.th,{children:"Responsibility"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"version"})}),(0,t.jsx)(n.td,{children:"Selects the configuration contract."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"listeners"})}),(0,t.jsx)(n.td,{children:"Defines request-facing and management listeners."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"providers"})}),(0,t.jsx)(n.td,{children:"Binds logical model names to provider identifiers and endpoints."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"evaluation"})}),(0,t.jsx)(n.td,{children:"Optionally defines operator-owned benchmarks, index DAGs, and model-linked records."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"routing"})}),(0,t.jsx)(n.td,{children:"Defines the default model cards, signals, projections, decisions, algorithms, and plugins."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"entrypoints"})}),(0,t.jsx)(n.td,{children:"Maps request-facing model names to the default profile or a named recipe."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"recipes"})}
1),(0,t.jsx)(n.td,{children:"Defines additional isolated routing profiles that share providers and global infrastructure."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"global"})}),(0,t.jsx)(n.td,{children:"Holds router-wide services, stores, integrations, model modules, and sparse runtime overrides."})]})]})]}),"\n",(0,t.jsx)(n.p,{children:"Unknown or retired shapes should fail with a clear validation error rather than be\nsilently translated at runtime."}),"\n",(0,t.jsx)(n.h2,{id:"provider-and-model-boundary",children:"Provider and model boundary"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"providers.defaults"})," owns the default provider behavior and default model.\n",(0,t.jsx)(n.code,{children:"providers.models[].backend_refs[]"})," owns physical backend bindings.\n",(0,t.jsx)(n.code,{children:"providers.models[].api_format"})," owns only the upstream wire format and never\nselects a Provider. A physical model in a Router-owned listener configuration\nmust declare an explicit backend Provider; metadata-only external-gateway\nconfiguration and built-in virtual models may remain backendless.\nThe local CLI serve path owns Envoy transport and rejects backendless physical\nmodels; external-gateway metadata-only configurations are deployed through the\ngateway integration rather than converted into a standalone Envoy data plane.\n",(0,t.jsx)(n.code,{children:"providers.models[].pricing"})," owns optional deployment cost metadata used by\ncost-aware selection and accounting. Pricing does not belong to routing model cards."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"evaluation"})," owns optional operator benchmark definitions, index DAGs, and\nmeasurement records. Each ",(0,t.jsx)(n.code,{children:"evaluation.records[].model"})," references one canonical\nModel Card identity, so reusable scoring semantics and model evidence have one\ntop-level owner without being embedded in routing metadata."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"routing.modelCards"})," describes routing-facing model identity. Optional\n",(0,t.jsx)(n.code,{children:"routing.modelCards[].loras"})," declare LoRA adapters that decisions may select with\n",(0,t.jsx)(n.code,{children:"lora_name"}),". Signals and decisions reference logical model names, not endpoints or\ncredentials."]}),"\n",(0,t.jsx)(n.h2,{id:"routing-and-dsl-boundary",children:"Routing and DSL boundary"}),"\n",(0,t.jsx)(n.p,{children:"Routing owns:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"model cards;"}),"\n",(0,t.jsx)(n.li,{children:"named signals and projections;"}),"\n",(0,t.jsxs)(n.li,{children:["decisions, candidate ",(0,t.jsx)(n.code,{children:"modelRefs"}),", algorithms, and plugins;"]}),"\n",(0,t.jsx)(n.li,{children:"route-local output and adaptation policy."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Algorithms may declare ",(0,t.jsx)(n.code,{children:"minimum_candidates"})," as a portable Recipe contract.\nModel-free assets can carry the declaration with empty ",(0,t.jsx)(n.code,{children:"modelRefs"}),"; a concrete\nEntrypoint binding must satisfy it, and request-time eligibility filters must\npreserve it before selection or multi-model execution begins."]}),"\n",(0,t.jsxs)(n.p,{children:["A plugin's configuration surface stays generic even when the plugin only\ntargets one provider wire format. ",(0,t.jsx)(n.code,{children:"prompt_cache"}),", for example, is disabled by\ndefault and declares ",(0,t.jsx)(n.code,{children:"on_unsupported: skip|reject"})," rather than assuming every\nroute resolves to an Anthropic-shaped backend; a route with no eligible target\neither leaves the request unchanged or fails with a typed error, per that\nsetting."]}),"\n",(0,t.jsx)(n.p,{children:"Structured request controls remain facts at the signal boundary. For example,\nconversation signals expose whether the protocol requires or forbids tool\nexecution, projections reconcile those facts with text-derived observations,\nand decisions consume the resulting policy-facing output."}),"\n",(0,t.jsxs)(n.p,{children:["Top-level ",(0,t.jsx)(n.code,{children:"entrypoints"})," select the default routing profile or a named item from\ntop-level ",(0,t.jsx)(n.code,{children:"recipes"}),"; they are not nested inside ",(0,t.jsx)(n.code,{children:"routing"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"The DSL is an authoring view of routing semantics. It does not own provider\ncredentials, listeners, evaluation definitions or records, stores, or global\nruntime services. Import and export preserve the same canonical routing\ndocument rather than
1inventing another steady-state schema."}),"\n",(0,t.jsxs)(n.p,{children:["Classifier backend failures enter decision evaluation as ",(0,t.jsx)(n.code,{children:"Unknown"}),". ",(0,t.jsx)(n.code,{children:"NOT"})," preserves\nthat state, while ",(0,t.jsx)(n.code,{children:"AND"})," and ",(0,t.jsx)(n.code,{children:"OR"})," use CEL-style short-circuit semantics. A decision\nresolves a terminal ",(0,t.jsx)(n.code,{children:"Unknown"})," with root-level\n",(0,t.jsx)(n.code,{children:"rules.on_unknown: no_match|match|fail_request"}),"; omission preserves the existing\nper-family compatibility behavior."]}),"\n",(0,t.jsx)(n.h2,{id:"entrypoints-and-multi-recipe-routing",children:"Entrypoints and multi-recipe routing"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"entrypoints[]"})," map request model names to either top-level routing or one named\nrecipe. ",(0,t.jsx)(n.code,{children:"recipes[]"})," contain isolated routing profiles that reuse the same provider\ninventory and global runtime."]}),"\n",(0,t.jsx)(n.p,{children:"This keeps the public API stable while allowing several routing policies to coexist in\none process. An entrypoint resolves the recipe before signals and decisions run."}),"\n",(0,t.jsx)(n.h2,{id:"defaults-and-configuration-source",children:"Defaults and configuration source"}),"\n",(0,t.jsxs)(n.p,{children:["Built-in defaults live in the router. ",(0,t.jsx)(n.code,{children:"global.router.config_source"})," selects file-backed\nconfiguration or Kubernetes CRD reconciliation. External templates must not apply\nhidden defaults after validation."]}),"\n",(0,t.jsxs)(n.p,{children:["Built-in category/domain inference keeps its runtime policy in\n",(0,t.jsx)(n.code,{children:"global.model_catalog.modules.classifier.domain"}),". The local model uses the\ncanonical ",(0,t.jsx)(n.code,{children:"variant"})," field; a remote classifier uses the shared ",(0,t.jsx)(n.code,{children:"backend"})," block\n(",(0,t.jsx)(n.code,{children:"protocol"}),", ",(0,t.jsx)(n.code,{children:"contract"}),", ",(0,t.jsx)(n.code,{children:"model"}),", and ",(0,t.jsx)(n.code,{children:"deadline_ms"}),") and resolves ",(0,t.jsx)(n.code,{children:"model"})," by\nexact external-catalog name. The category consumer currently accepts\n",(0,t.jsx)(n.code,{children:"http_classify"})," plus ",(0,t.jsx)(n.code,{children:"label_distribution.v1"}),", preserving the full label-score\ndistribution. Prompt guard remains on its existing configuration surface until\nits separately scoped migration."]}),"\n",(0,t.jsxs)(n.p,{children:["Complexity is the second consumer and keeps its runtime policy in\n",(0,t.jsx)(n.code,{children:"global.model_catalog.modules.complexity"}),", so a backend survives the per-recipe\nreplacement of ",(0,t.jsx)(n.code,{children:"routing.signals"}),". It accepts ",(0,t.jsx)(n.code,{children:"http_classify"})," with either\n",(0,t.jsx)(n.code,{children:"score.v1"}),", a continuous score the signal converts into a verdict through\nper-rule boundaries, or ",(0,t.jsx)(n.code,{children:"label_distribution.v1"}),", where the winning label is the\nverdict. A consumer that reads more than one contract cannot default the field:\nomitting it would leave the runtime guessing which response shape to expect,\nand guessing wrong surfaces per request rather than at config load. Consumers\nreading exactly one contract keep it as the default, so category is unchanged.\nConnector byte ceilings belong to the connector configuration. External LLM\nclassifier entries and the MCP classifier module use ",(0,t.jsx)(n.code,{children:"max_response_bytes"}),".\nThe dashboard, Helm chart, and operator may help users author or transport config, but\nthe resulting document still uses the same contract."]}),"\n",(0,t.jsx)(n.h2,{id:"repository-sources",children:"Repository sources"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"config/config.yaml"})," is the exhaustive canonical reference config. Reusable examples\nlive under:"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"config/fragments/signal/"}),";"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"config/fragments/decision/"}),";"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"config/fragments/algorithm/"}),"; and"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.code,{children:"config/fragments/plugin/"}),"."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["Runtime deployment examples remain separate from routing fragments. Contract tests\nand ",(0,t.jsx)(n.code,{children:"make check"})," keep the reference config, schema, examples, and public docs\naligned."]}),"\n",(0,t.jsx)(n.h2,{id:"migration",children:"Migration"}),"\n",(0,t.jsxs)(n.p,{children:["Use ",(0,t.jsx)(n.code,{children:"vllm-sr config migrate --config old-config.yaml"})," to convert supported legacy\nlayouts. Review the result, resolve credentials through the deployment's secret\nmechanism, and validate it before serving."]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"vllm-sr init"})," was removed. Canonical YAML is the steady-state configuration source;\ninteractive or graphical authoring tools must export that same document."]}),"\n",(0,t.jsx)(n.h2,{id:"scope-and-non-goals",children:"Scope and n
1on-goals"}),"\n",(0,t.jsx)(n.p,{children:"The contract unifies configuration ownership. It does not require every authoring\nsurface to expose every advanced field in one form, nor does it make the DSL a\ndeployment-language replacement."}),"\n",(0,t.jsx)(n.h2,{id:"references",children:"References"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"../installation/configuration",children:"Current configuration guide"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"../installation/configuration-workflows",children:"Configuration workflows"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"../overview/signal-driven-decisions",children:"Signals, decisions, and model selection"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"../tutorials/global/entrypoints-and-recipes",children:"Virtual Models"})}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"https://github.com/vllm-project/semantic-router/issues/1505",children:"Related issue #1505"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>s,x:()=>d});var r=i(96540);const t={},o=r.createContext(t);function s(e){const n=r.useContext(o);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:s(e.components),r.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.