Cost governance¶
mini-ork meters every dispatch per model at list price and enforces a spend cap in two places: once before a run starts, and once around every individual dispatch. This page explains the levers, what happens when they trip, and how to check what a run actually cost.
The one lever that matters¶
| Variable | Default | Effect |
|---|---|---|
MO_DAILY_BUDGET_USD |
50 |
Rolling 24-hour USD spend cap |
The window is rolling, not a calendar day. The gate sums llm_calls.cost_usd
over the trailing 24 hours and compares it with the cap. Budget returns when
older calls age out — there is no midnight reset.
Raise it for one session by exporting it before the run:
The budget: block in agents.yaml is descriptive
agents.yaml accepts a budget: block (daily_cap_usd, per_run_usd, …),
and the Python SDK client prints it, but no gate reads it.
MO_DAILY_BUDGET_USD is the enforced lever. Treat the YAML block as
documentation of intent, not a control.
Two enforcement points¶
flowchart TD
A[mini-ork run] --> B{trailing 24h spend<br/>>= MO_DAILY_BUDGET_USD?}
B -- yes --> C[exit 75<br/>verdict: blocked<br/>blocked_reason: cost_limit]
B -- no --> D[execute nodes]
D --> E{per-dispatch check}
E -- over cap --> F[refuse dispatch<br/>rc 42<br/>cost_limit row written]
E -- under cap --> G[dispatch to lane]
Before the run¶
mini-ork run refuses to dispatch anything, exits 75 (EX_TEMPFAIL), and
records a blocked verdict with blocked_reason: cost_limit.
This is deliberate. An in-run trip used to fail every node within seconds, and that looked identical to a model that tried and failed. Saying it once, before anything is spent, is the honest failure.
During the run¶
mini_ork.dispatch.llm_dispatch.cost_circuit_open refuses each individual
dispatch, writes a cost_limit refusal row, and returns 42. The scheduler
re-checks the same cap before every epic admission.
If the state database is missing or unreadable, the gate fails open — absent
spend reads as $0.00. A broken database never blocks work.
What a trip looks like¶
A cost cap trip pauses the run rather than destroying it. Resume it after raising the cap:
board resume is the documented verb for a cost-paused run. The run continues
from its last checkpoint; succeeded nodes are not re-executed.
Premium feature knobs¶
Some features carry a cost multiplier above 1.5× the baseline. Those are premium: they are off for a run unless you opt in explicitly.
| Gate | Default | Behaviour |
|---|---|---|
MO_ACCEPT_PREMIUM |
unset (off) | 1 allows premium knobs; otherwise they are dropped |
| multiplier | — | A feature above 1.5× is premium; baseline features keep their defaults |
The check runs server-side in the launch path (mini_ork.web.control.launch_run),
not in a UI, and reports what it dropped as blocked_premium. A caller cannot
smuggle a premium knob past it by posting raw environment to the run API.
When a premium knob is dropped, the run still starts — it just runs the baseline path. That is why a stray click cannot triple a bill.
Per-run orchestrator budget¶
The detached per-run watcher (mini-ork orchestrator watch <run_id>) makes no
model calls but watches a node's cost and duration between boundaries.
| Variable | Default | Effect |
|---|---|---|
MO_RUN_ORCHESTRATOR |
off | Enables the per-run orchestrator |
MO_ORCH_BUDGET_USD |
— | Budget the orchestrator shares with the run |
MO_WATCH_COST_USD |
1.0 |
Per-node cost overrun threshold |
MO_WATCH_STALL_SECONDS |
300 |
Stall detection |
MO_WATCH_LONG_NODE_SECONDS |
900 |
Long-node detection |
Overruns are written as cost_overrun events in run_events. With
MO_ORCH_WATCH_AUTONOMY set to steer (the default), the watcher can steer the
run rather than only record the event.
Recursive self-improvement caps¶
The recursive loop has its own budget gates, separate from the daily cap:
| Variable | Default | Effect |
|---|---|---|
MO_RECURSION_BUDGET_CAP_TOTAL_USD |
150.0 |
Total spend across iterations |
MO_RECURSION_BUDGET_CAP_PER_ITER_USD |
unset | Per-iteration cap |
MO_OPTIMIZER_BUDGET |
"4" |
GEPA optimizer budget |
MO_GATE_HACKABILITY_BUDGET_USD |
0.50 |
Per-audit proposer budget |
Inspecting spend¶
Spend is recorded per call, so it is always queryable:
sqlite3 .mini-ork/state.db \
"SELECT date(created_at,'unixepoch') AS day, round(sum(cost_usd),2) AS usd
FROM llm_calls GROUP BY day ORDER BY day DESC LIMIT 7;"
Per-run:
sqlite3 .mini-ork/state.db \
"SELECT run_id, round(sum(cost_usd),4) AS usd, count(*) AS calls
FROM llm_calls WHERE run_id='<run-id>' GROUP BY run_id;"
The board document also carries per-run cost:
And the IDE run page shows the same numbers, with per-node attribution.
Choosing lanes to control cost¶
Cost scales with the lane each role uses. A practical default:
| Role | Cheap lane | Why |
|---|---|---|
planner |
mid-tier | Plan quality matters, but the plan is small |
implementer |
strong | This is where correctness is decided |
reviewer |
strong | Cross-model dissent catches what a same-family judge misses |
reflector |
cheap | Pattern extraction, not creative work |
verifier |
mid-tier | Verification is mostly mechanical |
Change lanes in agents.yaml; see
Providers and lanes.
Lane health is a separate gate¶
A run can fail before spending anything because a lane is dead — an expired key, an exhausted balance, a model that no longer exists.
| Variable | Default | Effect |
|---|---|---|
MO_LANE_HEALTH_GATE |
"1" |
Probe every model the DAG will dispatch; abort with rc 75 / lane_unhealthy if any is dead |
MO_LANE_REQUIRE_VERIFICATION |
"0" |
1 also aborts when a lane could not be verified (lane_unverified) |
Lane health is not a cost control, but it belongs here because it is the other gate that stops a run before any money is spent.
Practical advice¶
- Set
MO_DAILY_BUDGET_USDto what you are willing to lose in a day, not to what a run should cost. - Use
boardafter the first few runs to see which role dominates spend. - Keep the reviewer on a strong, different model family. Downgrading it saves cents and costs verdicts.
- Dry-run the shape of a large plan before executing it:
MO_DRY_RUN=1.