Working memory vs long-term memory
The mutable, decaying scratch state a run holds for the current task, versus the durable typed entries that survive it — and the checkpoint bridge between them.
Mubit keeps two stores with deliberately opposite behavior. Working memory is what the run is thinking about right now: mutable variables that expire, decay, and get pruned. Long-term memory is what the instance knows: durable typed entries that survive runs and feed retrieval. Read this page when you're unsure which side something belongs on, or when in-flight state is leaking into places it shouldn't outlive.
The mental model: working memory is the whiteboard, long-term memory is the filing cabinet. You scribble freely on the whiteboard while the task is live; only what's worth keeping gets written up and filed. Mubit enforces that discipline mechanically — the whiteboard erases itself.
The two stores compared
| Working memory | Long-term memory | |
|---|---|---|
| Unit | Named variables (JSON values), a context stack, and active goals | Typed entries — see the memory model |
| Scope | One run_id, held by the runtime | Run-scoped by default; widens via the scope ladder |
| Mutability | Mutated in place — set, update, remove | Written once, then reinforced, superseded, or archived by the learning loop |
| Lifetime | The current task: TTL expiry, relevance decay, pruning | Survives runs |
| Retrieval | Injected into assembled context as the "Current State" section | Similarity retrieval plus scope overlays |
| What ends it | TTL, relevance pruning below threshold, circuit break | Explicit deletion (forget(), delete_run) or consolidation-driven archival |
Mechanically, a working-memory variable can be ephemeral — created with a TTL and an optional decay rate. Expired variables are removed on access; decayed ones fall below a relevance threshold and get pruned. The runtime can hand pruned variables to long-term memory before discarding them, which is the general shape of the system: scratch state either dies quietly or graduates into a durable entry. The context stack tracks nested task contexts (push on entering a subtask, pop on return), so "what am I in the middle of" survives even deep detours.
What goes where
Put it in working memory when it's only true for this task, changes as you work, and would be noise in any other run. Put it in long-term memory when a future run — or another agent — should be able to retrieve it.
At Nimbus, while Ari triages ticket 1042, the in-flight state is pure working memory: active_ticket = "TCK-1042", attempts = 2, current_hypothesis = "row-count limit". None of that should exist next week. What the triage produces is long-term: the resolution trace, the "Acme Corp is on the Scale plan" fact, and the durable lesson — check the report row count before retrying a timed-out export. The whiteboard is erased; the filing cabinet grew by three entries.
How working memory reaches the prompt
You don't query working memory separately. recall() and get_context() fold the run's live variables into their results by default — include_working_memory=True — where they appear as the Current State section of the assembled context block:
context = client.get_context(
session_id="nimbus:ari:ticket-1042",
query="Draft the next reply to Acme.",
include_working_memory=True, # default — pass False to exclude scratch state
max_token_budget=800,
)In the context priority order, Current State lands near the end — after mental models, rules, lessons, and facts, just before raw traces. In-flight state informs the next step; it doesn't compete with validated knowledge. Pass include_working_memory=False when you want retrieval over durable knowledge only.
Working memory is populated by the runtime as the run progresses. There is also a wire-level surface for setting variables directly (POST /v2/control/variables/set, plus get/list/delete) that the typed SDK helpers do not forward — send it over the transport directly if you need explicit variable writes.
Checkpoints: the bridge
The deliberate way to make working context durable is checkpoint() — it writes a checkpoint entry (a Working-type snapshot in long-term memory) that survives whatever happens to the live run state:
client.checkpoint(
session_id="nimbus:ari:ticket-1042",
label="pre-compaction",
context_snapshot="Triage state: timeout reproduced at 500k rows; batched-exporter fix pending verification.",
)Checkpoint before compacting your LLM conversation, before a risky transition, or before handing off — the snapshot is retrievable later even from a different session. This is the standard pre-compaction move; see Sessions & branching for how checkpoints pair with reversible core branch sessions.
Circuit break: clear, but snapshot first
When a run's state has gone bad — a loop, a poisoned hypothesis — circuit_break() resets working memory atomically. It does not just wipe the whiteboard: it first snapshots the live variables into a durable, run-scoped entry tagged with the break reason, then clears the state and emits a CIRCUIT_BROKEN event.
client.circuit_break(run_id="nimbus:ari:ticket-1042", reason="repeated identical retries")The response includes the snapshot's id, so if the reset was a mistake the pre-break state is recoverable from long-term memory. This is the involuntary version of the same bridge: nothing leaves working memory without the option of leaving a durable record behind.
Related
- The memory model — the durable entry types and their context priority
- Runs, sessions, and scopes — the
run_idboundary both stores live inside - Sessions & branching — checkpoints vs reversible scratch branches
- How Mubit works — where both stores sit in the memory loop
- SDK helpers —
checkpoint(),get_context(), and friends