# `ExAthena.Budget`
[🔗](https://github.com/udin-io/ex_athena/blob/v0.7.1/lib/ex_athena/budget.ex#L1)

Usage + cost accounting and budget checks for agent runs.

Aggregates `Usage` (input/output/total tokens) across loop iterations and
computes cost in USD from provider metadata. A run can be capped by
`:max_budget_usd`; the loop tests `Budget.exceeded?/2` before each
iteration and trips `:error_max_budget_usd` when the cap is hit.

## Shape

    %Budget{
      usage: %{input_tokens: _, output_tokens: _, total_tokens: _},
      cost_usd: float | nil,
      started_at: integer,       # monotonic ms
    }

# `t`

```elixir
@type t() :: %ExAthena.Budget{
  cost_usd: float() | nil,
  started_at: integer() | nil,
  usage: usage_map()
}
```

# `usage_map`

```elixir
@type usage_map() :: %{
  optional(:input_tokens) =&gt; non_neg_integer(),
  optional(:output_tokens) =&gt; non_neg_integer(),
  optional(:total_tokens) =&gt; non_neg_integer()
}
```

# `add`

```elixir
@spec add(t(), usage_map() | nil, float() | nil) :: t()
```

Merge a single turn's usage + optional cost into the accumulator.

Missing keys on the incoming usage are treated as 0. Cost additions that
start from `nil` become a float.

# `duration_ms`

```elixir
@spec duration_ms(t()) :: non_neg_integer()
```

Wall-clock milliseconds since budget was opened.

# `exceeded?`

```elixir
@spec exceeded?(t(), float() | nil) :: boolean()
```

Test whether the budget has exceeded an optional cap.

When `max_budget_usd` is `nil`, budget is never exceeded. When non-nil,
returns `true` as soon as `cost_usd` meets or exceeds it.

# `new`

```elixir
@spec new() :: t()
```

New budget accumulator.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
