SDK methods
Three integration layers: global helpers, Client namespaces, and the raw wire.
| Layer | What it does | When to use |
|---|---|---|
Global helpers (mubit.init, run, step, outcome, context, note, remember, recall, wrappers) | Context-propagated closed loop: inject before each model call, capture after it, distil at run end. Python and JS; fail open with one warning | Application code; agents learn with a few lines of setup |
Client namespaces (client.memory, runs, outcomes, lessons, policy, kill, jobs, proposals, snapshots, agents, projects, audit, admin) | Typed methods that raise by default; identical in Python, JS and Rust | Explicit control over scope, runs and metadata; operators and workers |
Raw wire (client.raw.invoke(op, payload), client.raw.stream) | 1:1 access to every contract operation | Wire debugging, operations without a namespace method yet |
Start with the helpers or the namespaces. Drop to client.raw only when you need exact payload control.
Global helpers (closed loop)
Before each wrapped model call, the SDK fetches the context block for the current run and injects it into the system message. After the call, the turn is sent as a model.call event. When the run ends, the server distils new lessons under the effective policy.
Namespace map
| Namespace | Methods | Wire |
|---|---|---|
client.memory | remember, recall, context, lookup, search, archive, dereference, forget, health, diagnose | /v2/control/ingest, query, context, /v2/loop/context, archive, dereference |
client.runs | create, end, status, capabilities, emit, link, unlink, history, get_history, delete, heartbeat, circuit_break; runs.steps.record | /v2/loop/events, /v2/loop/status, /v2/control/runs*, /v2/control/step_outcome |
client.outcomes | record, feedback | outcome events, /v2/control/outcome, /v2/control/feedback |
client.lessons | list, get, delete, create, transitions, strategies, reflect | /v2/loop/lessons, /v2/loop/transitions, /v2/control/reflect, /v2/control/lessons/delete |
client.policy | get, set, unset, replace, reset, promote, diff (Python) | /v2/loop/policy* |
client.kill | get, set | /v2/loop/kill |
client.jobs | claim, get, list, run_one, work, serve; Job.heartbeat, complete, fail, refresh | /v2/loop/jobs* |
client.proposals | list, resolve | /v2/loop/proposals* |
client.snapshots | create, list, get, restore (lesson-set snapshots) | /v2/loop/snapshots* |
client.agents | register, list, heartbeat; agents.definitions.create, get, list, update, delete; agents.handoffs.create, receive, feedback | /v2/control/agents*, /v2/control/projects/agents/*, /v2/control/handoff, /v2/control/feedback |
client.projects | create, get, update, delete, list | /v2/control/projects/* |
client.audit | list, export_skills, run_history, list_run_history, subscribe, watch | /v2/loop/audit, /v2/loop/skills, /v2/control/runs, event streams |
client.export | skills, audit | /v2/loop/skills, /v2/loop/audit |
client.admin | users.*, api_keys.*, permissions.* | /v2/core/auth/* |
client.raw | invoke(op, payload), stream(op, payload) | any contract operation |
| top-level typed methods | client.checkpoint, client.optimize_prompt, client.optimize_skill, client.circuit_break, client.learned, client.health, client.capabilities | /v2/control/checkpoint, /v2/control/prompt/optimize, /v2/control/skills/optimize, ... |
Prompt versions, skills, agent definitions and projects have no typed argument list: the namespace methods (client.agents.definitions.*, client.projects.*) and client.raw.invoke take the wire field names (project_id, agent_id, version_id, ...) as keyword arguments or one payload dict. Prompt and skill version operations other than optimize_* are raw calls: client.raw.invoke("control.set_prompt", {...}), "control.get_prompt", "control.list_prompt_versions", "control.activate_prompt_version", "control.get_prompt_diff", "control.create_skill", "control.list_skills", "control.list_skill_versions", "control.activate_skill_version", "control.get_skill_diff" (see Projects, Agents, Skills, Prompts).
JS uses the same names in camelCase (client.runs.steps.record, client.audit.exportSkills, client.jobs.runOne); Rust uses accessor methods (client.memory().recall(...), client.runs().steps().record(...)). Three entries of this map have no Rust method: client.runs.list (the /v2/loop/runs listing), client.export, and jobs.serve — in Rust, page runs through client.raw().invoke(...), reach the export routes through client.audit().export_skills(...), and use client.jobs().work(...) for a long-running worker. The namespace methods that wrap a typed helper (memory.*, outcomes.*, runs.steps.record, agents.register, agents.handoffs.*, lessons.*) take camelCase options in JS (runId, agentId, entryTypes); the payload-style methods (projects.*, agents.definitions.*, runs.link, audit.subscribe, client.raw.invoke) take the wire field names in every language.
Helper bundles by use case
| Use case | Methods | What they do |
|---|---|---|
| Basic memory | client.memory.remember, client.memory.recall | Write one item with a kind and visibility; answer-oriented query with evidence scoring |
| Prompt context | client.memory.context / run.context | ContextBlock for LLM injection (text, units, budget, scope, reason) |
| Exact artifacts | client.memory.archive, client.memory.dereference | Bit-exact storage with stable reference ids; retrieval without semantic search |
| Run lifecycle | client.runs.create / client.runs.end, client.checkpoint, client.lessons.reflect, client.outcomes.record, client.runs.steps.record(...) | Explicit run boundaries; durable state; on-demand distillation; run-level signal; per-step signals |
| Ingest lifecycle | client.jobs.get(job_id, run_id=...), client.raw.invoke("control.get_run_ingest_stats", ...) | Poll async ingest (details); per-run counters. client.memory.remember() waits for its job by default; client.raw.invoke("control.ingest", ...) returns a job_id: call client.jobs.get(job_id, run_id=...) until done is true before reading the same run |
| Multi-agent | client.agents.register, client.agents.list, client.agents.handoffs.create / receive / feedback | Scoped access per agent; task transfer |
| Diagnostics | client.memory.health, client.memory.diagnose, client.lessons.strategies, client.memory.forget | Staleness metrics; error debugging; lesson clustering; deletion |
Until you upgrade call sites, the 0.13 names keep working with a deprecation warning: client.record_step_outcome(...) / client.recordStepOutcome(...) for client.runs.steps.record(...), and client.advanced.get_ingest_job(...) / client.advanced.getIngestJob(...) for client.jobs.get(...). See Migration.
The learning loop
1. mubit.run(name) → run.start; wrapped model calls inject the context block and send model.call events
2. step.outcome(...) → per-step signals (optional, for dense attribution)
3. run.outcome(...) → run-level signal, credited to the units that were injected
4. run end → the server distils lessons under the effective policy (starter@1 by default)
5. next run's context → the lesson set for (project, env) is injected before the first model callWith the global helpers, steps 1, 4 and 5 happen automatically. With the namespaces, you call client.runs.create / emit / end, client.outcomes.record and client.memory.context yourself.
Under the built-in starter@1 policy, lessons distilled at run end are stored active with scope=session and become visible to other runs in the same env according to the overlay gate (same actor, or promotion, or gate.min_actors distinct actors). Explicit lessons written with remember(kind="lesson") or note(intent="lesson") are gated and enter as pending; with gate.mode=propose they appear in the console's Proposals tab. Every change is a transition with a reason; client.lessons.transitions(lesson_id=...) lists them.
Current helper catalog
Step-level outcomes
Record per-step signals inside a run. The step's events carry a step_id, so an outcome recorded on the step is credited to the units injected for that step.
import mubit
with mubit.run("research-task"):
with mubit.step("search_api") as step:
doc = search_api("quarterly numbers") # your code
step.outcome(score=0.8, label="success", rationale="Found the correct document on first try")The low-level form takes an explicit run and step id:
client.runs.steps.record(
run_id="my-run",
step_id="tool_call_1",
step_name="search_api",
outcome="success",
signal=0.8,
rationale="Found the correct document on first try",
directive_hint="Keep using search before browsing",
)Distillation at run end sees step outcomes automatically. To distil on demand with step outcomes folded in, pass the wire field through the raw operation:
Lane-scoped memory
Lanes partition memory within a shared run so each agent sees only relevant entries.
# Write into a specific lane
client.memory.remember("Planning output: task A depends on B", kind="fact", lane="planning", run_id="shared-run")
# Query only the planning lane
result = client.memory.recall("task dependencies", lane="planning", run_id="shared-run")
# Register an agent with lane participation
client.agents.register(agent_id="planner", role="planner", shared_memory_lanes=["planning", "shared"])lane (multi-agent memory isolation) is distinct from direct_lane (core data-plane retrieval routing). They serve different purposes and do not interact.
Step-wise reflection
Scope on-demand distillation to recent evidence:
# Distil over only the 5 most recent items
client.raw.invoke("control.reflect", {"run_id": "my-run", "last_n_items": 5})
# Bring your own extractor: skip the built-in agent, run the gate and promotion
# ladder over lessons you already extracted. Same response, same `decisions`.
client.raw.invoke("control.reflect", {
"run_id": "my-run",
"lessons": [{"content": "Retry 429s with backoff", "lesson_type": "observation"}],
"confidence": 0.9,
})client.lessons.reflect(run_id=..., include_linked_runs=False) is the typed form for a whole run. lessons and confidence are also accepted as keyword arguments by the reflect() helper in all three SDKs; stored rows carry auto_extracted: false. See Step-wise reflection parameters for the field shapes and defaults.
When to use what
| Scenario | Use |
|---|---|
| Agents should learn with minimal code | mubit.init() + mubit.wrap_openai() (Python), mubit.init() + mubit.wrapOpenAI() (JS), client.run(...) (Rust) |
| Place the context block yourself | run.context(task) and mubit.run(name, inject="manual") |
| Control exactly what gets remembered | client.memory.remember() + client.memory.recall() |
| Multiple agents with scoped access | client.agents.register() + client.agents.handoffs.create() |
| Bit-exact artifact storage | client.memory.archive() + client.memory.dereference() |
| Run an external distiller or gatekeeper | @mubit.distiller / client.jobs.work(handler) |
| Operate the loop (policy, kill, snapshots) | client.policy.*, client.kill.*, client.snapshots.*, or the mubit CLI |
| Wire-level debugging | client.raw.invoke(op, payload) |
When to use the raw client directly
Use client.raw.invoke(op, payload) when you need one of these explicitly:
control.ingestplusclient.jobs.getjob polling, orcontrol.batch_insert- exact raw request/response debugging against HTTP or gRPC
- advanced or compatibility state-management routes
control.get_run_ingest_statsfor per-run counterscontrol.context_snapshotfor a full context snapshot
The op names are the contract names in Control HTTP reference. client.raw.stream(op, payload) returns an iterator for streaming operations.
Ingest job tracking
client.memory.remember() waits for its ingest job by default (wait=True), so the item is visible to the next recall() or context(). The raw control.ingest operation is asynchronous: it returns a job_id immediately. If the next step in your code reads the same run, poll client.jobs.get(job_id, run_id=...) until done is true. client.jobs.get serves both queues: a loop job id (job_<uuid>, distill / gate) comes back as a Job; any other id is looked up as an ingest job (GET /v2/control/ingest/jobs/:job_id) and comes back as an IngestJob with done, status and error. The ingest lookup needs the run the job was submitted for: pass run_id (JS { runId }), or rely on the client's run scope or the current loop run. Framework adapters that advertise synchronous put semantics (for example MubitStore in mubit-langgraph) do this polling for you.
For bulk writes, prefer one control.ingest call with a list of items over many remember() calls: it is one job instead of N. Each ingest call accepts at most 1000 items; chunk larger workloads into multiple calls (requests over the cap return 400 / BadRequestError).
Run management
List run history, link, unlink, and delete runs. The server clamps the limit on run-history listing to a max of 1000 (default 100).
Context snapshot
Retrieve a full context snapshot for a run, including working memory, attention state, and active goals.
Temporal and quality features
Occurrence time
Mubit tracks two time dimensions for every memory entry: ingestion time (when the system learned it) and occurrence time (when the event actually happened). occurrence_time is a wire-level item field; route it through the raw ingest operation, which forwards arbitrary item fields.
Temporal queries
Use min_timestamp and max_timestamp to filter evidence to a specific time window. The filter checks occurrence_time first, falling back to ingestion time. Both are wire-level query fields; route them through the raw query operation.
Without temporal bounds, queries like "What happened last week?" use natural language temporal intent detection and prioritize entries by occurrence time in the recency ranking.
Search budget
The budget query field controls the depth of retrieval. Use "low" for real-time agents and "high" for accuracy-critical offline analysis.
| Budget | Behavior | Typical latency |
|---|---|---|
"low" | Fewer candidates, skip deep traversal | < 500ms |
"mid" | Standard retrieval (default) | 500ms–2s |
"high" | More candidates, deeper graph traversal | 1–5s |
# Fast retrieval for a real-time chatbot
fast = client.raw.invoke("control.query", {"run_id": "my-run", "query": "user question", "budget": "low"})
# Deep retrieval for a research report
deep = client.raw.invoke("control.query", {"run_id": "my-run", "query": "comprehensive analysis topic", "budget": "high"})Staleness detection
When a newer fact contradicts an older one, Mubit marks the older entry as stale and deprioritizes it in ranking. The staleness metadata is available on each evidence item.
Stale entries are still returned for transparency. The ranking penalty ensures they appear below the current fact. Filter them out in your application if you only want current information.
Mental models
The mental_model kind stores consolidated entity summaries that are prioritized over raw facts in context assembly. Use this for entities your agent tracks over time.
Mental models are returned with higher priority than individual facts in recall() and context(). Update them periodically as your agent learns more about an entity.
Failure modes and troubleshooting
| Symptom | Root cause | Fix |
|---|---|---|
| SDK usage becomes inconsistent across teams | Raw and namespace paths mixed arbitrarily | Set the namespaces as the default integration contract; reserve client.raw for investigations |
| A call silently returns an empty result | Global helper failed open | Read the reason on the result; run with MUBIT_LOG=info; switch to on_error="raise" in tests |
| Deprecation warnings in logs | 0.13 names in use | Follow the link in the warning to Migration |
Next steps
- See the concrete helper flow at Quickstart.
- See the wire contract at Control HTTP reference.