Showing posts with label kimi. Show all posts
Showing posts with label kimi. Show all posts

Sunday, October 4, 2026

AI CLI Tools, Provider Diversity, and the Consensus No Vendor Will Build You

AI CLI Tools, Provider Diversity, and the Consensus No Vendor Will Build You

I run several different AI CLI tools against the same work on purpose, not out of brand loyalty to any of them, and I've been doing it for real for over a year: multiple specialized agents, an independent Critic that rules pass or fail, production code shipping through it. This post is the concept and the setup, not a lab report, there's a short recipe near the end for checking the review step yourself.


Every AI vendor wants the same thing from you: stay on their model. That's not a conspiracy, it's just the business. Anthropic wants you on Claude, OpenAI wants you on GPT, xAI wants you on Grok. None of them has a roadmap line that reads "route 20% of calls to a competitor to catch our own mistakes." There's no product incentive for that. There's only a user incentive for it, and only if that user doesn't care which model is right, just that one of them is.

I do care about that, so I built it: different providers create, review, and rule on each other's work, held to a bar none of them set for themselves. Not a score, a ruling, pass or fail, with a reason attached that another person or another model can re-check. I've written about pieces of this system for over a year, but always from the outside, the hardware, the memory problem, the economics. This post is from the inside: the concept itself, how it actually works, and how I use it.

The industry already fights over who gets to train on whom, which is exactly why none of them would ever build this

I didn't have to speculate about whether vendors mind a competitor learning from their model: it's been a live fight. In August 2025, Anthropic revoked OpenAI's API access to Claude after discovering OpenAI was using it internally to benchmark GPT-5's responses against Claude's. Anthropic's terms ban exactly that: no access "to build a competing product or service, including to train competing AI models." OpenAI's defense was that benchmarking a rival is "industry standard." Both things can be true, and Anthropic cut the access anyway, its second such cutoff that year, after Windsurf during OpenAI's acquisition talks.

A messier, disputed version of the same fight is still running in 2026: a White House tech policy official publicly accused Moonshot AI of distilling Anthropic's Claude-Fable model to build Kimi K3, citing "millions of exchanges" identified by IP and metadata. I'm flagging that one as contested, not settled. Neither Anthropic nor the White House has published a query log, training manifest, or forensic analysis, and independent researchers argue the timeline doesn't work: Fable 5 had only been public a few weeks, not enough time to distill and train a model Kimi K3's size.

Worth being precise about what these fights actually are, so this post doesn't get lumped in with them: they're about training a rival frontier model on another lab's outputs at scale, without permission, straightforward IP extraction. Every major lab's terms ban it; OpenAI even sells a distillation product, restricted to teacher=OpenAI, student=OpenAI. What I'm doing in this post is categorically different: I'm a paying customer of several separate services, asking each one the same question and comparing their answers. That's not distillation, it's comparison shopping.

But notice what that proves beyond "no incentive." These aren't neutral competitors who simply haven't gotten around to building a shared comparison tool. They're actively revoking each other's API keys and getting the White House involved, in the same eighteen months. A consortium of Anthropic, OpenAI, and Moonshot jointly shipping a product that routes your question across all three and tells you when they disagree isn't a roadmap gap waiting to be filled. It's organizationally impossible between parties that ban each other's access and accuse each other of IP theft at a federal level. There is no boardroom where that feature gets greenlit. The only place a tool like that can exist is outside all of their walls, built by someone who's a paying customer of all of them and a competitor to none, which is exactly the position an end user, and only an end user, is in.

I'm not claiming this is new

