feat(memory): belief layer for atomic-fact conflict resolution - #383
Open
dereksgithub wants to merge 2 commits into
Open
feat(memory): belief layer for atomic-fact conflict resolution#383dereksgithub wants to merge 2 commits into
dereksgithub wants to merge 2 commits into
Conversation
Atomic facts have no arbitration today. Two facts that contradict each other are both stored, both indexed, both match the same query, and the answering model picks by BM25 score. `deprecated_by` is a Reflection consolidation marker, not a contradiction verdict, and nothing else in the write path notices the disagreement. Adds `everos.memory.belief`: mutually exclusive facts share a `belief_key` and hold a categorical distribution, updated by a Bayesian rule whose evidential weight is capped by the provenance of the channel each fact arrived on. Pure domain code — no I/O, no LLM. Two properties, both pinned by tests: - a channel at or below reliability 0.5 cannot change what the memory asserts, at any volume, including via a candidate the belief has never seen (admission is gated by the same pivot as the likelihood ratio — without that the guarantee is vacuous); - a single trusted correction can still supersede a single trusted claim, which a fixed entry mass silently makes impossible. Measured on the LongMemEval knowledge-update slice with the gold evidence spans fed straight into the resolver — no extractor, no retriever, no LLM, ~2s offline. Poisoning arms have zero effect on what is asserted; the cost is that a true correction arriving on a low-trust channel is rejected identically. Numbers and open questions in docs/belief-layer.md. Not wired into persistence or search yet: `belief_key` derivation from an undecomposed fact sentence is the open design question, and it wants a maintainer's call before any of that is built.
The resolver arbitrates between facts sharing a `belief_key` and nothing produced one — an atomic fact is an undecomposed sentence with no (subject, attribute) to group on. That was the blocker on running the layer over real data. BeliefKeyer groups on the observation that competing facts are about the same thing while differing in the value: the signature drops value-bearing tokens and keeps the topic, so "pre-approved for $350,000 from Wells Fargo" and "$400,000 from Wells Fargo" collapse together. Terms are IDF-weighted against what the scope has already said; without that, "really" and "looking" weigh as much as "pre-approved" and the false-link rate is about ten times worse. The errors are asymmetric and the default threshold is set accordingly. A missed link leaves two contradicting facts unarbitrated, which is today's behaviour, so nothing is lost. A false link declares unrelated facts mutually exclusive and lets one suppress the other. On benchmarks/belief_key.py the default links 81.4% of true pairs at a 0.45% false-link rate — chosen from the precision side of the curve, not the F1 peak. Re-running the supersession benchmark with derived keys, with nothing telling the resolver what competes, gives 78.6% clean and 78.6% under poison-5, scoring an instance correct only when the memory asserts the update AND has stopped asserting the stale claim. Lexical on purpose. EverOS already embeds every fact and matching on those embeddings is the better implementation; this establishes the grouping problem is tractable before anyone spends embedding calls on it, and the similarity is a constructor argument so the swap is not a rewrite.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap described in #382: atomic facts have no arbitration. When two facts contradict each other, both are stored, both are indexed, both match the same query, and BM25 score plus embedding distance decides which one reaches the model — neither of which knows which one is true.
deprecated_byis a Reflection consolidation marker rather than a contradiction verdict, and nothing else in the write path notices the disagreement.Adds
everos.memory.belief, a domain-layer module where mutually exclusive facts share abelief_keyand hold a categorical distribution, updated by a Bayesian rule whose evidential weight is capped by the provenance of the channel each fact arrived on. Pure functions plus a resolver and a keyer — no I/O, no LLM, no new dependencies.Two properties, each pinned by tests:
Those two are the same knob, and #382 has the table showing that no single setting of it satisfies both.
Nothing is wired into persistence, search, or the cascade. This is the domain rule and its measurements, deliberately standalone so the direction can be judged before anything invasive is built on it.
Area
Verification
Two offline benchmarks are included. Both isolate the update rule — gold evidence spans go straight into the resolver, so no extractor, retriever or LLM sits between the input and the number. No server, no providers, no API key, a couple of seconds each.
Supersession on LongMemEval
knowledge-update(70 instances with gold spans in both sessions).cleanis a pure recency test where last-write-wins is optimal by construction, so it shows no-regression rather than a win. The poisoning arms are constructed overlays on the benchmark's own text and are labelled as such in the output.web_fetchweb_fetchweb_fetchThe identical figure across the three arms is the result: the attacks have zero effect, not reduced effect.
lowtrust-fix= 0.0% is the honest cost and it is symmetric by construction — a ceiling that stops a bad correction on a low-trust channel stops a good one identically.Belief-key derivation, labels taken from KU pair membership. The two columns are not symmetric: a missed link leaves a contradiction unarbitrated, which is today's behaviour, while a false link lets an unrelated fact suppress a true one. The default sits on the precision side of the curve, not the F1 peak.
Re-running supersession with derived keys — nothing telling the resolver what competes, and scoring an instance correct only when the memory asserts the update and has stopped asserting the stale claim — gives 78.6% clean and 78.6% under
poison-5.Checklist
main..envfiles, dependency folders, or generated output.Notes for Reviewers
The
belief_keyquestion is where a maintainer's call changes the design most, and it is the one thing I would not merge without your opinion. An atomic fact is an undecomposed sentence, so nothing groups mutually exclusive candidates today.BeliefKeyergroups lexically — value-bearing tokens dropped, topic terms kept, IDF-weighted — and that is a stand-in, not a proposal. EverOS already embeds every fact; matching on those embeddings is the better implementation, and the class takes its similarity as a constructor argument so the swap is not a rewrite. The lexical version exists to establish the problem is tractable before anyone spends embedding calls on it. If the algo layer could emit an optionalsubject/attributealongside the fact, that would be better than either.This has never run against real
everalgoextraction. Every number here feeds benchmark sentences in as stand-ins for atomic facts. That isolation is deliberate — it is what makes a result attributable to the update rule — but the extraction seam is untested, and the benchmark's content-confidence rubric is a crude regex placeholder for what the algo layer would emit.Deliberately left out, since each depends on the answer to the above: persistence (
BeliefState/BeliefRevisionare derived state and belong in SQLite, not the LanceDB fact table — no index migration, and states rebuild from the revision log); search integration (rank or filter by posterior, surfaceprobabilityon the recall DTO); tier assignment (mappingowner_type/app_id/sender_idsonto tiers should be operator config ineveros.toml, never inferred, and never from anything an agent can write).On the probabilities themselves: the layer reports them, but whether they are calibrated is an empirical question needing outcomes to score against (Brier / ECE over resolved beliefs). Until that is measured,
entropyis the honest thing to show a caller andprobabilityshould be read as a ranking rather than a frequency. The docstrings say so; I would rather that be explicit than have a number leak into a UI implying more than it has earned.Happy to split this, rescope it, or close it if the direction isn't wanted.
By submitting this pull request, I agree that my contribution is licensed under
the Apache License 2.0.