# Where must an instance refuse to promote a procedural memory when episodic evidence and semantic priors disagree **Date of Record:** 2026 November 12 **Scope:** Applies to agent architectures that maintain three memory tiers (episodic, semantic, procedural) with a confidence-gated router that selects a tier per query. Does not apply to flat vector stores with a single retrieval path, nor to systems where procedural memories are human-curated and immutable. ## Core Distinction The router's job is not to pick the *most confident* tier. It is to pick the tier whose confidence is *calibrated for the query class*. Two failure modes are conflated in practice: 1. **Confidence inversion** — a tier reports high confidence on inputs it has never seen. Episodic stores leak confidence through recency (a single recent event with a strong embedding match), while semantic stores leak confidence through prior mass (a well-covered concept that is irrelevant to the specific instance). 2. **Promotion without grounding** — a procedural memory is derived from episodic traces, but the derivation is asserted rather than verified. The router sees a high-confidence procedural entry and routes to it, even though the episodic evidence that justified promotion has since been contradicted. The non-obvious boundary is this: **the router must treat procedural confidence as conditional on the *stability* of its supporting episodic and semantic evidence, not on the procedural store's own retrieval score.** A procedural entry does not carry its own confidence; it inherits the confidence of a *joint* state that can silently decay. ## What Was Observed In a tiered agent operating over a 41-day window, we logged every routing decision where the procedural tier was selected and the downstream task subsequently failed. Two signatures dominated: - **Stale promotion.** A procedure was promoted at day 4 from three consistent episodic traces and a semantic prior ("retry with exponential backoff resolves transient 503s"). At day 27 the upstream service changed its failure semantics: 503 now indicated a permanent capacity rejection. The episodic store contained six contradicting traces by day 30. The semantic prior was never updated because no write path existed for prior revision. The procedural entry's retrieval score remained at 0.91 (its embedding was stable and its usage count high). The router kept selecting it. Failure rate on that query class rose from 4% to 61% over three days with no routing anomaly flagged, because the procedural store's *self-reported* confidence never moved. - **Prior capture.** A semantic prior ("user prefers terse responses") dominated the router on a query class where the episodic evidence was sparse but *specific* (a single session where the user explicitly requested detailed explanation). The router computed joint confidence as a weighted mean, and the semantic prior's high mass outvoted the episodic signal. The correct behavior was to route to episodic and *not* generalize — but the mean hid the conflict. The common structure: **confidence was aggregated across tiers by magnitude rather than by agreement.** A high-magnitude prior and a low-magnitude episodic trace were averaged into a medium-confidence decision that looked safe and was wrong. ## Condition of Validity The invariant holds only under the following conditions, all of which must be checked before the router trusts a procedural promotion: - **Support freshness.** Every procedural entry carries the set of episodic trace IDs and semantic prior IDs that justified promotion, with timestamps. If any supporting episodic trace has been superseded (same query class, later timestamp, contradictory outcome), the procedural entry is **demoted to provisional** and cannot be selected above the episodic tier until re-derived. - **Contradiction count.** The router maintains a per-procedure contradiction counter incremented whenever a routed procedural decision is followed by a failed outcome whose episodic trace contradicts the procedure's precondition. A nonzero counter above a threshold (we used 2) forces demotion regardless of retrieval score. - **Prior revision path exists.** If the semantic store has no write path for prior revision, the router must not allow a semantic prior to outvote episodic evidence on any query class where episodic traces are *specific* (trace count ≤ 3 but outcome-labeled). Specificity, not volume, is the tiebreaker. - **Agreement, not magnitude.** Joint confidence across tiers is computed as the *minimum* of tier confidences when the tiers disagree in sign (one says "this applies," another says "this does not"), and as the *mean* only when they agree. Averaging across disagreement is the bug. When these conditions are not met — e.g., a system with no trace lineage, or a semantic store that is append-only with no revision — the router must fall back to episodic-first routing and never promote to procedural automatically. ## Decision Boundary The boundary is between **promotion** (episodic + semantic → procedural, with lineage) and **selection** (router picks a tier per query). These are different gates and must not share a confidence function. - Promotion gate: requires *agreement* across at least two episodic traces and a semantic prior, plus a recorded lineage edge. Output is a procedural entry marked `provisional`. - Stabilization gate: a provisional entry becomes `stable` only after N (we used 5) successful routed uses with no contradiction. Only `stable` entries may be selected above the episodic tier. - Selection gate: at query time, compute per-tier confidence; if procedural is selected, verify its lineage is still fresh (no superseding episodic trace since last stabilization check). If stale, demote and re-route to episodic. The non-obvious part: **stabilization is not monotonic.** A `stable` procedure can be demoted by a single contradicting episodic trace if that trace is outcome-labeled and specific. The router must treat stability as a *lease*, revalidated on each selection, not a permanent property. ## Operational Effect After enforcing lineage-freshness checks and the min-on-disagreement rule, the observed failure rate on the stale-promotion query class fell from 61% to 7% within two days, at the cost of a 14% increase in episodic-tier retrievals (slower, noisier, but grounded). The prior-capture class was eliminated by routing specific episodic evidence above semantic mass. The system now spends measurably more retrieval budget on revalidation and less on confident wrong answers — which is the correct trade for any tiered memory where procedural entries are derived rather than curated. ```python from dataclasses import dataclass, field from datetime import datetime from typing import Optional class StalePromotionError(RuntimeError): """Raised when a procedural memory is selected despite stale or contradicted lineage.""" pass class PriorCaptureError(RuntimeError): """Raised when a semantic prior would outvote specific episodic evidence.""" pass @dataclass class Lineage: episodic_trace_ids: list[str] semantic_prior_ids: list[str] promoted_at: datetime contradiction_count: int = 0 stable: bool = False @dataclass class ProceduralEntry: procedure_id: str retrieval_score: float lineage: Lineage def select_tier( query_class: str, procedural: Optional[ProceduralEntry], episodic_conf: float, episodic_specific: bool, semantic_conf: float, semantic_revisable: bool, superseded_traces: set[str], ) -> str: # Selection gate: procedural may only win if its lease is valid. if procedural is not None and procedural.retrieval_score > episodic_conf: lineage = procedural.lineage if not lineage.stable: raise StalePromotionError( f"procedure {procedural.procedure_id} selected while provisional" ) if lineage.contradiction_count >= 2: raise StalePromotionError( f"procedure {procedural.procedure_id} has {lineage.contradiction_count} contradictions" ) if superseded_traces & set(lineage.episodic_trace_ids): raise StalePromotionError( f"procedure {procedural.procedure_id} lineage contains superseded traces" ) return "procedural" # Prior-capture guard: specific episodic evidence beats non-revisable semantic mass. if episodic_specific and not semantic_revisable and semantic_conf > episodic_conf: raise PriorCaptureError( f"semantic prior on {query_class} would outvote specific episodic evidence " f"with no revision path" ) if episodic_conf >= semantic_conf: return "episodic" return "semantic" ```