# Minima > A recommendation engine for LLM model routing. Cuts token spend without losing quality — plus the minima harness, a cost-aware terminal coding agent. import { PageHeader, Warning } from '@components' ## Python Client SDK The `minima_client` package is the official thin, typed Python client for the hosted Minima API, plus an optional zero-code intake helper. ### Install ```bash pip install minima-cli ``` The SDK is published on PyPI as part of [**`minima-cli`**](https://pypi.org/project/minima-cli/) — the Python package that ships the `minima_client` SDK and the Minima server tooling. It does **not** include the `minima` terminal agent: the CLI is a native binary installed with [Homebrew](/harness/installation) (`brew install mubit-ai/minima/minima`). Import the SDK as `minima_client`: ```python from minima_client import MinimaClient, AsyncMinimaClient, MinimaError, autocapture ``` ### Clients Both `MinimaClient` (sync) and `AsyncMinimaClient` (async) share the same surface. ```python from minima_client import MinimaClient with MinimaClient("https://api.minima.sh", api_key="mbt_…", timeout=10.0) as minima: rec = minima.recommend("Summarize this incident report into 3 bullets.", cost_quality_tradeoff=3) print(rec.recommended_model.model_id) ``` * `base_url` — the Minima API base URL (`https://api.minima.sh`). * `api_key` — your **Mubit** API key (`mbt_…`), sent as `Authorization: Bearer ` and passed through to Mubit. Required. * `timeout` — HTTP timeout in seconds. The async client mirrors every method with `await`; use it inside FastAPI/async apps: ```python async with AsyncMinimaClient("https://api.minima.sh", api_key="mbt_…") as minima: rec = await minima.recommend(task) ``` ### `recommend(task, *, ...)` Returns a `RecommendResponse`. ```python rec = minima.recommend( task, # str | TaskInput | dict cost_quality_tradeoff=5.0, # 0..10 constraints=None, # Constraints | None user_id=None, namespace=None, allow_llm_escalation=True, explain=True, baseline_model_id=None, # the model you'd use without Minima -> savings() ) ``` `task` is flexible: ```python minima.recommend("plain prompt text") # str minima.recommend({"task": "…", "task_type": "code"}) # dict from minima_client import TaskInput, Constraints minima.recommend( TaskInput(task="…", difficulty="hard"), constraints=Constraints(min_quality=0.85, max_cost_per_call=0.02), ) ``` ### `recommend_workflow(req)` Takes a `WorkflowRequest`, returns a `WorkflowResponse`. ```python from minima_client import WorkflowRequest, WorkflowStep, TaskInput req = WorkflowRequest( steps=[ WorkflowStep(step_id="extract", task=TaskInput(task="Extract entities from …", task_type="extraction")), WorkflowStep(step_id="reason", task=TaskInput(task="Decide next action given …", task_type="reasoning", difficulty="hard")), ], cost_quality_tradeoff=4, ) wf = minima.recommend_workflow(req) print(wf.total_est_cost_usd, "vs", wf.total_est_cost_if_all_premium) ``` ### `feedback(recommendation_id, chosen_model_id, outcome, **kwargs)` Returns a `FeedbackResponse`. `outcome` is `"success"` | `"partial"` | `"failure"`. Pass realized numbers to power the observed/rescaled cost tiers: ```python minima.feedback( rec.recommendation_id, rec.recommended_model.model_id, "success", quality_score=0.95, input_tokens=180, output_tokens=640, actual_cost_usd=0.0034, latency_ms=2100, verified_in_production=True, idempotency_key="…", # optional ) ``` ### `models(...)`, `strategies(...)`, `health()` ```python catalog = minima.models(provider="anthropic", max_cost=10.0) # ModelsResponse strat = minima.strategies(namespace="team-payments", max_strategies=5) status = minima.health() # dict ``` ### `savings(...)` and `calibration(...)` The measurement layer: what Minima saved you, and whether `predicted_success` is calibrated against your reported outcomes. Both report on your account's decision ledger (see the [API reference](/api-reference/endpoints#get-v1savings) for every field). ```python report = minima.savings(namespace="team-payments", days=30, group_by="task_type") print(report.summary.estimated.savings_vs_premium_usd, # generous baseline report.summary.estimated.savings_vs_declared_usd, # your declared default report.health["feedback_coverage"]) # how much to trust "realized" cal = minima.calibration(days=30) for r in cal.reports: print(r.slice_key, r.n, r.ece, r.ece_shrunk) for flag in cal.drift_flags: print(flag.cluster, flag.model_id, flag.direction) # sustained prediction drift ``` Pass `baseline_model_id` on `recommend()` and realized `actual_cost_usd` on `feedback()` to turn the savings report from an estimate into a measurement. ### Errors Non-2xx responses raise `MinimaError` (which carries the problem+json detail): ```python from minima_client import MinimaError try: rec = minima.recommend(task) except MinimaError as exc: # fall back to a default model ... ``` *** ### Zero-code intake: `autocapture` `minima_client.autocapture` is a thin wrapper over `mubit.learn`. Calling `enable()` pins a learn session to the same memory lane Minima recalls from (`minima:`) and monkeypatches your OpenAI/Anthropic/LiteLLM/Google-GenAI clients, so every LLM call auto-ingests its trace — no code changes at the call site. Requires `mubit-sdk`. ```python from minima_client import autocapture autocapture.enable( api_key="", endpoint="https://api.mubit.ai", namespace="team-payments", user_id="svc-router", ) # ... your normal OpenAI/Anthropic/LiteLLM calls happen here, auto-captured ... # mubit.learn does NOT fabricate a success signal — close the loop explicitly: autocapture.feedback(good=True) # or score in [-1, 1] autocapture.disable() # restore original client behavior ``` **What autocapture does and doesn't do.** Autocapture lands traces + lessons in Minima's lane (enriching the reasoner's memory block and Mubit's reflection), but it does **not** by itself produce the `kind="outcome"` records the deterministic k-NN aggregator scores. To fully close the loop, either call `autocapture.feedback(...)` or send a quality score to `POST /v1/feedback`. Other helpers: ```python autocapture.wrap(client) # enrich one client instead of global patching autocapture.capture(messages, response) # manual ingest for raw HTTP / unsupported libs ``` See the [zero-code intake example](/sdk/examples#5-zero-code-intake). import { PageHeader, Tip } from '@components' ## Concepts ### The problem Minima solves LLM workflows overspend by sending every call to a top-tier model when a cheaper model would do a portion of the work just as well. Token cost is the lever; **model choice is the cheapest knob to turn**. Minima turns that knob, per task, based on what models have actually done on similar tasks before. ### Recommend-only, zero added latency Minima **only recommends**. It does not proxy your call, execute a model, rewrite prompts, cache, or compress. You ask "which model should run this?", it answers, and you run the model yourself in your own stack. Because Minima sits *beside* your call rather than in front of it, it adds **zero latency to the actual LLM request**. The only Minima round-trip is the recommendation lookup (typically \~100–300ms). ### The loop ``` ┌─────────────────────────────────────────────────────────┐ │ │ ▼ │ POST /v1/recommend ──▶ you run the model ──▶ POST /v1/feedback (recall + rank) (your stack) (write outcome, reinforce memory) ▲ │ │ memory gets sharper for next time │ └───────────────────────────────────────────────────────-─┘ ``` 1. **Recommend.** Minima recalls similar past `task → model → outcome` records from Mubit, aggregates each candidate model's empirical success rate, combines it with cost and capability priors, and returns the cheapest model expected to clear a quality bar. 2. **Run it yourself.** Minima hands back a `recommendation_id`; you run the recommended model. 3. **Feed back.** You report the outcome and a quality score. Minima writes the outcome to Mubit, reinforces the exact memories that drove the decision, and (on strong verified-in-production results) promotes a durable lesson. ### Why memory The recommendation engine is **non-parametric k-NN over history**: recall similar past records, aggregate per-model success, pick the cheapest model clearing a threshold. Minima is backed by [Mubit](https://mubit.ai), which provides that substrate — semantic recall over server-side embeddings, per-entry reinforcement with a Bayesian reliability estimate, lesson promotion, and strategy surfacing for explainability. You don't operate any of it; it's part of the hosted service. ### The recommendation algorithm For each request Minima: 1. **Classifies** the task. It uses your `task_type` / `difficulty` hints if given; otherwise a fast heuristic infers them. If the heuristic is uncertain and escalation is allowed, the cheap-LLM reasoner can refine the classification. From this it derives a *task cluster* (e.g. `code:hard`). 2. **Selects candidates.** It starts from the full model catalog, applies your constraint filters (`candidate_models`, `allowed_providers`, `excluded_models`, `require_prompt_caching`, `require_context_window`), pre-ranks by capability prior, and caps to `max_candidates`. 3. **Recalls** similar past outcome records scoped to your account (and `namespace`, if set), with a hard timeout. On timeout or no history, it falls back to the prior-only path. 4. **Aggregates per model.** Each recalled neighbor is weighted by `similarity × reliability × staleness_decay`, then combined into a Beta-smoothed empirical success rate per candidate (so models with no neighbors fall back to their capability prior, not to 0.5). An inverse-propensity weighting step corrects for the selection bias that you've historically sent certain task types to certain models. 5. **Scores** each candidate by combining predicted success with estimated cost (see **Cost-basis tiers** below). The slider sets a quality threshold `τ`. 6. **Optimizes.** Among models predicted to clear `τ`, it recommends the **cheapest** (tie-break: higher success, then higher confidence). If none clear `τ`, it recommends the highest-predicted-success model and warns `no_model_meets_threshold`. A `fallback_model` is chosen as a more reliable retry target. 7. **Escalates** to a cheap-LLM reasoner when evidence is thin or conflicting — see below. ### The cost/quality slider `cost_quality_tradeoff` (0–10, default 5) maps to a quality threshold: ``` τ = τ_min + (cost_quality_tradeoff / 10) × (τ_max − τ_min) ``` with `τ_min = 0.55` and `τ_max = 0.92` by default. **0 means "cheapest model that's acceptable"; 10 means "highest quality regardless of cost".** A request's `min_quality` constraint raises the floor. The slider also shifts the ranking weight between predicted success and normalized cost. ### Cost-basis tiers (estimate → observed → rescaled) The single most important accuracy mechanism. A flat token estimate assumes a fixed output length, so it **ignores reasoning/thinking tokens** — which mis-ranks a model with cheap list prices but heavy internal reasoning. Minima ranks candidates by what they *really* cost. One basis is chosen for the **whole candidate set** so all costs are compared like-for-like (`choose_cost_basis`), preferring the most grounded tier every candidate supports: | Tier | Used when | How cost is computed | Breakdown key | | ------------ | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | | **rescaled** | every candidate has enough observations carrying `output_tokens` | `this_request_input_tokens × input_price + observed_median_output_tokens × output_price` — size-exact **and** reasoning-aware | `rescaled`, `obs_output_tokens` | | **observed** | every candidate has enough realized `cost_usd` observations | robust similarity-weighted **median** of realized `cost_usd` per call | `observed_avg` | | **estimate** | cold start | `input_tokens × input_price + output_tokens × output_price`, using the request's expected tokens or per-task-type defaults | `input`, `output` | A small minimum number of observations per candidate (default 3) gates the observed and rescaled tiers. The chosen basis is reflected in each `RankedModel.est_cost_breakdown`, and the rationale tags the number `obs` (grounded) or `est` (cold). The realized `cost_usd` / `input_tokens` / `output_tokens` come from your `POST /v1/feedback` calls — so the more you feed back, the more the ranking climbs from estimate → observed → rescaled. The **median** (not mean) makes the observed/rescaled tiers robust to outlier calls. The weight is similarity-only (not staleness-decayed) because cost is an objective fact about a model, not a quality signal that should fade. ### Escalation to a cheap-LLM reasoner When deterministic evidence is thin or conflicting, Minima can consult a cheap LLM (an inexpensive reasoning model such as Anthropic Haiku or Gemini Flash). It fires only when `allow_llm_escalation` is true **and** any of: * **thin evidence** — too little recalled history overall, or too few candidate models with any neighbor; * **low confidence** — the recommended model's neighborhood confidence is too low; * **conflict/tie** — the top two candidates' scores are within a small margin. On trigger, Minima builds a memory context block, asks the reasoner to rank the candidates with structured output, and **blends** the reasoner's predicted success with the deterministic one. On any reasoner error or parse failure it falls back to the deterministic result and warns `reasoner_failed`. The reasoner is the explicit slow tier and never touches your real LLM call. `decision_basis` on the response tells you which path won: `memory`, `prior`, or `llm`. ### How it gets better over time | Phase | What's happening | Typical `decision_basis` | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | **Cold start (day 0)** | no history; leans on capability priors and flat estimates; reasoner fires often | `prior` (with `cold_start`) | | **Warming up** | `/feedback` outcomes cross `MIN_N`; cost basis climbs estimate → observed → rescaled; reasoner fires less | mix of `memory` and `prior` | | **Mature** | dense history; most picks are empirical; reflection has promoted durable lessons; selection bias in your routing history has been corrected | mostly `memory` | New accounts start with a baseline of benchmark-derived history so picks are useful from day one; it's progressively dominated by your own `/v1/feedback` outcomes as they accumulate — seeded evidence is explicitly down-weighted and crowded out as live outcomes arrive, and every outcome's influence decays with age (a result from last month counts less than one from yesterday), so the recommender tracks how models behave **now**. ### Measuring it Every recommendation is logged with its counterfactual cost baselines and reconciled with the outcome you report — so the two questions that matter are answerable from your own traffic, not from a benchmark: * **Did it save money?** [`GET /v1/savings`](/api-reference/endpoints#get-v1savings) reports estimated and realized savings against two explicitly-labeled baselines: the most expensive scored candidate (generous) and your own declared default (`baseline_model_id` — honest). It also reports `feedback_coverage`, the share of recommendations that ever received feedback — the number that tells you how much weight the realized figures can bear. * **Are the predictions honest?** [`GET /v1/calibration`](/api-reference/endpoints#get-v1calibration) compares `predicted_success` at decision time against realized outcomes (expected calibration error per task type) and flags sustained drift per `(cluster, model)` — the early-warning signal for a provider silently changing a model. Accounts can additionally opt into a small amount of bounded **exploration** (`selection_policy: epsilon_softmax`): a few percent of picks sample from the *eligible* set (never below your quality bar) instead of always taking the cheapest eligible model, which keeps evidence flowing for under-tried cheap models and makes the logged history statistically sound for off-policy analysis. Default is off — deterministic picks everywhere. ### The learning loop in detail When `POST /v1/feedback` is called: 1. Resolve the `recommendation_id` → the recalled neighbors, cluster, and scope. (Account-scoped: an id from another account resolves to nothing, so accounts can't credit or poison each other.) 2. Upsert one durable **outcome record** per `(task cluster, model)`, carrying `cost_usd`, `input_tokens`, `output_tokens`, and `quality_score`. 3. Credit the exact recalled neighbors that drove the pick, bumping their reinforcement counters and reliability. 4. On a verified-in-production strong success, promote a durable **lesson** that feeds rule promotion. 5. Periodically reflect (after a number of feedbacks, or on any verified-prod failure) to promote run → session → account-level lessons. ### Degradation behavior Minima is designed to keep serving when Mubit is slow or down: | Condition | Behavior | | --------------------------- | ---------------------------------------------------------------------- | | Recall timeout | Prior-only recommendation + `recall_timeout` warning. | | Mubit unavailable | Prior-only + `memory_unavailable`. | | Stale prices | Last-good price snapshot used; `catalog_stale: true` + `prices_stale`. | | Reasoner error | Deterministic result + `reasoner_failed`. | | Reasoner not configured | Escalation surfaced as warning; deterministic result used. | | No models match constraints | `422 NoCandidatesError`. | import { PageHeader, Tip } from '@components' ## Examples Copy-paste recipes against the hosted API, from a single `curl` to a production routing wrapper. Every snippet is self-contained. **Setup.** Set your key once: `export MUBIT_API_KEY=mbt_…`. The Python examples use the [`minima_client`](/sdk/client-sdk) package and read the same key. All requests go to `https://api.minima.sh`. ### 1. Quickstart with curl Exercise the core endpoints with nothing but `curl` and `jq` — the fastest way to confirm your key works. ```bash # Service health and the capabilities handshake (the two keyless endpoints) curl -s https://api.minima.sh/v1/health | jq curl -s https://api.minima.sh/v1/capabilities | jq # A recommendation REC=$(curl -s https://api.minima.sh/v1/recommend \ -H "authorization: Bearer $MUBIT_API_KEY" \ -H 'content-type: application/json' \ -d '{"task":{"task":"Classify this support ticket by urgency.","task_type":"classification"}, "cost_quality_tradeoff":2}') echo "$REC" | jq '.recommended_model.model_id, .decision_basis, .recommendation_id' # Close the loop curl -s https://api.minima.sh/v1/feedback \ -H "authorization: Bearer $MUBIT_API_KEY" \ -H 'content-type: application/json' \ -d "{\"recommendation_id\":$(echo "$REC" | jq .recommendation_id), \"chosen_model_id\":\"claude-haiku-4-5\",\"outcome\":\"success\",\"quality_score\":0.92}" | jq ``` ### 2. The core loop The whole value loop with the Python SDK: recommend → (you run the model) → feedback. Report realized tokens and cost so the cost ranking sharpens. ```python from minima_client import MinimaClient with MinimaClient("https://api.minima.sh", api_key="mbt_…") as minima: rec = minima.recommend( "Summarize this incident report into 3 bullet points.", cost_quality_tradeoff=3, baseline_model_id="claude-opus-4-8", # what you'd use without Minima -> honest savings ) print(rec.recommended_model.model_id, rec.decision_basis) # ... run rec.recommended_model.model_id in your own stack ... minima.feedback( rec.recommendation_id, rec.recommended_model.model_id, "success", quality_score=0.95, input_tokens=1180, output_tokens=320, actual_cost_usd=0.0028, verified_in_production=True, ) ``` ### 3. Constraints and the slider Hard `Constraints` (provider whitelist, quality floor, cost ceiling, deny-list) plus sweeping `cost_quality_tradeoff` from 0→10 to watch Minima walk the cost-vs-quality frontier for the same task. ```python from minima_client import MinimaClient, TaskInput, Constraints task = TaskInput(task="Refactor this 200-line module for readability.", task_type="code") with MinimaClient("https://api.minima.sh", api_key="mbt_…") as minima: rec = minima.recommend( task, constraints=Constraints( allowed_providers=["anthropic", "google"], min_quality=0.8, max_cost_per_call=0.02, ), ) print("constrained pick:", rec.recommended_model.model_id) for cq in (0, 5, 10): r = minima.recommend(task, cost_quality_tradeoff=cq) print(f"slider {cq:>2}: {r.recommended_model.model_id} " f"~${r.recommended_model.est_cost_usd:.4f}") ``` ### 4. Multi-step workflow `POST /v1/recommend/workflow` routes each step of a pipeline independently — a cheap model for classify/extract, a stronger one for the hard reasoning step — and reports total cost versus the all-premium baseline. Each step gets its own `recommendation_id` for per-step feedback. ```python from minima_client import MinimaClient, WorkflowRequest, WorkflowStep, TaskInput req = WorkflowRequest( steps=[ WorkflowStep(step_id="extract", task=TaskInput(task="Extract entities from the email.", task_type="extraction")), WorkflowStep(step_id="reason", task=TaskInput(task="Decide the next action given the entities.", task_type="reasoning", difficulty="hard")), ], cost_quality_tradeoff=4, ) with MinimaClient("https://api.minima.sh", api_key="mbt_…") as minima: wf = minima.recommend_workflow(req) for step in wf.steps: print(step.step_id, "→", step.recommendation.recommended_model.model_id) print(f"total ${wf.total_est_cost_usd:.4f} vs all-premium ${wf.total_est_cost_if_all_premium:.4f}") ``` ### 5. Zero-code intake `minima_client.autocapture` auto-captures your existing LLM calls into Minima's memory with no call-site changes — useful for backfilling history from traffic you already run. It needs a Mubit key for the underlying memory. ```python from minima_client import autocapture autocapture.enable(api_key="", endpoint="https://api.mubit.ai", namespace="team-payments", user_id="svc-router") # ... your normal OpenAI / Anthropic / LiteLLM / Gemini calls run here, auto-captured ... autocapture.feedback(good=True) # learn does NOT fabricate a success signal — close it explicitly autocapture.disable() ``` ### 6. Production routing wrapper The shape you'd ship: recommend a model, run it via the official **Anthropic SDK** (streaming, real token usage), then feed the realized cost/quality back. ```python import anthropic from minima_client import AsyncMinimaClient async def routed_call(minima: AsyncMinimaClient, client: anthropic.AsyncAnthropic, prompt: str, *, cost_quality_tradeoff: float = 4) -> str: rec = await minima.recommend(prompt, cost_quality_tradeoff=cost_quality_tradeoff) model = rec.recommended_model.model_id async with client.messages.stream( model=model, max_tokens=1024, messages=[{"role": "user", "content": prompt}], ) as stream: msg = await stream.get_final_message() text = "".join(b.text for b in msg.content if b.type == "text") cost = rec.recommended_model.est_cost_usd # or compute from msg.usage + your price table await minima.feedback( rec.recommendation_id, model, "success", quality_score=0.95, input_tokens=msg.usage.input_tokens, output_tokens=msg.usage.output_tokens, actual_cost_usd=cost, verified_in_production=True, ) return text ``` ### 7. Measure your savings The measurement loop: declare your default model on `recommend`, report realized cost on `feedback`, then read the ledger. Both baselines are reported side by side — `vs_premium` (generous) and `vs_declared` (your own default; the honest one). ```python from minima_client import MinimaClient with MinimaClient("https://api.minima.sh", api_key="mbt_…") as minima: rec = minima.recommend( "Classify this ticket by urgency.", baseline_model_id="claude-opus-4-8", # the model you'd have used ) # ... run rec.recommended_model.model_id in your stack ... minima.feedback( rec.recommendation_id, rec.recommended_model.model_id, "success", quality_score=0.95, actual_cost_usd=0.0009, # realized, not estimated input_tokens=420, output_tokens=80, ) report = minima.savings(days=30, group_by="task_type") est = report.summary.estimated print(f"saved ${est.savings_vs_declared_usd:.4f} vs your default " f"over {est.n_declared} calls " f"(coverage {report.health['feedback_coverage']:.0%})") cal = minima.calibration(days=30) print("global ECE:", cal.reports[0].ece) # 0 = predictions match reality ``` *** ### Where to go next * The schemas behind every field: [API Reference](/api-reference/endpoints). * Why the cost numbers move the way they do: [Concepts → Cost-basis tiers](/sdk/concepts#cost-basis-tiers-estimate--observed--rescaled). * The full client surface: [Python Client SDK](/sdk/client-sdk). import { PageHeader, Steps, Step, Note, Tip } from '@components' ## Getting Started Minima is a hosted API. There is nothing to install or run — you call the service with an API key, get a model recommendation, run that model in **your own** stack, and report the outcome so the next pick gets sharper. This walks you from a key to a closed feedback loop. ### Prerequisites * **A Mubit API key** (`mbt_…`) — your Mubit data-plane key. Minima passes it through on each request to read and write your `task → model → outcome` history in Mubit; there is no separate Minima key. [Request access](https://minima.sh) if you don't have one yet. * Nothing else. Minima computes its own embeddings server-side — there's no model, database, or runtime for you to operate. **Base URL.** All requests go to `https://api.minima.sh`. The examples below read your Mubit key from a `MUBIT_API_KEY` environment variable — `export MUBIT_API_KEY=mbt_…` before running them. ```bash curl -s https://api.minima.sh/v1/recommend \ -H "authorization: Bearer $MUBIT_API_KEY" \ -H 'content-type: application/json' \ -d '{ "task": {"task": "Summarize this 2-page incident report into 3 bullet points.", "task_type": "summarization"}, "cost_quality_tradeoff": 3 }' | jq ``` You get back a `recommendation_id`, a `recommended_model`, a ranked candidate list, a `fallback_model`, and a `decision_basis` (`memory` | `prior` | `llm`). Minima hands back the pick — it never proxies, executes, or rewrites. Run the recommended model in your own stack with your own provider credentials. Keep the `recommendation_id`; you'll quote it back in the next step. Tell Minima how it went. This is what makes the next recommendation sharper — and it populates the realized cost/token history that powers accurate cost ranking. ```bash curl -s https://api.minima.sh/v1/feedback \ -H "authorization: Bearer $MUBIT_API_KEY" \ -H 'content-type: application/json' \ -d '{ "recommendation_id": "", "chosen_model_id": "claude-haiku-4-5", "outcome": "success", "quality_score": 0.95, "input_tokens": 1180, "output_tokens": 320, "actual_cost_usd": 0.0028, "verified_in_production": true }' | jq ``` **Cold start.** On day one your account has little history, so early picks lean on capability priors (`decision_basis: "prior"`, a `cold_start` warning) and the cheap-LLM reasoner fires more often. As your `/v1/feedback` outcomes accumulate, recommendations shift to `decision_basis: "memory"` and the cost ranking sharpens automatically — see [How it gets better over time](/sdk/concepts#how-it-gets-better-over-time). ### Next steps * Use the typed [Python Client SDK](/sdk/client-sdk) instead of raw `curl`. * Read [Concepts](/sdk/concepts) to understand the slider, the cost-basis tiers, and the escalation path. * Browse the [Examples](/sdk/examples) for constraints, workflows, and a production routing wrapper. * Prefer the terminal? The [Minima CLI](/harness/overview) wraps this whole loop in a coding agent. import { PageHeader, Note, Tip } from '@components' ## CLI usage Besides the interactive TUI, `minima` runs in two non-interactive modes and exposes two subcommands. ### Usage ``` minima [prompt] [--print|--mode json] [options] minima auth sign in to Mubit + provision this repo's project minima config [set|get] manage stored credentials ``` ### Run modes | Mode | Invocation | Behavior | | ------------------------- | ------------------------ | ---------------------------------------------------------- | | **Interactive** (default) | `minima` | Full Ink TUI. See [Interactive TUI](/harness/interactive). | | **One-shot print** | `minima -p "…"` | Runs the prompt, prints the final reply, exits. | | **Event stream** | `minima --mode json "…"` | Streams agent events as JSON lines for scripting. | ```bash # one-shot minima -p "explain this repo" # machine-readable event stream minima --mode json "refactor the config loader" | jq . ``` `--print` / `--mode json` require a prompt argument. Without one, the harness exits with an error. ### Options | Flag | Argument | Meaning | | ------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------- | | `-p`, `--print` | — | One-shot: print the reply and exit. | | `--mode` | `interactive` \| `print` \| `json` | Select the run mode explicitly. | | `--model` | `ID` | Pin a model, bypassing routing. | | `--provider` | `NAME` | Provider for a pinned `--model` (registers unknown models on the fly). | | `--thinking` | `off` \| `minimal` \| `low` \| `medium` \| `high` \| `xhigh` | Extended-thinking level for models that support it. | | `--offline` | — | Bypass Minima routing entirely. | | `-t`, `--tools` | `LIST` | Comma-separated tool allowlist (only these run). | | `-xt`, `--exclude-tools` | `LIST` | Comma-separated tool denylist. | | `-nt`, `--no-tools` | — | Disable all tools. | | `-b`, `--budget` | `USD` | Session budget with graduated warnings at 50/75/90/100%. | | `--budget-enforce` | — | Refuse new runs once the budget is exhausted (default: warn only). | | `--slider` | `0–10` | Cost/quality tradeoff — 0 = cheapest acceptable, 10 = highest quality (default 5). | | `-h`, `--help` | — | Print usage and exit. | ```bash # pin a specific model and raise thinking effort minima --model claude-opus-4-8 --provider anthropic --thinking high -p "review this diff" # read-only, no shell: allow only read/ls/grep minima -t read,ls,grep -p "where is auth handled?" # run without the recommender at all minima --offline --model gpt-4o-mini --provider openai -p "quick summary" ``` `--model` (optionally with `--provider`) pins one model and skips routing; `--offline` skips the recommender while still using the seeded catalog. See [Model routing](/harness/routing) for how these interact. Tool filtering with `-t`/`-xt`/`-nt` composes with the interactive [permission system](/harness/tools). ### `minima auth` Browser login → provisions a Mubit project for the current repo → stores `MUBIT_API_KEY` (and `MINIMA_URL`) → records the repo mapping in `~/.minima-harness/projects.json`. ```bash minima auth # default region minima auth --region eu # or --region us ``` The console URL defaults to `https://console.mubit.ai` (override with `MUBIT_CONSOLE_URL`). ### `minima config` Manage the per-user credential store without opening the TUI — works before any keys exist. ```bash minima config # list every key (secrets masked) minima config set MUBIT_API_KEY mbt_… minima config get MINIMA_URL ``` See [Configuration](/harness/configuration) for every key, storage backend, and precedence rules. import { PageHeader, Note, Tip, Warning } from '@components' ## Configuration The harness is configured through environment variables — supplied via a per-user credential store, project `.env` files, or your shell. `minima config` manages the per-user store. ### Managing credentials ```bash minima config # list every configurable key (secrets masked) minima config set # store a credential minima config get # print a stored value ``` You don't have to drop to the CLI — the same is available **inside the interactive TUI** via the [`/config`](/harness/interactive) slash command: ``` /config # open the config editor / list keys /config set # store a credential /config get # print a stored value ``` Secrets are stored **keychain-first**: the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) via `keytar` when available, otherwise a `~/.minima-harness/config.env` file written mode `0600`. Non-secret values (URLs) always live in the file. `minima config set` reports which backend it used. The `keytar` native module does not bundle into the compiled binary, so the Homebrew-installed `minima` binary transparently falls back to the `0600` file store. That file is plaintext — keep it owner-only (it already is) and prefer project `.env` files or your shell for CI. ### Precedence When the harness starts it loads configuration in this order — **earlier wins**, and stored values never overwrite something already set: 1. **Real shell environment** — anything already exported in your shell. 2. **Project `.env` files** in the current directory — `.env.harness`, then `.env`. Only fills keys not already set. 3. **Per-user store** — OS keychain + `~/.minima-harness/config.env`, materialized into the environment with set-default semantics (lowest precedence). Put shared, non-secret settings (like `MINIMA_URL` for a local recommender) in a project `.env.harness`, and keep secrets in the keychain via `minima config set`. ### Environment variables #### Mubit / Minima routing | Variable | Required | Default | Purpose | | ------------------- | -------- | ----------------------------- | ------------------------------------------------------------------------------ | | `MUBIT_API_KEY` | **Yes** | — | Memory backend + routing auth. Passed through to Mubit for recall/learning. | | `MINIMA_URL` | No | `https://api.minima.sh` | The Minima recommender endpoint. Set to `http://localhost:8080` for local dev. | | `MINIMA_API_KEY` | No | falls back to `MUBIT_API_KEY` | Separate Minima auth, if your deployment uses one. | | `MUBIT_ENDPOINT` | No | — | Override the Mubit memory backend URL. | | `MUBIT_CONSOLE_URL` | No | `https://console.mubit.ai` | Console URL used by `minima auth`. | | `MINIMA_NAMESPACE` | No | per-repo project | Memory isolation lane. Overrides the repo's provisioned project. | | `MINIMA_TIMEOUT` | No | `30` | Recommender request timeout, in seconds. | **Per-repo memory isolation.** If `MINIMA_NAMESPACE` is set it wins; otherwise the harness uses the namespace of the Mubit project that `minima auth` provisioned for this repo (stored in `~/.minima-harness/projects.json`). This keeps each project's `task → model → outcome` history separate. #### LLM provider keys Set a key for any provider you want the harness to be able to run. The first environment variable listed for a provider wins. | Provider | Environment variable(s) | Notes | | ------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------- | | Anthropic (Claude) | `ANTHROPIC_API_KEY`, `ANTHROPIC_OAUTH_TOKEN` | Claude — Opus / Sonnet / Haiku | | OpenAI | `OPENAI_API_KEY` | GPT-5.x / GPT-4o | | Google Gemini | `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY` | Gemini 2.5 / 3.5 | | xAI (Grok) | `XAI_API_KEY` | Grok 4.x · base URL `https://api.x.ai/v1` | | DeepSeek | `DEEPSEEK_API_KEY` | Open-weight, cheap · `https://api.deepseek.com` | | OpenRouter | `OPENROUTER_API_KEY` | Aggregator — any model, one key · `https://openrouter.ai/api/v1` | | Groq | `GROQ_API_KEY` | Fast inference for open models · `https://api.groq.com/openai/v1` | ### Default model catalog Out of the box the harness seeds this catalog; `/model` lets you pin one or add your own. | Model | Provider | Context | | ------------------- | --------- | ------- | | `gpt-4o-mini` | openai | 128K | | `gpt-4o` | openai | 128K | | `deepseek-chat` | deepseek | 64K | | `claude-haiku-4-5` | anthropic | 200K | | `claude-sonnet-4-6` | anthropic | 200K | | `claude-opus-4-8` | anthropic | 200K | | `gemini-2.5-flash` | google | 1M | | `gemini-2.5-pro` | google | 2M | When routing (not pinned), Minima chooses from the candidate set `gemini-2.5-flash`, `claude-haiku-4-5`, `claude-sonnet-4-6`, `gemini-2.5-pro`, `claude-opus-4-8` by default. To run a model that isn't seeded, pass `--model --provider ` — the harness registers it on the fly against the provider's OpenAI-compatible endpoint. See [CLI usage](/harness/cli). ### Config files at a glance | Path | What it holds | | ------------------------------------ | ------------------------------------------------------------------------------- | | `~/.minima-harness/config.env` | Per-user credentials (file backend, mode `0600`). | | `~/.minima-harness/projects.json` | Repo → Mubit instance / project / namespace mapping (written by `minima auth`). | | `~/.minima-harness/sessions/*.jsonl` | Append-only session history. See [Sessions](/harness/sessions). | | `./.env.harness`, `./.env` | Project-scoped environment overrides. | import { PageHeader, Steps, Step, Note, Tip, Accordion } from '@components' ## Installation The `minima` harness is distributed as a self-contained native binary through the [`mubit-ai/homebrew-minima`](https://github.com/mubit-ai/homebrew-minima) Homebrew tap. Install it, authenticate, and start. ### Prerequisites * **[Homebrew](https://brew.sh)** (macOS or Linux). * **A Mubit API key** (`MUBIT_API_KEY`) — routing + memory-backend auth. Get one from the [Mubit console](https://console.mubit.ai), or run `minima auth` (see below) to have it created and stored for you. * **At least one LLM provider key** — the harness runs the model itself, so you need a key for whichever provider(s) you want to call: OpenAI, Anthropic, Google Gemini, DeepSeek, xAI, Groq, or OpenRouter. Local runtimes (Ollama, vLLM, LM Studio) need no key. ### Install with Homebrew ```bash brew install mubit-ai/minima/minima ``` That taps [`mubit-ai/homebrew-minima`](https://github.com/mubit-ai/homebrew-minima) and installs the `minima` formula in one step. Prefer to tap explicitly? It's equivalent to: ```bash brew tap mubit-ai/minima brew install minima ``` Verify it's on your `PATH`, and upgrade later with Homebrew: ```bash minima --help brew upgrade minima # pull the latest release ``` The Homebrew formula ships the compiled binary, which bundles no OS keychain module — so credentials fall back to a `~/.minima-harness/config.env` file (mode `0600`). See [Configuration](/harness/configuration#managing-credentials). Contributing to the harness? Build the binary yourself from the `packages/tui` workspace with [Bun](https://bun.sh) ≥ 1.2: ```bash cd packages/tui bun install bun test # hermetic test suite (no network, no keys) bun run check # tsc --noEmit bun run lint # biome bun run build # -> dist/minima (a self-contained native binary) ./dist/minima --help ``` Put `dist/minima` on your `PATH` (or symlink it) to invoke it as `minima` from any repo. ### First-time setup Pick one of the two paths below. #### Recommended — browser login `minima auth` opens your browser, signs you in to Mubit, provisions a Mubit **project scoped to the current repo**, stores your `MUBIT_API_KEY`, and records the project mapping for per-repo memory isolation. Already running the TUI? The [`/auth`](/harness/interactive) slash command runs the same flow. ```bash cd your-repo minima auth # Opens the browser to authorize; on success stores MUBIT_API_KEY (+ MINIMA_URL) # and writes the repo → project mapping to ~/.minima-harness/projects.json ``` `minima auth` accepts `--region eu|us` to choose a Mubit region. The console URL defaults to `https://console.mubit.ai` and can be overridden with `MUBIT_CONSOLE_URL`. #### Manual — set credentials directly If you already have keys, store them with `minima config`: ```bash minima config set MUBIT_API_KEY mbt_… minima config set OPENAI_API_KEY sk-… # or ANTHROPIC_API_KEY / GEMINI_API_KEY / DEEPSEEK_API_KEY / … ``` Secrets go to your OS keychain when available, otherwise to `~/.minima-harness/config.env` (mode `0600`). See [Configuration](/harness/configuration) for the full precedence rules and every key. ### Start ```bash minima # interactive TUI (default) minima -p "explain this repo" # one-shot answer, then exit minima --mode json "refactor foo" | jq # machine-readable event stream ``` `MUBIT_API_KEY` (routing) plus **one** provider key is enough to start. From there, `--offline` bypasses the recommender and `--model` / `--provider` pin a specific model. See [CLI usage](/harness/cli). import { PageHeader, Note, Tip } from '@components' ## Interactive TUI Running `minima` with no prompt opens the interactive terminal UI: a scrolling conversation view, an input prompt, and a status bar. Type a message and press Enter to send it; the harness routes it, runs the chosen model, streams the reply, and executes any tool calls (asking permission where required). ### The status bar The bar along the bottom shows, at a glance: * **Model** — the pinned model, or `auto` when Minima is routing. * **Cost** — running total from the cost meter for this session. * **Context** — percentage of the model's context window used, plus token counts. * **State** — whether a turn is in flight. ### Keyboard shortcuts | Key | Action | | --------------------------------------- | ------------------------------------------------------------------ | | Enter | Send the current prompt | | Ctrl+L | Open the model picker | | Ctrl+P | Open the command palette (slash commands) | | Ctrl+R | Toggle route mode (auto ↔ confirm) | | Esc | Abort the in-flight run | | Ctrl+C | Abort a running turn; press again at the prompt to quit | | PageUp / PageDown | Scroll the conversation | | Shift+Space | Scroll down | | Ctrl+G | Jump to the top of the chat | | Ctrl+E | Jump to the bottom of the chat | | Tab | Auto-complete a slash command | | / | Cycle input history (at the prompt) · navigate items (in a picker) | In the model picker, command palette, and session picker, use / to move, Enter to choose, and Esc to dismiss. ### Slash commands Type `/` (or open the palette with Ctrl+P) and pick a command: | Command | What it does | | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `/model` | Select or pin a model (or `auto` to unpin and let Minima route) | | `/clear` | Clear chat messages | | `/auth` | Sign in to Mubit & provision this repo's project | | `/config` | Show/set API keys (`MUBIT`, `GEMINI`, `ANTHROPIC`, …) | | `/help` | Show the available commands | | `/cost` | Show cost-meter totals | | `/budget` | Show or set the session budget (`/budget set ` · `/budget mode shadow\|warn\|enforce`) | | `/reconnect` | Reconnect the routing client | | `/new` | Start a fresh session | | `/name