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 methods

Three integration layers: global helpers, Client namespaces, and the raw wire.

LayerWhat it doesWhen 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 warningApplication 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 RustExplicit 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 operationWire 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.

import mubit, openai
 
mubit.init(agent="my-agent")                       # env: MUBIT_API_KEY, MUBIT_ENDPOINT, MUBIT_PROJECT, MUBIT_ENV
llm = mubit.wrap_openai(openai.OpenAI())
 
@mubit.agent("planner")                            # one run per call; run.end is sent on return
def plan_task(task):
    resp = llm.chat.completions.create(model="gpt-5-mini", messages=[{"role": "user", "content": task}])
    mubit.outcome(good=True)
    return resp.choices[0].message.content

Namespace map

NamespaceMethodsWire
client.memoryremember, recall, context, lookup, search, archive, dereference, forget, health, diagnose/v2/control/ingest, query, context, /v2/loop/context, archive, dereference
client.runscreate, 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.outcomesrecord, feedbackoutcome events, /v2/control/outcome, /v2/control/feedback
client.lessonslist, get, delete, create, transitions, strategies, reflect/v2/loop/lessons, /v2/loop/transitions, /v2/control/reflect, /v2/control/lessons/delete
client.policyget, set, unset, replace, reset, promote, diff (Python)/v2/loop/policy*
client.killget, set/v2/loop/kill
client.jobsclaim, get, list, run_one, work, serve; Job.heartbeat, complete, fail, refresh/v2/loop/jobs*
client.proposalslist, resolve/v2/loop/proposals*
client.snapshotscreate, list, get, restore (lesson-set snapshots)/v2/loop/snapshots*
client.agentsregister, 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.projectscreate, get, update, delete, list/v2/control/projects/*
client.auditlist, export_skills, run_history, list_run_history, subscribe, watch/v2/loop/audit, /v2/loop/skills, /v2/control/runs, event streams
client.exportskills, audit/v2/loop/skills, /v2/loop/audit
client.adminusers.*, api_keys.*, permissions.*/v2/core/auth/*
client.rawinvoke(op, payload), stream(op, payload)any contract operation
top-level typed methodsclient.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 caseMethodsWhat they do
Basic memoryclient.memory.remember, client.memory.recallWrite one item with a kind and visibility; answer-oriented query with evidence scoring
Prompt contextclient.memory.context / run.contextContextBlock for LLM injection (text, units, budget, scope, reason)
Exact artifactsclient.memory.archive, client.memory.dereferenceBit-exact storage with stable reference ids; retrieval without semantic search
Run lifecycleclient.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 lifecycleclient.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-agentclient.agents.register, client.agents.list, client.agents.handoffs.create / receive / feedbackScoped access per agent; task transfer
Diagnosticsclient.memory.health, client.memory.diagnose, client.lessons.strategies, client.memory.forgetStaleness 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 call

With 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.

ℹ️Note

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

  • Global: mubit.init, run, step, outcome, context, note, remember, recall, forget, learned, checkpoint, reflect, scope, agent, session, async_session, wrap, wrap_openai, wrap_anthropic, instrument, uninstrument, current_run, current_step, capabilities, flush, shutdown, get_context, set_prompt, get_prompt, get_skills, set_skill, __version__
  • mubit.aio.<name> (import mubit.aio) for the async mirror of the helpers
  • Module-level control namespaces bound to the init() client: mubit.policy, jobs, proposals, snapshots, export, kill, kill_status, lessons, transitions
  • Client(...), Client.from_env(), client.with_options(...), client.capabilities(), client.health()
  • Namespaces: memory, runs (+ runs.steps), outcomes, lessons, policy, kill, jobs, proposals, snapshots, export, agents (+ definitions, handoffs), projects, audit, admin, raw
  • Distillers: @mubit.distiller, @mubit.gatekeeper for external distill / gate jobs

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:

client.raw.invoke("control.reflect", {"run_id": "my-run", "include_step_outcomes": True})

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"])
ℹ️Note

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

ScenarioUse
Agents should learn with minimal codemubit.init() + mubit.wrap_openai() (Python), mubit.init() + mubit.wrapOpenAI() (JS), client.run(...) (Rust)
Place the context block yourselfrun.context(task) and mubit.run(name, inject="manual")
Control exactly what gets rememberedclient.memory.remember() + client.memory.recall()
Multiple agents with scoped accessclient.agents.register() + client.agents.handoffs.create()
Bit-exact artifact storageclient.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 debuggingclient.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.ingest plus client.jobs.get job polling, or control.batch_insert
  • exact raw request/response debugging against HTTP or gRPC
  • advanced or compatibility state-management routes
  • control.get_run_ingest_stats for per-run counters
  • control.context_snapshot for 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.

import time
 
# Submit + poll until the job is durable
job = client.raw.invoke("control.ingest", {"run_id": "my-run", "items": [{"text": "…", "intent": "fact"}]})
while not client.jobs.get(job["job_id"], run_id="my-run").done:
    time.sleep(0.2)
 
# Per-run aggregate stats
stats = client.raw.invoke("control.get_run_ingest_stats", {"run_id": "my-run"})
ℹ️Note

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).

runs = client.runs.history(limit=100)
 
# Link a child run to a parent
client.runs.link(run_id="parent-run", linked_run_id="child-run")
 
# Unlink
client.runs.unlink(run_id="parent-run", linked_run_id="child-run")
 
# Delete a run and its data
client.runs.delete("old-run")

Context snapshot

Retrieve a full context snapshot for a run, including working memory, attention state, and active goals.

snapshot = client.raw.invoke("control.context_snapshot", {"run_id": "my-run"})

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.

import time
 
# Event happened 3 days ago, ingested now
client.raw.invoke("control.ingest", {
    "run_id": "my-run",
    "items": [{
        "text": "New CI/CD pipeline reduced deployment time by 60%.",
        "intent": "fact",
        "occurrence_time": int(time.time()) - 86400 * 3,
    }],
})
 
# Historical event from January 2025
client.raw.invoke("control.ingest", {
    "run_id": "my-run",
    "items": [{
        "text": "Server migration to AWS completed with zero downtime.",
        "intent": "fact",
        "occurrence_time": 1736899200,  # Jan 15 2025 UTC
    }],
})

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.

# "What happened in January 2025?"
results = client.raw.invoke("control.query", {
    "run_id": "my-run",
    "query": "What technical changes were made?",
    "min_timestamp": 1735689600,   # Jan 1 2025
    "max_timestamp": 1738367999,   # Jan 31 2025
})
 
for evidence in results["evidence"]:
    print(f"  {evidence['content'][:80]}")

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.

BudgetBehaviorTypical latency
"low"Fewer candidates, skip deep traversal< 500ms
"mid"Standard retrieval (default)500ms–2s
"high"More candidates, deeper graph traversal1–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.

results = client.memory.recall("Where is the office?", run_id="my-run")
for evidence in results["evidence"]:
    status = " [STALE]" if evidence.get("is_stale") else ""
    print(f"  {evidence['content'][:60]}{status}")
ℹ️Note

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.

client.memory.remember(
    "Alice Chen is a senior engineer specializing in distributed systems. "
    "She prefers async communication and reviews PRs within 24 hours.",
    kind="mental_model",
    metadata={"entity": "alice chen", "consolidated": True},
    run_id="my-run",
)

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

SymptomRoot causeFix
SDK usage becomes inconsistent across teamsRaw and namespace paths mixed arbitrarilySet the namespaces as the default integration contract; reserve client.raw for investigations
A call silently returns an empty resultGlobal helper failed openRead the reason on the result; run with MUBIT_LOG=info; switch to on_error="raise" in tests
Deprecation warnings in logs0.13 names in useFollow the link in the warning to Migration

Next steps