Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content
Build with Mubit

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 with mubit.init(), they resolve the current run from context and fail open (one WARNING per endpoint and failure class, then a typed empty result with a reason). 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

HelperPurposeWhen to reach for it
mubit.init()Build the default client, resolve scope from arguments and env, install instrumentationOnce, at process start
mubit.run(name)Open a run; sends run.start, and run.end when the block exitsAround each unit of agent work
mubit.step(name)Open a step inside the run; step-level events carry step_idAround each tool call or sub-task
mubit.outcome(...) / run.outcome(...) / step.outcome(...)Record good, score or label for the run or stepWhen the result is known
mubit.context(task) / run.context(task)Read the ContextBlock the server would injectWhen you place the context yourself
mubit.note(text)Attach an observation to the runFacts the model did not produce
mubit.remember(content) / client.memory.remember()Write one memory itemStoring 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_idAnything you need back byte-for-byte
client.lessons.reflect()Distil lessons from a run on demandWhen you do not want to wait for run end
client.lessons.list() / client.lessons.transitions()List the lesson set; list what one run producedAudit, debugging
client.checkpoint()Snapshot memory state with a labelBefore compaction or risky transitions
client.outcomes.record()Low-level outcome against an existing entryRL-style signal on a specific memory entry

Minimal usage

getting_started.py
import mubit
 
mubit.init(agent="support-agent")                          # MUBIT_API_KEY, MUBIT_ENDPOINT, MUBIT_PROJECT, MUBIT_ENV
 
with mubit.run("ticket-42", user="taylor-1") as run:
    mubit.remember("Customer Taylor prefers concise Friday updates.",
                   kind="fact", metadata={"customer": "taylor", "source": "quickstart"})
 
    answer = mubit.recall("What update style does Taylor want?",
                          kinds=["fact", "lesson", "rule"], mode="agent_routed")   # default mode is evidence-only
    print(answer["final_answer"])
 
    ctx = run.context("Draft the next customer update.", budget=300)
    print(ctx.text, ctx.units, ctx.scope.env)
 
    run.outcome(good=True)

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 user scope and entry_types=["lesson"] (kinds= on mubit.recall(), entryTypes in 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:

FieldMeaning
textThe rendered block, ready to place in a system message
unitsThe injected units (id, kind, content, confidence, score, lesson_id)
injection_idReceipt id; pass it to outcome(injection_ids=[...]) for explicit attribution
budgetTokens requested and used
degraded, reasonTrue plus unreachable, unauthorized, killed, disabled or legacy when the read fell back to empty
scopeThe 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.ingest plus client.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.