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); the repo's docs/client-sdk.md documents that framing.
Install
pip install minima-cliThe SDK is published on PyPI as part of 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 (brew install mubit-ai/minima/minima). Import the SDK as minima_client:
from minima_client import MinimaClient, AsyncMinimaClient, MinimaError, autocaptureClients
Both MinimaClient (sync) and AsyncMinimaClient (async) share the same surface.
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 asAuthorization: Bearer <key>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:
async with AsyncMinimaClient("https://api.minima.sh", api_key="mbt_…") as minima:
rec = await minima.recommend(task)recommend(task, *, ...)
Returns a RecommendResponse.
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:
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.
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.
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()
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() # dictdiagnose(...) and memory_health(...)
The memory insight layer: 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:
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 for every field).
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 driftPass 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):
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:<namespace>) 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.
from minima_client import autocapture
autocapture.enable(
api_key="<mubit-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 behaviorWhat 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:
autocapture.wrap(client) # enrich one client instead of global patching
autocapture.capture(messages, response) # manual ingest for raw HTTP / unsupported libsSee the zero-code intake example.