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

Quickstart

Two scripts, run one after the other, show the whole loop: run one records a correction, run two starts with that correction injected into the model call. The terminal on the right runs the direct memory calls from the second section against api.mubit.ai; switch it between Python, Node, and Rust with the buttons in the title bar.

Get your API key

Sign in to the Mubit console, open Settings → API keys and pick the project: this lands on the project's Settings tab, whose API keys card creates a named key (User or Admin role). The key is shown once. Keys look like mbt_<instance>_<key_id>_<secret>: the instance segment routes your call to the right region; the key id is safe to log; the secret is not.

The console shows four .env lines with the key. Copy all four; the SDK, the CLI and the console read the same values.

.env
MUBIT_API_KEY="mbt_<instance>_<key_id>_<secret>"
MUBIT_ENDPOINT="https://api.mubit.ai"
MUBIT_PROJECT="first-project"
MUBIT_ENV="dev"

mubit.init() and Client() read all four on start. MUBIT_ENV matters: lessons are partitioned by env, so both runs below must use the same value. See SDK configuration for the full variable list.

Local server

To run against a local Mubit, set MUBIT_ENDPOINT=http://127.0.0.1:3000 and MUBIT_API_KEY to the value of the server's MUBIT_BOOTSTRAP_ADMIN_API_KEY (the server logs Bootstrap admin API key loaded … for instance 'local' at start). Keys whose instance tag is local are accepted only by a local endpoint.

Install

pip install mubit-sdk openai

The examples call OpenAI, so OPENAI_API_KEY must also be set. Any wrapped provider works the same way; see LLM providers.

Learn from one run, apply on the next

Both processes share three scenario values. Put them at the top of each script.

scenario (shared by both scripts)
MODEL = "gpt-5-mini"
Q1 = "Format the amount 1234.56 for one of our invoices. Reply with the formatted amount only."
CORRECTION = ("No - that is wrong for us. Our invoices are always in euros with European formatting: "
              "thousands separated by a dot, decimals by a comma, and the euro sign after the number, "
              "so 1234.56 is written 1.234,56 EUR. Remember this rule for all of my future amounts.")

Process 1: the model answers, the user corrects it, and the run records a negative outcome.

run1.py
import mubit, openai
mubit.init(agent="invoice-helper")                        # env: MUBIT_API_KEY, MUBIT_ENDPOINT, MUBIT_ENV, MUBIT_PROJECT; no network
llm = mubit.wrap_openai(openai.OpenAI())                  # explicit wrapper: inject before, capture after
with mubit.run("invoice-format") as run:
    msgs = [{"role": "user", "content": Q1}]
    a1 = llm.chat.completions.create(model=MODEL, messages=msgs)
    msgs += [a1.choices[0].message, {"role": "user", "content": CORRECTION}]
    llm.chat.completions.create(model=MODEL, messages=msgs)
    run.outcome(good=False, label="corrected_by_user")   # OutcomeReceipt(sent=True, reason="ok")
# run.end sent on exit; stderr (MUBIT_LOG=info): mubit: run run-d773… ended, 1 reflection scheduled  <console link>

Process 2: the wrapper injects the lesson into the system message before the first call.

run2.py
import mubit, openai
mubit.init(agent="invoice-helper")
llm = mubit.wrap_openai(openai.OpenAI())
with mubit.run("invoice-format") as run:
    a1 = llm.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": Q1}])   # ContextBlock injected into the system message
    run.outcome(good="1.234,56" in a1.choices[0].message.content)

To see and place the context yourself, replace the two inner lines of run2.py with an explicit read. run.context() is a pure read; it does not change how the run injects.

run2_explicit.py (inner block)
ctx = run.context(Q1)                                     # ContextBlock: ctx.text, ctx.units, ctx.scope.env, ctx.degraded, ctx.reason
msgs = [{"role": "system", "content": ctx.text}, {"role": "user", "content": Q1}]
a1 = llm.chat.completions.create(model=MODEL, messages=msgs)
run.outcome(good="1.234,56" in a1.choices[0].message.content)

Run one ends when the with block (Python) or the run() callback (JS) returns. The server distils lessons a few seconds later; mubit status --agent <id> shows reflections_finished and lessons_stored. Run two, using the same MUBIT_API_KEY and the same MUBIT_ENV, receives the lesson inside the system message.

pipx install mubit-sdk            # the CLI ships with the Python package
mubit status --agent invoice-helper

Every failure on this path is visible: a wrong key, an unreachable endpoint or a kill switch prints one WARNING mubit: … line per process, and run.outcome() returns an OutcomeReceipt whose reason says what happened (ok, nothing_to_record, unauthorized, unreachable, killed).

Didn't work?

Run mubit doctor. It prints the endpoint, the masked key, the server mode (loop_v1, legacy, or unreachable), the resolved project/env, and one line per problem. With a wrong key the SDK prints mubit: WARNING context fetch failed at https://… (HTTP 401 Unauthorized); running without memory once per process; if you see no lessons on run two and no warning, check that both runs used the same MUBIT_ENV.

mubit doctor
endpoint:      https://api.mubit.ai
key:           mbt_acme_k1_****
mode:          loop_v1 (server 0.4.0, policy starter@1)
scope:         project=first-project env=dev agent=(unset)
health:        ok (/v2/core/health)

More cases are in Troubleshooting.

Store and search memory directly

The loop above stores and injects lessons for you. When you want to write and read memory yourself, use the Client namespaces. The script below stores a memory under one run and recalls it from a second run, keyed by the same user. The right-hand terminal runs this flow.

v1_support.py
import mubit
 
client = mubit.Client()                                   # MUBIT_API_KEY and MUBIT_ENDPOINT from env
support = client.with_options(scope=mubit.Scope(agent="support-agent", user="taylor-1"))
 
support.memory.remember(
    "Taylor prefers concise written updates on Friday afternoons; no phone calls.",
    kind="lesson",
    visibility="global",
    run_id="support:taylor:s1",
)
 
answer = support.memory.recall(
    "how does Taylor like updates?",
    entry_types=["lesson"],
    run_id="support:taylor:s2",
)
print(answer["final_answer"])

recall() returns the wire response as a plain dict / object (answer["final_answer"] in Python, answer.final_answer in JS), roughly:

{
  "final_answer": "Taylor prefers to receive updates in a concise written format delivered on Friday afternoons, and explicitly dislikes receiving phone calls.",
  "confidence": 1.0,
  "mode": "agent_routed",
  "evidence": [
    { "id": "f7bfcb26-…", "score": 1.0, "content": "Taylor prefers concise written updates on Friday afternoons; no phone calls." }
  ]
}
ℹ️Note

Cross-run visibility: facts (kind="fact") are visible in the run that wrote them. To recall a memory from a different run keyed by user, store it as a lesson with visibility="global"; that is why the example above does so. See SDK helpers for the full set.

Pick your path

Most readers want the loop. Switch only when you outgrow it.

What to do next

mubit — python · support-agent