# Choose the model an agent uses

> Say what an agent's model must support, prefer or require a provider or model, and see which one answered.

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

- **An agent,** in a pack on a runtime such as `kindgi dev`, with Kindgi 0.1's CLI. This guide uses `acme.order-desk`.
- **Providers registered for your tenant,** as [Models](https://docs.kindgi.com/v0.1.9/guides/models.md) shows. Without one, `dev-echo` answers.

## 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](https://docs.kindgi.com/v0.1.9/start/coding-agents.md)). For reference, it adds `capabilities` to the agent's definition. This one needs a model that calls tools:

- **TypeScript**

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

- **Python**

  ```python excerpt
  # in agents/order_desk.py
      capabilities=[{"needs": [{"feature": "tool-use"}]}],
  ```

- **Java**

  ```java excerpt
  // in src/main/java/acme/agents/OrderDesk.java
        .capability(Map.of("needs", List.of(Map.of("feature", "tool-use"))))
  ```

- **Scala**

  ```scala excerpt
  // 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`, `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 use that. On every model, an agent's [typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md) comes from its instructions and the schema at the end of its system message, then a check against the schema and repairs. 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 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

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, 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](https://platform.claude.com/docs/en/about-claude/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](https://docs.kindgi.com/v0.1.9/guides/models/anthropic.md#register-the-preset-again) shows.

## 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:

- **TypeScript**

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

- **Python**

  ```python excerpt
  # in agents/order_desk.py
      capabilities=[{"needs": [{"feature": "tool-use"}]}],
      preferred_provider="anthropic",
      preferred_model="claude-sonnet-5-5",
  ```

- **Java**

  ```java excerpt
  // 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")
  ```

- **Scala**

  ```scala excerpt
  // 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:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run
  ```

- **Python**

  ```sh
  uv run kindgi runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run
  ```

- **Java**

  ```sh
  ./kindgiw runs start --agent=acme.order-desk --input='{"userMessage":"Where is my order A-1001?"}' --dry-run
  ```

- **Scala**

  ```sh
  ./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:

```json excerpt
{
  …
  "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

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:

- **TypeScript**

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

- **Python**

  ```python excerpt
  # in agents/order_desk.py
      capabilities=[{"needs": [{"feature": "tool-use"}, {"models": {"allow": ["claude-sonnet-5-5"]}}]}],
  ```

- **Java**

  ```java excerpt
  // 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"))))))
  ```

- **Scala**

  ```scala excerpt
  // 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:

```text
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. 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'] }`:

```json excerpt
{
  "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](https://docs.kindgi.com/v0.1.9/concepts/security.md#what-a-tenant-cant-make-the-server-do), 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`](https://docs.kindgi.com/v0.1.9/guides/models.md#check-a-registration).

## 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](https://docs.kindgi.com/v0.1.9/guides/models/openai-compatible.md) shows. Ask your coding agent: "Rank my local models above the others for my agent." For reference, it adds a weight for `local`:

- **TypeScript**

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

- **Python**

  ```python excerpt
  # in agents/order_desk.py
      capabilities=[{"needs": [{"feature": "tool-use"}], "prefer": [{"feature": "local", "weight": 1}]}],
  ```

- **Java**

  ```java excerpt
  // 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))))
  ```

- **Scala**

  ```scala excerpt
  // 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

`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:

```text excerpt
⚠ 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:

```json excerpt
"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."
  }
]
```

```text excerpt
⚠ 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

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](https://docs.kindgi.com/v0.1.9/guides/observability/trace-an-answer.md) shows.

## Next

- [Models](https://docs.kindgi.com/v0.1.9/guides/models.md)
- [Set an agent's budget](https://docs.kindgi.com/v0.1.9/guides/agents/budgets.md)
- [Give an agent a typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md)
