Choose the model an agent uses
This guide says what an agent's model must support, prefers or requires a provider or a model, and shows which one answered.
An agent doesn't name a model. Its capabilities say what the model must
support. When a turn starts, Kindgi picks one from the providers registered
for your tenant. You can prefer a provider or a model, or require one.
Before you start
Section titled “Before you start”- An agent, in a pack on a runtime such as
kindgi dev, with Kindgi 0.1's CLI. This guide usesacme.order-desk. - Providers registered for your tenant, as Models
shows. Without one,
dev-echoanswers.
What the model must support
Section titled “What the model must support”Ask your coding agent: "My agent needs a model that can call tools."
Kindgi's skills in your pack tell it how
(How it knows Kindgi). For reference, it
adds capabilities to the agent's definition. This one needs a model that
calls tools:
// in agents/order-desk/index.ts capabilities: [{ needs: [{ feature: 'tool-use' }] }],# in agents/order_desk.py capabilities=[{"needs": [{"feature": "tool-use"}]}],// in src/main/java/acme/agents/OrderDesk.java .capability(Map.of("needs", List.of(Map.of("feature", "tool-use"))))// in src/main/scala/acme/agents/OrderDesk.scala .capability(Map("needs" -> Seq(Map("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,visionand others. An agent that calls tools needstool-use.structured-outputsays the model can follow a JSON schema itself; Kindgi doesn't use that. On every model, an agent's typed answer comes from its instructions and the schema at the end of its system message, then a check against the schema and repairs. So needingstructured-outputonly narrows which models qualify.{ contextWindow: { op: '>=', value: 500000 } }: a context window.{ region: 'on-prem' }: a provider registered with thisregion.{ models: { allow: […] } },{ providers: { allow: […] } }: only these models or providers.denyexcludes them 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.
The agent's turns now run only on a model that meets its needs.
How Kindgi picks
Section titled “How Kindgi picks”- It keeps every registered model that meets all the needs.
- A fallback provider, such as
dev-echo, is considered only when no other model qualifies. - 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, such as 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 without a default falls back to its first model by name too. Register it again, as Connect Anthropic shows.
Prefer a provider or a model
Section titled “Prefer a provider or a model”A preference ranks a provider or a model first, without excluding the others. Ask your coding agent: "Prefer Claude Sonnet 5.5 for my agent." For reference, it adds the preference to the agent's definition:
// in agents/order-desk/index.ts capabilities: [{ needs: [{ feature: 'tool-use' }] }], preferredProvider: 'anthropic', preferredModel: 'claude-sonnet-5-5',# in agents/order_desk.py capabilities=[{"needs": [{"feature": "tool-use"}]}], preferred_provider="anthropic", preferred_model="claude-sonnet-5-5",// in src/main/java/acme/agents/OrderDesk.java .capability(Map.of("needs", List.of(Map.of("feature", "tool-use")))) .set("preferredProvider", "anthropic") .set("preferredModel", "claude-sonnet-5-5")// in src/main/scala/acme/agents/OrderDesk.scala .capability(Map("needs" -> Seq(Map("feature" -> "tool-use")))) .set("preferredProvider", "anthropic") .set("preferredModel", "claude-sonnet-5-5")A dry run shows the pick without calling the model:
pnpm exec kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-runuv run kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run--dry-run runs the turn without its model call. Its output names the
provider and model the turn would use:
{ … "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, such as anthropic, and
preferredModel a model name, such as 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.
Require a model
Section titled “Require a model”To make sure a turn runs on one model, ask your coding agent: "My agent must only run on Claude Sonnet 5.5." For reference, it puts the model in the needs:
// in agents/order-desk/index.ts capabilities: [{ needs: [{ feature: 'tool-use' }, { models: { allow: ['claude-sonnet-5-5'] } }] }],# in agents/order_desk.py capabilities=[{"needs": [{"feature": "tool-use"}, {"models": {"allow": ["claude-sonnet-5-5"]}}]}],// in src/main/java/acme/agents/OrderDesk.java .capability(Map.of("needs", List.of( Map.of("feature", "tool-use"), Map.of("models", Map.of("allow", List.of("claude-sonnet-5-5"))))))// in src/main/scala/acme/agents/OrderDesk.scala .capability(Map("needs" -> Seq( Map("feature" -> "tool-use"), Map("models" -> Map("allow" -> Seq("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 declarationThe run's journal says why, model by model, in the failed setup step. Read
it with kindgi runs journal <run-id>, where <run-id> is the id in 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", … } } ] } ]}A provider registered for the tenant that the runtime couldn't set up, such as
one whose
settings the deployment no longer allows,
is never a candidate. The reasons name it under provider set up, by its
provider id alone, with the code provider-setup-failed. The error's message
gives the first one's why, and counts the rest. A registration's settings can
be checked at any time with
GET /v1/providers/{providerId}/check.
Rank with weights
Section titled “Rank with weights”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. Here a local model is registered with "attributes": ["local"], as
Connect an OpenAI-compatible endpoint
shows. Ask your coding agent: "Rank my local models above the others for my
agent." For reference, it adds a weight for local:
// in agents/order-desk/index.ts capabilities: [{ needs: [{ feature: 'tool-use' }], prefer: [{ feature: 'local', weight: 1 }] }],# in agents/order_desk.py capabilities=[{"needs": [{"feature": "tool-use"}], "prefer": [{"feature": "local", "weight": 1}]}],// in src/main/java/acme/agents/OrderDesk.java .capability(Map.of( "needs", List.of(Map.of("feature", "tool-use")), "prefer", List.of(Map.of("feature", "local", "weight", 1))))// in src/main/scala/acme/agents/OrderDesk.scala .capability(Map( "needs" -> Seq(Map("feature" -> "tool-use")), "prefer" -> Seq(Map("feature" -> "local", "weight" -> 1))))With it, the dry run picks { "id": "ollama", "model": "llama3.1" } over the
Anthropic models.
dev-echo, the fallback
Section titled “dev-echo, the fallback”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, which
kindgi providers list shows. dev-echo has only tool-use, so an agent
that needs anything else fails with no model registered.
See which model answered
Section titled “See which model answered”The turn's result names it in output.provider, such as
{ "id": "anthropic", "model": "claude-sonnet-5-5" }. Each model call in the
turn's provenance record names its model and cost, as
Trace an answer and its cost shows.