# `ClaudeWrapper.Budget`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/budget.ex#L1)

Cumulative USD budget tracker with threshold callbacks.

A budget process accumulates cost across turns and fires
caller-supplied callbacks when configurable thresholds are first
crossed. When a `:max_usd` ceiling is set, `check/1` returns
`{:error, %ClaudeWrapper.Error{kind: :budget_exceeded}}` once the
running total is at or above the ceiling, giving callers a hard stop
before the next CLI invocation.

This is the Elixir port of the Rust crate's `BudgetTracker` /
`BudgetBuilder`. The Rust type is an `Arc<Mutex<...>>` handle shared
across clones; the idiomatic Elixir equivalent is a `GenServer` whose
pid (or registered name) is the shared handle. It mirrors the
process-based style of `ClaudeWrapper.SessionServer` and
`ClaudeWrapper.DuplexSession`.

It is self-contained: `ClaudeWrapper.Session` and
`ClaudeWrapper.SessionServer` are not aware of it. Drive it yourself
from a multi-turn loop -- record each turn's `cost_usd`, then
`check/1` before the next turn.

## Threshold semantics

  * `:warn_at_usd` -- `:on_warning` fires the first time the running
    total is at or above this value, and never again until `reset/1`.
  * `:max_usd` -- `:on_exceeded` fires the first time the running
    total is at or above this value, and `check/1` returns
    `{:error, %ClaudeWrapper.Error{kind: :budget_exceeded}}` from then
    on.

Both thresholds are "at or above" (`>=`), so a record that lands the
total exactly on the threshold crosses it. Non-positive and
non-finite costs are ignored. `reset/1` clears the total and re-arms
both callbacks so they can fire again.

## Usage

    {:ok, budget} =
      ClaudeWrapper.Budget.start_link(
        max_usd: 5.00,
        warn_at_usd: 4.00,
        on_warning: fn total -> IO.puts("warning: $#{total} spent") end,
        on_exceeded: fn total -> IO.puts("budget hit: $#{total}") end
      )

    # In a multi-turn loop, after each turn:
    :ok = ClaudeWrapper.Budget.record(budget, result.cost_usd)

    case ClaudeWrapper.Budget.check(budget) do
      :ok -> :keep_going
      {:error, %ClaudeWrapper.Error{kind: :budget_exceeded, reason: %{total_usd: total, max_usd: max}}} -> {:halt, total, max}
    end

    ClaudeWrapper.Budget.total(budget)
    #=> 4.32

# `callback`

```elixir
@type callback() :: (float() -&gt; any())
```

Callback invoked when a threshold is first crossed. Receives the
running total (in USD) at the moment of the crossing.

# `option`

```elixir
@type option() ::
  {:max_usd, float() | nil}
  | {:warn_at_usd, float() | nil}
  | {:on_warning, callback() | nil}
  | {:on_exceeded, callback() | nil}
  | {:name, GenServer.name()}
  | GenServer.option()
```

# `server`

```elixir
@type server() :: GenServer.server()
```

A running budget process: its pid or registered name.

# `check`

```elixir
@spec check(server()) :: :ok | {:error, ClaudeWrapper.Error.t()}
```

Check the budget against the configured ceiling.

Returns `{:error, %ClaudeWrapper.Error{kind: :budget_exceeded}}` (with
`:reason` `%{total_usd: total, max_usd: max}`) when the running total
is at or above `:max_usd`, and `:ok` otherwise (including when no
ceiling is set).

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `max_usd`

```elixir
@spec max_usd(server()) :: float() | nil
```

Configured ceiling in USD, or `nil` when none is set.

# `record`

```elixir
@spec record(server(), number() | nil) :: :ok
```

Record an additional cost in USD.

Fires `:on_warning` the first time the running total reaches
`:warn_at_usd`, and `:on_exceeded` the first time it reaches
`:max_usd`. Non-positive and non-finite costs are ignored, and a `nil`
cost is a no-op (so recording a `%ClaudeWrapper.Result{}`'s `cost_usd`
directly is safe -- it is `nil` on an interrupted/cancelled turn).

Returns `:ok`.

# `remaining`

```elixir
@spec remaining(server()) :: float() | nil
```

Remaining budget in USD.

Returns `nil` when no `:max_usd` ceiling is set. Otherwise returns
`max - total`, clamped at `0.0` once the ceiling is reached.

# `reset`

```elixir
@spec reset(server()) :: :ok
```

Clear the running total and re-arm both callbacks.

After a reset the total is `0.0` and `:on_warning` / `:on_exceeded`
can fire again the next time their thresholds are crossed.

Returns `:ok`.

# `start_link`

```elixir
@spec start_link([option()]) :: GenServer.on_start()
```

Start a budget tracker.

## Options

  * `:max_usd` - Hard ceiling in USD. Once the running total reaches
    this value, `check/1` returns
    `{:error, %ClaudeWrapper.Error{kind: :budget_exceeded}}`. `nil`
    (default) means no ceiling.
  * `:warn_at_usd` - Warning threshold in USD. `:on_warning` fires
    the first time the running total reaches this value. `nil`
    (default) means no warning.
  * `:on_warning` - 1-arity function fired once when `:warn_at_usd`
    is crossed. Receives the running total.
  * `:on_exceeded` - 1-arity function fired once when `:max_usd` is
    crossed. Receives the running total.
  * `:name` - Register the process with a name.

Also accepts standard `GenServer.start_link/3` options.

# `total`

```elixir
@spec total(server()) :: float()
```

Cumulative cost recorded so far, in USD.

# `warn_at_usd`

```elixir
@spec warn_at_usd(server()) :: float() | nil
```

Configured warning threshold in USD, or `nil` when none is set.

---

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