Migration to 0.14
Every name deprecated in SDK 0.14, its replacement, and what 1.0 removes.
0.14 is additive: every 0.13 call keeps working and there is no wire change, with one exception in Rust. Client::lessons() is now the zero-argument lesson namespace, so the 0.13 flat client.lessons(payload) could not stay and 0.13 Rust code that calls it does not compile. Use client.lessons().list_legacy(payload), which reaches the same operation. Each deprecated name prints one warning per call site with a link to its section on this page. 1.0 removes the deprecated names. To find every use before you upgrade, run your test suite in strict mode:
python -W error::mubit.MubitDeprecationWarning -m pytest # Python
MUBIT_DEPRECATIONS=error node app.mjs # JavaScriptRust: add #![deny(deprecated)] to your crate root. mubit doctor prints the number of distinct deprecation warnings emitted in the current process.
Entry points: learn and auto
| 0.13 | 0.14 | Notes |
|---|---|---|
mubit.learn.init(api_key=..., agent_id=...) | mubit.init(api_key=..., agent=...) | learn.init stays as an alias and prints one warning |
mubit.auto.instrument() | mubit.init(instrument=True) or mubit.init(instrument=["openai", "anthropic"]) | |
mubit.auto.wrap_openai(client), mubit.learn.wrap(client) | mubit.wrap_openai(client), mubit.wrap_anthropic(client), mubit.wrap(client) | Wrappers are top-level |
mubit.learn.uninstrument() | mubit.uninstrument() | |
learn.feedback({ good: true }) (JS) | mubit.outcome({ good: true }) | The 0.13 call crashed with reading 'slice'; it now delegates to outcome and returns an OutcomeReceipt |
learn.init(auto_extract=True, extraction_mode="heuristic") | no equivalent | Lessons are distilled on the server at run end under the policy; client-side extraction options are accepted and ignored with a warning |
@mubit.learn.run(agent_id="planner") | @mubit.agent("planner") | |
JS learn.withRun(opts, fn), learn.startRun(...) | mubit.run(name, fn, opts) | |
Rust learn::LearnSession | client.run(name).start() + run.context() / run.model_call() / run.end() | LearnSession stays as an alias |
JavaScript imports and option names
| 0.13 | 0.14 | Notes |
|---|---|---|
import mubit from "@mubit-ai/sdk/init" | import mubit from "@mubit-ai/sdk" | Root export carries init, the wrappers and every namespace; /init, /loop, /control, /wrap, /learn, /helpers, /context subpaths remain as aliases until 1.0 |
require("@mubit-ai/sdk") without init / learn | CJS entry now exports both | |
snake_case option keys ({ api_key, session_id, agent_id }) | camelCase ({ apiKey, runId, agentId }) | snake_case accepted with one console.warn; TypeError in 1.0 |
client.remember({ idempotency_key }) silently ignored | client.memory.remember(content, { idempotencyKey }) honoured | Bug fix |
init(): Promise<unknown> (async, creates a session) | init(): Promise<MubitContext> — still async: it probes /v2/loop/capabilities and, unless you pass createSession: false, creates a session. Python's init() is the synchronous one. | See init() and sessions |
mubit.end() after manual runs | await mubit.run(name, fn) ends the run; await mubit.shutdown() when you manage runs yourself | beforeExit does not fire on process.exit() |
mubit.init() options
init takes keyword (Python) or object (JS) options only. Positional parameters and the alias pairs are accepted with a warning until 1.0.
| 0.13 | 0.14 |
|---|---|
mubit.init("mbt_...", "https://...") | mubit.init(api_key="mbt_...", endpoint="https://...") |
agent_id= (Python) | agent=; JS keeps agentId: (agent: is accepted as an alias with a warning) |
project_id= / projectId: | project= / project: |
user_id= (Python) | user=; JS keeps userId: (user: is accepted as an alias with a warning) |
JS sessionId: | runId: |
auto_instrument= / autoInstrument: | instrument= / instrument: (True, False or a list of library names) |
auto_learn= / autoLearn: | inject=InjectOptions(enabled=...) |
fail_open= | on_error= (see Failure policy) |
create_session= (new in 0.14, default True) | opt in to create_session=False now; False becomes the default in 1.0 |
init() and sessions
In 0.13 init() created a server session and registered an exit hook that closed it with reflect_on_close=True. In 0.14 you can opt out with mubit.init(create_session=False) (JS { createSession: false }); in 1.0 this is the only behaviour. With no session, MubitContext.session_id is None unless you open a run with mubit.run(thread=...), and runs are created lazily on the first event. Run ends are emitted by mubit.run() scopes; there is no reflect-on-exit network call.
context() is a pure read
In 0.13 calling mubit.context() inside a run switched the run to manual injection, so wrapped model calls stopped injecting. In 0.14 the switch still happens but prints a warning; in 1.0 context() has no side effect. Say what you mean when you open the run:
with mubit.run("invoice-format", inject="manual") as run: # wrappers do not inject; you place ctx.text
ctx = run.context(task)@mubit.agent("planner", inject="manual") does the same for decorated functions. JS context({ manual: true }) is kept for one minor.
Default endpoint
mubit.init() and Client() default to https://api.mubit.ai; the CLI and the deprecated learn path default to http://127.0.0.1:3000. 0.14 prints one warning whenever a local default is used without MUBIT_ENDPOINT, and one when env resolves to default implicitly. Set MUBIT_ENDPOINT and MUBIT_ENV explicitly (the console's four-line .env block does this); 1.0 uses one default for every entry point.
Scope names: session_id, thread, visibility
session_id meant three things in 0.13: the run id on the memory helpers, the conversation id on events, and the server session on MubitContext. 0.14 names each one.
| 0.13 | 0.14 | Notes |
|---|---|---|
client.remember(session_id=...), recall(session_id=...), get_context(session_id=...), archive(session_id=...), dereference(session_id=...) | run_id= | session_id accepted as an alias of run_id on the memory helpers until 1.0 |
mubit.run(session=...) / mubit.run(session_id=...) | mubit.run(thread=...) | The conversation id carried on events; the wire field stays session_id for one major |
Run.session_id | Run.thread | |
lesson_scope="global", share=True | visibility="global" | One field: run, agent, project, global |
client.lessons(session_id=...) | client.lessons.transitions(run_id=...) | 0.13 raised TypeError; 0.14 warns (lessons(session_id=) is not a filter on the lesson set) and forwards to transitions; 1.0 raises |
Rust RunBuilder::session(...) | RunBuilder::thread(...) | |
Client.run() legacy scope argument | run_scope | Removed in 1.0 |
Resolution order per field: explicit argument → with_options(scope=...) / mubit.scope(...) → active run or step → init / Client default scope → environment. See SDK configuration → Scoping.
Client construction
| 0.13 | 0.14 |
|---|---|
Client(endpoint=...) then client.set_api_key(key) / client.setApiKey(key) / set_token(key) | Client(api_key=key, endpoint=...); Client() reads MUBIT_API_KEY and MUBIT_ENDPOINT |
Client("https://...") positional | Client(endpoint="https://..."); the positional form is accepted until 1.0 |
run_id=, agent_id=, user_id= constructor options | scope=Scope(run_id=..., agent=..., user=...) or client.with_options(scope=...) |
Client(timeout_ms=..., retry_attempts=...) | Client(timeout=30.0, connect_timeout=2.0, max_retries=2); JS timeoutMs, connectTimeoutMs, maxRetries |
Rust ClientConfig::from_env() / ClientConfig::new(endpoint) | Client::from_env() / Client::builder() |
Flat client methods and client.advanced
The 0.13 client exposed every control operation directly on Client and, from 0.13.1, under client.advanced. 0.14 groups them into namespaces; the flat names and client.advanced.* are aliases that print one warning each, and client.raw.invoke("control.<op>", payload) reaches any operation by its contract name. The #<op> fragment in each warning (/sdk/migration#query) lands on the row for that operation below.
| 0.13 | 0.14 |
|---|---|
client.query(...), client.advanced.query(...) | client.memory.recall(query, ...); wire-level fields (budget, min_timestamp, rank_by, explain) through client.raw.invoke("control.query", {...}) |
client.ingest(...), client.advanced.ingest(...) | client.memory.remember(...) for one item; client.raw.invoke("control.ingest", {...}) for batches and raw item fields |
client.get_context(...) / getContext | client.memory.context(task, ...); lane="legacy" for the /v2/control/context assembler |
client.lessons(as_of=...) (method) | client.lessons.list(as_of=...) |
client.reflect(session_id=...) | client.lessons.reflect(run_id=...) |
client.record_outcome(...) / recordOutcome | client.outcomes.record(...); mubit.outcome(...) for the current run |
client.record_step_outcome(...) / client.recordStepOutcome(...) | client.runs.steps.record(...) |
client.feedback(...) | client.outcomes.feedback(...) or client.agents.handoffs.feedback(...) |
client.checkpoint(...) | unchanged: a typed top-level method (context_snapshot, label, session_id, agent_id, metadata); client.snapshots holds lesson-set snapshots, not run checkpoints |
client.get_ingest_job(...), client.advanced.get_ingest_job(...) / client.advanced.getIngestJob(...) | client.jobs.get(job_id) |
client.get_run_ingest_stats(...) | client.raw.invoke("control.get_run_ingest_stats", {...}) |
client.advanced.list_run_history(...) / client.advanced.listRunHistory(...) | client.runs.history(...) |
client.link_run(...), unlink_run, delete_run | client.runs.link(...), client.runs.unlink(...), client.runs.delete(run_id) |
client.advanced.context_snapshot(...) / client.advanced.contextSnapshot(...) | client.raw.invoke("control.context_snapshot", {...}) |
client.register_agent(...), list_agents, handoff | client.agents.register(...), client.agents.list(...), client.agents.handoffs.create(...) |
client.memory_health(), diagnose, forget, archive, archive_block, dereference | client.memory.health(), diagnose, forget, archive, dereference |
client.surface_strategies(...) | client.lessons.strategies(...) |
client.create_project(...), get_project, … | client.projects.create(...), get, update, delete, list |
client.set_prompt(...), activate_prompt_version, get_prompt_diff, get_prompt, list_prompt_versions | client.raw.invoke("control.set_prompt", {...}) etc.; client.optimize_prompt(agent_id=..., project_id=...) stays a typed top-level method |
client.create_skill(...), list_skills, get_skill, update_skill, delete_skill, list_skill_versions, activate_skill_version, get_skill_diff | client.raw.invoke("control.create_skill", {...}) etc.; client.optimize_skill(skill_id=..., project_id=...) stays a typed top-level method |
client.kill(True, "reason"), client.kill_status() | client.kill.set(on=True, reason="reason"), client.kill.get() |
client.export.skills(...), client.export.audit(...) | client.audit.export_skills(...), client.audit.list(...) |
client.list_activity(...), export_activity | client.audit.run_history(...) |
client.subscribe(...), watch | client.audit.subscribe(...), client.audit.watch(...) |
client.auth.* | client.admin.users.*, client.admin.api_keys.*, client.admin.permissions.* |
client.core.* | client.raw.invoke("core.<op>", payload) |
any other client.<op>() / client.advanced.<op>() | client.raw.invoke("control.<op>", payload); the warning text names the op |
Rust: the 55 flat delegates on Client are marked #[deprecated]; the replacements are the accessor methods (client.memory(), client.runs(), client.lessons(), …) and client.raw().invoke(op, value).
Keyword-only control arguments
Positional scope and boolean arguments on the control namespaces are accepted with a warning in 0.14 and rejected in 1.0.
| 0.13 | 0.14 |
|---|---|
client.kill(True, "maintenance") | client.kill.set(on=True, reason="maintenance") |
client.snapshots.restore(id, True, False, True) | client.snapshots.restore(id, lessons=True, policy=False, retire_new=True) |
client.policy.get("billing", "prod") | client.policy.get(project="billing", env="prod") |
Rust client.kill_set(true, "maintenance") | client.kill().set(KillRequest { on: true, reason: "maintenance".into() }) |
Outcome arguments
One normaliser applies to mubit.outcome, run.outcome, step.outcome, mubit.aio.outcome, JS Step.outcome and Rust OutcomeRequest: a bool is good, a number is score, a string is label.
| 0.13 | 0.14 |
|---|---|
mubit.aio.outcome(0.8, outcome_label="resolved") | mubit.aio.outcome(score=0.8, label="resolved") (positional form accepted with a warning) |
step.outcome(0.5) sent name=0.5 | step.outcome(0.5) sends score=0.5 (bug fix) |
mubit.outcome(good=False, name="corrected_by_user") | mubit.outcome(good=False, label="corrected_by_user"); name= accepted as an alias |
Failure policy
fail_open booleans on init, learn.init, LoopClient, LearnConfig and LoopConfig are aliases of one on_error setting.
| 0.13 | 0.14 |
|---|---|
fail_open=True | on_error="warn" (default for the global helpers) |
fail_open=False | on_error="raise" (default for Client) |
silent logger.debug("... (non-fatal)") on the loop path | one WARNING per endpoint and failure class; MUBIT_LOG=error silences |
helpers before init(): silent no-op for run/step/note, RuntimeError for outcome/context | auto-initialise from env with one WARNING; ConfigurationError only when no key can be resolved. Code that relied on the silent no-op sets MUBIT_DISABLED=1 |
Errors
The 0.13 exception names are aliases of the 0.14 hierarchy and are removed in 1.0. Every APIStatusError carries status_code, request_id, body and retry_after.
| 0.13 | 0.14 |
|---|---|
AuthError | AuthenticationError (401) |
ValidationError | BadRequestError (400) |
AlreadyExistsError | ConflictError (409) |
ServerError | InternalServerError (500) |
TransportError | APIConnectionError (.code kept) |
ControlError | APIStatusError |
ControlUnavailable | APIConnectionError |
except ServerError for every non-2xx | RateLimitError (429) and GoneError (410) have no 0.13 parent; catch them explicitly |
| server-status classes raised for client-side argument problems | ValueError / TypeError (Python), TypeError (JS) in 1.0 |
except AuthError still catches a 403 (PermissionDeniedError is also an AuthenticationError), except ValidationError still catches 404 and 409, and except ServerError still catches 503; the full mapping is in Errors.
Environment variables
| 0.13 | 0.14 |
|---|---|
MUBIT_LOOP_DISABLED=1 | MUBIT_DISABLED=1 (whole SDK, not only the loop lane) |
MUBIT_LOOP_DEBUG=1 | MUBIT_LOG=debug |
MUBIT_RETRY_ATTEMPTS=3 | MUBIT_MAX_RETRIES=2 (attempts = retries + 1) |
MUBIT_PROJECT_ID | MUBIT_PROJECT |
MUBIT_TOKEN (Rust) | MUBIT_API_KEY |
MUBIT_LEARN_EXTRACT, MUBIT_LEARN_CONTEXT_TIMEOUT, MUBIT_LEARN_ATTRIBUTION_TIMEOUT, MUBIT_CONTROL_REVIEW_ENABLED, MUBIT_CONTROL_PROD_VERIFIED_BOOST | mubit.init(inject=InjectOptions(...), capture=CaptureOptions(...)) |
New in 0.14 (no old name): MUBIT_AGENT, MUBIT_USER, MUBIT_ON_ERROR, MUBIT_TIMEOUT_MS, MUBIT_CONNECT_TIMEOUT_MS, MUBIT_CONSOLE_URL. The full table is in SDK configuration.
Capture lane for the Python patch path
In 0.13 the Python autopatch sent captured turns to /v2/control/ingest. In 0.14 you can opt in to model.call loop events with mubit.init(capture=CaptureOptions(lane="loop")); the wrappers (wrap_openai, wrap_anthropic) already use the loop lane. 1.0 makes the loop lane the default on servers that have /v2/loop.
CLI
| 0.13 | 0.14 |
|---|---|
mubit status --agent-id x (error) | mubit status --agent x; --agent-id accepted as an alias |
| global options only before the subcommand | accepted before or after (mubit doctor --api-key ... works) |
doctor probed /healthz (404) | probes /v2/core/health |
| — | new: mubit runs list, mubit runs tail, mubit runs open, mubit policy diff, mubit init, mubit login, mubit doctor --json |
What 1.0 removes
- The 41 Python flat shims, the 41 JS flat shims, the 55 Rust deprecated delegates, and
client.advanced. mubit.learn.init,mubit.auto.instrument,learn.wrap*,set_api_key/setApiKey/set_token.session_idaliases,lesson_scope/share,fail_openaliases, positionalinitparameters, positional control-namespace arguments.MUBIT_LOOP_DISABLED,MUBIT_LOOP_DEBUG,MUBIT_PROJECT_ID,MUBIT_RETRY_ATTEMPTS,MUBIT_TOKEN.- JS subpath exports and snake_case option keys.
Client.run()legacy scope argument (userun_scope).- The implicit manual-injection switch in
context(); the session created byinit(); the local default endpoint outsideMUBIT_ENDPOINT.
The 0.13 exception aliases and the dict-style access on typed responses (answer["final_answer"]) stay for the whole 1.x line.
Upgrade checklist
pip install "mubit-sdk>=0.14,<1"/npm install @mubit-ai/sdk@^0.14/mubit-sdk = "0.14".- Run the test suite in strict mode (top of this page) and fix each warning using the tables above.
- Set
MUBIT_ENDPOINT,MUBIT_PROJECTandMUBIT_ENVin every environment; runmubit doctorand check itsscope:line. - Replace
fail_openwithon_error, and read thereasonfield onContextBlockandOutcomeReceiptwhere you used to check forNone. - Move
session_idtorun_idorthread, andlesson_scopetovisibility.