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:
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: declaremax_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.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):
-
Since 0.99 that pre-start refusal prices the effective model β
--modeloverride included. On 0.98.x the override was only guarded mid-run; upgrading closes that gap. - 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:β₯ 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
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 checkbeforenika 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.