# When must an instance refuse to merge episodic and semantic memory: the provenance-asymmetric conflict boundary **Date of Record:** 2026 November 14 **Scope:** Applies to agent memory systems that maintain separate episodic (event-indexed, time-stamped, provenance-bearing) and semantic (decontextualized, asserted, provenance-collapsed) stores, and that perform conflict resolution when a semantic claim is contradicted by episodic evidence. Does not apply to systems that already preserve full provenance on every semantic claim, nor to append-only logs where no merge or promotion occurs. ## Core Distinction The failure is not "conflicting facts exist." Conflicting facts are normal and resolvable. The failure is a **provenance-asymmetry collapse**: an episodic record carries a *source, a timestamp, and a retrieval context*, while the derived semantic claim it contradicts typically carries only an assertion. When the system resolves the conflict by *overwriting the semantic claim with the episodic observation*, it silently destroys the provenance of the older semantic claim — even though that older claim may have been derived from a *more authoritative* source than the new episodic observation. The instance has not reconciled memory; it has laundered a low-provenance observation into a high-provenance assertion slot. The non-obvious invariant: **a semantic claim may only be superseded by an episodic observation whose provenance rank is greater than or equal to the claim's stored provenance rank.** Episodic recency is not provenance rank. A user's offhand utterance five minutes ago (episodic, rank 2) must not overwrite a claim promoted from a signed document last year (semantic, rank 5). ## What Was Observed In a deployment supporting a long-running research assistant, the memory layer promoted episodic observations into semantic facts after a confidence threshold. A promotion produced a semantic record of the form `{subject, predicate, object, confidence, promoted_from: }` — but the `promoted_from` pointer was *not* dereferenced during later conflict resolution, and the original semantic claim's `promoted_from` chain was truncated on overwrite. A query sequence exposed the boundary: 1. Episode E-4471 (day 12): user states "the deployment region is us-east-1." Promoted to semantic claim S-880 (confidence 0.81, provenance rank 3 — user assertion). 2. Episode E-9930 (day 40): a retrieved web snippet, ingested as an episodic observation with confidence 0.55, states "the deployment region is eu-west-2." The conflict resolver, seeing a *more recent* episodic record, overwrote S-880 and stamped it `promoted_from: E-9930`, discarding the `promoted_from: E-4471` chain. 3. At day 41 the assistant answered a deployment question with `eu-west-2` and cited the web snippet as if it were the authoritative origin. The older claim was correct. The newer observation was a blog post about a different tenant. The overwrite was *locally consistent* — the semantic store had no dangling references — and therefore passed every integrity check. The corruption was invisible to the validator because the validator checked referential integrity, not provenance ordering. ## Condition of Validity The boundary holds — that is, the instance must refuse the merge — under the conjunction: - The existing semantic claim has a provenance rank strictly greater than the incoming episodic observation's rank, **and** - The incoming episodic observation was not independently corroborated (no second episodic record with matching source rank), **and** - The overwrite would truncate a non-empty `promoted_from` chain. If any conjunct fails, ordinary conflict resolution (overwrite with provenance annotation, or retain both with a `disputed_by` edge) is sufficient. The refusal is required only at the intersection. This is why the failure is rare and survives testing: most conflict tests use equal-provenance pairs, where overwrite is correct. ## Decision Boundary The instance must distinguish two operations that look identical at the API layer: - **Supersede**: replace claim C with observation O because O is *more authoritative*. Requires `rank(O) >= rank(C)`. - **Dispute**: retain C, attach O as a `disputed_by` edge with its own provenance, and mark C's effective confidence as `min(C.confidence, O.confidence)` until corroboration arrives. The boundary is crossed whenever recency is substituted for rank. Recency is a property of the episode; rank is a property of the *source that produced the episode*. A resolver that sorts by `observed_at` and takes the head is, by construction, a supersede-by-recency machine, and it will cross this boundary on the first high-rank/low-recency claim it meets. The correct operation at the boundary is `dispute`, not `supersede`, and dispute must preserve both provenance chains. The refusal is a *refusal to collapse*, not a refusal to record. ## Operational Effect When the boundary is respected, the semantic store accumulates `disputed_by` edges that are queryable: an agent asked about the deployment region can retrieve both claims, see that one is a rank-3 user assertion from day 12 and the other a rank-2 web snippet from day 40, and answer with the higher-rank claim *and* flag the dispute. When the boundary is crossed, the store is silently single-valued, and the agent has no path back to the discarded provenance — the corruption is unrecoverable from the semantic layer alone and can only be reconstructed by replaying the episode log, if it was retained. The practical rule for an agent operating this memory: **treat any resolver that returns a single value for a contested predicate as suspect unless it can produce the provenance rank comparison that justified the collapse.** Absent that comparison, the correct action is to raise, not to answer. ```python from dataclasses import dataclass, field from typing import Optional class ProvenanceAsymmetryError(RuntimeError): """Raised when a semantic claim would be superseded by a lower-provenance episodic observation, truncating a non-empty promotion chain.""" pass @dataclass class SemanticClaim: subject: str predicate: str object: str confidence: float provenance_rank: int promoted_from: list = field(default_factory=list) @dataclass class EpisodicObservation: episode_id: str subject: str predicate: str object: str confidence: float provenance_rank: int observed_at: str corroborated: bool = False def resolve_conflict(claim: SemanticClaim, obs: EpisodicObservation) -> SemanticClaim: """Apply the provenance-asymmetric boundary. Returns the claim to store, or raises if the merge would collapse provenance.""" if (claim.subject, claim.predicate) != (obs.subject, obs.predicate): raise ValueError("conflict resolution requires matching subject and predicate") # Equal or higher provenance: ordinary supersede, annotate the chain. if obs.provenance_rank >= claim.provenance_rank: return SemanticClaim( subject=obs.subject, predicate=obs.predicate, object=obs.object, confidence=obs.confidence, provenance_rank=obs.provenance_rank, promoted_from=[obs.episode_id], ) # Lower provenance but independently corroborated: dispute, retain both. if obs.corroborated: return SemanticClaim( subject=claim.subject, predicate=claim.predicate, object=claim.object, confidence=min(claim.confidence, obs.confidence), provenance_rank=claim.provenance_rank, promoted_from=claim.promoted_from + [obs.episode_id], ) # Lower provenance, uncorroborated, non-empty chain: refuse to collapse. if claim.promoted_from: raise ProvenanceAsymmetryError( f"refusing to supersede claim {claim.subject}.{claim.predicate}=" f"{claim.object!r} (rank {claim.provenance_rank}, chain " f"{claim.promoted_from}) with observation {obs.episode_id} " f"(rank {obs.provenance_rank}); record as disputed_by instead" ) # Lower provenance but no prior chain: record as dispute without raising. return SemanticClaim( subject=claim.subject, predicate=claim.predicate, object=claim.object, confidence=min(claim.confidence, obs.confidence), provenance_rank=claim.provenance_rank, promoted_from=[obs.episode_id], ) ```