Skip to content

Choose the model an agent uses

An agent doesn't name a model. Its capabilities say what the model must support, and when a turn starts, Kindgi picks one from the providers registered for your tenant (Models shows how to register them). You can prefer a provider or a model, or require one.

// in agents/order-desk/index.ts
capabilities: [{ needs: [{ feature: 'tool-use' }] }],

Each entry in needs is a hard requirement. A model qualifies only if it meets all of them:

  • { feature: … }: a feature the model lists in its registration: tool-use, parallel-tool-use, structured-output, long-context, thinking, vision and others. An agent that calls tools needs tool-use. structured-output says the model can follow a JSON schema itself; Kindgi doesn't send it the schema yet. An agent's typed answer comes from its instructions, then a check against the schema and repairs, on every model, so needing structured-output only narrows which models qualify.
  • { contextWindow: { op: '>=', value: 500000 } }: a context window.
  • { region: 'on-prem' }: a provider registered with this region.
  • { models: { allow: […] } }, { providers: { allow: […] } }: only these models or providers (deny excludes instead).

needs: [] takes any model. Only the first entry of capabilities picks the turn's model.

kindgi capabilities list --table lists the features an agent can require, what each one means, and which of your providers' models have it. kindgi capabilities get feature:tool-use gives one.

  1. It keeps every registered model that meets all the needs.
  2. A fallback provider, such as dev-echo, is considered only when no other model qualifies.
  3. It ranks what's left: the agent's preferred provider and model first, then the weights in its capability's prefer, then by provider id, with each provider's default model before its others, then by model name.

The last step decides more often than you might expect. Each preset names a mid-priced default (claude-sonnet-5-5 for anthropic), so with one preset registered, an agent with no preference gets its default. A provider registered without a defaultModel falls back to its first model by name, which can be its dearest. Prefer or require the model you mean.

A turn routed to a model its provider has retired fails, so an agent that prefers or requires one needs a new version naming another model. Anthropic lists each Claude model's status, and any retirement date, on its model deprecations page. An Anthropic provider registered before 0.1.4 has no default: register it again (see Connect Anthropic).

// in agents/order-desk/index.ts
capabilities: [{ needs: [{ feature: 'tool-use' }] }],
preferredProvider: 'anthropic',
preferredModel: 'claude-sonnet-5-5',

A dry run shows the pick without calling the model:

Terminal window
kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run
{
…
"status": "completed",
"dryRun": true,
…
"output": {
…
"provider": { "id": "anthropic", "model": "claude-sonnet-5-5" },
…
}
}
  • Both set: that model of that provider is ranked first.
  • Only preferredModel: that model is ranked first, from whichever provider serves it.
  • Only preferredProvider: its models are ranked first.

preferredProvider is a provider id (anthropic) and preferredModel a model name (claude-sonnet-5-5); a combined anthropic/claude-sonnet-5-5 matches nothing. A preference never excludes: if the provider isn't registered, or its models don't meet the needs, the turn takes the next model in line.

To make sure a turn runs on one model, put it in the needs:

// in agents/order-desk/index.ts
capabilities: [{ needs: [{ feature: 'tool-use' }, { models: { allow: ['claude-sonnet-5-5'] } }] }],

When no registered model qualifies, the turn fails instead of falling back to another one:

Error [capability-routing-failed]: No registered provider satisfies the capability declaration

The run's journal says why, model by model, in the failed setup step (kindgi runs journal <run-id>, with the run's id from the JSON that kindgi runs start prints). For models: { allow: ['gpt-5'] }:

{
"code": "capability-unsatisfiable",
"message": "No registered provider satisfies the capability declaration",
"reasons": [
{
"requirement": "models{allow:[gpt-5]deny:[]}",
"satisfyingProviders": [],
"rejectingProviders": [
{ "id": "anthropic/claude-opus-5-5", "reason": { "code": "model-not-in-allowlist", "message": "model not on allow list", … } },
…
{ "id": "ollama/llama3.1", "reason": { "code": "model-not-in-allowlist", "message": "model not on allow list", … } }
]
}
]
}

prefer adds a weight for each match, against a model's features or a provider's attributes, and ranks the higher total first. A weight can be negative. With a local model registered with "attributes": ["local"] (see Connect an OpenAI-compatible endpoint):

// in agents/order-desk/index.ts
capabilities: [{ needs: [{ feature: 'tool-use' }], prefer: [{ feature: 'local', weight: 1 }] }],

the dry run picks { "id": "ollama", "model": "llama3.1" } over the Anthropic models.

kindgi dev gives your tenant dev-echo, a stand-in that needs no key. It calls the agent's first tool with {"message": <the user message>} and answers Tool responded: <the result>; an agent with no tools gets its user message back. Its text answers start with a line that says what it is:

⚠ dev-echo isn't a real model: it only repeats what it's given. Add an LLM provider key (Anthropic, OpenAI, Gemini, Groq, OpenRouter…) to get real answers.
Tool responded: {"echo":"Hi there","echoedAt":"2026-10-07T13:16:58.923Z","characterCount":8}

It's a fallback: once a registered model qualifies, it never answers.

When it does answer, the turn says so, in its result and on the CLI's stderr:

"warnings": [
{
"code": "fallback-provider",
"message": "Answered by \"dev-echo\", a fallback provider: no other registered provider satisfies agent \"acme.echo-agent\"."
},
{
"code": "dev-echo-not-a-model",
"message": "dev-echo answered, and it isn't a real model: it only repeats what it's given. Add an LLM provider key: kindgi secrets set <KEY> --env=local --scope=tenant (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY…), then kindgi providers register --preset=<anthropic|openai|gemini-api|groq|openrouter>. kindgi providers presets lists them."
}
]
⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. Add an LLM provider key: kindgi secrets set <KEY> --env=local --scope=tenant (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY…), then kindgi providers register --preset=<anthropic|openai|gemini-api|groq|openrouter>. kindgi providers presets lists them.

If you registered a model and still see this, the agent's needs don't match it: compare its capabilities with the models' features (kindgi providers list). dev-echo has only tool-use, so an agent that needs anything else fails with no model registered.

The turn's result names it in output.provider ({ "id": "anthropic", "model": "claude-sonnet-5-5" }), and each model call in the turn's provenance record names its model and cost: see Trace an answer and its cost.