Bifrost Complexity Router with Nadir

Use Nadir as the decision model in Bifrost's Complexity Router: tier routing through a custom provider and routing rules, with no plugin or code changes.

Prompt-aware model choice for Bifrost, through its Complexity Router. Bifrost asks Nadir which tier a request belongs to, and your own routing rules pick the model. There is no plugin to install and no application code to change.

Info

This uses the Complexity Router's decision-model classifier, which ships in Bifrost HTTP v2.3.2. It is configuration only: Bifrost calls Nadir as a custom provider on the OpenAI base format.

What it adds

Bifrost's Complexity Router publishes a tier, complexity_tier, that routing rules can match. It ships its own classifiers, and it can also ask a decision model. Nadir plugs in as that decision model.

  • Bifrost sends the recent user messages and its tier question to POST /v1/decisions, Nadir's endpoint in OpenAI's Decisions API shape.
  • Nadir's classifier answers SIMPLE, MEDIUM or COMPLEX, with a confidence. It generates no text.
  • A Bifrost routing rule maps each tier to a provider and model, so keys, load balancing, fallbacks and logs all stay in Bifrost.

Provider keys stay in Bifrost and the completion never passes through Nadir. Only the decision does.

Set it up

1. Give Bifrost a Nadir key

Create a key under API Keys in the Nadir dashboard and expose it to Bifrost as NADIR_API_KEY.

.env
NADIR_API_KEY=ndr_...

Use a key even to try it. Bifrost treats a keyless custom provider as serving every model, so ordinary chat traffic could be sent to it.

2. Add Nadir as a decision provider

config.json
{
  "providers": {
    "nadir": {
      "keys": [
        {
          "name": "nadir-key",
          "value": "env.NADIR_API_KEY",
          "models": ["nadir-bucket"],
          "weight": 1
        }
      ],
      "network_config": {
        "base_url": "https://api.getnadir.com"
      },
      "custom_provider_config": {
        "base_provider_type": "openai",
        "allowed_requests": {
          "list_models": false,
          "decisions": true
        }
      }
    }
  }
}

In the Web UI this is Models, Model Providers, Add New Provider, with Base Format OpenAI, the same base URL, Decisions on, List Models off, and a key whose Allowed Models is nadir-bucket.

  • Give the bare origin. Bifrost appends /v1/decisions itself.
  • Keep List Models off. Nadir's /v1/models lists the chat models it proxies. With listing on, Bifrost would treat this provider as serving them.
  • Keep the key's models to one name. The name itself is not checked, and Nadir echoes it back. nadir-bucket is a convention.

3. Make it the Complexity Router's classifier

config.json
{
  "governance": {
    "complexity_analyzer_config": {
      "classifier": "decision",
      "decision": {
        "provider": "nadir",
        "model": "nadir-bucket",
        "previous_message_count": 1
      },
      "keywords": {
        "simple_keywords": ["fix the grammar in this sentence."],
        "medium_keywords": ["apply a standard method to this technical question."],
        "complex_keywords": ["design a solution under several competing constraints."]
      }
    }
  }
}

Bifrost requires the three keywords lists in config.json even when a decision model classifies. Without them it logs keyword lists must be non-empty at startup, drops the whole block, and no request gets a tier.

4. Route each tier

The router only publishes the tier. A rule per tier decides where it goes. Classification runs only for requests that a rule referencing complexity_tier evaluates.

config.json, governance.routing_rules
[
  {
    "id": "tier-simple",
    "name": "Simple",
    "enabled": true,
    "cel_expression": "complexity_tier == \"SIMPLE\"",
    "targets": [{ "provider": "anthropic", "model": "claude-haiku-4-5", "weight": 1 }],
    "scope": "global",
    "priority": 0
  },
  {
    "id": "tier-medium",
    "name": "Medium",
    "enabled": true,
    "cel_expression": "complexity_tier == \"MEDIUM\"",
    "targets": [{ "provider": "anthropic", "model": "claude-sonnet-5", "weight": 1 }],
    "scope": "global",
    "priority": 1
  },
  {
    "id": "tier-complex",
    "name": "Complex",
    "enabled": true,
    "cel_expression": "complexity_tier == \"COMPLEX\"",
    "targets": [{ "provider": "anthropic", "model": "claude-opus-5", "weight": 1 }],
    "scope": "global",
    "priority": 2
  }
]

Then send requests as you already do. When a rule matches, Bifrost replaces the requested model with that rule's target.

Tier guidance does not apply

Bifrost lets you edit each tier's definition, signals and examples, and sends them with every decision request. They steer a prompted model. Nadir's classifier is trained, so it accepts the guidance and ignores it. To change which model serves which prompts, change the routing rules' targets.

What reaches Nadir

Each classification sends the latest user message, plus as many earlier user messages as previous_message_count allows, the tier question and the tier guidance. No system prompt, no assistant turns, no tools and no provider keys. Nadir classifies the user text of those messages together.