I said as much the first time I put names on these roles, back in December 2025, running a four-role review loop (ProjectManager, Developer, Reviewer, and an adversarial critic I called "Lucifer" at the time, because it was the devil's advocate point of view, now I just call it the Critic) on local Ollama models:

"This is not novel in human teams. It's basic team dynamics. What's novel is making it work with open source models that fit in a local machine."

Dual-approval review teams and adversarial gates aren't my idea, and there's a real research lineage behind the pattern: MetaGPT (Aug 2023) assigned PM/Architect/Engineer/QA roles to GPT instances over a year before I named mine; AutoGen (Microsoft, same month) generalized multi-agent conversation as infrastructure; and a steady line of Multi-Agent Debate research since has been studying whether structured disagreement between models actually improves output over a single model's answer.

The closest match I found is current: an ICML 2026 DL4C workshop paper, "Adversarial Review: Structured Disagreement for Grounded Agentic Code Review," built around almost exactly my shape, a coding agent, a Reviewer, and a Critic who audits the Reviewer's findings through structured disagreement rather than trusting them. Their own stated motivation is the one my Critic's seat file was already built around, in their own words: "when two LLM agents are asked to agree on a joint output, they tend to agree with each other. They do not always find the truth." I use three roles throughout this post because three is the simplest version that demonstrates the mechanism, not because any specific benchmark says three is optimal; more on how far you can scale that past three, under "How it's actually built, briefly."

Why this works

Four things have to be true for a multi-model review loop to beat a single model, and I can point to a reason for each one instead of just asserting it. One more thing closes out the section afterward, not a fifth requirement, just the historical note that none of this is actually new.

Different labs make different mistakes. A model reviewing its own output, or output from a model trained the same way on similar data, shares its own blind spots with what it's checking. That's the whole reason to use multiple providers, DeepSeek, Zhipu's GLM, and Moonshot's Kimi for example, or Claude, or Grok, instead of three calls to the same model: a Reviewer with a genuinely different training lineage is more likely to notice something a same-lineage reviewer would also miss.

The persona has to be disciplined, not just adversarial. "Be adversarial, find bugs" sounds like the right instruction and isn't. A model told to find problems will find problems, real ones and manufactured ones, with no way to tell you which is which. A model told to prove every claim against a stated scope, and told explicitly not to penalize what's outside that scope, produces a ruling you can act on instead of a pile of busywork.

Re-verification has to be independent, not inherited. A Critic that reads "Reviewer says it's fine" and agrees has added nothing; it's just a second vote for the first model's blind spot. A Critic that re-derives the claim itself, from the code, and only then compares notes with the Reviewer, catches the specific failure where a Reviewer's reasoning was wrong but its conclusion happened to be right anyway. This isn't just my own rule, it's written into the team law every seat in my setup runs on: "if Developer, Reviewer, and Critic all agree without friction, look harder. Structured disagreement > comfortable consensus." The Critic's own file says the same thing in its own words: "When all three of us agree, look harder, that is when groupthink is most likely." Agreement is the moment to get more suspicious, not less, because three independent reads landing on the same wrong answer is a real failure mode, not a reassurance. This isn't just theory: the workshop paper named above, studying this exact reviewer-critic pattern, found naive agreement scoring worse than a single reviewer alone (F1 0.457 vs. 0.495), until the critic was forced to ground every disagreement in cited code, which pushed it to 0.533, the best of the methods tested. An undisciplined second opinion can make review worse than no second opinion at all.

The gate has to be a ruling, not a feeling. "Looks good to me" doesn't compose across models and doesn't survive someone asking "compared to what, measured how, what would prove this wrong?" A pass or fail that has to name its reasoning, and gets rejected and sent back if it can't, is falsifiable in a way "looks fine" never is. It's also the only version of "the team approved this" that another person, or another model, can actually audit after the fact, because there's a stated reason to check, not just a vibe.

As a note, this isn't new to engineering, it's just new to applying it to AI. Humans already run on this: when two people give different versions of the same story, the truth is usually somewhere in the middle, and that's exactly why no developer certifies their own code for production. The formal name for it is separation of duties, requirement, design, review, and production shouldn't sit with one person, and it's slower on purpose. The counterargument is real too, and worth taking seriously rather than ignoring: one study found overly specialized software teams can produce worse output once people stop sharing knowledge across tasks, which is part of why the industry also moved toward embedded QA and "you build it, you run it." The lesson isn't "more separation is always better," it's that the separation has to be structured, not just present, which is what an independent, falsifiable ruling is doing for a team of models instead of a team of people.

How it's actually built, briefly

This is architecture, simply put:

Picture a TechOps team: different people, different roles, a shared ticket queue. Swap "different people" for "different models" and the ticket queue for a ledger, and that's the whole architecture. The role, Developer, Reviewer, Critic, whoever, is a folder and a system prompt; the model sitting in it is the one thing allowed to change without anyone else noticing. That's not a metaphor, it's literally the folder layout, ten seats, each with a real AGENTS.md and a CLAUDE.md that's a 9-byte symlink to it (one file, two names, so Claude Code's convention and every other harness's convention both resolve to the same instructions):

AI_TEAM/: AGENTS.md, CLAUDE.md -> AGENTS.md
CRITIC/, DBA/, REVIEWER/, and 7 more seats, each the same two files

Three layers do three different jobs. Mind is the durable part: an append-only coordination ledger (a database trigger blocks UPDATE and DELETE at the engine level, trg_events_no_update/trg_events_no_delete, verified live) plus a separate knowledge store with three views, MySQL for structured facts, Neo4j for relationships, MemPalace for semantic recall. Cognition is a single model call, reasoning over whatever's in Mind, then ending; it has no memory of its own. Display is what a human watches through, a separate CLI channel per seat for me day to day, without becoming the coordination mechanism itself.

Seats are independent Cognition sessions, woken on their own schedule, reading and writing the ledger through MCP instead of a hand-off from one orchestrating thread, which is both what makes it decentralized and what makes it scale: adding a fourth or fifth seat means pointing another folder at the same ledger, not building new plumbing.

How I actually use this

This isn't a system I stood up to write a post about. It's been running for over a year, and the seats aren't a demo roster, they're doing real work: several Developer agents, not one, each with a narrow lane, a DBA agent for schema and migrations, a Security agent for auth and secrets, each with a sharper, narrower prompt than "review everything." JavaScript and Python work go to different Developer seats on purpose, because a model that's spent its whole context on one language's idioms writes better code in that language than one switching between three.

The CLI control isn't a feature I'm describing in the abstract, it's how I spend my day: a separate terminal per seat, and from each one I can run a seat on auto, step it one iteration at a time, or interrupt it mid-task, depending on how much I trust what it's doing right now. The gate applies to whatever's actually shipping that day, not a toy example picked for a blog post: the Critic rules pass or fail, with a reason, before anything goes in.

What a year of this has actually bought me: fewer surprises after something ships, and code I trust more by the time it clears the gate. That's not a number I can back with a single command, it's a year of living with it, which is a different kind of evidence than anything else in this post and I want to be clear about that difference rather than blur it. If you want to see the mechanism itself rather than take my word for the year, here's how to do that with whatever you've got.

Run this yourself, with whatever you've got

None of this needs my config, my ledger, or my seat files. It's three prompts and a loop. You don't need Ollama or any specific models; you need three genuinely different providers, any three, and a way to call each one with a system prompt. Here's the actual shape, trimmed down.

Pick a task with a clear, checkable scope, something you can run and look at the output of. Then three system prompts:

DEVELOPER:
"No placeholders, no 'fix later.' Quality over speed.
Write the code. Don't review or gate your own work."

REVIEWER:
"Never assume the Developer is correct. Prove every claim.
If you can't re-run it and see the output, it's not approved.
Don't penalize anything outside the stated scope.
End with exactly one line: VERDICT: PASS or VERDICT: FAIL, plus the reason."

CRITIC:
"Never guess, always validate. Every claim is a hypothesis
until you've independently re-derived it, not just trusted
the Reviewer's sign-off. Ask: compared to what? measured how?
what would failure look like? Don't rewrite the code, only rule.
End with exactly one line: VERDICT: PASS or VERDICT: FAIL, plus the reason."

Then the loop, plain language, not a specific framework:

Developer writes a draft.
Send draft to Reviewer.
  If Reviewer FAILs it: send the specific reason back to Developer, repeat.
  If Reviewer PASSes it: send draft + Reviewer's reasoning to Critic.
    If Critic FAILs it: send Critic's reason back to Developer, repeat.
    If Critic PASSes it: done.

There's no percentage to tune at the gate itself. The Critic's ruling stays pass, fail, and why, not a score crossing a line. Numbers do show up a layer down once you automate the deliberation that feeds the gate, agreement ratios, dissent counts, that kind of thing, in other automated pipelines I run, and that's fine; a number deciding whether a sub-step converged is a different thing than a number deciding whether the Critic approves. Don't let the second one quietly replace the first. If you automate this, cap the loop at a set number of rounds before raising it back to you, so a bad run doesn't repeat forever and run up a bill. Use at least three different models from three different labs, not three calls to the same one; the whole mechanism depends on that. Beyond that, the specific providers don't matter; I used Ollama's cloud tier because it was the cheapest way to reach DeepSeek, GLM, and Kimi from one script, but the same three prompts work against three API keys, three browser tabs, or three different vendors' free tiers.

Where this sits

This is my opinion on how AI should get used, for development, but for anything else too: if you lean on just one provider, for code or for a simple chat question, you're trusting only that provider's training data and whatever blind spots come with it. I don't, and my own workspace reflects that, a concept, a reason for each piece of why it works instead of just an assertion, and a short recipe for checking the review step yourself instead of taking my word for it. That's less dramatic than "AI team builds better code than any single model," and it's also the only version of the claim I can actually back, a year of living with it, kept separate from the parts that are still just argument rather than blurred together. None of it came from a vendor, and it couldn't have: this exists because someone outside all of their walls had a reason to build it that none of them do. Take the three prompts under "Run this yourself," point them at whatever three models you've already got, and see what it catches on something of yours. That's the only test that actually matters to you.


Related reading


Keith is a database consultant and infrastructure engineer with 25+ years of open-source experience. He builds the ApocryiaAI brand and DRAVEN work under 3k1o LLC (apocryia.com and related domains). He writes about MySQL, Proxmox, AI memory, multi-agent workflows, and building technology you can actually inspect.

Saturday, October 3, 2026

Claude Code at NVIDIA's Free Model Catalog. It Took a Proxy and Four Traps.

Claude Code at NVIDIA's Free Model Catalog. It Took a Proxy and Four Traps.

Claude Code only speaks Anthropic's Messages API. NVIDIA's hosted catalog at build.nvidia.com — free, no credit card, dozens of models — only speaks OpenAI chat-completions. Those two facts don't meet in the middle on their own. Here's the proxy that bridges them, a real /v1/models call to see what's actually on the shelf, and the four traps that will burn an afternoon if nobody warns you first.


Why bother with another backend

I've written before about running Claude Code against Ollama Cloud to get GLM and Kimi behind the same tooling for twenty bucks a month. NVIDIA's build.nvidia.com catalog is a different animal worth adding to the same rotation: it's free, it doesn't ask for a card, and the model list leans toward the NVIDIA-tuned Nemotron family plus a rotating cast of open-weight models — Kimi, GPT-OSS, and others — hosted on NVIDIA's own infrastructure. Sign up with the NVIDIA Developer Program, generate an API key, and you've got a bearer token against real inference with nothing charged to anything.

The catch is the shape of the API. Claude Code was built to talk to Anthropic's Messages API. NVIDIA's NIM endpoints speak OpenAI's chat-completions shape. Same idea — send messages, get a completion — different field names, different request structure. Claude Code doesn't know how to speak NVIDIA's dialect, and NVIDIA's validator doesn't know what to do with half of what Claude Code sends. You need a translator sitting in between, and once you add one, a few sharp edges show up that aren't obvious until you hit them.

The pieces

PieceRole
claude-code-router (ccr)Local daemon that speaks Anthropic's Messages API on one side and OpenAI chat-completions on the other
NVIDIA API keyFrom build.nvidia.com, used as a bearer token against integrate.api.nvidia.com
A small request-shape fixupNVIDIA validates request bodies strictly and rejects fields Claude Code sends by default
A profile wrapper (optional, recommended)Isolates config, trust state, and history per backend so NVIDIA doesn't tangle with your normal Claude install or your Ollama one

1. Install and start ccr

npm install -g @musistudio/claude-code-router   # verify the current package name on npm
ccr start

ccr listens on 127.0.0.1:3456 by default and proxies Anthropic-shaped requests out to whatever providers you configure.

2. Point it at NVIDIA

Edit ~/.claude-code-router/config.json:

{
  "Providers": [
    {
      "name": "nvidia",
      "api_base_url": "https://integrate.api.nvidia.com/v1/chat/completions",
      "api_key": "$NVIDIA_API_KEY",
      "models": [
        "moonshotai/kimi-k3",
        "nvidia/nemotron-3.5-lightning-30b-a3b",
        "openai/gpt-oss-20b"
      ],
      "transformer": {
        "use": ["nvidia-strip-unsupported"]
      }
    }
  ],
  "Router": {
    "default": "nvidia,moonshotai/kimi-k3"
  }
}

Two things worth calling out before you move on:

  • api_key: "$NVIDIA_API_KEY" — ccr interpolates environment variables in the config at request time. The key never sits in the file in plaintext; it just has to be present in the environment of the process that starts ccr.
  • The models list is a gate, not a hint. If a model isn't listed here, ccr quietly falls through to the Router default instead of calling it — see Trap #2 below. This bit me before I understood it.

3. Trap #1 — NVIDIA rejects Claude Code's thinking params

Claude Code sends a thinking block on nearly every turn. ccr's default transformer maps that onto an OpenAI-style reasoning parameter. NVIDIA's NIM validator rejects any parameter a given model hasn't explicitly declared support for:

HTTP 400 {"message":"Validation: Unsupported parameter(s): `reasoning`"}

Since Claude Code thinks on almost every request, this isn't an edge case — it kills the session on the first prompt. The bundled cerebras transformer that ships with ccr isn't a fix either: when the reasoning key is absent, it substitutes disable_reasoning: false, which several NVIDIA models reject just as hard.

Fix: a small custom ccr transformer plugin that strips the offending keys before the request leaves the box.

~/.claude-code-router/plugins/nvidia-strip-unsupported.js:

class NvidiaStripUnsupported {
  constructor(options = {}) {
    this.name = "nvidia-strip-unsupported";
    this.options = options || {};
    this.strip = this.options.strip ||
      ["reasoning", "disable_reasoning", "thinking", "enable_thinking"];
    this.keepReasoningFor = new Set(this.options.keepReasoningFor || []);
  }

  async transformRequestIn(request) {
    const body = { ...request };
    const exempt = this.keepReasoningFor.has(body.model);
    for (const key of this.strip) {
      if (!(key in body)) continue;
      if (exempt && (key === "reasoning" || key === "thinking")) continue;
      delete body[key];
    }
    return body;
  }

  async transformResponseOut(response) {
    return response;
  }
}

module.exports = NvidiaStripUnsupported;

Reference it from the provider's transformer.use list, as shown in step 2. You lose the thinking-budget hint for models that can't take it — a much cheaper cost than a dead session.

4. Trap #2 — bare model names silently fall through to the wrong provider

ccr only does a direct provider lookup when the model string contains a comma — its router checks for model.includes(","). A bare name like moonshotai/kimi-k3 doesn't match that, so it falls through to whatever Router.default is configured — which may be a different provider entirely if you're already running Ollama Cloud or OpenRouter through the same daemon. You will not get an error. You will just silently bill, and get answers from, the wrong backend.

Fix: always pass the provider-qualified form, nvidia,<model>:

claude --model nvidia,moonshotai/kimi-k3
# or mid-session:
/model nvidia,openai/gpt-oss-20b

If you want to keep typing bare model names on the CLI, wrap the invocation in a shell function that rewrites --model X to --model nvidia,X whenever the value doesn't already contain a comma. The profile script below does exactly that.

5. Trap #3 — the model also has to be in ccr's per-provider models[]

Separate from trap #2: even with the nvidia, prefix, if the model string isn't present in that provider's models array in config.json, ccr still falls back to the Router default instead of erroring. Keep that array current — NVIDIA's catalog changes, and retired model IDs get real deadlines: advance notice, then the ID starts 400/410ing. Re-check it against /v1/models (next section) periodically rather than trusting a list you wrote once.

6. See what's actually on the shelf: the /v1/models call

Before wiring anything into Claude Code, hit NVIDIA directly and get the real, current catalog — not a blog post's memory of it:

curl -sS https://integrate.api.nvidia.com/v1/models \
  -H "Authorization: Bearer $NVIDIA_API_KEY" | jq .

It comes back in the same OpenAI-compatible shape as /v1/chat/completions expects for the model field — a flat list with id, object, created, and owned_by. This is a real excerpt, pulled live against my own key while writing this:

{
  "object": "list",
  "data": [
    {
      "id": "deepseek-ai/deepseek-coder-6.7b-instruct",
      "object": "model",
      "created": 735790403,
      "owned_by": "deepseek-ai"
    },
    {
      "id": "deepseek-ai/deepseek-v4.1-flash",
      "object": "model",
      "created": 735790403,
      "owned_by": "deepseek-ai"
    },
    {
      "id": "moonshotai/kimi-k3",
      "object": "model",
      "created": 735790403,
      "owned_by": "moonshotai"
    },
    {
      "id": "nvidia/llama-3.1-nemotron-70b-instruct",
      "object": "model",
      "created": 735790403,
      "owned_by": "nvidia"
    },
    {
      "id": "nvidia/nemotron-3-ultra-550b-a55b",
      "object": "model",
      "created": 735790403,
      "owned_by": "nvidia"
    },
    {
      "id": "openai/gpt-oss-20b",
      "object": "model",
      "created": 735790403,
      "owned_by": "openai"
    },
    {
      "id": "z-ai/glm-5.3",
      "object": "model",
      "created": 735790403,
      "owned_by": "z-ai"
    },
    {
      "id": "z-ai/glm-5.3-flash",
      "object": "model",
      "created": 735790403,
      "owned_by": "z-ai"
    }
  ]
}

Notice created is identical — 735790403, which decodes to April 1993 — on every single one of the 80 models my key could see, not just these eight. That's not a copy-paste artifact in this post; it's the API itself. NVIDIA doesn't populate real per-model creation dates on this field, so don't use created to judge how new a model is — it's always the same placeholder epoch, for everything.

Filter it down to just the IDs when you're updating ccr's models[] array:

curl -sS https://integrate.api.nvidia.com/v1/models \
  -H "Authorization: Bearer $NVIDIA_API_KEY" | jq -r '.data[].id' | sort

Do this before you copy anyone's model list into your own config — including the one in this post. The catalog is not static; NVIDIA adds and retires IDs on its own schedule, and the only authoritative source is the endpoint itself.

7. What "free tier" actually means here

Worth being precise, because older write-ups about this catalog will mislead you. At 2024 launch, signups got roughly 1,000 API credits (one credit ≈ one call, not one token), extendable to 5,000 with a business email — that system was phased out by early 2025 in favor of a straight requests-per-minute ceiling, no balance, no card. Generate a key at build.nvidia.com and you're gated by RPM, not tokens or credits.

The ~40 RPM figure that gets quoted around applies per account key, not per model — it doesn't multiply when you add more models to the same key — and there's no self-serve way to raise it; the only path past it is self-hosting the NIM or NVIDIA AI Enterprise. You can watch developers hit that exact wall in NVIDIA's own forums (one thread, a second), since there's no single published limits page — the real number for your account is on your own dashboard. Check it live; don't trust a number here or anywhere else.

What that buys you: a lab, not a production backend. Right for "drive Claude Code without paying frontier rates" or "check a benchmark myself" — the same instinct behind the Ollama Cloud setup — not for paying-customer traffic.

How that stacks up against Groq's free tier

NVIDIA isn't the only free, no-card lab sandbox. Groq runs the same deal on different terms. Check your own numbers at console.groq.com/settings/limits or the community forum; these are the published numbers for its GPT-OSS-class free models:

NVIDIA build.nvidia.comGroq (gpt-oss-class free models)
Cost$0, no card$0, no card
Throttle shape~40 requests/min, per account key, shared across whatever models you call30 requests/min, 8,000 tokens/min, 1,000 requests/day, 200,000 tokens/day — per model
Self-serve increaseNone on the free tierNone on the free tier
Catalog size~80 models at last count (section 6), including NVIDIA's own Nemotron lineA much smaller curated set of open models
Production useNot permitted on the free key — deploy the NIM yourself or buy AI EnterpriseRate-limited, but it's the same real API you'd pay for at higher tiers

Neither is a free production API. NVIDIA wins on model quality — one key, frontier-class models (Kimi K3, GLM-5.3, Nemotron, DeepSeek) instead of Groq's smaller set. Groq wins if you want a ceiling that resets at midnight instead of a request-rate wall that's always just there. Pick based on which matters more for what you're building.

8. Set the environment and point Claude Code at ccr, not NVIDIA directly

export NVIDIA_API_KEY="nvapi-..."           # from build.nvidia.com
export ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
export ANTHROPIC_AUTH_TOKEN="dummy"         # ccr doesn't check this; Claude Code requires it set
export ANTHROPIC_API_KEY=""                 # must be empty, or Claude Code prefers it over AUTH_TOKEN
export ANTHROPIC_MODEL="nvidia,moonshotai/kimi-k3"
export ANTHROPIC_SMALL_FAST_MODEL="nvidia,nvidia/nemotron-3.5-lightning-30b-a3b"

claude

ANTHROPIC_BASE_URL pointing at localhost is correct and intentional — the real outbound call to NVIDIA is made by ccr, using NVIDIA_API_KEY from its own environment. Claude Code itself never talks to NVIDIA directly.

Also worth setting, since Claude Code doesn't know third-party context windows and will otherwise assume 200k and warn about it:

export CLAUDE_CODE_MAX_CONTEXT_TOKENS=128000   # NVIDIA's /v1/models doesn't expose this; treat it as an estimate

9. Isolate it as a profile

Running multiple backends — NVIDIA, Ollama Cloud, OpenRouter — out of the same ~/.claude config directory gets confusing fast: history, trust state, and MCP config all mix together. A small wrapper that sources a per-backend env file into an isolated CLAUDE_CONFIG_DIR keeps them from stepping on each other:

~/bin/claude-profile:

#!/usr/bin/env bash
set -euo pipefail
PROFILE_DIR="${CLAUDE_PROFILE_DIR:-$HOME/.claude-profiles}"
name=$1; shift
env_file="$PROFILE_DIR/$name.env"
[[ -r $env_file ]] || { echo "no such profile: $name" >&2; exit 1; }
set -a; source "$env_file"; set +a
: "${CLAUDE_CONFIG_DIR:?profile '$name' did not set CLAUDE_CONFIG_DIR}"
mkdir -p "$CLAUDE_CONFIG_DIR"
exec claude "$@"

~/.claude-profiles/nvidia.env:

: "${NVIDIA_API_KEY:?NVIDIA_API_KEY not set}"

# Make sure ccr is actually running before Claude Code tries to talk to it.
if ! (exec 3<>/dev/tcp/127.0.0.1/3456) 2>/dev/null; then
    nohup ccr start >/tmp/ccr-start.log 2>&1 &
    for _ in $(seq 1 15); do
        sleep 1
        (exec 3<>/dev/tcp/127.0.0.1/3456) 2>/dev/null && break
    done
fi

# Close the bare-model-name trap (#2) automatically.
_ccr_fix_model_args() {
    local -a out=(); local n=$#; local i=1
    while (( i <= n )); do
        local a=${!i}
        case $a in
            --model)
                local j=$((i+1))
                if (( j <= n )); then
                    local v=${!j}
                    [[ $v == *,* ]] || v="nvidia,$v"
                    out+=("$a" "$v"); i=$((i+2)); continue
                fi ;;
            --model=*)
                local v=${a#--model=}
                [[ $v == *,* ]] || v="nvidia,$v"
                out+=("--model=$v"); i=$((i+1)); continue ;;
        esac
        out+=("$a"); i=$((i+1))
    done
    printf '%s\0' "${out[@]}"
}
if (( $# )); then
    _ccr_argv=()
    while IFS= read -r -d '' a; do _ccr_argv+=("$a"); done < <(_ccr_fix_model_args "$@")
    set -- "${_ccr_argv[@]}"
fi
unset -f _ccr_fix_model_args

CLAUDE_CONFIG_DIR=$HOME/.claude-nvidia
ANTHROPIC_BASE_URL=http://127.0.0.1:3456
ANTHROPIC_AUTH_TOKEN=dummy
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=nvidia,moonshotai/kimi-k3
ANTHROPIC_SMALL_FAST_MODEL=nvidia,nvidia/nemotron-3.5-lightning-30b-a3b
CLAUDE_CODE_MAX_CONTEXT_TOKENS=128000
alias claude-nvidia='claude-profile nvidia'

Then:

claude-nvidia --model moonshotai/kimi-k3 -p "say hi"

Trap #4 — each profile has its own workspace-trust store

Not NVIDIA-specific, but it will absolutely make a correctly wired-up profile look broken: CLAUDE_CONFIG_DIR isolation means each profile tracks workspace trust separately, per working directory, in its own .claude.json. Trusting a directory under your normal claude or your claude-ollama profile does not carry over to claude-nvidia — it's a different config dir with a blank trust table.

Symptom:

Ignoring N permissions.allow entries from .claude/settings.json:
this workspace has not been trusted.

When that fires, your permissions allowlist is silently ignored and tool calls get gated — which looks exactly like "the model/provider is broken," especially for an agentic model that calls tools on almost every turn.

Fix: run the profile interactively once in that directory and accept the trust dialog, or set it directly (back the file up first — it's hand-editable JSON, not a stable schema):

python3 -c "
import json
path = '$HOME/.claude-nvidia/.claude.json'
d = json.load(open(path))
d['projects']['$PWD']['hasTrustDialogAccepted'] = True
json.dump(d, open(path, 'w'), indent=2)
"

10. Verify end to end

Skip Claude Code for a moment and confirm NVIDIA itself is answering:

curl -sS https://integrate.api.nvidia.com/v1/chat/completions \
  -H "Authorization: Bearer $NVIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"moonshotai/kimi-k3","messages":[{"role":"user","content":"say hi in 3 words"}],"max_tokens":20}'

A 200 with a choices[0].message.content means NVIDIA and the key are fine and any remaining problem is in ccr or Claude Code's config, not the provider. Then run the full path and check for a clean result with "is_error": false:

claude-nvidia --model moonshotai/kimi-k3 -p "reply with exactly: PONG" --output-format json

Three vendors, one call shape, live right now

Same curl, same prompt, same max_tokens, fired at three different vendors' models behind the one NVIDIA endpoint, run live while writing this:

curl -sS https://integrate.api.nvidia.com/v1/chat/completions \
  -H "Authorization: Bearer $NVIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"moonshotai/kimi-k3","messages":[{"role":"user","content":"Reply with exactly one short sentence: which model are you, in your own words."}],"max_tokens":300}'
# Kimi (Moonshot AI) — HTTP 200
"content": "I’m Kimi, an AI assistant developed by Moonshot AI."
curl -sS https://integrate.api.nvidia.com/v1/chat/completions \
  -H "Authorization: Bearer $NVIDIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"z-ai/glm-5.3","messages":[{"role":"user","content":"Reply with exactly one short sentence: which model are you, in your own words."}],"max_tokens":300}'
# GLM (Z.ai) — HTTP 200
"content": "I am GLM, a large language model developed by Z.ai."

Both clean, finish_reason: "stop", real content. DeepSeek didn't make it in either form I tried — full results in the table below. Listed in /v1/models isn't the same claim as callable right now, which is the entire reason to run the probe before trusting a model list, including this one.

Known-good / known-bad models, as of testing

Catalogs here churn — re-run the /v1/models call from section 6 before trusting this table in a post written more than a few weeks after you read it.

ModelStatus
moonshotai/kimi-k3Works, including tool calls. NVIDIA rate-limits it harder than most; can be slow (~2–110s observed, highly variable)
z-ai/glm-5.3Works, clean stop finish on a plain chat call
deepseek-ai/deepseek-v4.1-flashListed in /v1/models, but timed out on two separate live attempts (one 504, one 90s client timeout) — not usable as of testing
deepseek-ai/deepseek-coder-6.7b-instructListed in /v1/models, but the chat-completions call itself 404'd — present in the catalog, not actually callable
poolside/laguna-xs-2.1Works
nvidia/nemotron-3.5-lightning-30b-a3bWorks, fastest of the set
nvidia/nemotron-3-ultra-550b-a55bWorks, slow cold start (~45s+)
openai/gpt-oss-20bWorks
meta/llama-3.2-90b-vision-instructTimes out past ~120s
google/gemma-4-31b-itAnswers bare chat in ~120s but times out once tools are attached (Claude Code always attaches tools)
IDs not in NVIDIA's current catalog404, not a transient failure — don't retry, re-check the catalog
Deprecated/retired IDs410 Gone once NVIDIA's end-of-life date passes — drop them from models[] immediately, they won't come back

The failure modes, for the TL;DR crowd

  1. 400 "Unsupported parameter(s): reasoning" → strip thinking/reasoning params with a custom ccr transformer before they reach NVIDIA.
  2. Silently billing/answering from the wrong provider → always use the comma-qualified provider,model form; ccr's router keys off the comma.
  3. Falls back to Router default with no error → the model must also be listed in that provider's models[] in config.json. Verify against /v1/models, don't guess.
  4. "Workspace has not been trusted" / tool calls gated for no apparent reason → each CLAUDE_CONFIG_DIR profile has its own trust store; accept the dialog, or patch the flag, per profile, per directory.

Get past those four and NVIDIA's free catalog works in Claude Code like any other backend — full tool-call support, same CLI, same slash commands, just a different $ANTHROPIC_MODEL and nothing on the invoice.


Related reading


Keith is a database consultant and infrastructure engineer with 25+ years of open-source experience. He builds the ApocryiaAI brand and DRAVEN work under 3k1o LLC (apocryia.com and related domains). He writes about MySQL, Proxmox, AI memory, multi-agent workflows, and building technology you can actually inspect.