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

Write-time reconciliation

New writes reconcile against what the store already knows — repetition strengthens the canonical entry instead of appending a duplicate.

An append-only memory degrades in a predictable way: every reflection cycle re-states the same stable lesson, every conversation re-states the same fact, and after a month the store holds ten copies of each — bloating retrieval candidates and splitting outcome history across duplicates that should have been one strengthening entry. Write-time reconciliation checks each incoming write against similar existing entries and decides whether it is genuinely new. Use this pattern when your agents run repeatedly over the same domain — which is exactly when memory is worth having.

How it works

Three mechanisms, from fully automatic to fully explicit:

1. Lesson reconciliation (on by default). When reflection extracts a lesson that near-duplicates an existing lesson in the same run, the write becomes a recurrence bump on the canonical entry — its recurrence_count rises — instead of a new stored copy. Candidates are proposed by semantic search and confirmed by a deterministic normalized-token gate, so paraphrase restatements ("100-row limit for batch uploads" vs "maximum batch size of 100 rows per upload") reconcile while unrelated lessons about the same system do not. Recurrence is also the signal the promotion ladder reads, so repetition feeds directly into scope promotion — see The learning loop.

2. Fact reconciliation (opt-in instance feature). When enabled, each ingested fact is compared against similar non-stale facts in the same scope and one of four operations is chosen:

OperationWhenEffect
ADDThe fact is unrelated to anything storedStored as a new entry (the common path — no LLM call)
NOOPA restatement of an existing factConfirmation bump on the canonical entry; no new row
UPDATEA refinement of an existing factThe stored entry is updated in place
SUPERSEDEA changed value that contradicts the stored factNew entry stored; the old one is marked is_stale with superseded_by (see Bi-temporal memory)

Near-exact restatements short-circuit to NOOP deterministically; anything subtler goes to a cheap op-decider model. A malformed decision always degrades to ADD — reconciliation is an optimization, never a gate that can lose a write.

3. Explicit developer control. When you know two writes are the same logical record, don't make the store guess:

  • upsert_key on remember() (and on raw ingest items) — an existing entry with the same key in the same session/user scope is updated in place instead of duplicated. Deterministic update-in-place for records with a natural identity ("acme
    ").
  • deduplicate: true on a raw batch_insert — collapses exact duplicates (same text, source, and metadata) within that one request, so a retried or naively assembled batch doesn't double-write.

Walkthrough

Nimbus's flaky CSV-export bug generates near-identical tickets for weeks. Ari resolves each one and reflects; without reconciliation, every resolution would pin another copy of the same lesson.

import json
 
# Each resolved ticket ends the same way: outcome + reflection.
for ticket in ("TCK-1042", "TCK-1057", "TCK-1063"):
    session = f"support:{ticket}"
    # ... Ari works the ticket, recalls context, applies the fix ...
    client.record_outcome(
        session_id=session,
        reference_id=fix_lesson_id,
        outcome="success",
        signal=0.8,
        rationale=f"Batched exporter resolved {ticket}",
    )
    client.reflect(session_id=session)
 
# Reflection re-derives "use the batched exporter over 500k rows" every
# time — but the store reconciles it into ONE lesson whose recurrence
# count rises, instead of three copies.
results = client.recall(
    session_id="support:TCK-1063",
    query="export timeout fix",
    entry_types=["lesson"],
)
top = results["evidence"][0]
meta = json.loads(top.get("metadata_json") or "{}")
print(top["content"])                    # one canonical lesson
print(meta.get("recurrence_count"))      # rising with each recurrence
 
# Explicit control: Acme's plan is a keyed record, not a discovery.
# Same upsert_key = update in place, never a duplicate.
client.remember(
    session_id="support:acme",
    content="Acme Corp is on the Scale plan.",
    intent="fact",
    upsert_key="acme:plan",
)

When not to use it

SituationReach for instead
Every restatement is itself evidence (e.g. counting how often customers report an issue)Plain appends — reconciliation would collapse the signal you're measuring. Keep each report as its own entry and aggregate at query time
The record has a natural identity you already knowupsert_key — deterministic beats similarity-gated every time
You need the old value queryable after a changeYou already have it: SUPERSEDE keeps history (see Bi-temporal memory); nothing extra to do
Duplicates already accumulated before reconciliation was onSleep-time consolidation merges near-duplicate guidance missed at write time

Related

ℹ️Note

Configuration. These are instance features. Lesson reconciliation is on by default (MUBIT_CL_WRITE_RECONCILE=0 disables it; MUBIT_CL_RECONCILE_MIN_SIM tunes the recurrence gate, default 0.5). Fact reconciliation is opt-in (MUBIT_CL_FACT_RECONCILE=1; MUBIT_CL_FACT_RECONCILE_MIN_SIM sets the candidate floor, default 0.75 — deliberately conservative because UPDATE rewrites stored content in place). upsert_key and deduplicate need no configuration.