SDK Helpers
The global helpers (init, run, step, outcome, context) and the Client namespaces for memory, lessons and outcomes.
The SDK has two tiers on top of the wire:
- Global helpers (
mubit.*): configured once withmubit.init(), they resolve the current run from context and fail open (oneWARNINGper endpoint and failure class, then a typed empty result with areason). Python and JS only. - Client namespaces (
client.memory,client.runs,client.lessons,client.outcomes, …): typed, raise by default, identical in Python, JS and Rust. Use them when you need explicit control over scope, runs and metadata, or from a process that has no ambient run.
Helper map
| Helper | Purpose | When to reach for it |
|---|---|---|
mubit.init() | Build the default client, resolve scope from arguments and env, install instrumentation | Once, at process start |
mubit.run(name) | Open a run; sends run.start, and run.end when the block exits | Around each unit of agent work |
mubit.step(name) | Open a step inside the run; step-level events carry step_id | Around each tool call or sub-task |
mubit.outcome(...) / run.outcome(...) / step.outcome(...) | Record good, score or label for the run or step | When the result is known |
mubit.context(task) / run.context(task) | Read the ContextBlock the server would inject | When you place the context yourself |
mubit.note(text) | Attach an observation to the run | Facts the model did not produce |
mubit.remember(content) / client.memory.remember() | Write one memory item | Storing facts, lessons, observations |
mubit.recall(query) / client.memory.recall() | Answer-oriented retrieval (final_answer, evidence, citations) | Asking the memory a question |
client.memory.archive() / client.memory.dereference() | Store and fetch an exact artifact by reference_id | Anything you need back byte-for-byte |
client.lessons.reflect() | Distil lessons from a run on demand | When you do not want to wait for run end |
client.lessons.list() / client.lessons.transitions() | List the lesson set; list what one run produced | Audit, debugging |
client.checkpoint() | Snapshot memory state with a label | Before compaction or risky transitions |
client.outcomes.record() | Low-level outcome against an existing entry | RL-style signal on a specific memory entry |
Minimal usage
The loop results are typed: ContextBlock, OutcomeReceipt, NoteReceipt, RunEndReceipt, Capabilities and Scope support attribute access (ctx.text) and dict access (ctx["text"]) in Python. recall() and remember() return the wire response as a plain dict (Python) or object (JS), so read answer["final_answer"] / answer.final_answer. The recall() response includes a citations array: 0-based indices into evidence marking which items grounded final_answer (empty when the answer cites no specific evidence). mubit.recall() (Python) defaults to mode="direct_bypass" with evidence_only=True, which leaves final_answer empty; pass mode="agent_routed" for a synthesised answer. client.memory.recall() defaults to agent_routed.
Cross-run recall
recall() reads the current run by default. To read memory written by a different run for the same user:
- Store the memory as a lesson with
visibility="global". - On recall, use the same
userscope andentry_types=["lesson"](kinds=onmubit.recall(),entryTypesin JS).
support.memory.remember(
"Taylor prefers concise written updates on Friday afternoons.",
kind="lesson", visibility="global", run_id="s1",
)
# Different run, same user scope: recall returns the lesson.
support.memory.recall("how does Taylor like updates?", entry_types=["lesson"], run_id="s2")kind="fact" memories stay with the run that wrote them even when the user matches. visibility takes run, agent, project or global; it replaces the 0.13 lesson_scope and share arguments.
The context block
run.context(task) and client.memory.context(task) return a ContextBlock:
| Field | Meaning |
|---|---|
text | The rendered block, ready to place in a system message |
units | The injected units (id, kind, content, confidence, score, lesson_id) |
injection_id | Receipt id; pass it to outcome(injection_ids=[...]) for explicit attribution |
budget | Tokens requested and used |
degraded, reason | True plus unreachable, unauthorized, killed, disabled or legacy when the read fell back to empty |
scope | The resolved project, env, agent: check this when you get zero units |
Reading the context does not change how the run injects. To stop the wrappers from injecting automatically because you place the block yourself, open the run with mubit.run(name, inject="manual").
The legacy assembler (/v2/control/context) is still reachable with client.memory.context(task, lane="legacy", mode=...); its mode values are "full" (one context_block string), "summary" (section_summaries[]) and "sections".
Exact references
archive() and dereference() are the exact-recovery pair. Use them for anything semantic recall is the wrong tool for: original diffs, raw tool outputs, generated SQL you will re-execute later.
archived = support.memory.archive(
content="Original billing diff and remediation note", # keyword-only
artifact_kind="billing_postmortem",
labels=["billing", "exact"],
run_id="ticket-42",
)
# Later, possibly in a different run: fetch the exact content back.
exact = support.memory.dereference(archived["reference_id"], run_id="ticket-42")archive() requires the archive_block write scope; dereference() requires the matching read scope. Register your agent with both if it needs to round-trip artifacts.
Reflection and outcomes
Record an outcome for the current run with mubit.outcome(good=True) (or score=0.8, or label="resolved"). client.outcomes.record(reference_id=...) is the low-level form and requires the id of an existing memory entry (for example the id of an item in recall()["evidence"], or a lesson_id from client.lessons.reflect(...)["lessons"]); a made-up reference_id fails with NotFoundError: referenced entry was not found.
receipt = mubit.outcome(good=True) # OutcomeReceipt(sent=True, reason="ok")
print(receipt.sent, receipt.reason)
# Low-level: attribute an outcome to a specific entry and its contributors.
hit = support.memory.recall("SSO bypass", run_id="ticket-42")["evidence"][0]
support.outcomes.record(
run_id="ticket-42",
reference_id=hit["id"],
good=True,
rationale="Customer confirmed the SSO bypass resolved the ticket.",
unit_ids=["entry-7f3", "entry-9a1"],
)One normaliser applies everywhere: a bool is good, a number is score, a string is label. mubit.outcome(0.8) and step.outcome("resolved") therefore mean what they look like.
Pass unit_ids to attribute the outcome to every injected unit that contributed, not just the primary reference_id (which is never double-counted). The units of the ContextBlock that grounded the answer you acted on are the usual source.
Distillation runs on the server when the run ends. To distil on demand:
ref = client.lessons.reflect(run_id="ticket-42")
for lesson in ref.get("lessons") or []:
print(f"[{lesson.get('lesson_type')}] {lesson.get('content')}")Listing lessons
client.lessons.list(as_of=None, status=None, scope=None, project=None, env=None, limit=None) lists the lesson set the server injects (a list of dicts). To list what one run produced: client.lessons.transitions(run_id=...).
for lesson in client.lessons.list(status="active", env="dev"):
print(lesson["id"], lesson["scope"], lesson["confidence"], lesson["content"])
for t in client.lessons.transitions(run_id="ticket-42"):
print(t["lesson_id"], (t.get("from") or {}).get("status"), "->", t["to"]["status"], t["reason"])The same lists are the Lessons tab of the Loop console.
When to drop down to the raw client
Reach for client.raw.invoke(op, payload) only when you need:
- Async ingest with explicit job polling (
control.ingestplusclient.jobs.get(job_id, run_id=...)). - Raw wire payloads for tooling or observability that needs the full response shape.
- A contract operation that has no namespace method yet.
job = client.raw.invoke("control.ingest", {"run_id": "ticket-42", "items": [{"text": "…", "intent": "fact"}]})Most application code never needs this layer. See the Control HTTP reference for the full surface and SDK methods for the namespace map.
Migration
session_id is accepted as a deprecated alias of run_id on the memory helpers and of thread on mubit.run() until 1.0. lesson_scope and share are accepted as aliases of visibility. Each use prints one deprecation warning naming the replacement; the full list is in Migration.