Skip to content
Kavachकवच

ask<T>: Choosing the model

● Intermediate5 min

An agent chooses its AI model in code, with model; a single call can choose another, with model:. The key comes from an environment variable, never from a file.

use std.fs

type Verdict {
    /// true if the text is risky
    risky: Bool
}

fn careful() -> Model {
    Model(name: "claude-opus-5-5", max_tokens: 2000)
}

fn quick() -> Model {
    Model(name: "claude-haiku-5-5", thinking: false)
}

agent Reviewer {
    model careful()
    settings AgentSettings(audit: Audit())
    can Ai, Console

    on check(text: String) ! Error {
        const deep = try ask<Verdict>("Is this clause risky? {text}")
        const fast = try ask<Verdict>("Is this a greeting? {text}", model: quick())
        print("risky: {deep.risky}, greeting: {fast.risky}")
    }
}

fn main() ! Error {
    const bot = Reviewer.start()
    try bot.check("The buyer pays all fees.")
    for line in try fs.lines("agents/Reviewer/audit.log") {
        print(line.split("Z ").last() ?? line)
    }
}

The program ran and printed:

kavach run

risky: true, greeting: falseReviewer request#1 ask("Is this clause risky? The buyer pays all fees.")  verdict: dry run  model: claude-opus-5-5 (dry run: no tokens)  result: ok Verdict(risky: true)Reviewer request#1 ask("Is this a greeting? The buyer pays all fees.")  verdict: dry run  model: claude-haiku-5-5 (dry run: no tokens)  result: ok Verdict(risky: false)[dry run] ask("Is this clause risky? The buyer pays all fees.") -> Verdict(risky: true)[dry run] ask("Is this a greeting? The buyer pays all fees.") -> Verdict(risky: false)
src/main.kav: Kavach code, then what running it prints
  • model careful() sets the model for every ask in the agent's handlers.
  • ask<T>(prompt, model: quick()) chooses another for one call. The call's model wins over the agent's.
  • With neither, ask uses claude-haiku-5-5 with answers of at most 4,096 tokens.
  • This ran with kavach run --dry-run, so no model was called and nothing cost.

A Model

Model(name: "claude-opus-5-5", effort: Effort.High, max_tokens: 2000)
syntax: Kavach code

Every setting, its type, what it is when left out and what it does are in one table, on Agents: model, with how agents share one model through a function like careful().

Thinking before answering

Most current models think before they answer, and for a short answer most of the output tokens are that thinking. thinking: false asks the model to answer straight away, as quick() does above. Kavach sends each model the form it takes. A model that always thinks, such as claude-opus-5-5, can't turn it off, and the compiler says so: lower its effort instead.

It saves tokens, but can cost answers. Measured in the agent benchmark, three runs each:

CaseRight, thinking onRight, thinking offOutput tokens, onOutput tokens, off
Small19–20/2019/201,668–1,864556–561
Large30/3029/302,012–2,283334
Complex12/1212/123,079–3,666771–857
Health records10/109/10780–1,366353–508

Off can get an answer wrong that on gets right, so Kavach keeps the model's own default. Turn it off where a short, simple answer is enough, and check the answers. The whole benchmark is in Agents in Practice.

Known when the program is built

The model is worked out when the program is built, so you can always read which model an agent uses. A model that depends on a value while running is refused:

type Verdict {
    risky: Bool
}

fn pick(level: Int) -> Model {
    if level > 1 { Model(name: "claude-opus-5-5") } else { Model(name: "claude-haiku-5-5") }
}

agent Reviewer {
    model pick(2)
    can Ai

    on check(text: String) -> Verdict ! Error {
        try ask<Verdict>(text)
    }
}

The compiler refused the program:

kavach run model-known.kav

error: a model is chosen when the program is built: write `Model(name: "…")` with plain values, or call a function with no parameters that gives one, such as `careful()` (spec 8.4)
model-known.kav: Kavach code, then what running it prints

Choosing it in kavach.toml, as before, now says where it moved:

[package]
name = "reviews"
version = "0.1.0"

[ai]
model = "claude-sonnet-5-5"

The compiler refused the program:

error: `model` in `[ai]` moved into the code: give the agent `model Model(name: "…")`, or a call `model:` (spec 8.4, decision 0056)
kavach.toml: TOML code, then what running it prints

[ai] keeps only the provider:

KeyValuesDefault
provider"anthropic""anthropic"

The key and the address

  • The key is read from ANTHROPIC_API_KEY, or from the variable a Model's api_key_env names. Without it, ask fails with NoKey, naming the variable.
  • A Model's api_url sends its calls to another address, such as another account, a proxy or a gateway. KAVACH_AI_URL does the same for every call without one, such as a test server.
  • An answer cut off at max_tokens fails with Failed, without asking again.

In the audit log

Each ask an agent makes is in its audit log with the model it used. In a dry run, as in the output above, the line names the model a real run would use, so you can check each call's model before paying for one. In a real run it gives the tokens too, so what each agent costs can be read from its log:

2026-11-04T22:40:12Z Reviewer request#3 ask("Review this clause: …")
  verdict: allowed
  model: claude-opus-5-5, tokens: 412 in, 96 out
  result: ok Verdict(risky: true, reason: "…")
audit.log, as spec 8.6 shows it: Text code