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)
model careful()sets the model for everyaskin 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,
askusesclaude-haiku-5-5with 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)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:
| Case | Right, thinking on | Right, thinking off | Output tokens, on | Output tokens, off |
|---|---|---|---|---|
| Small | 19–20/20 | 19/20 | 1,668–1,864 | 556–561 |
| Large | 30/30 | 29/30 | 2,012–2,283 | 334 |
| Complex | 12/12 | 12/12 | 3,079–3,666 | 771–857 |
| Health records | 10/10 | 9/10 | 780–1,366 | 353–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)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)[ai] keeps only the provider:
| Key | Values | Default |
|---|---|---|
provider | "anthropic" | "anthropic" |
The key and the address
- The key is read from
ANTHROPIC_API_KEY, or from the variable aModel'sapi_key_envnames. Without it,askfails withNoKey, naming the variable. - A
Model'sapi_urlsends its calls to another address, such as another account, a proxy or a gateway.KAVACH_AI_URLdoes the same for every call without one, such as a test server. - An answer cut off at
max_tokensfails withFailed, 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: "…")