# Set an agent's budget

> Cap the model calls, the spend and the time of each agent turn, and handle a turn that runs out.

This guide caps the model calls, the spend and the time of each agent turn, and handles a turn that runs out.

A budget caps each turn of an agent: how many model calls it makes, what they cost, and how long it takes. A turn that runs out fails, and the run says which limit it hit.

## 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`, from [Write an agent](https://docs.kindgi.com/v0.1.8/guides/agents/write-an-agent.md).
- **A model registered with prices,** for the cost budget. A model with a price of zero never uses it.
- **For the app's half:** Node 22.12 or later, Python 3.11 or later, or Java 17 or later, with Kindgi 0.1's client, and `KINDGI_API_URL` and `KINDGI_API_TOKEN` set.

## Set the budget

Ask your coding agent: "Limit what each turn of my agent can spend, and how long it can take." Kindgi's skills in your pack tell it how ([How it knows Kindgi](https://docs.kindgi.com/v0.1.8/start/coding-agents.md)). For reference, it adds `budget` to the agent's definition:

- **TypeScript**

  ```ts excerpt
  // in agents/order-desk/index.ts
    budget: { maxSteps: 6, maxCostUsd: 0.05, maxWallMs: 60_000 },
  ```

- **Python**

  ```python excerpt
  # in agents/order_desk.py
      budget={"maxSteps": 6, "maxCostUsd": 0.05, "maxWallMs": 60_000},
  ```

- **Java**

  ```java excerpt
  // in src/main/java/acme/agents/OrderDesk.java
        .set("budget", Map.of("maxSteps", 6, "maxCostUsd", 0.05, "maxWallMs", 60_000))
  ```

- **Scala**

  ```scala excerpt
  // in src/main/scala/acme/agents/OrderDesk.scala
      .set("budget", Map("maxSteps" -> 6, "maxCostUsd" -> 0.05, "maxWallMs" -> 60000))
  ```

| Field        | Caps                                   | Default |
| ------------ | -------------------------------------- | ------- |
| `maxSteps`   | Model calls in the turn.               | 8       |
| `maxCostUsd` | The turn's model spend, in US dollars. | none    |
| `maxWallMs`  | The turn's time, in milliseconds.      | 120000  |

Each turn of the agent now stops at the first limit it reaches.

