> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nika.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Cost honesty

> Know what a run can cost BEFORE you run it — floors, ceilings, unpriced truth, and the budget gate that refuses to start.

> The doctrine in one line: **unknown stays unknown**. A cost Nika cannot
> prove is never rendered as `$0.00`, a local model is *unpriced* — your
> compute, your electricity — never « free », and an estimate always says
> which side of the truth it sits on: a **floor** (`≥`) or a **ceiling**
> (`≤`).

## The vocabulary

Every cost surface speaks the same four words:

| Word          | Meaning                                                               | Where you see it                             |
| ------------- | --------------------------------------------------------------------- | -------------------------------------------- |
| **FLOOR**     | the provable minimum — real spend can only be higher                  | `check` COST rung · run totals (`≥ $…`)      |
| **ceiling**   | the provable worst case — real spend can only be lower                | `check` COST rung once every task is bounded |
| **UNBOUNDED** | a task whose spend has no provable limit, with the WHY                | `check` per-task rows                        |
| **unpriced**  | tokens were spent but no catalog price exists (local · unknown model) | run totals (`(N unpriced)`) · `explain`      |

There are exactly two ways a task goes UNBOUNDED, and the rung names
which one:

* `UNBOUNDED — no max_tokens declared` · the model has a price but the
  task has no output limit. **You can fix this one**: declare
  `max_tokens:` and the report upgrades from floor to hard ceiling
  (the check prints that exact hint).
* `UNBOUNDED — no catalog price (local/unknown model)` · the task is
  bounded but the model has no price row. Local models stay here **by
  design** — pricing your own hardware would be invention.

## Before a token: the COST rung

`nika check` audits cost statically, next to the plan and the permits —
the same ladder, every time:

```text theme={"system"}
 ⚠  COST     $0.0000 – $0.0000 FLOOR (unbounded tasks present)
   think  ollama/qwen3.5:4b  UNBOUNDED — no max_tokens declared
 ↳ HINT     [cost] declare `max_tokens` on `think` — the cost report
            becomes a hard ceiling instead of UNBOUNDED
```

Bound every task and the same rung flips to
`✔ COST $0.0001 – $0.0001 worst-case ceiling` with a per-task
`≤N tk · $…` row. `nika explain <file>` narrates the same numbers in
beginner words — *« ≥ \$0.0000 — a FLOOR, not a ceiling · local models:
your compute · tokens unpriced — not « free » »* — and
`nika run --dry-run` carries them onto the plan.

## The budget gate: refusal, not remorse

`--max-cost-usd` is a **block-before-spend** gate, not a post-hoc alarm:

* If the static floor already exceeds the budget, the run **refuses to
  start** (exit 2, before any provider is touched):

  ```text theme={"system"}
  refusing to start: the workflow's unavoidable cost floor $0.040000
  exceeds --max-cost-usd $0.000100 … (nika check shows the envelope)
  ```

* If unbounded work rides along, the gate says so loudly on stderr —
  a budget over work with no ceiling is a promise it names, not one it
  fakes.

* Mid-run, the ledger stops the workflow the moment real spend crosses
  the budget (`NIKA-1704`) — settled tasks stay settled, the trace
  records what was spent.

* The flag itself is guarded: a non-finite value (`NaN`, `inf`) is
  rejected at parse time — a budget that cannot compare is not a budget.

## After the run: the totals stay honest

Every run ends with the same honest line, mock or cloud:

```text theme={"system"}
── 1/1 done · ≥ $0.00 (1 unpriced) · elapsed 0.0s ──
```

The `≥` is load-bearing: it is printed **because** an unpriced task
rode the run. Per-task spend rides the trace itself (`cost_usd` on the
terminal events), so `nika trace show` and the run report read the
recorded ledger — never a summary's opinion.

## Where prices come from — and when they rot

Prices are a **catalog fact with provenance**, not a constant:
`nika check --json` carries the pricing snapshot (source · date ·
hash · derived counts) so a cost claim is traceable to the table that
produced it, and `nika doctor` warns when the snapshot is stale enough
to distrust — an old price table silently *undercounts*, which is the
one direction honesty cannot tolerate. Cache-aware accounting follows
the OpenTelemetry GenAI convention (input includes cache reads), so
exported traces mean the same thing your dashboards expect.

## The workflow-side controls

```yaml theme={"system"}
tasks:
  - id: think
    infer:
      model: ollama/qwen3.5:4b
      prompt: "${{ vars.question }}"
      max_tokens: 600        # the ceiling lever — every infer should carry one
```

* `max_tokens:` per task · turns FLOOR into ceiling.
* `agent:` budgets (`max_turns` · token budgets) bound the loop the
  same way — exhaustion is a named failure, never a silent overrun.
* `nika check` before `nika run`, always: the cost story is part of
  the same pre-flight as permits and secrets.

<Note>
  Local-first corollary: a workflow that runs entirely on `ollama/…`,
  `llamacpp/…` or `vllm/…` reports `$0.00` **with the unpriced count
  attached** — Nika never converts « I don't know the price » into
  « it's free ». That distinction is the whole doctrine.
</Note>