A keyed decision is logged to your Nadir account. By default the log row includes the prompt text, so the dashboard can show it back to you. Turn prompt storage off under Settings, or per key, and Nadir stores a sha256: digest in its place. Routing is unaffected either way.

Guarantees

  • Opt-in. Bifrost calls Nadir only when a routing rule references complexity_tier. Other requests are untouched.
  • Fail-open. A timeout, a non-2xx response or an unreachable host publishes no tier, and the request continues through Bifrost's normal routing. With session-aware routing on, a session that already has a tier keeps it.
  • Bounded. Each classification waits at most decision.timeout, 1.5 seconds by default. If requests log the tier as skipped, raise it.

Seeing the decision

Bifrost's request log records complexity_tier and complexity_mechanism, which reads decision when Nadir answered and skipped when no tier was published. The routing decision log names the model and confidence.

Decision model complexity: tier=SIMPLE confidence=0.87 model=nadir/nadir-bucket

What has been validated

The October 22, 2026 validation run used Bifrost built from its dev branch, with Nadir as the decision model against api.getnadir.com, three tier rules, and a local mock provider as every rule's target. Every request named the same model, and Nadir's tier moved it: a short arithmetic question and a greeting went to the simple target, a Go refactor across handlers and a distributed-systems design question to the complex target. The mock supplied the completions; this did not validate real model inference.

With the keyed configuration above and a deliberately invalid key, the decision call reached Nadir with the key and got HTTP 401, no tier was published, and the request was served on the model it named. In the same setup, an unprefixed chat request never reached the Nadir provider. With the base URL pointed at a dead port, requests still completed.

Not yet tested: a valid key end to end, a packaged Bifrost release, and the Web UI path.

What Nadir is

Nadir is an LLM router. Nadir sends each prompt to the cheapest model that can handle it. A small decision model reads each prompt against your model menu and picks the lowest-cost model it expects to handle it. When the decision model is uncertain or unavailable, the request goes to the strongest model on the menu. The routing step makes no call to your model provider.

Nadir runs two ways. The decision API returns a model, reasoning-effort, cache, context, and policy recommendation without calling a model provider, beside the gateway you already run. That is how a shadow-mode evaluation works, and its projected savings stay advisory. The OpenAI compatible managed proxy executes the route, and migration is a two-line change: point the base URL at api.getnadir.com and set model to auto. On that path an optional verifier can score a complete non-streaming answer and escalate to a stronger model when it misses the configured bar. Streaming bypasses post-generation verification. BYOK is supported on every tier.

For coding agents, Nadir Auto is a terminal launcher for Claude Code or Codex that asks Nadir for the main-session model before each turn, with your configured model as the fallback and cost ceiling. Delegation integrations for Claude Code, Codex, or Cursor instead recommend a model tier for subagent work, and the agent decides; the Claude plugin can also move a main-thread turn to Haiku when Nadir's cache check prices it cheaper, unless its switch_main_model option is off. Either way inference runs on your own provider account, so Nadir holds no provider keys on that path.

What the numbers are, and what they are not

Nadir publishes each evaluation with its scope. On checkable code, run-check-escalate solved 392 of 395 common HumanEval and MBPP problems (99.2%), graded by running the canonical tests; that applies only to tasks with runnable deterministic tests. Nadir-Tumbler posts an arena_score of 72.3 on RouterArena's public scorer, 5th of 23 routers, which measures the routing decision on RouterArena's own model pool. A reference-assisted RouterBench evaluation over 11,420 held-out triples produced a 60% lower projected cost than always-Opus with about 98% retained quality and a 1.7% catastrophic-route rate. That experiment gave the verifier the expensive-model reference answer, which production does not have, so it is a research ceiling and not the deployed path.

None of these is a production guarantee, a universal savings rate, or a forecast for any particular workload. Customer savings are reported from measured execution against a declared baseline, and customer quality only from outcome-labelled traffic. Projected savings and realized savings are separate artifacts and are never blended.

Design-partner program

Three rungs, picked by risk appetite. Rung 0 Shadow runs advisory decision calls alongside live traffic and returns a projected receipt, with nothing in the request path changed. Rung 1 Hosted is the two-line swap on a production slice and returns a realized receipt. Rung 2 On-prem is a supervised six-week proof of concept inside the partner's VPC, where no prompt, response, or usage reaches Nadir. The commitments are the same at every rung. Apply for a rung directly: Rung 0 Shadow, Rung 1 Hosted, or Rung 2 On-prem. Not sure which fits? Start at getnadir.com/contact/?reason=design-partner.

Licensing

NadirClaw is the self-hosted core, source-available under the PolyForm Noncommercial License. Source-available is the correct label; NadirClaw is not open source. Nadir Route's hosted plan has no base fee and charges a variable fee only on measured savings from requests Nadir executed.

Pages on this site

Machine-readable summaries of this site: llms.txt and llms-full.txt.