A **step** is one model call. The tool calls the model asks for run between steps, so a turn that looks an order up and then answers takes two. Repairing a [typed answer](https://docs.kindgi.com/v0.1.8/guides/agents/typed-answer.md#when-the-answer-doesnt-fit) takes a step too.

Cost is what the models' registered prices make of the tokens each call used, as [Models](https://docs.kindgi.com/v0.1.8/guides/models.md) describes. A model registered with a price of zero never uses the cost budget. Cost is an estimate from the vendor's published prices: your provider's invoice is what you pay.

## When a turn runs out

Each limit fails the turn with its own error. `kindgi runs start` prints it and exits `1`. The run, on stdout, has `"status": "failed"` and the error in its `failureMessage`. These are from `acme.order-desk` with one limit set low at a time, on a local `llama3.1` registered with a price.

**Steps.** After a model call that asks for more tool calls, with no steps left:

```text
Error [budget-exceeded]: Agent turn steps budget exceeded (limit 1, observed 1)
```

**Cost.** Checked after each model call, against the turn's total so far:

```text
Error [budget-exceeded]: Agent turn cost budget exceeded (limit 0.0001, observed 0.0001084)
```

**Time.** A timer starts with the turn. When it fires, the model call in flight is cancelled:

```text
Error [agent-turn-aborted]: Agent turn aborted: Request was aborted.
```

`kindgi runs journal <run-id>` shows how far the run got, where `<run-id>` is the failed run's id.

> **Caution: The last step has happened**
>
> The limits are checked after a model call and its tool calls. So when steps or cost run out, the call that crossed the limit was made and paid for, and the tools it asked for have run. The cost error shows it: `observed 0.0001084` against a limit of `0.0001`. Set `maxCostUsd` with one call's worth of room.

Leave room for real models: one call to a hosted model takes a second or more, and a model on your own machine can take ten. A turn with a tool call makes two calls.

## Handle it in your app

Your app can tell a turn that ran out from other failures. The failed turn comes back as an error, with the limit in its fields. Ask your coding agent: "I want my app to tell when a turn ran out of budget, and which limit it hit." For reference, the code looks like this:

- **TypeScript**

  `scripts/budget.ts`

  ```ts
  import { KindgiApiError, createClient } from '@kindgi/sdk/client';

  const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev

  try {
    await kindgi.runs.start({
      agent: 'acme.order-desk',
      input: { userMessage: 'Where is my order A-1001?' },
    });
  } catch (err) {
    if (!(err instanceof KindgiApiError)) throw err;
    const e = err.error;
    if (e.code === 'server' && e.serverCode === 'budget-exceeded') {
      console.log(e.fields?.kind, e.fields?.limit, e.fields?.observed, e.fields?.runId);
    } else {
      throw err;
    }
  }
  ```

  ```text
  steps 1 1 2cc56724-92e7-42e8-aefd-14ad5c294d1f
  ```

- **Python**

  `scripts/budget.py`

  ```python
  from kindgi.client import Kindgi, KindgiApiError

  kindgi = Kindgi()  # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
  try:
      kindgi.runs.start(agent="acme.order-desk", input={"userMessage": "Where is my order A-1001?"})
  except KindgiApiError as err:
      if err.server_code == "budget-exceeded":
          print(err.details["kind"], err.details["limit"], err.details["observed"], err.details["runId"])
      else:
          raise
  ```

  ```text
  steps 1 1 3deffc12-053e-43e3-b735-9a415d95645f
  ```

- **Java**

  `src/main/java/com/acme/app/Budget.java`

  ```java
  package com.acme.app;

  import com.kindgi.client.Kindgi;
  import com.kindgi.client.KindgiApiException;
  import com.kindgi.client.models.StartRunBody;
  import java.util.Map;

  public final class Budget {
    public static void main(String[] args) {
      Kindgi kindgi = Kindgi.create(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev

      try {
        kindgi.runs().start(StartRunBody.WithAgent.builder()
            .agent("acme.order-desk")
            .input(Map.of("userMessage", "Where is my order A-1001?"))
            .build());
      } catch (KindgiApiException e) {
        if (!"budget-exceeded".equals(e.serverCode())) throw e;
        Map<String, Object> d = e.details();
        System.out.println(d.get("kind") + " " + d.get("limit") + " " + d.get("observed") + " " + d.get("runId"));
      }
    }
  }
  ```

  ```text
  steps 1 1 e95037bd-6df7-43c9-a706-743270a947b1
  ```

- **Scala**

  `src/main/scala/acme/app/Budget.scala`

  ```scala
  package acme.app

  import com.kindgi.client.{Kindgi, KindgiApiException}
  import com.kindgi.client.models.StartRunBody
  import scala.jdk.CollectionConverters._

  object Budget {
    def main(args: Array[String]): Unit = {
      val kindgi = Kindgi.create() // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev

      try {
        kindgi.runs().start(StartRunBody.WithAgent.builder()
          .agent("acme.order-desk")
          .input(Map("userMessage" -> "Where is my order A-1001?").asJava)
          .build())
      } catch {
        case e: KindgiApiException if e.serverCode == "budget-exceeded" =>
          val d = e.details.asScala
          println(s"${d("kind")} ${d("limit")} ${d("observed")} ${d("runId")}")
      }
    }
  }
  ```

  ```text
  steps 1 1 4eb76089-0d4a-4256-8a2c-4df1c12c7f46
  ```

It prints which limit the turn hit, the limit, what the turn used, and the run's id. A turn that runs out of time has `serverCode` `agent-turn-aborted` instead, with `reason` `timeout`.

## In a flow

An agent step whose turn runs out fails, and the flow run fails with it. The run carries the reason:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=acme.notify-customer --input='{"orderId":"A-1001"}'
  ```

`--flow` is the flow to run, and `--input` its input. It prints the failed run:

```json excerpt
{
  …
  "flowId": "acme.notify-customer",
  …
  "status": "failed",
  …
  "failureMessage": "budget-exceeded: Agent turn cost budget exceeded (limit 0.00001, observed 0.00031)",
  "output": {},
  …
}
```

Here the step's agent, `acme.order-update` from [Give an agent its input](https://docs.kindgi.com/v0.1.8/guides/agents/give-an-agent-input.md#in-a-flow-step), had `budget: { maxCostUsd: 0.00001 }`.

## Next

- [Choose the model an agent uses](https://docs.kindgi.com/v0.1.8/guides/agents/choose-a-model.md)
- [Give an agent a typed answer](https://docs.kindgi.com/v0.1.8/guides/agents/typed-answer.md)
- [See why runs failed](https://docs.kindgi.com/v0.1.8/guides/runs/see-why-runs-failed.md)
