# 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, Note, Tip } from '@components'
## SDK architecture
Minima ships two official SDKs over the same `/v1/*` HTTP contract:
* **`@mubit-ai/minima-sdk`** — TypeScript. Pure `fetch`, **zero runtime dependencies**, works in Node/Bun/Deno/edge runtimes. `npm add @mubit-ai/minima-sdk`.
* **`minima_client`** — Python (sync + async), shipped in the [`minima-cli`](https://pypi.org/project/minima-cli/) package. See [Python Client SDK](/sdk/client-sdk).
Both encode the same workflow and the same honesty rules; this page is about *how*.
### One wire contract
The server's schemas are the single source of truth. The Python SDK imports them directly (it cannot drift); the TypeScript SDK hand-mirrors them in `schemas.ts`, and a mechanical test in CI pins every field of the mirror against the server models. Whatever language you're in, the field names and semantics on the wire are identical — the [API reference](/api-reference/endpoints) describes both.
### The loop, made hard to get wrong
```
recommend → run the model yourself → judge quality → feedback
```
The SDKs bake in the operational asymmetry between the loop's two calls:
* **`recommend` fails fast and never retries.** Routing advice sits in front of your real work, so the SDK surfaces an error immediately — the intended pattern is to catch it and *fail open* to your default model. Your LLM call must never block on Minima.
* **`feedback` retries transparently.** Closing the loop is bookkeeping that should survive a blip: both SDKs retry transient faults (transport errors, 502/503/504) up to 3 attempts — TypeScript with fixed delays, Python with exponential backoff — and the server dedupes via idempotency keys. Client errors (4xx, including 429) surface immediately.
Both SDKs raise a typed error ladder: `MinimaRateLimited` (with the server's `retry-after`), `MinimaUnavailable` (5xx gateway states), and `MinimaError` (everything else, carrying the problem+json detail).
### Label honesty is in the types
The feedback surface is designed so dishonest labels are hard to send by accident:
* `usage` fields are **realized** numbers (`input_tokens`, `output_tokens`, `actual_cost_usd`, `latency_ms`) — what the provider actually billed, never Minima's own estimate echoed back. An explicit `0` counts as a real measurement; an omitted field claims nothing.
* `evidence_source` declares label provenance: `"gate"` (a deterministic check passed — the only origin that may claim verified-in-production), `"judge"` (an LLM judge scored it), `"human"` (you asserted it), or `"none"` (no label — the outcome rides as cost/latency telemetry and never touches quality learning).
* `error_cause: "infra"` marks provider faults so a rate limit is never learned as model quality.
* `step_outcomes[]` carries per-step verdicts for multi-step work (each with its own `step_id`, outcome, and optional signal in `[-1, 1]`) — the TypeScript SDK types these first-class.
The quality thresholds the ecosystem uses for mapping scores to outcomes: `success ≥ 0.8`, `partial ≥ 0.4`, else `failure`.
### Surface parity
Both SDKs expose the full endpoint surface — `recommend`, `recommendWorkflow`/`recommend_workflow`, `feedback`, `savings`, `calibration`, `policyValue`/`policy_value`, `strategies`, `diagnose`, `memoryHealth`/`memory_health`, `models`, `capabilities`, `health`. Use `capabilities()` once at startup to gate optional features on what the server actually supports.
### TypeScript quickstart
```ts
import { MinimaClient } from "@mubit-ai/minima-sdk";
const minima = new MinimaClient({
baseUrl: "https://api.minima.sh",
apiKey: process.env.MUBIT_API_KEY,
});
const rec = await minima.recommend("refactor the auth module", {
constraints: { candidate_models: ["claude-haiku-4-5", "claude-sonnet-4-6"] },
});
// run rec.recommended_model.model_id yourself, measuring real usage…
await minima.feedback(rec.recommendation_id, rec.recommended_model.model_id, "success", {
usage: { inputTokens: 1800, outputTokens: 600, costUsd: 0.0042, latencyMs: 9000 },
evidenceSource: "gate", // only because a real check passed
});
```
The client takes an injectable `fetch` (hermetic tests, custom transports) and per-call `AbortSignal`s; the ergonomic `feedback()` maps camelCase to the wire, while `feedbackRaw()` accepts a full wire-shaped request for advanced fields like `step_outcomes`.
### What differs between the two
| Area | TypeScript | Python |
| -------------------- | ------------------------------------- | --------------------------------------------------------------- |
| Timeout | per-call `AbortSignal` | `timeout=10.0` constructor default |
| `recommend` defaults | `explain` / `max_candidates` unset | `explain=True`, `max_candidates=8` |
| Async | promise-based | separate `AsyncMinimaClient` |
| `step_outcomes` | typed `StepOutcome[]` + `feedbackRaw` | passed via `**kwargs` |
| Extras | client only | `autocapture`, LiteLLM + OpenHands adapters, `minima-route` CLI |
| Feedback retry | fixed `[500ms, 2000ms]` | exponential 0.5s→4s |
Building an agent harness rather than a single integration? The [Minima CLI](/harness/architecture) is the reference consumer of this contract — gate-verified labels, per-step outcomes, budget-aware routing — and everything it does goes through these same SDK surfaces.
import { PageHeader, Note, 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.
**This page targets the hosted service** (`https://api.minima.sh`, authenticated with your Mubit key). The same package works against a self-hosted Minima — point `base_url` at your deployment (see [Self-hosting](/sdk/self-hosting)); the repo's [`docs/client-sdk.md`](https://github.com/mubit-ai/minima/blob/main/docs/client-sdk.md) documents that framing.
### 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, and declare where the label came from with `evidence_source`:
The typed `Usage` parameter is the loop's **single biggest accuracy lever** — report what the provider **actually** billed (never echo Minima's own `est_cost_usd` back as `cost_usd`). Realized `input_tokens` / `output_tokens` / `cost_usd` / `latency_ms` are what let the cost basis climb `estimate → observed → rescaled` for your account.
```python
from minima_client import Usage
minima.feedback(
rec.recommendation_id,
rec.recommended_model.model_id,
"success",
usage=Usage(input_tokens=180, output_tokens=640, cost_usd=0.0034, latency_ms=2100),
quality_score=0.95, # your judge/eval score, if you have one
evidence_source="gate", # gate | judge | human | none — label provenance
idempotency_key="…", # optional
)
```
Label honesty matters: `evidence_source="none"` (no real label) makes the outcome cost/latency **telemetry only** — it never teaches the success posterior. An outcome you asserted yourself is `"human"`; a deterministic check that passed is `"gate"` — the only origin that may claim verified-in-production. For provider/infra faults pass `error_cause="infra"` so a rate limit never reads as model quality. Per-step results for multi-step work ride as `step_outcomes=[...]`.
The feedback call retries transient faults automatically (3 attempts, exponential backoff); the server dedupes via the idempotency key.
### `models(...)`, `strategies(...)`, `capabilities()`, `health()`
```python
catalog = minima.models(provider="anthropic", max_cost=10.0) # ModelsResponse
strat = minima.strategies(namespace="team-payments", max_strategies=5)
caps = minima.capabilities() # feature handshake — gate optional features on this
status = minima.health() # dict
```
### `diagnose(...)` and `memory_health(...)`
The [memory insight layer](/sdk/mubit#failure-lessons--memory-for-the-error-path): failure lessons matching an error, and your namespace's memory hygiene. Both degrade gracefully — a Mubit outage returns an empty result plus a `memory_unavailable` warning, never an exception:
```python
diag = minima.diagnose("TypeError: cannot read properties of undefined", limit=3)
for lesson in diag.failure_lessons:
print(lesson.content, lesson.confidence)
mh = minima.memory_health(namespace="team-payments", stale_threshold_days=30)
print(mh.entry_counts, mh.stale_entries, mh.contradictions, mh.promotion_candidates)
```
### `savings(...)`, `calibration(...)`, and `policy_value(...)`
The measurement layer: what Minima saved you, whether `predicted_success` is calibrated against your reported outcomes, and what alternative routing policies would have been worth (`policy_value(...)` — doubly-robust regret-vs-oracle). All 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. **[How Minima uses Mubit](/sdk/mubit)** walks through the whole memory layer — recall, reinforcement, drift signals, and failure lessons.
### 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.
* **Was the policy itself right?** [`GET /v1/policy-value`](/api-reference/endpoints#get-v1policy-value) runs doubly-robust off-policy estimates over your own decision log — what alternative routing policies would have been worth, and the regret vs. an oracle — with the log's stochasticity and label trust surfaced so the number can't overclaim.
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. Record any `step_outcomes[]` as **per-step process rewards** — for multi-step work, the recommender learns which parts of a task the model handled, not just the overall result.
5. On a gate-verified strong success (`evidence_source: "gate"`), promote a durable **lesson** that feeds rule promotion. Label provenance is preserved throughout: an outcome with `evidence_source: "none"` is cost/latency telemetry and never teaches the success posterior.
6. Periodically reflect (after a number of feedbacks, or on any verified-prod failure) to promote run → session → account-level lessons.
Memory also watches itself: recall responses carry **drift signals**, surfaced as `memory_drift:*` warnings on recommendations when the evidence neighborhood is repeating or stagnating, and [`POST /v1/diagnose`](/api-reference/endpoints#post-v1diagnose) / [`GET /v1/memory/health`](/api-reference/endpoints#get-v1memoryhealth) expose the failure-lesson and hygiene views directly.
### 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, Warning } 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 — computed from the provider's own `msg.usage`, priced with the catalog's per-Mtok rates.
**Never echo `est_cost_usd` back as `actual_cost_usd`.** Minima's `est_cost_usd` is its own *prediction* — feeding it back teaches the ranker nothing. `actual_cost_usd` must be the **realized** cost of the call (provider-billed, or provider token counts × your price table). Real numbers are what let the cost basis climb `estimate → observed → rescaled` — the single biggest accuracy lever of the loop.
```python
import time
import anthropic
from minima_client import AsyncMinimaClient, Usage
def price_per_mtok(catalog, model_id: str) -> tuple[float, float]:
"""(input $/Mtok, output $/Mtok) from the catalog; (0, 0) if unknown."""
for card in catalog.models:
if card.model_id == model_id:
return card.input_cost_per_mtok, card.output_cost_per_mtok
return 0.0, 0.0
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
started = time.monotonic()
async with client.messages.stream(
model=model, max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
) as stream:
msg = await stream.get_final_message()
latency_ms = int((time.monotonic() - started) * 1000)
text = "".join(b.text for b in msg.content if b.type == "text")
# Realized cost: the provider's actual token counts priced at catalog rates.
catalog = await minima.models(provider=rec.recommended_model.provider)
in_p, out_p = price_per_mtok(catalog, model)
actual_cost = (msg.usage.input_tokens / 1e6 * in_p
+ msg.usage.output_tokens / 1e6 * out_p)
await minima.feedback(
rec.recommendation_id, model, "success",
usage=Usage(
input_tokens=msg.usage.input_tokens,
output_tokens=msg.usage.output_tokens,
cost_usd=round(actual_cost, 8),
latency_ms=latency_ms,
),
quality_score=0.95, # your judge/eval score
evidence_source="human", # or "gate" if a deterministic check passed
)
return text
```
The runnable version — with graceful degradation and a real quality gate — is [`examples/06_routed_llm_call.py`](https://github.com/mubit-ai/minima/blob/main/examples/06_routed_llm_call.py).
### 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 — or run your own instance instead: see [Self-hosting](/sdk/self-hosting). 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. `evidence_source` declares where the label came from (`gate` for a deterministic check, `judge` for an LLM judge, `human` for your own assertion, `none` for unlabeled telemetry).
```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,
"evidence_source": "human"
}' | 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, Warning } from '@components'
## How Minima uses Mubit
Minima's recommender is **non-parametric**: it doesn't train a model on your traffic, it *remembers* your traffic. [Mubit](https://mubit.ai) is the memory substrate that makes that work — and it is the **only external dependency** of the core recommender. Every capability below is hosted; you operate none of it.
### Your Mubit key is the whole story
Minima uses **pass-through auth**: the `Authorization: Bearer mbt_…` key you send *is* your Mubit data-plane key. Minima forwards it to read and write your `task → model → outcome` history in **your** Mubit instance — there is no separate Minima account, and your instance is your data boundary. Within it, `namespace` maps to an isolated memory lane (`minima:`), so teams, projects, and environments learn independently.
### Recall — where recommendations come from
When you call `POST /v1/recommend`, Minima sends your task text to Mubit, which embeds it **server-side** (you never run an embedding model) and returns the most similar past outcome records from your lane. That recall is tuned for routing:
* **Evidence-only retrieval** — only durable, outcome-bearing records are recalled, keeping the hot path fast (it's a large share of Minima's p95).
* **Weighted aggregation** — each recalled neighbor is weighted by `similarity × reliability × staleness_decay`. The reliability term is Mubit's per-entry `knowledge_confidence`, a Bayesian estimate that grows as an entry keeps being right and shrinks when it's contradicted.
* **Version-aware recall** — `tags` on your task (e.g. `lang:python`) propagate to Mubit `env_tags`, so evidence from the wrong stack doesn't vote.
* **Freshness vs. relevance** — recall ranking balances semantic similarity, lexical match, and recency with temporal decay; stale or superseded entries are penalized and flagged (`is_stale` on the evidence refs you see with `explain=true`).
The evidence behind every ranked model is surfaced to you: each `RankedModel.evidence[]` entry carries the Mubit entry id, its similarity score, its `knowledge_confidence`, and the recorded outcome — the recommendation is auditable down to the individual memories that drove it.
### Reinforcement — what feedback actually does
`POST /v1/feedback` is not a log line; it's a memory write with attribution:
1. The `recommendation_id` resolves to the exact neighbors that were recalled at decision time. (It's account-scoped — another account's id resolves to nothing, so accounts can't credit or poison each other.)
2. A durable **outcome record** is upserted per `(task cluster, model)` carrying your realized `cost_usd`, tokens, and quality — this is what powers the observed and rescaled [cost-basis tiers](/sdk/concepts#cost-basis-tiers-estimate--observed--rescaled).
3. The recalled neighbors that drove the pick are **credited**: their reinforcement counters and `knowledge_confidence` rise (you see this as `reinforced_entry_ids` and `updated_confidence` in the response). Memories that keep producing good picks gain influence; ones that don't, fade.
4. On strong verified-in-production results, Mubit's **reflection** promotes a durable **lesson**; accumulated lessons surface back to you as [`GET /v1/strategies`](/api-reference/endpoints#get-v1strategies) — the "why" behind your routing patterns.
5. Outcomes are written **bi-temporally**: the record carries both when the work actually happened and when it was recorded, so late feedback doesn't masquerade as fresh evidence.
Feedback labels keep their provenance all the way into memory: an outcome verified by a deterministic check (`evidence_source: "gate"`) teaches the success posterior with full weight, a judge label is judge-weighted, and unlabeled telemetry never touches quality at all. Honest inputs are what keep the recalled evidence trustworthy.
### Drift signals — memory watching itself
Every Mubit recall also returns **drift signals** about the neighborhood it just searched: whether the same evidence keeps being recalled without new outcomes arriving (`stagnant`), or the same task keeps repeating (`repeated`). Minima surfaces these as `memory_drift:*` warnings on the recommendation and feeds low recall confidence into its escalation logic — thin or aging evidence is a reason to double-check, and you see it in `warnings[]` rather than it being silently absorbed.
### Failure lessons — memory for the error path
Two endpoints expose Mubit's failure-side memory directly:
* [`POST /v1/diagnose`](/api-reference/endpoints#post-v1diagnose) matches an error text against **failure lessons** in your lane — "here's how this failed before". The [Minima CLI's recovery ladder](/harness/architecture#the-recovery-ladder) calls this automatically when a verified failure triggers a replan, so the retry starts briefed by memory.
* [`GET /v1/memory/health`](/api-reference/endpoints#get-v1memoryhealth) reports your lane's hygiene: entry counts, stale entries, contradictions, low-confidence entries, and promotion candidates — a diagnostics view of the memory your routing runs on.
### Degradation — memory is an accelerant, not a crutch
Minima keeps serving when Mubit is slow or unreachable: recall has a hard timeout, and on timeout or error the recommendation falls back to catalog priors with an explicit warning (`recall_timeout`, `memory_unavailable`). The insight endpoints degrade the same way — an outage returns an empty result plus a warning, never a 500. See [Degradation behavior](/sdk/concepts#degradation-behavior).
### The CLI's extra touchpoints
The [Minima CLI](/harness/overview) leans on the same substrate a little harder: `minima auth` provisions a **Mubit project per repo** (so each codebase learns in its own lane), its traces land in a dedicated lane partition, and its [recovery ladder](/harness/architecture#the-recovery-ladder) consumes `diagnose` automatically. The Python SDK's [`autocapture`](/sdk/client-sdk#zero-code-intake-autocapture) helper writes traces into the same lane Minima recalls from, enriching the memory block Mubit builds for the escalation reasoner.
The compounding loop, end to end: your feedback becomes outcome records → records become recalled evidence → evidence drives cheaper picks → picks generate more feedback. Every piece of that loop lives in **your** Mubit instance, inspectable via `explain=true`, `/v1/strategies`, and `/v1/memory/health`.
import { PageHeader, Note, Steps, Step, Tip } from '@components'
## Self-hosting
The hosted service at `api.minima.sh` needs nothing installed — but Minima is also a small FastAPI service you can run yourself. The core recommender's only external dependency is a [Mubit](https://docs.mubit.ai) memory backend. This page condenses the repo's operator docs; the deep versions are [`docs/getting-started.md`](https://github.com/mubit-ai/minima/blob/main/docs/getting-started.md), [`docs/configuration.md`](https://github.com/mubit-ai/minima/blob/main/docs/configuration.md), and [`docs/seeding.md`](https://github.com/mubit-ai/minima/blob/main/docs/seeding.md).
### Prerequisites
* **Python 3.11+** and [`uv`](https://github.com/astral-sh/uv).
* **A Mubit runtime** to store and recall `task → model → outcome` history. Default endpoint is a local runtime at `http://127.0.0.1:3000`; or point `MUBIT_ENDPOINT` at a hosted instance. Minima uses Mubit's server-side embeddings, so it needs no embedding model of its own.
From a checkout of [`mubit-ai/minima`](https://github.com/mubit-ai/minima):
```bash
uv sync --extra dev
```
Optional extras: `--extra reasoner-anthropic` / `--extra reasoner-gemini` (the cheap-LLM escalation reasoner) and `--extra seed` (RouterBench cold-start seeding).
```bash
cp .env.example .env
```
The only **required** value (single-tenant mode) is `MUBIT_API_KEY` — a Mubit data-plane key for the instance Minima should read/write. If your Mubit isn't local, also set `MUBIT_ENDPOINT`. Everything else has sensible defaults — the full environment-variable reference is [`docs/configuration.md`](https://github.com/mubit-ai/minima/blob/main/docs/configuration.md).
```bash
make run
# == uv run --extra server uvicorn minima.main:app --reload --host 0.0.0.0 --port 8080
```
Interactive API docs (OpenAPI/Swagger) are served at `http://localhost:8080/docs`.
With no history, picks lean on capability priors (`decision_basis: "prior"`, a `cold_start` warning). `minima-seed` loads a base of outcome history so day-one recommendations are grounded:
```bash
uv run minima-seed --limit 2000 --lane minima:default
# or, no external dataset download:
uv run minima-seed --dataset synthetic --limit 2000 --lane minima:default
```
RouterBench mode needs the `seed` extra. Verify by re-requesting a similar task and checking `decision_basis` is `"memory"`. Full flag reference: [`docs/seeding.md`](https://github.com/mubit-ai/minima/blob/main/docs/seeding.md).
### Pointing clients at your deployment
Every client takes a base URL — nothing else changes:
* **Python**: `MinimaClient("http://localhost:8080", api_key=…)` — see [Python Client SDK](/sdk/client-sdk).
* **TypeScript**: `new MinimaClient({ baseUrl: "http://localhost:8080", … })` — see [TypeScript SDK](/sdk/typescript).
* **Minima CLI**: set `MINIMA_URL=http://localhost:8080` — see [harness configuration](/harness/configuration).
### Multi-tenancy: pass-through auth
One self-hosted Minima can serve many organizations with **no provisioning step**: auth is pass-through, meaning the caller's Mubit API key **is** the credential. Each request's `Authorization: Bearer mbt_…` key is used directly against the configured Mubit endpoint, and Minima builds an isolated, cached per-key context — recommendation store, decision log, and memory are all scoped by an org id derived from the key, so one org's `recommendation_id`s resolve to nothing for another and orgs cannot credit or poison each other's learning. There are no Minima-issued keys to mint or manage: each org brings its own Mubit key, and gets its own memory.
Without a `Bearer` key on the request, Minima falls back to the server's own `MUBIT_API_KEY` (single-tenant mode). A malformed bearer token (not `mbt_…`-shaped) is rejected with `401` before any work is done.
### Next steps
* [Using Minima correctly](/sdk/usage-guide) — the loop's golden rules apply identically to self-hosted deployments.
* [`docs/operations.md`](https://github.com/mubit-ai/minima/blob/main/docs/operations.md) — deployment, health checks, degradation behavior, and monitoring.
import { PageHeader, Note, Tip, Warning } from '@components'
## TypeScript SDK
[`@mubit-ai/minima-sdk`](https://www.npmjs.com/package/@mubit-ai/minima-sdk) is the official TypeScript client for the Minima API: pure `fetch`, **zero runtime dependencies**, typed against the same `/v1/*` wire contract as the [Python SDK](/sdk/client-sdk). It works in Node, Bun, Deno, and edge runtimes.
### Install
```bash
npm install @mubit-ai/minima-sdk
# or: bun add / pnpm add / yarn add @mubit-ai/minima-sdk
```
### Client
```ts
import { MinimaClient } from "@mubit-ai/minima-sdk";
const minima = new MinimaClient({
baseUrl: "https://api.minima.sh",
apiKey: process.env.MUBIT_API_KEY, // your Mubit key (mbt_…), sent as Bearer auth
});
```
`MinimaClientOptions` also accepts `feedbackRetryDelaysMs` (default `[500, 2000]`) and an injectable `fetch` for hermetic tests.
### The full loop: recommend → run → feedback
You run the model — Minima only recommends. Close the loop with **realized** usage measured from the provider's own response:
```ts
import Anthropic from "@anthropic-ai/sdk";
import { MinimaClient } from "@mubit-ai/minima-sdk";
const minima = new MinimaClient({
baseUrl: "https://api.minima.sh",
apiKey: process.env.MUBIT_API_KEY,
});
const anthropic = new Anthropic();
// $/Mtok — from your provider contract, or GET /v1/models (input_cost_per_mtok / output_cost_per_mtok)
const PRICES: Record = {
"claude-haiku-4-5": { in: 1.0, out: 5.0 },
"claude-sonnet-4-6": { in: 3.0, out: 15.0 },
};
// 1. recommend
const rec = await minima.recommend(
{ task: "Summarize this incident report into 3 bullets.", task_type: "summarization" },
{ costQualityTradeoff: 3 },
);
const model = rec.recommended_model.model_id;
// 2. run the model YOURSELF, measuring real usage
const started = Date.now();
const msg = await anthropic.messages.create({
model,
max_tokens: 1024,
messages: [{ role: "user", content: "Summarize this incident report into 3 bullets." }],
});
const latencyMs = Date.now() - started;
// 3. realized cost from the provider's actual token counts × your price table
const p = PRICES[model] ?? { in: 0, out: 0 };
const costUsd =
(msg.usage.input_tokens / 1e6) * p.in + (msg.usage.output_tokens / 1e6) * p.out;
// 4. close the loop with what ACTUALLY happened
await minima.feedback(rec.recommendation_id, model, "success", {
usage: {
inputTokens: msg.usage.input_tokens,
outputTokens: msg.usage.output_tokens,
costUsd,
latencyMs,
},
qualityScore: 0.95, // your judge/eval score, if you have one
evidenceSource: "human", // gate | judge | human | none — label provenance
});
```
**Never echo `est_cost_usd` back as the actual cost.** `rec.recommended_model.est_cost_usd` is Minima's own *prediction*; `usage.costUsd` must be the **realized** cost of the call. Real numbers are what let the cost basis climb `estimate → observed → rescaled` — the single biggest accuracy lever of the loop.
### `recommend(task, opts)`
`task` is a string or a `TaskInput` (`{ task, task_type?, difficulty?, expected_input_tokens?, expected_output_tokens?, tags? }`). Options (camelCase; mapped to the wire):
```ts
const rec = await minima.recommend("refactor the auth module", {
costQualityTradeoff: 5, // 0..10 (default 5)
constraints: { // hard filters
candidate_models: ["claude-haiku-4-5", "claude-sonnet-4-6"],
},
namespace: "team-payments",
baselineModelId: "claude-opus-4-8", // what you'd use without Minima -> honest savings()
incumbentModelId: undefined, // session's prompt-cache holder, if any
phase: "interactive", // rides as a `phase:` tag
signal: undefined, // per-call AbortSignal
});
```
`recommend` **fails fast and never retries** — catch the error and fail open to your default model; your LLM call must never block on Minima.
### `feedback(recommendationId, chosenModelId, outcome, opts)`
`outcome` is `"success" | "partial" | "failure"`. Options: `usage` (`{ inputTokens, outputTokens, costUsd, latencyMs }` — all realized), `qualityScore`, `evidenceSource` (`"gate" | "judge" | "human" | "none"`), `errorCause` (`"infra" | "quality"`), `chosenEffort`, `iterations`, `notes`, `idempotencyKey`, `signal`.
Feedback **retries transparently** on transient faults (transport errors, 502/503/504) with the `feedbackRetryDelaysMs` schedule; the server dedupes via the idempotency key. For wire-level fields like `step_outcomes`, use `feedbackRaw(req)` — the raw request shape with the same retry behavior.
`evidenceSource: "none"` marks an unlabeled outcome — it rides as cost/latency **telemetry only** and never teaches the success posterior. `"gate"` (a deterministic check passed) is the only origin that may claim verified-in-production. For provider faults pass `errorCause: "infra"` so a rate limit never reads as model quality.
### Reporting and catalog
```ts
await minima.savings({ days: 30, group_by: "task_type" }); // SavingsResponse
await minima.calibration({ days: 30 }); // CalibrationResponse
await minima.policyValue({ days: 30 }); // doubly-robust regret-vs-oracle
await minima.strategies({ namespace: "team-payments" }); // promoted rules
await minima.diagnose({ error_text: "TypeError: …" }); // failure lessons
await minima.memoryHealth({ namespace: "team-payments" }); // memory hygiene
await minima.models({ provider: "anthropic" }); // catalog with $/Mtok prices
await minima.capabilities(); // feature handshake
await minima.health();
```
### Errors
Non-2xx responses throw a typed ladder: `MinimaRateLimited` (429, with `retryAfter` seconds when the server provides it), `MinimaUnavailable` (502/503/504), and `MinimaError` (everything else — carries `status` and the problem+json `body`).
```ts
import { MinimaError } from "@mubit-ai/minima-sdk";
try {
const rec = await minima.recommend(task);
} catch (exc) {
// fail open: fall back to your default model
}
```
### Where to go next
* [SDK architecture](/sdk/architecture) — how both SDKs encode the loop: the wire contract, retry discipline, label honesty, and the differences between TypeScript and Python.
* [Using Minima correctly](/sdk/usage-guide) — the golden rules of the loop in one place.
* [API Reference](/api-reference/endpoints) — every field on the wire.
import { PageHeader, Note, Tip, Warning } from '@components'
## Using Minima correctly
Minima is **recommend-only**. Everything about using it well follows from that one fact: it tells you which model to run, *you* run the model, and you tell it what actually happened. This page is the whole proper-use flow in one place.
### 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 │
└───────────────────────────────────────────────────────-─┘
```
`recommend → run the model yourself → judge quality → feedback`. Minima never proxies, executes, or caches an LLM call — so it adds zero latency to your real request, and everything it learns comes from the feedback you send.
### The golden rules
#### 1. Run the model yourself
The response gives you `recommended_model.model_id` and a `recommendation_id`. Run that model in your own stack with your own provider credentials. If `recommend` fails, **fail open** to your default model — your LLM call must never block on Minima.
#### 2. Report realized usage and cost — never echo `est_cost_usd`
Feedback must carry what **actually** happened: realized `input_tokens`, `output_tokens`, `actual_cost_usd`, and `latency_ms`, measured from the provider's own response.
`est_cost_usd` on the recommendation is Minima's *prediction*. Echoing it back as `actual_cost_usd` teaches the ranker nothing — it just confirms its own guess. Real numbers are what let the cost basis climb `estimate → observed → rescaled`, the **single biggest accuracy lever** of the whole system: `estimate` prices catalog rates against assumed token counts, `observed` uses the median realized cost per call, and `rescaled` prices *your request's* input against observed output behavior (which is what catches models with cheap list prices but heavy internal reasoning). See [Cost-basis tiers](/sdk/concepts#cost-basis-tiers-estimate--observed--rescaled).
#### 3. Report outcomes honestly
`outcome` is `"success"` | `"partial"` | `"failure"`. When you have a quality score in `[0, 1]`, the ecosystem-wide mapping is:
| Score | Outcome |
| ------ | --------- |
| ≥ 0.8 | `success` |
| ≥ 0.4 | `partial` |
| \< 0.4 | `failure` |
Declare where the label came from with `evidence_source`: `"gate"` (a deterministic check passed — the only origin that may claim verified-in-production), `"judge"` (an LLM judge), `"human"` (you asserted it), or `"none"` (no label — the outcome rides as cost/latency telemetry only and never teaches the success posterior). Never fabricate a score you don't have; an honest `"none"` is more useful than an invented `0.9`.
#### 4. Keep `recommendation_id` as the join key
The `recommendation_id` from each recommendation is the single key that joins your run to Minima's decision log and memory. Carry it through your pipeline and quote it back on feedback — feedback with a lost or invented id can't credit the memories that drove the pick.
#### 5. Send feedback even for failures
A failure is a learning signal, not an embarrassment to hide — it's how Minima learns a model *can't* handle a task type and stops recommending it. For provider/infra faults (429s, 5xx, timeouts) pass `error_cause: "infra"` so a rate limit is never learned as model quality.
#### What degrades if you skip feedback
* The **cost basis stays at `estimate`** — ranking runs on list prices and assumed token counts instead of your realized costs.
* Recommendations stay **prior-driven** (`decision_basis: "prior"`) instead of grounded in your own history.
* The measurement layer goes dark: [`/v1/savings`](/api-reference/endpoints#get-v1savings) reports low `feedback_coverage` (its realized figures can't bear weight), and [`/v1/calibration`](/api-reference/endpoints#get-v1calibration) has no outcomes to check predictions against.
* No reinforcement, no lesson promotion — the system never converges on your workload.
Feedback is cheap (one POST, retried transparently, deduped server-side) and it is the entire mechanism by which routing gets better. If you only adopt one habit from this page: **close the loop every time, with real numbers.**
### Where to go from here
| You are… | Go to |
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| Using the `minima` terminal agent | [Minima CLI](/harness/overview) — the harness runs this whole loop for you, with gate-verified labels |
| Writing Python | [Python Client SDK](/sdk/client-sdk) and the [examples](/sdk/examples) |
| Writing TypeScript | [TypeScript SDK](/sdk/typescript) |
| Operating your own deployment | [Self-hosting](/sdk/self-hosting) |
import { PageHeader, Note, Tip, Warning } from '@components'
## Under the hood
This page explains how the `minima` agent actually works internally — what happens between you pressing Enter and the next recommendation getting sharper. You don't need any of it to use the CLI, but it's the mental model behind the slash commands, the confidence badges, and the cost figures you see.
Four design principles shape everything below:
* **State lives in a local ledger, the model sees projections.** Anything that must survive scroll, compaction, restart — or a model that misremembers — is stored in a local SQLite database. Each turn, the harness re-injects a compact projection (the plan of record, curated memories) into the model's context.
* **Enforcement in the harness, guidance in the prompt.** Permission gates, plan completion, and budget stops are enforced by harness code at tool-dispatch time — never by prompt text, which a model can ignore.
* **Feedback truth.** Only realized cost and honestly-labeled outcomes are reported to Minima. A deterministic check outranks an LLM judge; an unjudged turn is telemetry, never a fabricated score.
* **The server only recommends.** All execution state — plans, budgets, checkpoints — is local. Minima's server never holds your plan or your code.
### The life of a routed turn
Every prompt runs the Minima loop with retries built in:
1. **Assemble context.** The harness recalls similar past work from [Mubit](/sdk/mubit), injects the active plan's current state, and projects your curated [memories](#the-memory-ledger) into the system prompt.
2. **Route.** The turn goes to `POST /v1/recommend`; the candidate set is pre-filtered to providers you have keys for. A pinned model (`/model`) skips this entirely.
3. **Reserve budget.** Before any spend, a padded estimate is held against the session [budget](#the-budget-ledger).
4. **Run.** The chosen model streams, tools execute behind the permission gate, and stop-conditions watch the turn: a budget cutoff, a doom-loop detector (same failing action repeating), and a stall detector (repeated turns with no progress).
5. **Settle.** Realized cost replaces the budget hold; latency, tokens, and turn counts are recorded.
6. **Judge and feed back.** The outcome is labeled by the strictest available evidence (see [Feedback truth](#feedback-truth-what-gets-reported)) and reported to `POST /v1/feedback` with realized usage.
7. **Recover if needed.** If the turn verifiably failed and retries remain, the [recovery ladder](#the-recovery-ladder) re-decides — possibly with a stronger model — after rolling the transcript back to a clean state.
### Plan verification
The harness doesn't take the model's word for "done". With plan verification on (the default — opt out with `MINIMA_TUI_BIG_PLAN=0`), work is organized around **verifiable plan steps**:
* **Plans carry checks.** `/plan` drafts a plan where each step has a `verify` shell command — a test, a build, a grep — and records its **baseline**: the check must be *red before the work* (a check that already passed proves nothing).
* **Completion is gated.** The harness refuses to let the model mark a step complete unless its verify command actually passes. This is enforced at tool dispatch, so the model cannot talk its way past it.
* **Verdicts are tiered.** Every check run lands in a local **gates ledger** with a confidence tier: 🟢 a trusted check passed · 🟡 no discriminative check, or verified only by an LLM · 🔴 a check failed or looks fabricated. The tier shows in the TUI and decides what may be reported as verified.
* **Fresh eyes at the end.** A cheap **planning critic** reviews the plan at finalize (are the checks discriminative?), and a **zero-context diff reviewer** re-reads the run's whole diff when a plan closes — with no session context, so it can't be talked into agreement. An objection can flag a plan, never green-light it.
* `/verify` runs an adversarial re-verification pass over the whole plan; `/why` shows the evidence behind any step's verdict; `/bp` shows Plan Overview status; `/audit` lints the active plan.
Sub-agents never inherit verification authority: a child agent can't write gate verdicts against the lead's plan, and only checks that originate from your repo, CI, or you (not ones the agent just wrote) can claim production-grade verification.
### The recovery ladder
When a turn fails — a check goes red, the judge scores below threshold, or the model errors — the harness classifies the failure and picks the cheapest intervention that could fix it:
| What happened | What the harness does |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A verify check failed for the first time | **Escalate** — exclude the failed model and re-route to a stronger one |
| The check keeps failing across retries | **Replan** — keep the model, revise the approach; the retry is briefed with the check's full failure output *and* matching failure lessons recalled from memory (`POST /v1/diagnose`) |
| A provider fault (rate limit, 5xx, timeout) | **Back off and retry** — same model; the fault is reported as infrastructure, never as model quality |
Each retry rolls the conversation back to the pre-attempt state, so failed attempts don't pollute the context. When retries are exhausted, the failure is recorded honestly in the gates ledger.
### Feedback truth — what gets reported
Every outcome reported to Minima carries an **evidence source**, and the harness applies a strict hierarchy:
1. **A green-tier deterministic check** (your test passing) labels the outcome `gate` — the strongest evidence, and the only kind that can claim verified-in-production.
2. **A sampled LLM judge** grades \~15% of unchecked turns (`/judge` toggles it) and labels them `judge`.
3. **Everything else is telemetry** — realized cost and latency ride to the server with *no* quality claim attached, rather than a made-up score.
Verified plan steps also ride along individually: each gate-checked step becomes a per-step outcome in the same feedback call, so the recommender learns *which parts* of a task a model handled, not just the overall result. Provider faults are tagged as infrastructure errors so a rate-limited model doesn't look like a low-quality one.
This is why harness-driven routing sharpens fast: real tests, real costs, honest labels — at a cadence no manual integration matches.
### The budget ledger
`--budget ` (or `/budget set`) opens a persistent budget scope. Every routed turn **reserves** a padded estimate up front, runs, then **reconciles** the hold down to realized cost — including harness overhead like judge calls, which is booked too. Graduated warnings fire once each at 50/75/90/100%. Three modes: `shadow` (track only), `warn` (default), `enforce` (refuse new turns when exhausted, stop a running turn that crosses the limit, and tell the recommender the remaining headroom so it picks affordable models).
### The memory ledger
Beyond Mubit's server-side memory, the harness keeps a **local curated memory**: durable notes, lessons, and guardrails that project into the system prompt each turn (pinned entries first, then gate-cited, then recent — under a hard size cap). Manage it with `/memory` (list · add · pin · confirm · reject · delete).
The model **cannot write its own memories** — there is no memory-write tool. The only automated writer is a background **scribe** that mines the run's ledgers (verified failures, checks that flipped, your corrections — never the raw transcript) for recurring patterns. A pattern must recur before it earns one cheap extraction call; only candidates backed by a verified check auto-activate, the rest wait for your `/memory confirm`. Deleting a memory invalidates it (with full history) rather than erasing it, and every injection is audited — you can always replay exactly what the model saw.
### Sub-agents
The `task` tool fans work out to child agents, each **routed independently** through Minima — cost-aware routing applies per worker, not just to the lead. Delegations form a validated DAG (no cycles, no dangling dependencies) executed concurrently under a semaphore; each child gets its own budget slice, scoped tools, optional git-worktree isolation, and effort-scaled time limits, and the parent's abort fans out to all children. See [Tools & permissions](/harness/tools#sub-agents-the-task-tool).
### Local persistence
Everything above is backed by one local SQLite database (`~/.minima-harness/minima.db`): an append-only event log plus derived tables for routing decisions (keyed by the server's `recommendation_id` — the join key across harness, server, and memory), gates, plans, file changes, budgets, checkpoints, and memories. Pre-mutation **git-shadow checkpoints** power `/undo` and `/rewind` without touching your real git history. Database writes fail open: a persistence problem degrades bookkeeping, never your turn.
### Related
* [Model routing](/harness/routing) — auto vs. pinned, offline mode, judging.
* [How Minima uses Mubit](/sdk/mubit) — what recall and reinforcement do server-side.
* [Tools & permissions](/harness/tools) — the permission gate and the `task` tool.
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.
This page covers the **CLI harness**. Configuration for the Minima **service** itself (only relevant when [self-hosting](/sdk/self-hosting)) is a separate set of environment variables — the full server reference is [`docs/configuration.md`](https://github.com/mubit-ai/minima/blob/main/docs/configuration.md) in the repo.
### 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.
#### Harness features
These are on by default — set the variable to `0` to opt out. Every one fails open: with the feature off the harness behaves exactly as it did before it existed.
| Variable | Default | What it turns off |
| ---------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MINIMA_TUI_BIG_PLAN` | on | [Plan verification](/harness/architecture#plan-verification) — verify commands, gates, confidence tiers. |
| `MINIMA_TUI_MEMORY` | on | The [memory ledger](/harness/usage-flows#4-memory-that-compounds) and its per-turn injection. |
| `MINIMA_TUI_ARTIFACTS` | on | Artifact spill: oversized tool output is written to a content-addressed file the model can page back with `read`, instead of being truncated into the transcript. |
| `MINIMA_TUI_ARTIFACT_GC_MB` | `512` | LRU cap (in MB) on the artifact directory; the current run is exempt. `0` disables GC entirely. |
| `MINIMA_TUI_COMPACT2` | on | Lossless compaction: the pre-compaction transcript is kept as an artifact the summary points at, so nothing is unrecoverable. Inert when `MINIMA_TUI_ARTIFACTS=0`. |
| `MINIMA_TUI_EDIT_GUARD` | on | Edit guard: `read`/`grep` stamp `[snap:…]` tags and record which lines were actually seen, so an edit against unseen or stale content gets a deterministic re-read instead of a blind write. |
| `MINIMA_TUI_STEER` | on | Bash steering + replay guard — a command repeated verbatim after failing is steered rather than looped. |
| `MINIMA_TUI_REWIND` | on | The `checkpoint` / `rewind` tool pair (context rewind; the DB transcript always keeps every row). |
| `MINIMA_TUI_BGJOBS` | on | `bash` background jobs (`background: true`) and the `bgjob` control tool. Background jobs are killed at session end. |
| `MINIMA_TUI_TYPED_TASK` | on | `output_schema` on the `task` tool — typed sub-agent results with a shape check. |
| `MINIMA_TUI_PLAN_CRITIC` | on | The planning critic: one cheap pass over the approved steps at `/plan` finalize, flagging non-discriminative checks and hidden dependencies. Advisory — it never blocks a plan. |
| `MINIMA_TUI_DIFF_REVIEW` | on | The zero-context diff review that fires when a plan closes fully completed. An objection writes a yellow gate; it can never turn a plan green. |
| `MINIMA_TUI_AUTO_GATES` | on | Auto-gates: steps the model gave no check for are filled with your repo's own commands, mined from `package.json` / `Makefile`. With it off those steps stay unverified. |
| `MINIMA_TUI_PLAN_PREMIUM` | on | Premium models for plan synthesis and the design council. Off routes planning through the ordinary session pool. |
| `MINIMA_TUI_TOOL_ALLOWLIST` | on | Per-step tool allowlists — a step naming its tools cannot use others while it is in progress. |
| `MINIMA_TUI_FAILURE_MATCHER` | on | Failure classification on the recovery ladder. Off falls back to the blunt always-escalate behavior. |
| `MINIMA_TUI_GRADED_OUTCOME` | on | Graded outcome labels, which report unverified-but-positive evidence distinctly from verified evidence. |
Two variables work the other way — they grant something the harness refuses by default:
| Variable | Default | What setting it does |
| ------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MINIMA_TUI_FETCH_LOCAL` | unset (deny) | `=1` lets `web_fetch` reach loopback, link-local, and private addresses. Default is DENY, so a model-authored URL cannot reach your local network or a cloud metadata endpoint. |
| `MINIMA_TUI_ALLOW_VERIFY` | unset (deny) | `=1` lets plan verify commands run in headless (`-p` / `--mode json`) runs, where they otherwise fail closed. |
Opt-in features live behind the experimental umbrella — see [Experimental features](/harness/usage-flows#8-experimental-features).
#### 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, plus plan/verification state when [plan verification](/harness/architecture#plan-verification) is active.
### Keyboard shortcuts
| Key | Action |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Enter | Send the current prompt |
| ↑ / ↓ | Cycle input history (at the prompt) · navigate items (in a picker) |
| Alt+←/→ | Jump by word (also Alt+B/F) |
| Ctrl+A / Home·End | Line start / start·end |
| Ctrl+K / Ctrl+U | Kill to end / to start of line |
| Ctrl+W | Kill the previous word (also Alt+Backspace) |
| Ctrl+V | Paste clipboard (terminal paste also works) |
| Ctrl+Y | Copy the last assistant reply |
| Esc / Ctrl+C | Abort the in-flight run (Ctrl+C twice at the prompt quits) |
| Ctrl+Z | Suspend to the shell (`fg` returns) |
| Shift+Tab | Cycle permission modes (build → accept → plan → bypass) |
| Ctrl+L | Open the model picker |
| Ctrl+P | Open the command palette (slash commands) |
| Ctrl+R | Toggle route mode (auto ↔ confirm) |
| Ctrl+E | Toggle thinking display |
| Ctrl+T | Table of contents |
| Ctrl+G | Plan Overview |
| Ctrl+B | Toggle the task panel |
| Tab | Auto-complete a slash command |
Scrolling, text selection, and copy are native to your terminal (wheel/trackpad) — the TUI doesn't capture the mouse. In pickers, use ↑/↓ to move, Enter to choose, Esc to dismiss.
### Slash commands
Type `/` (or open the palette with Ctrl+P) and pick a command:
#### Routing & cost
| Command | What it does |
| ------------------ | -------------------------------------------------------------------------------- |
| `/model` | Select or pin a model (or `auto` to unpin and let Minima route) |
| `/cost` | Show cost-meter totals |
| `/budget` | Show/set the session budget (`/budget set ` · `/budget mode warn\|enforce`) |
| `/judge [on\|off]` | Toggle LLM quality judging |
| `/reconnect` | Reconnect the routing client |
#### Planning & verification
| Command | What it does |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/plan` | Plan mode (Shift+Tab) + the planning council (`start` · `status` · `finalize` · `cancel`) — drafts a plan whose steps carry verify checks |
| `/mode` | Show/set the permission mode: `build` \| `accept` \| `plan` \| `bypass` |
| `/bp` | Show Plan Overview status |
| `/why [n]` | Show the verification evidence behind the plan (or open step `n`'s card) |
| `/verify` | Adversarial whole-plan verification pass (refutation sub-agent) |
| `/audit` | Lint the active plan — non-discriminative checks, missing allowlists, vague steps |
| `/tasks` | Toggle the task panel (Ctrl+B) · `/tasks cancel` rejects the list + the plan |
| `/tree` | Toggle the live [sub-agent tree panel](/harness/tools#sub-agents-the-task-tool) |
#### Memory
| Command | What it does |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/memory` | Curated memory: `list` · `add ` · `pin`/`confirm`/`reject`/`delete ` · `dream` — see [the memory ledger](/harness/architecture#the-memory-ledger) |
#### History & recovery
| Command | What it does |
| --------------------------- | -------------------------------------------------------------------------- |
| `/undo` | Undo the last change — checkpoint restore + re-prompt (stacks) |
| `/rewind [n]` | Rewind to an earlier prompt (picker, or `/rewind [convo\|code\|both]`) |
| `/ckpt` | List git-shadow checkpoints (`/ckpt gc` prunes old runs' refs) |
| `/new` · `/resume [id]` | Start fresh · resume a previous session |
| `/name