Goal
Freeze the Decision + trace the engine produces, and the request/response API surface. Pure model selection — no verdict/route-category/action in core (those are trust-customer concerns expressed via the outputs pass-through bag).
Approval gate — blocks dispatch/response work.
Request
Standard OpenAI chat body addressed to a collection.router collection name. Routing inputs ride the standard OpenAI metadata field (Q1, locked):
The engine exposes the whole metadata map (plus input text + model/has_tools/has_images/char-count) to conditions. Which keys exist is the policy author's business (trust puts task_class/consent there).
Decision (engine output) + how it surfaces
- Header
x-lemonade-route = matched_rule id — always present, survives streaming, lets proxies/infra branch. Configurable off for strict deployments.
- Standard
model field already carries the selected candidate (e.g. "Qwen3-8B-GGUF") — no header needed for that.
- Body
x_lemonade_route = small always; trace[] only when route_trace is set:
Resolved API decisions (locked)
- Q1 — transport: reuse the OpenAI
metadata body field (max drop-in; stock SDK, no extra_body). Lists comma-encoded. Escape hatch: a custom top-level field later only if nested structure is needed. Tradeoff accepted: mild semantic overload of metadata + string-only.
- Q2 — trace: server-side audit always captures the full trace (trust milestone); client-facing trace is minimal by default, full only on opt-in (
route_trace). Rationale: the per-condition trace is policy-internal/sensitive and must not leak to end users by default.
- Q3 — header/body split: header carries only the scalar
matched_rule; the x_lemonade_route body object carries route_to/matched_rule/default_used always and trace[] on opt-in. Full trace never goes in a header (size).
Contract notes
Deliverable
Committed JSON Schema (request metadata/route_trace + x_lemonade_route) + fixtures + dev-doc section. No runtime code.
Depends on: #2375
Goal
Freeze the Decision + trace the engine produces, and the request/response API surface. Pure model selection — no verdict/route-category/action in core (those are trust-customer concerns expressed via the
outputspass-through bag).Approval gate — blocks dispatch/response work.
Request
Standard OpenAI chat body addressed to a
collection.routercollection name. Routing inputs ride the standard OpenAImetadatafield (Q1, locked):{ "model": "user.Router-Shopping", "messages": [{ "role": "user", "content": "..." }], "metadata": { "task_class": "payment", "site_tags": "shopping" }, // routing inputs; list values comma-encoded "route_trace": true // opt-in full trace (Q2); default omitted }The engine exposes the whole
metadatamap (plus input text +model/has_tools/has_images/char-count) to conditions. Which keys exist is the policy author's business (trust putstask_class/consentthere).Decision (engine output) + how it surfaces
x-lemonade-route=matched_ruleid — always present, survives streaming, lets proxies/infra branch. Configurable off for strict deployments.modelfield already carries the selected candidate (e.g."Qwen3-8B-GGUF") — no header needed for that.x_lemonade_route= small always;trace[]only whenroute_traceis set:{ "route_to": "Qwen3-8B-GGUF", "matched_rule": "keep-private", "default_used": false, "outputs": { "verdict": "warn" }, // pass-through, engine-opaque "trace": [ // ONLY when route_trace=true { "condition": "classifier:pii", "score": 0.81, "result": true }, { "condition": "keywords_any", "result": false } ] }Resolved API decisions (locked)
metadatabody field (max drop-in; stock SDK, noextra_body). Lists comma-encoded. Escape hatch: a custom top-level field later only if nested structure is needed. Tradeoff accepted: mild semantic overload ofmetadata+ string-only.route_trace). Rationale: the per-condition trace is policy-internal/sensitive and must not leak to end users by default.matched_rule; thex_lemonade_routebody object carriesroute_to/matched_rule/default_usedalways andtrace[]on opt-in. Full trace never goes in a header (size).Contract notes
outputsis copied verbatim from the matched rule; the engine never interprets it.Deliverable
Committed JSON Schema (request
metadata/route_trace+x_lemonade_route) + fixtures + dev-doc section. No runtime code.Depends on: #2375