Skip to main content

Model & Output Policy

Two agent-level controls that bracket the call: model policy decides what may go upstream, output validation decides whether what came back is usable.

Model policy

Four fields on the agent, all optional:

Evaluation order

The order matters, because a rewrite changes what the lists see:
  1. No model in the body? If default_model is set it is written into the body and the request proceeds. If it isn’t, nothing is enforced — the request passes through and the upstream decides (most reject a missing model).
  2. model_rewrite — an exact key match on the requested name substitutes the target. The rewritten name is what the lists then match, so you cannot smuggle a denied model in by rewriting to it.
  3. model_denylist — a match refuses the request. Denylist beats allowlist; a model on both lists is denied.
  4. model_allowlist — when the list is non-empty, a model that matches nothing in it is refused. An empty allowlist means “no allowlist”, not “allow nothing”.

Patterns

Allowlist and denylist entries are globs: * matches any run of characters, including dashes, anywhere in the name — claude-* matches every Claude model, *-opus-* matches by family. ? is a literal question mark, not a wildcard. model_rewrite is not a pattern language: its keys are exact model names. Patterns are compiled with the platform’s linear-time regex engine, so a pathological pattern cannot stall a worker.
Both lists fail closed, in opposite directions. A pattern the engine refuses to compile counts as a match on the denylist (so an unreadable deny rule blocks rather than silently stopping denying) and as a non-match on the allowlist (so an unreadable allow rule grants nothing). Either way the request is refused, never quietly let through. Check your patterns on save rather than discovering this in production.

Responses

A refused model gets a 403 naming the reason and the patterns it could have matched:
error is model_denylisted or model_not_allowed. allowed is null when the refusal came from the denylist rather than the allowlist. Every data-plane refusal uses this { error, error_description } shape — it is not the {data, meta} envelope the /v1 management API returns, because the data plane speaks your provider SDK’s protocol, not KnoxCall’s. A rewrite or a default substitution is silent to the caller — the request succeeds — but is recorded on the usage row, so the model you are billed for is the model that actually ran.

Day-zero models

Model policy is opt-in. With no allowlist an agent will proxy any model its upstream accepts, which is what makes a new model usable the day it ships — see Providers & day-zero models.

Output validation

Set an agent’s output_schema to a JSON Schema and the gateway validates the model’s answer against it before the response leaves the building.

What is validated

The gateway extracts the assistant’s text content from the provider response, parses it as JSON and checks it. The supported schema subset is deliberately small — type, required, properties, items, enum — which covers “return this exact object shape” without a full JSON-Schema engine. Two cases pass without complaint by design:
  • a response with no extractable text content (nothing to validate);
  • content that is not JSON, unless the schema’s top-level type is object or array — then non-JSON is a violation.

Actions

output_validation_action decides what happens on a violation. Default is warn.
A warn violation is reported to the caller in a response header only. It does not write an audit row or a queryable event, so “how often does this agent break its schema” has no answer on the server side today — read the header in your client if you need to count them.
A retry costs a second upstream call, charged like any other. It is worth it for a flaky structured-output model and not worth it for a model that never gets the shape right — fix the prompt instead.
Output validation runs on the buffered path only. A streaming request (stream: true) is forwarded token by token and there is no complete document to validate until the client already has it, so output_schema is not applied. If the response shape is load-bearing for your application, turn streaming off for that agent (streaming_enabled: false) rather than assuming the schema is being enforced.
A retried response goes through exactly the same downstream stages as a first response — PII redaction and detokenization, the canary scan, the cache-store gate — so a retry cannot be a way around a control that applied to the first answer.