Models
An agent doesn't name an API. It says what its model must support, and Kindgi picks a model from the providers registered for your tenant. A provider is one connection, to a vendor's API or to an endpoint you serve, with the models it offers: each model's features, context window and price.
- Connect Anthropic: Claude, from a preset.
- Connect OpenAI, Gemini, Groq or OpenRouter: from a preset each, with the provider's API key.
- Connect Gemini on Vertex AI: Gemini, from a preset, with your Google Cloud credentials.
- Connect an OpenAI-compatible endpoint: Ollama or a hosted endpoint, from a spec file.
- Connect a model you serve: an open model on your own hardware or in your own network.
How a turn picks among them is on Choose the model an agent uses.
A model call that fails with a temporary error (a timeout, 408; a rate
limit, 429; an overloaded or failing server, 500, 502, 503 or 504)
is tried again, waiting longer each time, three attempts in all, before the
turn fails. Every adapter does this: the Gemini adapter sets it, and the
Anthropic and OpenAI-compatible ones keep their SDKs' defaults. The waits
count against the turn's time budget (maxWallMs).
Before you register one
Section titled “Before you register one”A new pack's tenant has dev-echo, a stand-in that answers without a model
(it calls the agent's first tool and replies with what the tool returned,
after a first line saying it isn't a real model). It's
a fallback: it answers only while no other provider fits the agent, so a
provider you register takes over at the next turn, with nothing to switch off
and no restart.
Presets
Section titled “Presets”kindgi providers presetsIt prints each preset as JSON: its adapter, models, key and when its prices were checked. They are:
| Preset | What | Key |
|---|---|---|
anthropic |
Claude on the Anthropic API | ANTHROPIC_API_KEY |
gemini-api |
Gemini on the Gemini Developer API, with an API key from Google AI Studio | GEMINI_API_KEY |
gemini |
Gemini on Vertex AI, with Google Application Default Credentials (--project=<gcp-project>; in kindgi dev, set KINDGI_DEV_GOOGLE_CREDENTIALS=adc) |
none |
groq |
Open-weight models on Groq's fast inference | GROQ_API_KEY |
openai |
GPT on the OpenAI API | OPENAI_API_KEY |
openrouter |
A hosted service in front of several vendors: your prompts pass through OpenRouter | OPENROUTER_API_KEY |
A preset carries its models' features, context windows, output limits and
prices. pricesCheckedAt is when the prices were last compared with the
vendor's. --max-output-tokens=<n> registers the models with another output
limit.
A provider spec
Section titled “A provider spec”Anything that isn't a preset registers from a spec: a JSON file you pass with
kindgi providers register --spec=@<file>.
{ "adapter_id": "@kindgi/adapter-model-openai-compat", "adapter_config": { "baseURL": "http://localhost:11434/v1" }, "metadata": { "id": "ollama", "region": "local", "models": [ { "name": "llama3.1", "contextWindow": 8192, "features": ["tool-use"], "cost": { "promptUsdPer1kTokens": 0, "completionUsdPer1kTokens": 0 } } ] }}adapter_idis the adapter's full package name.adapter_configholds the connection's settings, such as the endpoint's URL: flat keys with string, number or boolean values. Never a key.secret_refnames the secret that holds the key:{ "envName": "local", "name": "GROQ_API_KEY" }. Leave it out for an endpoint that takes none.send_traceparent(optional,trueorfalse) sends each model call's W3Ctraceparentto the provider as a request header, so its request logs can be matched to the run that made the call (Follow a request or a run). Only the ids go: the trace id and a span id, never a prompt or an answer. It's off by default: nothing about a run's trace leaves your deployment unless you turn it on (see below).metadata.idnames the connection; agents prefer it by this id.metadata.modelslists the models, by the name the endpoint knows them by, each with itscontextWindow, itsfeatures(what agents can require) and itscost; optionally itsmaxOutputTokens,samplingandthinking(see Temperature and thinking).metadata.defaultModel(optional) is the model an agent gets when nothing else decides: no preference, and equal weights. Without it, the first model by name. Another name is refused:provider "ollama" defaultModel must be one of its models (llama3.1), got "llama3.2".metadata.attributes(optional) are labels agents can rank by, such as"local";metadata.fallback: truemakes the provider a fallback, likedev-echo.metadata.labels(optional) are your own key-value notes, such as who manages the registration:{ "kindgi.com/managed-by": "terraform" }. Routing ignores them;kindgi providers getshows them. Up to 32, each key 1 to 63 lowercase letters and digits (with.,-,_or/inside), each value up to 256 characters. Another key is refused:provider "ollama" labels key "Team Name" must be 1-63 lowercase letters and digits, with ".", "-", "_" or "/" inside.
With send_traceparent: true, a model call's request to the provider carries
the run's trace id and a span of its own:
traceparent: 00-57fb198d8afd340a2b81454221085849-6fef3833e512d758-01kindgi runs get <run-id> shows the same trace id as the run's traceId. A
value other than true or false is refused, and nothing is registered:
Error [invalid-request]: Provider "acme-vendor": send_traceparent must be true or false. ✗ /send_traceparent: send_traceparent must be true or false.A spec its adapter can't use
Section titled “A spec its adapter can't use”The runtime checks a spec against its adapter when you register it, without
calling the endpoint or reading the key. A setting the adapter can't use is
refused, with each problem on a line of its own, and nothing is registered.
Here, the spec above without its baseURL, and with an api it doesn't
take:
Error [invalid-request]: Provider "ollama" doesn't fit adapter @kindgi/adapter-model-openai-compat: needs adapter_config.baseURL, an http(s) URL (e.g. https://api.openai.com/v1) (and 1 more). ✗ /adapter_config/baseURL: needs adapter_config.baseURL, an http(s) URL (e.g. https://api.openai.com/v1). ✗ /adapter_config/api: adapter_config.api must be one of responses, chat-completions.- A key it needs: an adapter that takes a key refuses a spec without
secret_ref:✗ /secret_ref: needs secret_ref: its Anthropic API key. - An adapter the runtime doesn't have:
This runtime has no adapter "@kindgi/adapter-model-nope", so it can't build provider "ollama". - What isn't checked: whether the key works, the endpoint answers, or it serves the models you list. The first turn finds that out.
- From the API:
POST /v1/providersanswers422provider-config-invalid, with each problem indetails.issues: itspath, a JSON pointer into the spec, and itsmessage. The clients read it as an invalid request with thoseissues(TypeScriptcode: 'invalid-request', PythonInvalidRequestError).
Temperature and thinking
Section titled “Temperature and thinking”Some models take no temperature: the Claude 5.5 models and OpenAI's GPT-6
models reject one. Their registrations say "sampling": false, and Kindgi
sends their calls without it. The turn then carries a sampling-unsupported
warning instead of failing, and kindgi runs start prints it. Here from a
local llama3.1 registered with "sampling": false and a model-settings
block asking for 0.2:
⚠ llama3.1 doesn't take a temperature, so the call went without one (it asked for 0.2).Some models think before they answer. A model's thinking says how:
"mode": "adaptive" (on unless turned down) or "always", and lowest, the
vendor's own setting for the least thinking. Thinking counts against the
model's output limit (maxOutputTokens) and bills as output, so it's in a
turn's tokens and cost. A guardrail's model judge asks for the least thinking,
and allows 2,048 tokens for it on top of the 256 its verdict gets.
| Preset | Model | Takes a temperature | Thinks | Least thinking |
|---|---|---|---|---|
anthropic |
claude-opus-5-5 |
no | always | low |
claude-sonnet-5-5 |
no | adaptive | between_tools |
|
claude-haiku-5-5 |
no | adaptive | disabled |
|
claude-haiku-4-5 |
yes | not set | ||
openai |
gpt-6.1-sol |
no | adaptive | low |
gpt-6-astra |
no | adaptive | low |
|
gpt-6-luna |
no | adaptive | none |
|
gemini, gemini-api |
gemini-3.8-flash |
yes | adaptive | low |
gemini-3.5-flash-lite |
yes | adaptive | minimal |
|
gemini-api |
gemini-3.1-pro-preview |
yes | not set |
groq's models take a temperature and set no thinking. On openrouter,
anthropic/claude-sonnet-5.5, openai/gpt-6.1-sol and openai/gpt-6-luna
take no temperature. A model without thinking gets nothing sent for it: it
thinks, or not, as its vendor's default.
A preset registered before 0.1.4 has none of these marks: register it again to pick them up.
Declare them in your pack's config
Section titled “Declare them in your pack's config”Under kindgi dev, each project and each git worktree has its own database,
and a provider you register by hand is in that one only. Declare it in the
pack's config instead, and kindgi dev registers it on every boot: in a new
worktree, after --reset, and on a teammate's machine.
// in kindgi.config.tsproviders: [ { preset: 'anthropic', models: ['claude-sonnet-5-5'] }, // its key, ANTHROPIC_API_KEY, from the env files { preset: 'gemini', project: 'acme-gcp', models: ['gemini-3.8-flash'] }, { spec: { /* what --spec takes */ } },],# in pyproject.toml[[tool.kindgi.providers]]preset = "anthropic"models = ["claude-sonnet-5-5"] # its key, ANTHROPIC_API_KEY, from the env files
[[tool.kindgi.providers]]preset = "gemini"project = "acme-gcp"models = ["gemini-3.8-flash"]A preset entry takes models, project, maxOutputTokens, and secret: the
key's name, in place of the preset's own. The keys are spelled the same in
pyproject.toml. A spec entry is what --spec
takes. A key is always a secret's name, read from the env files; a spec with
a credential in adapter_config is refused.
At each boot, kindgi dev says what it did:
Providers from kindgi.config.ts: ✓ anthropic: registered ✓ qwen-local: registered ⚠ qwen-keyed: not registered: QWEN_API_KEY is not in .env, .env.local, .kindgi/secrets.env. Set it (pnpm exec kindgi secrets set QWEN_API_KEY --env=local --scope=tenant), then restart kindgi dev-
A provider whose key isn't in the env files is skipped. The config isn't watched: restart
kindgi devafter setting the key. -
A key in
.env, which your app loads too, gets a line of its own:⚠ ANTHROPIC_API_KEY is in .env, which your app loads too. To give Kindgi its own copy: pnpm exec kindgi secrets copy (it never edits your app's files). -
One
kindgi devregistered and whose declaration changed is registered again (qwen-local: registered again (changed in kindgi.config.ts)); one the config no longer declares is unregistered. -
If the runtime already has a provider with that id that
kindgi devdidn't register,kindgi devleaves it as it is. When its region or models (names, limits, prices) differ from the config's, a ⚠ line names thekindgi providers unregisterthat lets the config's version apply. The runtime doesn't list a provider's adapter, its settings or its key's name, so one that differs only there is left with a plain line.
kindgi dev keeps track of the ones it registered in
.kindgi/dev/providers.json. A runtime that kindgi dev doesn't run, such as
your staging or production one, gets its providers with
kindgi providers register.
With kindgi dev, a provider's key is read from your app's env files, .env
and then .env.local, and then Kindgi's own .kindgi/secrets.env, which wins
on a clash, when a turn calls the model. To store one without
echoing it, with kindgi dev running:
kindgi secrets set ANTHROPIC_API_KEY --env=local --scope=tenantIt prompts for the value and writes it to .kindgi/secrets.env, which your
app doesn't load. In a script, pipe it
in with --from-stdin. To replace a key that's already set, add
--write-mode=add-version.
A provider's key is used by that provider only. No tool, MCP endpoint or
webhook endpoint gets a secret that a provider registration names, in any
environment: a tool that calls a model itself
declares a key of its own.
Registering a provider whose key a tool or an endpoint already uses is
refused (provider-key-in-use), and the error names each one: store the
provider's key under a name of its own, then register.
List, change and remove
Section titled “List, change and remove”kindgi providers list # every provider, with its modelskindgi providers list --feature=structured-outputkindgi providers get ollamakindgi providers unregister ollamalist and get show the metadata only, never adapter_config or the key. A
registration can't be edited: registering an id that exists is refused, so
unregister it first, then register the new spec.
Unregistering takes a provider out of list, get and routing at once, and
its id is free to register again; Kindgi keeps the record until a retention
policy on provider ("Removed model providers" in the console) purges it:
Error [not-found]: No provider registered with id "ollama"Error [conflict]: Provider "ollama" is already registeredCheck a registration
Section titled “Check a registration”A registration stored before 0.1.5 wasn't checked when it was registered.
GET /v1/providers/{providerId}/check (providers.check(id) in the clients)
runs the same check over a stored one. Here, an Ollama registration whose
api doesn't fit:
{"providerId":"ollama","adapterId":"@kindgi/adapter-model-openai-compat","checked":true,"issues":[{"path":"/adapter_config/api","message":"adapter_config.api must be one of responses, chat-completions."}]}An empty issues means the runtime can build the provider. checked is
false for an adapter this runtime has no check for, such as dev-echo's.
kindgi doctor asks this for every provider and names each problem
(Check this machine). To fix one,
unregister it and register it again with a spec that fits.