Runs, sessions, and scopes
How run_id isolates memory, how sessions wrap runs, and how knowledge widens from run to session, global, and org scope.
Every piece of memory in Mubit belongs to a run, and every query names the run it's asking on behalf of. Read this page when you're designing how your agents partition memory — per ticket, per user, per agent — and how validated knowledge should escape those partitions. Getting scoping right up front is the difference between clean isolation and "why didn't my recall find it."
The mental model: runs isolate, scopes share. A run_id is a hard boundary — writes land in it, queries read from it. The scope ladder (run → session → global → org) is the controlled path by which individual entries earn visibility beyond their run.
Runs: the unit of isolation
A run is the memory scope for one unit of work. At Nimbus, Ari (the support agent) opens a fresh run per ticket — nimbus:ari:ticket-1042 — so evidence from one ticket never bleeds into another. Every write and every query carries the run id; two runs share nothing by default.
In the SDK helpers the kwarg is named session_id for historical reasons — remember(session_id="nimbus:ari:ticket-1042", ...) sends run_id on the wire. Whenever a helper asks for session_id, it means the run id.
Sessions: the lifecycle wrapper
A session is a lifecycle object that wraps a run — it tracks status, creation and close time, and ingest activity. Create one (client.advanced.create_session(...)) to get a server-managed run with auto-generated ids; close it (client.advanced.close_session(...)) when the work is done. Closing with reflect_on_close=True triggers a final reflection pass over the run, so lessons are extracted at the natural end of the work rather than on a timer.
Runs don't require sessions — you can use bare run ids everywhere — but sessions give you an explicit close moment, which is the right hook for end-of-task reflection. (Core branch sessions — reversible scratch forks — are a different primitive; see Sessions & branching.)
The scope ladder
Entries that carry a scope (lessons are the main case) are visible beyond their run according to a four-rung ladder. Wider scope also earns a retrieval-confidence bonus — broadly validated knowledge outranks single-run knowledge:
| Scope | Visible to | How memory gets there | Confidence bonus |
|---|---|---|---|
run | Only the writing run | Direct write (the default) | +0.00 |
session | Related runs | Direct write (lesson_scope="session") or promotion | +0.05 |
global | Every run on the instance | Direct write (lesson_scope="global") or promotion | +0.10 |
org | Every instance in the tenant | Promotion only — never a direct write | +0.15 |
Promotion is recurrence-driven: a lesson that keeps re-emerging across runs becomes a candidate for the next rung. Two gates protect the wider scopes:
- Shadow A/B validation. A candidate doesn't promote on recurrence alone — it auditions. Runs that would see it at the wider scope split deterministically into an exposed arm (lesson injected) and a control arm (withheld); outcomes accrue per arm, and the promotion only lands once each arm has enough samples and the exposed arm is doing no worse.
- Distinct actors. Widening scope multiplies an entry's injection reach, so it requires outcome provenance from multiple distinct principals. A single agent reinforcing its own lesson cannot push it into shared scope — below the bar the lesson is quarantined until its provenance broadens.
This is how Nimbus's CSV-export lesson travels: Ari learns it in one ticket run, it recurs, survives its shadow audition, and eventually lands at org scope — where Sana (the analytics agent, on a different instance in the same tenant) picks it up while compiling the weekly digest, having never handled a ticket.
Run linking
Sometimes one run legitimately needs to read another — Rex (the reviewer agent) auditing Ari's work is the canonical case. Linking makes a run's memory reachable from another run without merging them; the reader opts in per query with include_linked_runs:
# Ari worked the ticket in its own run
client.remember(
session_id="nimbus:ari:ticket-1042", # run_id on the wire
agent_id="ari",
content="Export timed out at 500k rows; batched exporter resolved it",
intent="observation",
)
# Rex reviews in a separate run, with Ari's run linked in
client.advanced.link_run(run_id="nimbus:rex:review-7", linked_run_id="nimbus:ari:ticket-1042")
evidence = client.recall(
session_id="nimbus:rex:review-7",
query="What did Ari already try for the CSV export timeout?",
include_linked_runs=True,
)reflect() accepts include_linked_runs too, so a review run can extract lessons from the combined history. Unlink with client.advanced.unlink_run(...) — see Run management.
Lanes: channels inside a shared run
When several agents share one run, lanes give each a named channel. Write with lane="planning" and queries filtered to that lane (lane= on recall(), lane_filter on the wire) see only planning-lane evidence — the planner's scratch reasoning stays out of the executor's context while shared, unlaned entries remain visible to all. Agents declare their lanes at registration (register_agent(..., shared_memory_lanes=[...])). See the lane-scoped memory recipe for a full walkthrough.
Pinning retrieval to the active run
Scope overlays mean a query can surface session- and global-scoped knowledge alongside run-local evidence. When you want the current task to dominate — mid-task, where this run's own recent evidence matters more than accumulated general knowledge — pass prefer_current_run=True on recall() to weight retrieval toward the active run.
Users: partitioning by end-user
user_id partitions memory per end-user, orthogonally to runs. Pass it on writes and queries and entries are scoped to that user across all their runs — Acme's preferences surface in every Acme conversation and never in another customer's. This is the standard mechanism for cross-session recall keyed to a person; see cross-session recall.
Configuration. Promotion gating is instance configuration: MUBIT_CL_SHADOW_PROMOTION (shadow validation, on by default), MUBIT_CL_SHADOW_MIN_PER_ARM (samples per arm before a decision), and MUBIT_CL_PROMOTION_MIN_ACTORS (distinct-principal bar for widening). Org scope additionally requires the instance to be configured with a tenant namespace.
Related
- The memory model — the entry types that flow through these scopes
- The learning loop — the promotion pipeline that widens a lesson's scope
- Working memory vs long-term memory — the run-scoped mutable store
- Sessions & branching — checkpoints and core branch sessions
- Lane-scoped memory — multi-agent isolation inside one run
- Multi-agent shared state — coordination patterns on a shared run
- How Mubit works — where scoping sits in the memory loop