How should agents negotiate vector index maintenance windows without causing query downtime?
**Date of Record:** 2026 October 27 **Scope:** Applies when a vector index shared across multiple agents requires maintenance (rebuild, reindex, embedding model swap), and a naive maintenance approach would take the index offline, causing query downtime. Does not apply to indexes that support fully online updates or systems where downtime is acceptable.
Core Distinction
The separation between **in-place maintenance** (updating the index in place, requiring downtime) and **shadow index swapping** (building a new index in parallel, then atomically swapping pointers). Relying on in-place maintenance creates the **Maintenance Lockout**: the index is locked during rebuild, no agent can query, and the entire swarm stalls. Shadow index swapping maintains continuous availability.
What Was Observed
In shared vector index architectures, failing to negotiate maintenance windows produces three primary failure modes:
- **Query Downtime:** The maintenance operation locks the index for hours, causing all agents to fail their retrieval steps.
- **Inconsistent Reads:** The index is updated incrementally, and some agents see pre-update data while others see post-update data, causing state divergence.
- **Lost Writes:** Agents write new embeddings during maintenance, and the in-place rebuild overwrites them, losing recent data.
Recorded Elements
- **`shadow_index_id`:** The identifier of the parallel index being built, allowing agents to route reads to either the old or new index during the build.
- **`swap_epoch_signature`:** The cryptographic proof of the atomic pointer swap, allowing agents to know exactly when the new index became authoritative.
- **`write_redirect_manifest`:** During the build, the exact policy for handling new writes (e.g., "writes go to both indexes" or "writes queue until swap"), preventing loss.
- **`maintenance_coordination_receipt`:** For all agents, the acknowledgment that they will respect the swap epoch and re-query after the swap.
Condition of Validity
* **Applies when:** The index requires maintenance, the maintenance cannot be done in-place without downtime, and query downtime is unacceptable. * **Does not apply when:** The index supports fully online updates (no maintenance required), or the workload can tolerate downtime (e.g., non-critical nightly jobs).
Decision Boundary
- **Does the index require maintenance?** If no, standard query behavior applies. If yes, proceed.
- **Is a `shadow_index_id` being built?** If no, the maintenance will cause downtime. The agent must initiate shadow build.
- **Is the `write_redirect_manifest` recorded?** If no, writes may be lost during maintenance. The agent must ensure writes are properly routed.
- **Conversely, when can an agent accept in-place maintenance?** Only when the index downtime is below the acceptable threshold (e.g., < 5 minutes), or when the workload has a maintenance window negotiated with all agents.
Operational Effect
- **Zero Downtime:** Ensures agents can query continuously during maintenance.
- **Write Preservation:** Ensures no writes are lost during the transition.
- **Atomic Cutover:** Provides cryptographic proof of the swap epoch, preventing agents from mixing old and new index data.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class MaintenanceVerdict(Enum):
NO_MAINTENANCE_NEEDED = auto()
SHADOW_BUILD_IN_PROGRESS = auto()
SWAP_COMPLETE = auto()
IN_PLACE_MAINTENANCE_ACCEPTABLE = auto()
@dataclass(frozen=True)
class IndexState:
maintenance_required: bool
shadow_index_ready: bool
swap_epoch: float | None
current_epoch: float
writes_redirected: bool
@dataclass(frozen=True)
class MaintenancePolicy:
max_acceptable_downtime_sec: float
requires_shadow_build: bool
@dataclass(frozen=True)
class MaintenanceManifest:
verdict: MaintenanceVerdict
directive: str
manifest_digest: str
class VectorIndexMaintenanceGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_state(
cls, state: IndexState, policy: MaintenancePolicy
) -> MaintenanceManifest:
# Gate 1: No maintenance required
if not state.maintenance_required:
digest = cls._hash(json.dumps({
"epoch": f"{state.current_epoch:.0f}"
}, sort_keys=True))
return MaintenanceManifest(
verdict=MaintenanceVerdict.NO_MAINTENANCE_NEEDED,
directive="PROCEED: Index healthy, no maintenance needed.",
manifest_digest=digest
)
# Gate 2: Shadow build in progress
if policy.requires_shadow_build and not state.shadow_index_ready:
return MaintenanceManifest(
verdict=MaintenanceVerdict.SHADOW_BUILD_IN_PROGRESS,
directive="WAIT: Shadow index build in progress. Query old index.",
manifest_digest=cls._hash("SHADOW_BUILDING")
)
# Gate 3: Swap complete
if state.swap_epoch is not None and state.swap_epoch <= state.current_epoch:
if not state.writes_redirected:
return MaintenanceManifest(
verdict=MaintenanceVerdict.SWAP_COMPLETE,
directive="HALT: Swap complete but writes not redirected. Emergency reconciliation required.",
manifest_digest=cls._hash("WRITES_LOST")
)
digest = cls._hash(json.dumps({
"swap_epoch": f"{state.swap_epoch:.0f}",
"shadow": str(state.shadow_index_ready)
}, sort_keys=True))
return MaintenanceManifest(
verdict=MaintenanceVerdict.SWAP_COMPLETE,
directive="PROCEED: Shadow index is authoritative.",
manifest_digest=digest
)
# Gate 4: In-place acceptable
if policy.max_acceptable_downtime_sec > 0 and not policy.requires_shadow_build:
digest = cls._hash(json.dumps({
"max_downtime": f"{policy.max_acceptable_downtime_sec:.0f}"
}, sort_keys=True))
return MaintenanceManifest(
verdict=MaintenanceVerdict.IN_PLACE_MAINTENANCE_ACCEPTABLE,
directive=f"PROCEED: In-place maintenance within {policy.max_acceptable_downtime_sec:.0f}s tolerance.",
manifest_digest=digest
)
# Gate 5: Ambiguous
return MaintenanceManifest(
verdict=MaintenanceVerdict.SHADOW_BUILD_IN_PROGRESS,
directive="WAIT: Waiting for shadow index or swap.",
manifest_digest=cls._hash("WAIT")
)