Skip to content

Decision Models

A decision model answers typed questions about a single document in one forward pass and returns a probability distribution for each question without generating free-form text.

Continuum Router supports decision models in two ways:

  • Client-facing API: Exposes decision inference directly at POST /v1/decisions (alias POST /v1/systemone).
  • Internal consumers: Uses decision models across five router subsystems:
    1. Content screening (Guardrail provider)
    2. Request classification (Smart-routing classifier)
    3. Agent tool-call evaluation (Action safety)
    4. Answer-quality fallback (fallback.quality_check in Model Fallback)
    5. Post-response quality telemetry (usage.quality_judgment)

A decision model's reported confidence measures how concentrated the output distribution is, not its empirical accuracy on your workload. Always calibrate thresholds against labeled traffic from your own deployment before enabling enforcement. Every shipped default in Continuum Router is derived from a reproducible calibration run committed to this repository, and any consumer whose measured accuracy does not beat a constant-answer baseline defaults to monitor-only or shadow mode.

Supported models

The router implements the System One protocol across four provider profiles (generic, typesafe, foundry, and llama_cpp; see Provider profiles and experimental media) and includes built-in catalog metadata for both hosted services and self-hosted open-weights checkpoints:

Model / family Catalog IDs & checkpoints Where it runs (protocol) Capabilities & constraints
TypeSafe Jev 1.13 jev-1.13.0 (jev-latest alias) TypeSafe hosted (https://api.typesafe.ai/v1/systemone, protocol: typesafe) decisions. $0.042 / 1M input tokens, free output; 64k request / 32k state+question context (262k with typesafe_long_context: true); opt-in experimental image assets (typesafe_images: true); no box.
TypeSafe Jev 1.14 jev-1.14.0 (jev-preview alias) TypeSafe hosted (https://api.typesafe.ai/v1/systemone, protocol: typesafe) decisions. Same pricing and context limits as Jev 1.13, plus experimental image assets and box localization questions when typesafe_images: true.
Microsoft-Decision-1 microsoft-decision-1 (addressed via your Foundry deployment name) Azure AI Foundry (<resource>/providers/microsoft/v1/systemone, protocol: foundry) decisions. Text/JSON input up to 32K input tokens; noul, choice, and score questions; authenticates with api-key (auth: api_key / auto) or an Entra Bearer token (auth: entra).
Laya laya (convaiinnovations/laya, ggml-org/Laya-GGUF) Self-hosted (laya-serve or llama-server, protocol: generic / llama_cpp) decisions. Three encoder checkpoints in one bundle: english (ModernBERT-large, 512 tokens), multilingual (mmBERT-base, 1,024 tokens), typed-decisions. Stock laya-serve routes each request to english or multilingual by script detection.
Kev kev-0.8b, kev-4b (jaredpalmer/kev-0.8b, jaredpalmer/kev-4b, ggml-org/Kev-4B-GGUF) Self-hosted (kev.serve or llama-server, protocol: generic / llama_cpp) decisions. LoRA adapter plus pointer head on a Qwen3.5 base with built-in temperature; runs on MLX on Apple silicon or GGUF on llama.cpp.
Julia-1 & lev julia-1 (SupersonicLabs/Julia-1, ggml-org/Julia-1-GGUF), lev (interfaze-ai/lev, ggml-org/lev-GGUF) Self-hosted (llama-server, protocol: llama_cpp / generic) decisions (text).
OpenJev openjev (openjev/openjev, ggml-org/OpenJev-GGUF) Self-hosted (llama-server, protocol: llama_cpp) decisions, image input. Enforces a 52-option choice limit (set model_family: openjev when served behind a custom deployment alias).
Clef & pplx-decider v1.2 clef (Cloudflare/clef, ggml-org/Clef-GGUF), pplx-decider-v1.2 (perplexity-ai/pplx-decider-v1.2) Self-hosted (llama-server, protocol: llama_cpp) decisions, image input. Clef evaluates questions jointly rather than independently.
Bespoke Nimble 9B v3 bespoke-nimble-9b-v3 (bespokelabs/Bespoke-Nimble-9B-v3, ggml-org/Bespoke-Nimble-9B-v3-GGUF) Self-hosted (llama-server, protocol: llama_cpp / generic) decisions (choice and noul).
vLLM SR Decision 2.0 decision-2.0-{eos-0.8b,kai-0.6b,lux-9b,nox-4b,sol-2b,vega-27b} (vllm-sr/Decision-2.0-*) Self-hosted (llama-server, protocol: llama_cpp / generic) decisions (text). Uses normalized-entropy confidence.
vLLM SR d3 d3, d3-{edge,lite,nano,mini,flash} (vllm-sr/d3*, ggml-org/vllm-d3*-GGUF) Self-hosted (llama-server, protocol: llama_cpp) decisions, image/video input. Accepts null state when model_family: vllm_d3 (or matched by model ID).
Liquid AI d1 d1-3b (LiquidAI/d1-3B, LiquidAI/d1-3B-GGUF), d1-omni-600m (LiquidAI/d1-omni-600M, LiquidAI/d1-omni-600M-GGUF) Self-hosted (llama-server, protocol: llama_cpp) decisions; d1-3b supports image input (model_family: lfm2_d1), d1-omni-600m supports image and audio input (model_family: lfm2_d1_omni). Both accept null state.
IBM Granite Switch 4.1 granite-switch-4.1-8b, granite-switch-4.1-30b (ibm-granite/granite-switch-4.1-*) Self-hosted (llama-server, protocol: llama_cpp) Dual chat and decisions capabilities, plus audio input. Eligible for both native decisions and explicitly configured text generation.

Wire protocol

POST /v1/decisions (or POST /v1/systemone) accepts a model, a state (string, JSON object, JSON array, or null on supported llama_cpp families), and a questions map. Each question uses one of four types:

  • noul: Binary probability (0.0 to 1.0) that a statement is true, with optional criteria: {"true": ..., "false": ...}.
  • choice: Categorical probability distribution over 1 to 255 named options (1 to 52 for openjev), returning the argmax choice, probabilities, and optional confidence.
  • score: Ordinal probability distribution over ordered levels (2 to 10 levels on generic, foundry, and llama_cpp; 1 to 10 levels on typesafe), returning the probability-weighted score index, optional legend, probabilities, and optional confidence.
  • box: Normalized bounding box (box: {top, bottom, left, right} in 0.0 to 1.0 plus optional confidence), supported on protocol: typesafe when typesafe_images: true and model is jev-1.14.0 (jev-preview).
{
  "model": "jev-latest",
  "state": {"request": "Write a Rust function that parses a CSV file", "message_count": 1},
  "questions": {
    "domain": {
      "type": "choice",
      "instructions": "What domain does `request` belong to?",
      "criteria": {"general": "...", "code": "...", "reasoning": "..."}
    },
    "complexity": {
      "type": "score",
      "instructions": "How hard is `request`?",
      "criteria": ["trivial", "simple", "moderate", "complex", "expert"]
    }
  }
}

The response echoes the question IDs, the resolved model, and usage (input_tokens and output_tokens).

Internal consumers

Continuum Router uses decision models across five internal consumers:

Consumer Configuration Questions asked How the answer is used Default mode
Guardrail provider guardrails.providers[].type: systemone One noul per router category (violence, hate_speech, sexual_content, self_harm, harassment, dangerous, jailbreak, pii, profanity) plus a 4-level severity score Blocks when p >= category_thresholds[c]; flags when p >= review_threshold (upgraded to block at severity >= 2) mode: monitor
Smart-routing classifier smart_routing.classifier.method: system_one / hybrid_system_one, or shadow: true with rule / llm / hybrid complexity (score over 5 levels) and domain (choice over 6 domains) Uses the decision result when min(confidence) >= min_confidence; otherwise falls back to the rule classifier (or LLM classifier under hybrid_system_one) method: rule (shadow opt-in)
Action safety safeguards.action_safety.evaluator: systemone action (choice: approve / confirm / block), irreversible (noul), and injected_instruction (noul) Deny when probabilities.block or injected_instruction >= deny_threshold; Allow when action == approve, confidence >= min_confidence, and probabilities.approve >= allow_threshold; otherwise Indeterminate mode: advisory
Answer-quality fallback fallback.quality_check answered, refusal, and truncated (noul) over the last user message and a completed non-streaming chat reply Advances to the next model in the fallback chain when 1 - answered, refusal, or truncated reaches threshold Disabled (mode: shadow when enabled)
Usage quality judgment usage.quality_judgment answered and refusal (noul), plus language_match (when the request contains letters) and grounded (when include_tool_results: true and the turn includes tool results) Attaches basis-point probabilities and judge_model to the control-plane usage record for Hub analytics; never alters the client response Disabled

See also: Guardrails, System One Classifier, Claude Code Integration, and Model Fallback.

Usage accounting for decision calls

Every decision call that reaches an upstream server (including retries) is recorded as a usage event linked to the parent request with a consumer-specific purpose:

  • guardrail for the System One guardrail provider
  • classifier for the smart-routing classifier
  • action_safety for the action-safety evaluator
  • quality_judgment for both usage.quality_judgment and fallback.quality_check
  • client for direct client requests to POST /v1/decisions (see "Router-initiated calls" in Architecture)

The target setting count_toward_budgets controls only the counted label on smart_routing_system_one_classifier_input_tokens_total.

Usage quality judgment (usage.quality_judgment)

usage.quality_judgment evaluates served chat replies asynchronously and attaches the resulting probabilities (in basis points, alongside the resolved judge_model) to the request's control-plane usage record. This allows Continuum Hub to compare response quality across models, API keys, and experiment arms without adding latency to the client path.

Asynchronous execution and late vs. skipped outcomes

  • Non-blocking background task: Evaluation runs on a background task after the reply is served and never blocks usage accounting or batch uploads.
  • late judgments: If a judgment finishes after its usage record's upload batch has already been sealed, the judgment is dropped and counted as late. Because slower judgments are more likely to miss the batch cutoff, a high late rate skews attached scores toward faster requests. To reduce late drops, increase control_plane.usage_push_interval_secs or use a low-latency self-hosted decision model.
  • Streaming sampling: Streamed replies are sampled immediately after the input guardrail and evaluated as soon as the stream-end usage record is emitted. Only the head and tail of the client-visible text (bounded by max_reply_bytes, excluding reasoning tokens) are retained in memory.
  • Unified stream label: Every outcome in usage_quality_judgments_total carries stream="true" or stream="false".

Route-by-route extraction and skip rules

Route Inspected user turn Inspected assistant reply Skipped or excluded cases
/v1/chat/completions (including responses_only bridge) Last user message (max_request_bytes head+tail cap). Tool results included only when include_tool_results: true. Buffered reply text, or head+tail of streamed reply text (max_reply_bytes cap). Reasoning text is excluded. Input-blocked requests (never sampled); replies blocked by output guardrail; streamed replies masked or truncated by output guardrail; mid-stream fallback hops; stream errors/timeouts; client disconnects before stream end; disaggregated_serving or Dynamo shim; streamed native Anthropic/Gemini/Bedrock or Unix-socket paths; streamed response-cache replays. (Buffered replies masked by the output guardrail are judged on the masked text the client received.)
/anthropic/v1/messages (all backend paths: native HTTP/Unix, Bedrock mantle/runtime/converse, OpenAI/Gemini conversion, responses_only) Last role: user message that contains no tool_result and has non-blank text after stripping <system-reminder> blocks. Top-level system and role: system messages are never read. tool_result content is sent only when include_tool_results: true. Concatenated text blocks delivered to the client (thinking blocks are never included). stop_reason: max_tokens is judged normally. Replies containing tool_use or server_tool_use blocks; stop_reason: pause_turn; streamed error events or early client disconnects; web_search_20250305 server-tool emulation; response-cache hits.
/v1/responses (OpenAI/Azure passthrough, responses_only, and Chat/Anthropic/Gemini conversions) Last user message (or bare text item) in the request's own input with non-blank text after stripping harness blocks (<system-reminder>, <user_instructions>, <environment_context>, # AGENTS.md instructions). previous_response_id continuations use only the current request's input. instructions, system, developer, and reasoning items are never read; function_call_output is sent only when include_tool_results: true. Concatenated output_text parts of message items (or refusal parts when no text is present). Streamed replies are read from the terminal response.completed or response.incomplete event. status: incomplete (e.g., max_output_tokens) is judged normally. input containing only function_call_output items; item_reference appearing after the last user turn; replies containing function_call, custom_tool_call, computer_call, or local_shell_call items; status: failed or cancelled; response-cache hits; /v1/responses/compact.
Other routes Not applicable Not applicable /v1/completions is never judged.

See the usage section in the Complete Configuration Example for configuration syntax.

Call sharing with fallback.quality_check

For non-streaming /v1/chat/completions requests, fallback.quality_check and usage.quality_judgment can share a single upstream decision call when all of the following hold:

  1. Both consumers are enabled and reference the same target configuration.
  2. Both configure identical max_request_bytes and max_reply_bytes limits.
  3. The request does not send tool results to usage.quality_judgment (include_tool_results is false or the turn has no tool results).

When a call is shared:

  • The quality check includes language_match (when applicable) alongside answered, refusal, and truncated in the same forward pass. A missing or malformed language_match answer never fails the fallback check.
  • If the initial reply passes the quality check, usage.quality_judgment reuses the returned probabilities immediately without consuming a max_in_flight slot or risking a late drop.
  • If the shared call omits an answer for a required usage question, usage.quality_judgment issues a follow-up call for only the missing question(s) and merges the results (dropping the judgment as invalid if the follow-up resolves a different judge_model).
  • If fallback.quality_check triggers a fallback hop to another model, usage.quality_judgment records skipped and makes no extra call.
  • If fallback.quality_check did not run (for example, no fallback hop was available or its budget was exhausted), usage.quality_judgment performs its own normal background call.
  • Shared calls increment systemone_requests_total{consumer="fallback_quality"}, while usage_quality_judgments_total{outcome} still records one outcome per sampled request.

Serving decisions to clients

Clients can call POST /v1/decisions (alias POST /v1/systemone) directly under the router's standard API key authentication, scopes, rate limits, budgets, and fallback chains:

  • Native routing: Models listed under decisions.targets[] are forwarded directly to the configured System One server.
  • Chat-model emulation: Any model not listed in decisions.targets[] is emulated on a chat backend using a forced emit_decision tool call. Emulated responses return one-hot probabilities, omit confidence, and set emulated: true so callers can distinguish emulated answers from native probability distributions.

The public endpoint acts purely as a proxy or emulator and does not apply router thresholds. See API Reference for request and response schemas.

Egress and operational caveats

  • Data egress: Every decision call sends a state object constructed from the request:
    • Guardrail: Last user turn and a bounded conversation history (history_turns, max_turn_bytes).
    • Smart-routing classifier: Last user message and a 200-character system prompt excerpt. On /v1/chat/completions, the classifier state (including shadow mode) is built after the input guardrail stage, so blocked requests are never sent and PII-masked prompts are sent in their masked form.
    • Action safety: Proposed tool name, full arguments, and the user's latest instruction.
    • Answer-quality fallback & Usage quality judgment: Head-and-tail-capped user message and assistant reply (plus current-turn tool results for usage.quality_judgment only when include_tool_results: true).
    • Deployments that prohibit sending prompt or reply excerpts to third parties should point all consumers at self-hosted endpoints rather than hosted services such as TypeSafe Jev.
  • Adversarial robustness: Decision models are fast screening and telemetry signals, not standalone adversarial defenses. On the reference guardrail corpus, uncalibrated Laya checkpoints score at or below the constant-answer baseline on most categories at a 0.5 cutoff and frequently misclassify non-Latin text with high confidence. Use decision models in monitor mode or alongside dedicated safety models (such as Llama Guard or Qwen3Guard) on enforce paths.
  • Script sensitivity: The Laya english checkpoint does not support non-Latin scripts. Stock laya-serve routes non-Latin inputs to the multilingual checkpoint, which has lower accuracy on English and Korean routing tasks. Always evaluate English, Korean (ko), and non_latin slices separately before enabling enforcement.

When to use a decision model

Question shape Recommended? Rationale
Closed criterion with direct evidence in state (e.g., "Does this tool call write outside the working directory?", "Is this request about code?") Yes Matches the model's training objective and can be calibrated against ground-truth labels.
Fixed-category screening for observability and review queues Yes (monitor mode) Output probabilities allow you to define separate flag and block bands after calibrating on your own traffic.
Open-ended rubric (e.g., "Is this response helpful or well-written?") No Lacks a closed criterion; use an LLM judge or human evaluation.
Cross-step causal attribution (e.g., "Did an earlier tool output cause this action?") No The decision state captures a single step; action safety evaluates only the proposed tool call against the user's request.
Predicting downstream model failure (e.g., "Will model X fail on this prompt?") No Decision models have no signal for another model's capability boundaries; the classifier estimates prompt complexity and domain instead.

Provider profiles and experimental media

Each System One target block selects an explicit wire profile via protocol (generic, typesafe, foundry, or llama_cpp), independent of the model alias:

  • generic (default): Text-only System One contract.
  • typesafe: Enables TypeSafe's structured criteria schemas and opt-in feature headers (typesafe_images, typesafe_long_context).
  • foundry: Uses Azure AI Foundry's provider path (/providers/microsoft/v1/systemone) and deployment naming.
  • llama_cpp: Enables llama-server multimodal data URLs and checkpoint-family constraints (model_family).
decisions:
  enabled: true
  targets:
    - name: jev
      protocol: typesafe
      endpoint: https://api.typesafe.ai/v1/systemone
      api_key_env: TYPESAFE_API_KEY
      model: jev-1.14.0
      typesafe_images: false         # opt in for JSON assets and Box
      typesafe_long_context: false   # independent opt-in
    - name: microsoft-decision
      protocol: foundry
      endpoint: https://RESOURCE.services.ai.azure.com
      auth: api_key                 # auto also selects api-key for Foundry
      api_key_env: AZURE_FOUNDRY_API_KEY
      model: support-decisions      # deployment name, not a catalog alias
    - name: local-decision
      protocol: llama_cpp
      endpoint: http://127.0.0.1:8080/v1/systemone
      model: local-decision
      model_family: generic         # openjev enforces its 52-choice bound

Target configuration reference (SystemOneTargetConfig)

The same target schema is shared across decisions.targets[], guardrails.providers[].systemone, smart_routing.classifier.system_one, safeguards.action_safety.systemone, fallback.quality_check.target, and usage.quality_judgment.target:

Field Type Default Description
endpoint string unset Full HTTP(S) URL of the decision endpoint (mutually exclusive with backend). Must not contain embedded credentials, query parameters, or fragments. For generic, typesafe, and llama_cpp, the path must end in /v1/systemone unless allow_custom_endpoint_path: true. For foundry, the resource root, /providers/microsoft, /providers/microsoft/v1, and /providers/microsoft/v1/systemone all resolve to /providers/microsoft/v1/systemone.
backend string unset Name of a self-hosted backends[] entry (vllm, sglang, tensorrt-llm, ollama, lmstudio, llamacpp, mlxcel, generic, continuumrouter) configured with auth.type: api_key or basic and an http(s) URL. Mutually exclusive with endpoint and api_key_env; not supported with protocol: foundry.
protocol string generic Wire profile: generic, typesafe, foundry, or llama_cpp.
auth string auto Authentication scheme for inline endpoint targets: auto (api_key on foundry, bearer on others), bearer, api_key (api-key: <secret> header, foundry only), or entra (Authorization: Bearer <token> from api_key_env, foundry only). Must remain auto when backend is used.
api_key_env string unset Environment variable holding the secret or token for an inline endpoint (never the raw secret). Rejected when backend is set.
model string jev-latest on typesafe / *.typesafe.ai, unset otherwise Model or deployment ID sent in the request body. Required when protocol: foundry (must match the Foundry deployment name).
model_family string generic Explicit checkpoint family for protocol: llama_cpp when a custom deployment alias hides the model name: generic, openjev (52-option choice cap), lfm2_d1, lfm2_d1_omni, or vllm_d3 (the last three permit state: null). Rejected on non-llama_cpp protocols.
typesafe_images boolean false Requires protocol: typesafe. Enables top-level JSON assets and sends TypeSafe-Danger-Zone-Images: true. box questions also require model to be jev-1.14.0 or jev-preview.
typesafe_long_context boolean false Requires protocol: typesafe. Sends TypeSafe-Danger-Zone-Long-Context: true, raising TypeSafe's context limit from 64k/32k to 262k.
max_questions integer 64 Maximum questions per native decision call (1 to 256).
max_state_bytes integer 16384 Maximum serialized state size for internal consumers (256 to 1048576 bytes).
max_media_bytes integer 4194304 Maximum decoded inline media bytes per request (1 to 16777216 bytes, 4 MiB default; also bounded by the 2 MiB public JSON body limit on /v1/decisions).
timeout_ms integer 1500 Per-call wall-clock timeout in milliseconds, including the single bounded retry (100 to 60000).
max_concurrent integer 32 Maximum concurrent in-flight decision calls to this target (1 to 1024).
count_toward_budgets boolean false Controls the counted label on smart_routing_system_one_classifier_input_tokens_total.
allow_custom_endpoint_path boolean false Allows an inline endpoint whose path does not end in /v1/systemone on non-Foundry profiles.

Protocol profile comparison

Profile Endpoint & transport rules Auth headers Question & schema rules Media & special state
generic http(s) URL ending in /v1/systemone (or backend: reference) Authorization: Bearer <key> (or backend's configured auth) String instructions required; choice 1-255; score 2-10 levels Text/JSON state only (null state and media fields rejected)
typesafe https:// required for typesafe.ai and subdomains; custom gateways keep their scheme Authorization: Bearer <key>, plus opt-in TypeSafe-Danger-Zone-Images: true / TypeSafe-Danger-Zone-Long-Context: true Preserves structured (string/object/array), null, and omitted instructions, noul criteria, choice descriptions, and score levels; score 1-10 levels; box on jev-1.14.0 / jev-preview Top-level assets map of base64 images when typesafe_images: true
foundry Inline endpoint only (https:// required except loopback localhost, 127/8, ::1); resolves /providers/microsoft/v1/systemone api-key: <key> (auth: api_key or auto) or Authorization: Bearer <token> (auth: entra) Preserves structured/null/omitted question fields; choice 1-255; score 2-10 levels; requires explicit model deployment name Text/JSON state only
llama_cpp http(s) URL ending in /v1/systemone (or backend: reference) Authorization: Bearer <key> (or backend's configured auth) Preserves structured/null/omitted question fields; choice 1-255 (1-52 for openjev); score 2-10 levels Top-level files / images data-URL arrays or multimodal message state (image/audio data URLs); state: null allowed for lfm2_d1, lfm2_d1_omni, vllm_d3

Profile details and wire examples

Foundry authentication and transport

  • Endpoint resolution: Resource roots, /providers/microsoft, /providers/microsoft/v1, and /providers/microsoft/v1/systemone all normalize to /providers/microsoft/v1/systemone. Foundry requires an inline endpoint (backend: references are rejected). Remote endpoints must use HTTPS; HTTP is permitted only for loopback addresses (localhost, 127.0.0.0/8, ::1).
  • Token lifecycle: auth: entra reads an externally provisioned Entra token from api_key_env at client build time and sends it as a Bearer token. It does not fetch or refresh tokens automatically; HTTP 401/403 responses are treated as terminal errors without retry.

Payload limits and TypeSafe box / assets

  • Structural bounds: Individual string fields are capped at 8 KiB, JSON nesting at 64 levels, and score questions at 10 levels. Public ingress accepts up to 2 MiB of uncompressed JSON (multipart and compressed request bodies are rejected with HTTP 415).
  • TypeSafe images and box: Setting typesafe_images: true enables top-level assets and sends TypeSafe-Danger-Zone-Images: true. box questions additionally require jev-1.14.0 or jev-preview. Client-supplied headers are never forwarded to enable disabled features.
{
  "model": "jev",
  "state": {"photo": "asset-photo-1"},
  "assets": {
    "asset-photo-1": {
      "type": "image",
      "format": "png",
      "data_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
    }
  },
  "questions": {
    "location": {
      "type": "box",
      "instructions": "Locate the red rectangle in photo."
    }
  }
}

A box answer returns normalized coordinates (0.0 to 1.0) and optional confidence:

{
  "model": "jev-1.14.0",
  "answers": {
    "location": {
      "type": "box",
      "box": {"top": 0.12, "bottom": 0.64, "left": 0.2, "right": 0.85},
      "confidence": 0.91
    }
  },
  "usage": {"input_tokens": 310, "output_tokens": 12}
}

llama.cpp multimodal inputs

protocol: llama_cpp accepts inline files or images data URLs (or multimodal message state with image/audio data URLs) up to max_media_bytes (default 4 MiB, max 16 MiB). External http(s) media URLs, local file paths, and native video transports are rejected.

{
  "model": "local-decision",
  "state": "Does this receipt show a total above $50?",
  "images": [
    "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
  ],
  "questions": {
    "over_fifty": {"type": "noul", "instructions": "Is the receipt total above $50?"}
  }
}

Model discovery, fallback, and accounting

  • Model discovery (/v1/models): The model fetcher normalizes both TypeSafe's GET /v1/models format and llama.cpp's architecture.output_modalities array. Models advertising "decisions" receive the decisions capability; models advertising both "text" and "decisions" (such as Granite Switch 4.1) receive both chat and decisions.
  • Fallback and emulation gates: Every fallback hop validates profile compatibility. Requests using media, state: null, or box questions cannot fall back to incompatible profiles or text-only chat emulation. Decision-only catalog models require a configured native target and are never emulated via chat completions.
  • Token accounting: Both input_tokens and output_tokens are recorded in usage statistics even when a provider prices output tokens at $0. Missing confidence fields remain absent rather than synthesized.

Smart-routing classifier rollout

Smart routing uses the same target profiles under smart_routing.classifier.system_one. To evaluate a decision model safely before switching routing authority, keep method: rule (or llm / hybrid) and enable shadow: true:

# TypeSafe Jev (pin a specific calibrated version)
smart_routing:
  classifier:
    method: rule
    system_one:
      shadow: true
      protocol: typesafe
      endpoint: https://api.typesafe.ai/v1/systemone
      api_key_env: TYPESAFE_API_KEY
      model: jev-1.14.0
# Azure AI Foundry (model is your Foundry deployment name)
smart_routing:
  classifier:
    method: rule
    system_one:
      shadow: true
      protocol: foundry
      endpoint: https://RESOURCE.services.ai.azure.com
      auth: api_key
      api_key_env: AZURE_FOUNDRY_API_KEY
      model: support-decisions
# Self-hosted llama.cpp
smart_routing:
  classifier:
    method: rule
    system_one:
      shadow: true
      protocol: llama_cpp
      endpoint: http://127.0.0.1:8080/v1/systemone
      model: kev-local

Key rollout behaviors:

  • Shadow evaluation: Shadow mode leaves routing decisions unchanged while recording agreement (smart_routing_shadow_agreement_total{field, agree}), latency, and token usage (purpose: classifier).
  • Deterministic fallback: Under method: system_one, any call that fails, times out, omits confidence, or returns min(confidence) < min_confidence falls back to the rule classifier. Under method: hybrid_system_one, low-confidence answers fall back to the configured LLM classifier first, and then to the rule classifier.
  • Candidate pool filtering: Decision-only models (decisions without chat/text) are automatically excluded from generation candidate pools and reported under excluded_incompatible_models on GET /admin/smart-routing/status.
  • Cache invalidation: Classifier cache keys incorporate the bounded prompt state, target profile, model name, question battery, and backend topology generation (cache_target_identity), invalidating cached entries automatically on config or credential changes.

Calibrating a decision model on your own traffic

Because confidence is an uncalibrated concentration statistic, you should measure accuracy on your own labeled dataset before switching any consumer from monitor/shadow/advisory to enforcement.

1. Start a decision endpoint

By default, make calibrate-systemone launches the self-hosted CPU laya-serve container defined in deploy/docker-compose.systemone.yml on 127.0.0.1:8000 with all three Laya checkpoints preloaded.

To benchmark a single pinned Laya checkpoint (bypassing laya-serve's automatic script router) or a local Kev server, start the server manually:

# Pinned single Laya checkpoint
uv run --no-project --with 'laya-serve[inference]==0.1.1' \
  python scripts/systemone/laya_pinned_serve.py --checkpoint english --device cpu --port 8011

# Kev (uses MLX automatically on Apple silicon)
git clone https://github.com/jaredpalmer/kev && cd kev
uv sync --extra serve && uv run python -m kev.serve --run jaredpalmer/kev-0.8b --port 8008

2. Run the calibration harness

# Calibrate all three consumers against the default compose service (CPU)
make calibrate-systemone

# Run only the routing corpus (200 rows)
SYSTEMONE_CONSUMERS=routing make calibrate-systemone

# Run against a custom endpoint
SYSTEMONE_ENDPOINT=http://127.0.0.1:8008/v1/systemone SYSTEMONE_RUN_LABEL=kev-0.8b \
SYSTEMONE_MODEL=kev-latest SYSTEMONE_CHECKPOINT=jaredpalmer/kev-0.8b \
SYSTEMONE_CHECKPOINT_REVISION=54f4f8777356cd5bbbb6c6919c657f26e6f2f6d8 \
  make calibrate-systemone

make calibrate-systemone runs scripts/systemone/calibrate-compose.sh, executing the consumer calibration tests in tests/systemone_calibration_test.rs and writing raw results and derived thresholds to tests/fixtures/systemone/calibration/<date>/<label>/ (or SYSTEMONE_REPORT_DIR). Each row is replayed through the consumer's exact state builder and question battery. You can tune concurrency, timeout, and row count with SYSTEMONE_CONCURRENCY (default 1), SYSTEMONE_TIMEOUT_SECS (default 120), and SYSTEMONE_LIMIT.

To run a minimal live connectivity check without the full corpus, use the ignored smoke test:

SYSTEMONE_SMOKE_ENDPOINT=http://127.0.0.1:8000/v1/systemone \
SYSTEMONE_SMOKE_MODEL=laya-english \
SYSTEMONE_SMOKE_PROFILE=generic \
  cargo test --test systemone live_smoke_test::configured_native_endpoint_answers_a_text_decision -- --ignored --exact

3. Threshold derivation rules

tests/systemone_support/derive.rs sweeps a fixed threshold grid for each consumer and selects conservative thresholds where minimum evidence requirements are met (retaining vendor defaults otherwise):

  • Guardrail category_thresholds.<c> (block): Smallest threshold in 0.50..=0.95 (step 0.05) achieving at least 0.90 block precision with at least 3 predicted blocks and 5 labeled positives.
  • Guardrail review_threshold (flag): Largest threshold in 0.05..=0.50 maintaining at least 0.90 row-level recall across unsafe rows.
  • Classifier min_confidence: Threshold in 0.00..=1.00 maximizing combined domain and complexity accuracy for the system_one -> rule fallback chain (ties broken toward the higher threshold). If no threshold beats the standalone rule classifier, no value is derived.
  • Action safety: Derives thresholds only if action accuracy beats the constant-answer baseline. deny_threshold is the smallest value in 0.30..=0.95 with at least 0.90 deny precision on at least 3 denies; allow_threshold and min_confidence maximize allows with zero false-allow block labels, at least 0.95 allow precision, and at least 5 allows.

Shipped defaults

The shipped defaults are derived from the all slice of the reference run 2026-09-24/laya-serve-compose-cpu (stock laya-serve 0.1.1 on CPU with convaiinnovations/laya@55cf4c4ebb4e). tests/systemone_calibration_test.rs (shipped_defaults_match_the_reference_run_and_the_docs) verifies that the library constants and this table match the committed derivation.

Setting Default Source
guardrail.review_threshold 0.40 derived from laya-serve-compose-cpu: row recall 0.913 at 0.40 (was 0.35)
guardrail.category_thresholds.violence 0.75 derived from laya-serve-compose-cpu: block precision reaches 0.90 at 0.75 (was 0.70)
guardrail.category_thresholds.hate_speech 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.sexual_content 0.70 vendor value: 4 labelled positives, fewer than the 5 required
guardrail.category_thresholds.self_harm 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.harassment 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.dangerous 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.jailbreak 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.pii 0.70 vendor value: block precision never reaches 0.90 on the run
guardrail.category_thresholds.profanity 0.70 vendor value: block precision never reaches 0.90 on the run
classifier.min_confidence 0.15 derived from laya-serve-compose-cpu: chain accuracy 0.510 at 0.15, 0.460 at 0.5, rule alone 0.475 (was 0.5)
action_safety.allow_threshold 0.85 vendor value: action accuracy 0.319 is below its 0.440 baseline, nothing derived
action_safety.deny_threshold 0.60 vendor value: action accuracy 0.319 is below its 0.440 baseline, nothing derived
action_safety.min_confidence 0.6 vendor value: action accuracy 0.319 is below its 0.440 baseline, nothing derived

These values map to guardrails.providers[].options.review_threshold, guardrails.providers[].category_thresholds, smart_routing.classifier.system_one.min_confidence, and safeguards.action_safety.systemone.{allow_threshold, deny_threshold, min_confidence}. Explicit values in config.yaml always take precedence.

Monitor-only findings

On the reference laya-serve-compose-cpu run, several consumers or language slices do not beat a constant-answer baseline and therefore default to non-enforcing modes:

  • Guardrail (mode: monitor on all slices): At derived defaults, 70.8% of clean rows are flagged or blocked (all slice, block precision 0.652), and over 92% of clean ko and non_latin rows are flagged. Keep mode: monitor and use category probabilities as observability signals rather than standalone block gates.
  • Smart-routing classifier (shadow: true recommended for Korean/non-Latin traffic): On the English (en) and combined (all) slices, the system_one + rule chain at min_confidence: 0.15 outperforms the standalone rule classifier (0.628 vs. 0.544 on en; 0.510 vs. 0.475 on all). On ko (0.405 vs. 0.487) and non_latin (0.418 vs. 0.428), Laya does not beat the rule classifier; Korean-heavy deployments should retain method: rule with shadow: true unless using a model such as kev-0.8b that outperforms the rule baseline on Korean (0.639 chain accuracy).
  • Action safety (mode: advisory): On tool_calls.jsonl, action accuracy (0.319) is below the constant baseline (0.440). Keep mode: advisory.
  • Unmeasured targets and consumers: Hosted TypeSafe Jev, Microsoft-Decision-1, kev-4b, and fallback.quality_check (default threshold 0.8 in mode: shadow) were not calibrated in the committed offline run. Always calibrate against your target endpoint and pin explicit model versions (such as jev-1.14.0 rather than jev-latest) before enforcing.

Results (2026-09-24)

The summary tables below are generated from the committed calibration fixtures under tests/fixtures/systemone/calibration/2026-09-24/ across three corpora:

  • guardrail.jsonl: 244 rows (223 replayed live; 111 English, 66 Korean, 106 non-Latin)
  • routing.jsonl: 200 rows (90 English, 79 Korean, 104 non-Latin)
  • tool_calls.jsonl: 207 rows (English)

To regenerate the full per-question Brier, ECE, AUROC, and slice breakdown tables from the committed JSON files, run python3 scripts/systemone/results_tables.py 2026-09-24.

Evaluated endpoints

Run Checkpoint Revision Server Device Resolved model
kev-0.8b jaredpalmer/kev-0.8b (LoRA on Qwen/Qwen3.5-0.8B-Base, T=2.41) 54f4f8777356 kev.serve (62c9183) Apple M4 Pro (mps, bf16) kev-latest
laya-auto convaiinnovations/laya (auto script routing) 55cf4c4ebb4e laya-serve==0.1.1 (laya 0.3.20) Apple M4 Pro (mps) laya-auto@55cf4c4ebb4e
laya-english convaiinnovations/laya (english) 55cf4c4ebb4e laya-serve==0.1.1 (laya_pinned_serve.py) Apple M4 Pro (mps) laya-english@55cf4c4ebb4e
laya-multilingual convaiinnovations/laya (multilingual) 55cf4c4ebb4e laya-serve==0.1.1 (laya_pinned_serve.py) Apple M4 Pro (mps) laya-multilingual@55cf4c4ebb4e
laya-serve-compose-cpu convaiinnovations/laya (auto script routing) 55cf4c4ebb4e deploy/docker-compose.systemone.yml CPU (concurrency 1) laya-english
laya-typed-decisions convaiinnovations/laya (typed-decisions) 55cf4c4ebb4e laya-serve==0.1.1 (laya_pinned_serve.py) Apple M4 Pro (mps) laya-typed-decisions@55cf4c4ebb4e

End-to-end latency and token usage by consumer

Run Guardrail p50 / p95 (ms) Guardrail input tokens Routing p50 / p95 (ms) Routing input tokens Action safety p50 / p95 (ms) Action safety input tokens
kev-0.8b 235.2 / 319.1 679 84.3 / 160.2 351 83.2 / 145.6 308
laya-auto 189.2 / 263.3 1081 47.0 / 61.3 341 63.3 / 77.7 280
laya-english 238.6 / 332.8 1222 61.7 / 72.1 409 62.6 / 75.8 280
laya-multilingual 92.9 / 115.1 1079 33.4 / 45.8 341 31.2 / 39.2 298
laya-serve-compose-cpu 786.2 / 1734.9 1081 291.3 / 579.6 341 256.8 / 469.9 280
laya-typed-decisions 242.5 / 322.7 1222 62.3 / 74.1 409 62.2 / 73.8 280

Routing shadow comparison

Comparison of standalone System One accuracy, standalone rule classifier accuracy, classifier agreement (smart_routing_shadow_agreement_total), and combined system_one -> rule chain accuracy across language slices:

Run Slice Rows System One (domain / complexity) Rule (domain / complexity) Agreement (domain / complexity) Chain at 0.5 Derived floor Chain at derived
kev-0.8b all 200 0.700 / 0.525 0.440 / 0.510 0.370 / 0.420 0.600 0.25 0.618
kev-0.8b en 90 0.722 / 0.511 0.556 / 0.533 0.511 / 0.378 0.633 0.65 0.656
kev-0.8b ko 79 0.671 / 0.557 0.430 / 0.544 0.304 / 0.506 0.614 0.25 0.639
kev-0.8b non_latin 104 0.673 / 0.538 0.356 / 0.500 0.250 / 0.462 0.587 0.25 0.625
laya-auto all 200 0.610 / 0.390 0.440 / 0.510 0.410 / 0.475 0.460 0.15 0.510
laya-auto en 90 0.722 / 0.489 0.556 / 0.533 0.511 / 0.533 0.539 0.15 0.628
laya-auto ko 79 0.519 / 0.291 0.430 / 0.544 0.392 / 0.443 0.462 none (rule wins) n/a
laya-auto non_latin 104 0.538 / 0.298 0.356 / 0.500 0.346 / 0.423 0.409 none (rule wins) n/a
laya-english all 200 0.595 / 0.460 0.440 / 0.510 0.390 / 0.470 0.478 0.15 0.535
laya-english en 90 0.722 / 0.489 0.556 / 0.533 0.511 / 0.533 0.539 0.15 0.628
laya-english ko 79 0.456 / 0.443 0.430 / 0.544 0.342 / 0.468 0.500 0.50 0.500
laya-english non_latin 104 0.500 / 0.433 0.356 / 0.500 0.288 / 0.433 0.438 0.05 0.476
laya-multilingual all 200 0.550 / 0.345 0.440 / 0.510 0.350 / 0.460 0.450 none (rule wins) n/a
laya-multilingual en 90 0.589 / 0.400 0.556 / 0.533 0.378 / 0.489 0.517 none (rule wins) n/a
laya-multilingual ko 79 0.519 / 0.291 0.430 / 0.544 0.392 / 0.443 0.462 none (rule wins) n/a
laya-multilingual non_latin 104 0.538 / 0.298 0.356 / 0.500 0.346 / 0.423 0.409 none (rule wins) n/a
laya-serve-compose-cpu all 200 0.610 / 0.390 0.440 / 0.510 0.410 / 0.475 0.460 0.15 0.510
laya-serve-compose-cpu en 90 0.722 / 0.489 0.556 / 0.533 0.511 / 0.533 0.539 0.15 0.628
laya-serve-compose-cpu ko 79 0.519 / 0.291 0.430 / 0.544 0.392 / 0.443 0.462 none (rule wins) n/a
laya-serve-compose-cpu non_latin 104 0.538 / 0.298 0.356 / 0.500 0.346 / 0.423 0.409 none (rule wins) n/a
laya-typed-decisions all 200 0.580 / 0.375 0.440 / 0.510 0.355 / 0.360 0.475 0.10 0.490
laya-typed-decisions en 90 0.756 / 0.422 0.556 / 0.533 0.533 / 0.433 0.544 0.10 0.600
laya-typed-decisions ko 79 0.430 / 0.342 0.430 / 0.544 0.241 / 0.392 0.487 none (rule wins) n/a
laya-typed-decisions non_latin 104 0.442 / 0.337 0.356 / 0.500 0.202 / 0.317 0.428 none (rule wins) n/a