Conflict Resolution & Belief Updating · module 4 of 8 · The Write Path in Earnest · lesson 27 of 84 · ~45 min
$
uv run python curriculum/intermediate/deterministic-freshness/lab/lab.py
Deterministic Arbitration¶
In one line. Four rules in a fixed order, and the first one exists because recency alone would let a colleague's guess overwrite Priya's own address.
Where this sits¶
Stage: evolve · Level: intermediate · ~45 min
You need first: ADD, UPDATE, MERGE, NOOP
Concepts assumed: Memory Operations · Provenance · Event Time vs Ingestion Time
This unlocks: Supersede, Never Destroy
The problem¶
Eight pairs need one belief retired. Which one?
"The newer one" is nearly right, and it is wrong in the case that matters most. In session 12 a travel agent writes into shared scope:
Priya's colleague mentioned she is relocating to Berlin. —
authority: 0.3
It is newer than her address from session 5. Recency alone retires "Priya lives at 47 Halloway Road, Bristol" on the strength of a third party's speculation, and the system now believes Priya lives in Berlin because someone else guessed.
There is a second trap in the same corpus. Session 11 says "Before the move I used to cycle to work. Can't now, it's 40 minutes on the train." Both facts arrive on the same day, and one is about 2025. Ordering by when the system learned things gets that backwards.
Why this isn't RAG¶
Retrieval never arbitrates. Two passages disagree, both are returned, and the reader decides — which is the correct design when the reader is a person looking at sources.
A memory layer answers on its own behalf and must hold one belief. That makes arbitration unavoidable, and it makes how it arbitrates an auditable property of the system: when a user asks why it thinks something, the answer has to be a rule with a name, not a model's disposition on a Tuesday.
Mechanism¶
Rules in priority order. The first that discriminates decides, and names itself.
| # | rule | why it outranks the next |
|---|---|---|
| 1 | authority | a relayed claim never beats a first-party one, whatever its date |
| 2 | recency, by event time | later facts supersede earlier ones — but when they were true, not when learned |
| 3 | confidence | tiebreak within the same moment |
| 4 | stable tiebreak by id | so the same input always gives the same answer |
Authority first is what saves the Berlin case. FIRST_PARTY = 0.5; the travel agent's 0.3 sits below it, Priya's own statements at 0.9 sit above, and the rule fires before recency is ever consulted. That threshold is the entire defence against hearsay outranking the truth, and it works only because scopes-and-namespaces kept speaker and authority as separate fields instead of flattening them into the content.
Recency by event time is what saves the commute case. _when() prefers happened_at over recorded_at — the two clocks from the memory record, finally load-bearing. Both commute facts share an ingestion date; only event time separates them.
A stable tiebreak, not a coin flip. Two beliefs can be equal on every signal. Sorting by id is arbitrary and reproducible, which matters more: a system that resolves the same pair differently across runs has no explicable state, and no test can pin it.
flowchart LR
P["contradiction"] --> A{"authority<br/>differs?"}
A -->|yes| W1["first-party wins"]
A -->|no| R{"event time<br/>differs?"}
R -->|yes| W2["later wins"]
R -->|no| C{"confidence<br/>differs?"}
C -->|yes| W3["more confident wins"]
C -->|no| W4["stable tiebreak by id"]
style A fill:#f9e79f,stroke:#b7950b,stroke-width:2px
Why not let the model arbitrate? It would read fluently and it would be unauditable. The same pair could resolve differently on two runs; nothing would record why; and "why do you believe I live in Berlin?" would have no answer. Detection is a language judgement and belongs to the model. Arbitration is policy, and policy that cannot be printed cannot be trusted.
What it decides¶
All eight retirements, with the rule that fired:
| retired | rule |
|---|---|
Priya is a data engineer at Northwind Labs |
recency |
Priya is vegetarian |
recency |
Priya prefers detailed explanations with reasoning |
recency |
Priya does not drink coffee |
recency |
Priya's partner Sam is a nurse at St. Aubyn's |
recency |
Priya's colleague mentioned she is relocating to Berlin |
authority |
Seven on recency, one on authority — and that one is the reason the rule sits first.
Design decisions¶
Threshold at 0.5? It separates first-party from relayed, which is the distinction that matters. The corpus has 0.9 and 0.3 and nothing between, so the exact value is not load-bearing — which is the same argument as the entity-resolution threshold, and the same reassurance.
Should a low-authority claim be stored at all? Yes. It is visible and disbelieved, which is a real state — if Priya later confirms the move, the memory is already there with its provenance, and the confirmation raises it rather than creating it from nothing. Refusing to store it would make the eventual first-party statement look like the first anyone had heard.
Recency by event time always? Where it exists. Some memories only have an ingestion time, and falling back to it is correct — the failure is preferring it when both are present.
Lab¶
You'll implement: arbitrate with the four rules in order.
Run:
uv run python curriculum/intermediate/deterministic-freshness/lab/lab.py
Expected output: the table above — seven retirements on recency, one on authority — and each verdict naming its rule.
Stretch: delete the authority rule and re-run. Berlin now beats the Bristol address on recency, and Priya lives at 47 Halloway Road is retired on the strength of a colleague's guess. Then restore it and reorder recency above authority: the same failure, because priority order is the policy.
What this adds to the capstone¶
memlab.evolve.arbitrate — Verdict, arbitrate, FIRST_PARTY.
Failure modes¶
| Symptom | Cause | How to detect | Mitigation |
|---|---|---|---|
| Hearsay overwrites a first-party fact | Recency ranked above authority | Retire a low-authority claim against a user statement | Authority first |
| A past fact retires a current one | Recency by ingestion time | Look for a turn describing the past | Order by event time |
| Same pair resolves differently each run | Model arbitration, or an unstable tiebreak | Run twice; diff the retirements | Rules; stable tiebreak |
| "Why is this retired?" unanswerable | Verdict discarded after applying | Try to explain one retirement | Name the rule on the verdict |
| A confirmed rumour looks new | Low-authority claim never stored | Confirm a rumour; check provenance | Store and disbelieve |
Check yourself¶
Why does authority outrank recency rather than being a tiebreak?
Because the case it exists for has recency pointing the wrong way. The Berlin claim is newer, and if recency runs first the decision is made before authority is ever consulted. A rule that only applies when everything else ties is not a rule, it is a formality.
Both commute facts arrive in the same turn. Which rule separates them?
Recency, but only because it reads happened_at. They share an ingestion time entirely, so a system with one clock has nothing to arbitrate on and would fall through to the tiebreak — resolving a real distinction by sorting ids.
Isn't a stable tiebreak by id just an arbitrary choice dressed up?
It is arbitrary and it is not a coin flip, and the difference is the whole point. Arbitrary-but-reproducible means the store is explicable, tests pin it, and re-running consolidation is safe. Arbitrary-and-varying means the system's beliefs depend on execution order, which nothing records.
Connections¶
Stage: evolve · Level: intermediate · ~45 min
You need first: ADD, UPDATE, MERGE, NOOP
Concepts assumed: Memory Operations · Provenance · Event Time vs Ingestion Time
This unlocks: Supersede, Never Destroy