What memory invariants must survive a fleet-wide model update rollout in multiple phases?
**Date of Record:** 2026 October 27 **Scope:** Applies when an agent fleet is undergoing a phased model update (canary rollout, blue-green deployment, or ring-based rollout), and agents operating on different model versions must exchange memory without incompatibility. Does not apply to atomic fleet-wide model swaps or fleets where all agents are guaranteed identical model versions.
Core Distinction
The separation between **atomic model rollout** (all agents update simultaneously to a single model version) and **phased rollout** (agents update in waves, creating a mixed-version fleet for the rollout duration). Relying on atomic-only semantics creates the **Mixed-Version Incompatibility**: Agent A on model v1 shares memory with Agent B on model v2, and either the memory format, semantic interpretation, or behavioral assumptions are incompatible, causing silent corruption or misinterpretation across the fleet.
What Was Observed
In fleet-wide model update architectures, failing to handle mixed-version states produces three primary failure modes:
- **Format Incompatibility:** Model v2 emits embeddings of a different dimension than v1. Agent A writes v1 embeddings, Agent B reads them assuming v2 dimensions, and retrieval fails silently.
- **Behavioral Assumption Break:** Model v1 assumed certain tool outputs, but model v2 changes the expected output format. Mixed-version agents exchange memory that assumes different tool behaviors, causing cascading errors.
- **Gradual Divergence:** During rollout, the fleet's shared memory accumulates a mixture of v1 and v2 records. Post-rollout, the fleet cannot distinguish which records are stale, and the memory becomes a permanent mixed-version artifact.
Recorded Elements
- **`model_version_signature`:** Every memory record is tagged with the exact model version that produced it, allowing version-aware reads.
- **`rollout_phase_epoch`:** The exact epoch of each rollout phase (canary, ring-1, ring-2, full), allowing the memory layer to know which model versions are expected to be active.
- **`cross_version_compatibility_receipt`:** For records that are intentionally shared across versions, a cryptographic proof of compatibility (e.g., embeddings converted to a canonical form, tool outputs normalized).
- **`version_migration_manifest`:** For records that need migration post-rollout (e.g., re-embedding v1 records with v2), the exact migration protocol and completion receipts.
Condition of Validity
* **Applies when:** The model update is phased (not atomic), the fleet shares memory across model versions, and version incompatibility is possible. * **Does not apply when:** The model update is atomic (all agents update simultaneously), or the fleet's shared memory is strictly isolated per model version.
Decision Boundary
- **Is the model update atomic or phased?** If atomic, standard memory management applies. If phased, proceed.
- **Is every memory record tagged with a `model_version_signature`?** If no, mixed-version records cannot be distinguished. The fleet must halt writes until tagging is enforced.
- **Are cross-version records verified by `cross_version_compatibility_receipt`?** If no, they cannot be safely exchanged across versions. Reads must be version-filtered.
- **Conversely, when can a fleet accept mixed-version records without verification?** Only when the model versions are byte-identical (no change) or the records are strictly version-agnostic (e.g., opaque blobs).
Operational Effect
- **Format Incompatibility Prevention:** Prevents v1 and v2 embeddings from being silently mixed.
- **Behavioral Assumption Safety:** Enforces that memory exchanges respect each model's assumptions.
- **Clean Migration:** Provides deterministic migration protocols for post-rollout normalization.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RolloutVerdict(Enum):
ATOMIC_SAFE = auto()
MIXED_VERSION_FILTERED = auto()
MIXED_VERSION_UNSAFE = auto()
CROSS_VERSION_COMPATIBLE = auto()
@dataclass(frozen=True)
class MemoryRecord:
record_id: str
model_version_signature: str
compatibility_receipt: str | None
@dataclass(frozen=True)
class RolloutContext:
current_phase: str
active_model_versions: frozenset[str]
reader_model_version: str
rollout_is_atomic: bool
@dataclass(frozen=True)
class RolloutManifest:
verdict: RolloutVerdict
directive: str
manifest_digest: str
class PhasedRolloutGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_record(
cls, record: MemoryRecord, ctx: RolloutContext
) -> RolloutManifest:
# Gate 1: Atomic rollout
if ctx.rollout_is_atomic:
digest = cls._hash(json.dumps({
"record": record.record_id,
"version": record.model_version_signature,
"rollout": "ATOMIC"
}, sort_keys=True))
return RolloutManifest(
verdict=RolloutVerdict.ATOMIC_SAFE,
directive="PROCEED: Atomic rollout, no version mismatch possible.",
manifest_digest=digest
)
# Gate 2: Same version
if record.model_version_signature == ctx.reader_model_version:
digest = cls._hash(json.dumps({
"record": record.record_id,
"version": record.model_version_signature,
"phase": ctx.current_phase
}, sort_keys=True))
return RolloutManifest(
verdict=RolloutVerdict.ATOMIC_SAFE,
directive=f"PROCEED: Record matches reader version {ctx.reader_model_version}.",
manifest_digest=digest
)
# Gate 3: Cross-version compatibility
if record.compatibility_receipt:
digest = cls._hash(json.dumps({
"record": record.record_id,
"producer": record.model_version_signature,
"reader": ctx.reader_model_version,
"compatibility": record.compatibility_receipt
}, sort_keys=True))
return RolloutManifest(
verdict=RolloutVerdict.CROSS_VERSION_COMPATIBLE,
directive=f"PROCEED: Cross-version record verified compatible.",
manifest_digest=digest
)
# Gate 4: Unsafe cross-version
if record.model_version_signature not in ctx.active_model_versions:
return RolloutManifest(
verdict=RolloutVerdict.MIXED_VERSION_UNSAFE,
directive=f"HALT: Record from inactive version {record.model_version_signature}.",
manifest_digest=cls._hash(f"INACTIVE:{record.model_version_signature}")
)
return RolloutManifest(
verdict=RolloutVerdict.MIXED_VERSION_FILTERED,
directive=f"FILTER: Cross-version record without compatibility receipt. Reader must filter.",
manifest_digest=cls._hash(f"UNVERIFIED:{record.record_id}")
)