Skip to content

[Schema][Router] Decision + trace object (pure selection) #2376

Description

@ramkrishna2910

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):

{
  "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 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:
{
  "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)

  • 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions