Skip to main content
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:
WordMeaningWhere you see it
FLOORthe provable minimum — real spend can only be highercheck COST rung · run totals (≥ $…)
ceilingthe provable worst case — real spend can only be lowercheck COST rung once every task is bounded
UNBOUNDEDa task whose spend has no provable limit, with the WHYcheck per-task rows
unpricedtokens 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:
 ⚠  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):
    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:
── 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

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.
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.