← Back to DevBytes

AI Agent Backup and Recovery: Never Lose Agent Memory

What is AI Agent Backup and Recovery?

AI Agent Backup and Recovery is a systematic approach to preserving and restoring an autonomous agent's complete operational state — its memory, context, learned patterns, and ongoing workflows. Just as database administrators maintain disaster recovery plans for transactional data, AI agent developers must implement robust backup strategies to safeguard the agent's accumulated intelligence. When an agent crashes, gets redeployed, or encounters a catastrophic failure, a properly designed backup and recovery system ensures the agent can resume exactly where it left off, without losing weeks of learning, conversation context, or critical decision-making history.

Agent memory encompasses far more than simple chat logs. It includes vector embeddings stored in retrieval systems, fine-tuned model weights, active task queues, persistent state machines, and the intricate web of relationships the agent has built between concepts, users, and actions. Losing this memory means forcing the agent to start from zero — a costly regression that destroys user trust and wastes computational resources invested in the agent's learning trajectory.

Why Agent Memory Backup Matters

The stakes for AI agent memory integrity grow proportionally with the agent's autonomy and deployment longevity. Consider these critical scenarios:

Beyond disaster prevention, robust backup enables time-travel debugging — the ability to restore the agent's exact state at any historical point to understand emergent behaviors, debug subtle bugs, or reproduce user-reported issues with perfect fidelity.

Anatomy of Agent Memory: What to Back Up

A production AI agent typically maintains several distinct memory layers. Each requires a tailored backup strategy:

1. Conversation and Interaction History

The chronological record of exchanges between the agent and users, including metadata like timestamps, user IDs, session contexts, and message-level annotations. This is typically stored in a relational database or time-series storage and forms the foundation for personalization and context-aware responses.

2. Vector Stores and Semantic Memory

Embedding-based retrieval systems (Pinecone, Weaviate, pgvector, ChromaDB) hold the agent's long-term knowledge. These contain dense vector representations of documents, memories, and facts — along with their metadata payloads. Vector stores are often the largest component and the most challenging to back up efficiently due to their size and indexing structures.

3. State Machine and Active Workflows

Multi-step operations in progress — like a travel booking agent mid-reservation or a coding agent in the middle of a complex refactor. The state includes the current step, accumulated partial results, pending confirmations, and timeout windows. This state is often ephemeral but critical to preserve for seamless recovery.

4. Agent Configuration and Policy Memory

The rules, constraints, and behavioral guidelines that define the agent's operational boundaries. This includes system prompts, safety filters, tool access permissions, rate-limiting configurations, and user-specific preferences. Configuration drift is a common failure mode — backing up configuration snapshots prevents silent behavioral changes.

5. Learned Patterns and Fine-tuned Artifacts

If the agent employs reinforcement learning from feedback, stores user preference models, or maintains locally fine-tuned adapter weights, these artifacts represent expensive learned intelligence that must be preserved across deployments.

Implementing Backup and Recovery: A Complete Blueprint

Let's build a production-grade backup system for an AI agent with conversation memory, a vector store, and active workflow state. We'll implement snapshot-based backups with point-in-time recovery capabilities.

Step 1: Define the Backup Manifest Schema

First, create a unified manifest that catalogs all backup components and their versions. This serves as the recovery roadmap.

# backup_manifest.py
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional, Dict
import json
import hashlib

@dataclass
class BackupComponent:
    component_type: str          # 'conversation', 'vector_store', 'workflow_state', 'config'
    storage_backend: str         # 'postgres', 'pinecone', 'redis', 's3'
    snapshot_id: str             # unique identifier for this snapshot
    checksum: str                # SHA-256 of serialized data
    timestamp: datetime
    record_count: Optional[int] = None
    size_bytes: Optional[int] = None
    metadata: Dict = field(default_factory=dict)

@dataclass
class BackupManifest:
    agent_id: str
    backup_id: str               # UUID-based unique backup identifier
    created_at: datetime
    components: List[BackupComponent]
    agent_version: str
    model_versions: Dict[str, str]  # e.g., {'llm': 'gpt-4-turbo-20240410', 'embedding': 'text-embedding-3-small'}
    total_size_bytes: int
    
    def to_json(self) -> str:
        return json.dumps(self.__dict__, default=str, indent=2)
    
    @classmethod
    def from_json(cls, json_str: str) -> 'BackupManifest':
        data = json.loads(json_str)
        data['created_at'] = datetime.fromisoformat(data['created_at'])
        data['components'] = [BackupComponent(**c) for c in data['components']]
        return cls(**data)

Step 2: Implement the Conversation History Backup

For conversation history stored in PostgreSQL, we'll use transaction-consistent snapshots with incremental change tracking. This allows both full backups and lightweight delta backups for efficiency.

# conversation_backup.py
import psycopg2
import hashlib
import json
from datetime import datetime, timedelta
from typing import Optional, List, Dict

class ConversationBackup:
    def __init__(self, db_connection_string: str):
        self.conn_string = db_connection_string
        self.last_backup_pointer = None  # tracks last backed up message ID
    
    def create_full_snapshot(self, agent_id: str, before_timestamp: datetime) -> Dict:
        """Create a complete snapshot of all conversations up to a point in time."""
        conn = psycopg2.connect(self.conn_string)
        try:
            with conn.cursor() as cursor:
                # Use a transaction with REPEATABLE READ for consistency
                cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
                
                cursor.execute("""
                    SELECT conversation_id, user_id, messages, metadata, created_at, updated_at
                    FROM agent_conversations
                    WHERE agent_id = %s AND updated_at <= %s
                    ORDER BY conversation_id, created_at
                """, (agent_id, before_timestamp))
                
                rows = cursor.fetchall()
                
                snapshot_data = {
                    'agent_id': agent_id,
                    'snapshot_type': 'full',
                    'snapshot_timestamp': before_timestamp.isoformat(),
                    'conversations': []
                }
                
                total_bytes = 0
                for row in rows:
                    conv = {
                        'conversation_id': row[0],
                        'user_id': row[1],
                        'messages': json.loads(row[2]) if isinstance(row[2], str) else row[2],
                        'metadata': json.loads(row[3]) if isinstance(row[3], str) else row[3],
                        'created_at': row[4].isoformat(),
                        'updated_at': row[5].isoformat()
                    }
                    snapshot_data['conversations'].append(conv)
                
                serialized = json.dumps(snapshot_data, default=str).encode('utf-8')
                checksum = hashlib.sha256(serialized).hexdigest()
                
                # Record the latest message timestamp as pointer for future incrementals
                cursor.execute("""
                    SELECT MAX(updated_at) FROM agent_conversations
                    WHERE agent_id = %s AND updated_at <= %s
                """, (agent_id, before_timestamp))
                max_ts = cursor.fetchone()[0]
                
                return {
                    'data': snapshot_data,
                    'checksum': checksum,
                    'size_bytes': len(serialized),
                    'last_timestamp': max_ts.isoformat() if max_ts else None,
                    'record_count': len(rows)
                }
        finally:
            conn.close()
    
    def create_incremental_snapshot(self, agent_id: str, 
                                    from_timestamp: datetime,
                                    to_timestamp: Optional[datetime] = None) -> Dict:
        """Capture only changes since the last backup for efficiency."""
        if to_timestamp is None:
            to_timestamp = datetime.utcnow()
        
        conn = psycopg2.connect(self.conn_string)
        try:
            with conn.cursor() as cursor:
                cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ")
                
                cursor.execute("""
                    SELECT conversation_id, user_id, messages, metadata, created_at, updated_at
                    FROM agent_conversations
                    WHERE agent_id = %s 
                      AND updated_at > %s 
                      AND updated_at <= %s
                    ORDER BY conversation_id, updated_at
                """, (agent_id, from_timestamp, to_timestamp))
                
                rows = cursor.fetchall()
                
                snapshot_data = {
                    'agent_id': agent_id,
                    'snapshot_type': 'incremental',
                    'base_timestamp': from_timestamp.isoformat(),
                    'snapshot_timestamp': to_timestamp.isoformat(),
                    'changes': []
                }
                
                for row in rows:
                    change = {
                        'conversation_id': row[0],
                        'user_id': row[1],
                        'messages': json.loads(row[2]) if isinstance(row[2], str) else row[2],
                        'metadata': json.loads(row[3]) if isinstance(row[3], str) else row[3],
                        'created_at': row[4].isoformat(),
                        'updated_at': row[5].isoformat()
                    }
                    snapshot_data['changes'].append(change)
                
                serialized = json.dumps(snapshot_data, default=str).encode('utf-8')
                checksum = hashlib.sha256(serialized).hexdigest()
                
                return {
                    'data': snapshot_data,
                    'checksum': checksum,
                    'size_bytes': len(serialized),
                    'record_count': len(rows),
                    'from_timestamp': from_timestamp.isoformat(),
                    'to_timestamp': to_timestamp.isoformat()
                }
        finally:
            conn.close()

Step 3: Vector Store Backup with Efficient Serialization

Vector stores present unique challenges: they can be enormous (millions of embeddings) and tightly coupled to specific embedding model versions. We'll implement a streaming backup approach that exports vectors in batches while preserving index structure metadata.

# vector_store_backup.py
import numpy as np
import hashlib
import json
import pickle
from typing import List, Dict, Generator
import struct

class VectorStoreBackup:
    def __init__(self, vector_db_client, index_name: str):
        self.client = vector_db_client  # Abstracted client (Pinecone, Weaviate, etc.)
        self.index_name = index_name
        self.batch_size = 1000
    
    def export_vectors_streaming(self, namespace: str = "") -> Generator:
        """Stream vectors in batches to avoid memory exhaustion on large stores."""
        # Fetch vector IDs in batches using cursor-based pagination
        pagination_token = None
        
        while True:
            batch_result = self.client.fetch_batch(
                index=self.index_name,
                namespace=namespace,
                limit=self.batch_size,
                pagination_token=pagination_token,
                include_vectors=True,
                include_metadata=True
            )
            
            if not batch_result.vectors:
                break
            
            for vector_record in batch_result.vectors:
                yield {
                    'id': vector_record.id,
                    'values': vector_record.values.tolist() if hasattr(vector_record.values, 'tolist') else vector_record.values,
                    'metadata': vector_record.metadata,
                    'sparse_values': vector_record.sparse_values if hasattr(vector_record, 'sparse_values') else None
                }
            
            pagination_token = batch_result.pagination_token
            if not pagination_token:
                break
    
    def create_backup_file(self, namespace: str = "", output_path: str = "vector_backup.jsonl"):
        """Write vectors to a JSONL file for portable backup."""
        import os
        
        total_vectors = 0
        file_checksums = []
        
        with open(output_path, 'w') as f:
            for vector_batch in self._batch_generator(self.export_vectors_streaming(namespace)):
                for vector in vector_batch:
                    serialized = json.dumps(vector, default=str)
                    f.write(serialized + '\n')
                    total_vectors += 1
                    
                    # Compute rolling checksum per 1000 vectors
                    if total_vectors % 1000 == 0:
                        current_checksum = hashlib.sha256(
                            serialized.encode('utf-8')
                        ).hexdigest()
                        file_checksums.append(current_checksum)
        
        # Final checksum of the entire file
        with open(output_path, 'rb') as f:
            full_checksum = hashlib.sha256(f.read()).hexdigest()
        
        return {
            'file_path': output_path,
            'total_vectors': total_vectors,
            'checksum': full_checksum,
            'batch_checksums': file_checksums,
            'index_name': self.index_name,
            'namespace': namespace,
            'size_bytes': os.path.getsize(output_path)
        }
    
    def _batch_generator(self, stream: Generator, batch_size: int = 100):
        """Group streamed items into batches for efficient I/O."""
        batch = []
        for item in stream:
            batch.append(item)
            if len(batch) >= batch_size:
                yield batch
                batch = []
        if batch:
            yield batch
    
    def restore_vectors(self, backup_file_path: str, namespace: str = "", 
                        batch_size: int = 100, verify_checksum: bool = True):
        """Restore vectors from backup file with integrity verification."""
        if verify_checksum:
            with open(backup_file_path, 'rb') as f:
                actual_checksum = hashlib.sha256(f.read()).hexdigest()
            # Compare against stored manifest checksum
            # (Implementation would fetch manifest checksum from metadata store)
        
        vectors_restored = 0
        batch = []
        
        with open(backup_file_path, 'r') as f:
            for line in f:
                vector_data = json.loads(line.strip())
                
                # Reconstruct vector with proper types
                vector_id = vector_data['id']
                values = vector_data['values']
                metadata = vector_data.get('metadata', {})
                
                batch.append({
                    'id': vector_id,
                    'values': values,
                    'metadata': metadata
                })
                
                if len(batch) >= batch_size:
                    self.client.upsert_batch(
                        index=self.index_name,
                        namespace=namespace,
                        vectors=batch
                    )
                    vectors_restored += len(batch)
                    batch = []
            
            # Upsert remaining batch
            if batch:
                self.client.upsert_batch(
                    index=self.index_name,
                    namespace=namespace,
                    vectors=batch
                )
                vectors_restored += len(batch)
        
        return {'vectors_restored': vectors_restored, 'status': 'completed'}

Step 4: Workflow State and Active Task Backup

Active workflows represent in-progress operations that span multiple steps. Backing these up requires capturing the exact state machine position, accumulated partial results, and expiration timers.

# workflow_state_backup.py
import redis
import json
import hashlib
from datetime import datetime, timedelta
from typing import Dict, Optional, List

class WorkflowStateBackup:
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        self.workflow_prefix = "agent:workflow:"
    
    def snapshot_active_workflows(self, agent_id: str) -> Dict:
        """Capture all active workflow states with TTL preservation."""
        pattern = f"{self.workflow_prefix}{agent_id}:*"
        workflow_keys = self.redis.keys(pattern)
        
        workflows = {}
        total_count = 0
        
        for key in workflow_keys:
            decoded_key = key.decode('utf-8') if isinstance(key, bytes) else key
            
            # Extract workflow state hash
            workflow_data = self.redis.hgetall(decoded_key)
            if workflow_data:
                decoded_data = {}
                for field, value in workflow_data.items():
                    f = field.decode('utf-8') if isinstance(field, bytes) else field
                    v = value.decode('utf-8') if isinstance(value, bytes) else value
                    decoded_data[f] = v
                
                # Capture TTL for timeout-aware recovery
                ttl_seconds = self.redis.ttl(decoded_key)
                
                workflows[decoded_key] = {
                    'state': decoded_data,
                    'ttl_remaining_seconds': ttl_seconds if ttl_seconds > 0 else None,
                    'snapshot_time': datetime.utcnow().isoformat()
                }
                total_count += 1
        
        serialized = json.dumps(workflows, default=str).encode('utf-8')
        
        return {
            'agent_id': agent_id,
            'workflow_count': total_count,
            'workflows': workflows,
            'checksum': hashlib.sha256(serialized).hexdigest(),
            'size_bytes': len(serialized),
            'snapshot_timestamp': datetime.utcnow().isoformat()
        }
    
    def restore_workflows(self, backup_data: Dict, adjust_timers: bool = True):
        """Restore workflow states with optional TTL adjustment."""
        restored_count = 0
        now = datetime.utcnow()
        
        for workflow_key, workflow_info in backup_data['workflows'].items():
            state_dict = workflow_info['state']
            ttl_remaining = workflow_info.get('ttl_remaining_seconds')
            
            # Restore the hash fields
            self.redis.hset(workflow_key, mapping=state_dict)
            
            # Adjust TTL: subtract elapsed time since snapshot
            if adjust_timers and ttl_remaining is not None:
                snapshot_time = datetime.fromisoformat(workflow_info['snapshot_time'])
                elapsed = (now - snapshot_time).total_seconds()
                new_ttl = max(1, int(ttl_remaining - elapsed))
                self.redis.expire(workflow_key, new_ttl)
            elif ttl_remaining is not None:
                self.redis.expire(workflow_key, int(ttl_remaining))
            
            restored_count += 1
        
        return {'restored_workflows': restored_count}

Step 5: Orchestrating the Complete Backup Pipeline

The orchestrator coordinates all backup components, generates the manifest, and handles atomicity — ensuring all snapshots are taken at consistent logical points.

# backup_orchestrator.py
import uuid
import json
import os
import shutil
from datetime import datetime
from typing import Dict, Optional
import boto3  # For S3/cloud storage persistence

class AgentBackupOrchestrator:
    def __init__(self, 
                 conversation_backup: 'ConversationBackup',
                 vector_backup: 'VectorStoreBackup',
                 workflow_backup: 'WorkflowStateBackup',
                 config_store,  # Configuration management system
                 storage_backend=None):  # S3, GCS, local filesystem
        self.conversation_backup = conversation_backup
        self.vector_backup = vector_backup
        self.workflow_backup = workflow_backup
        self.config_store = config_store
        self.storage_backend = storage_backend or LocalStorageBackend("./agent_backups")
    
    def create_full_backup(self, agent_id: str, backup_label: str = "") -> BackupManifest:
        """Execute a complete point-in-time backup of all agent memory components."""
        backup_id = str(uuid.uuid4())
        snapshot_time = datetime.utcnow()
        components = []
        total_size = 0
        
        print(f"[Backup] Starting full backup {backup_id} for agent {agent_id}")
        
        # 1. Backup conversation history
        print("[Backup] Snapshotting conversation history...")
        conv_result = self.conversation_backup.create_full_snapshot(
            agent_id=agent_id, 
            before_timestamp=snapshot_time
        )
        
        conv_component = BackupComponent(
            component_type='conversation',
            storage_backend='postgres',
            snapshot_id=f"conv_{backup_id}",
            checksum=conv_result['checksum'],
            timestamp=snapshot_time,
            record_count=conv_result['record_count'],
            size_bytes=conv_result['size_bytes']
        )
        components.append(conv_component)
        total_size += conv_result['size_bytes']
        
        # Store conversation backup data
        self.storage_backend.store(
            key=f"{agent_id}/{backup_id}/conversations.json",
            data=json.dumps(conv_result['data'], default=str).encode('utf-8')
        )
        
        # 2. Backup vector store
        print("[Backup] Exporting vector store...")
        vector_backup_path = f"/tmp/vector_backup_{backup_id}.jsonl"
        vector_result = self.vector_backup.create_backup_file(
            namespace=agent_id,
            output_path=vector_backup_path
        )
        
        # Upload vector backup to persistent storage
        with open(vector_backup_path, 'rb') as f:
            self.storage_backend.store(
                key=f"{agent_id}/{backup_id}/vectors.jsonl",
                data=f.read()
            )
        
        vector_component = BackupComponent(
            component_type='vector_store',
            storage_backend=self.vector_backup.index_name,
            snapshot_id=f"vec_{backup_id}",
            checksum=vector_result['checksum'],
            timestamp=snapshot_time,
            record_count=vector_result['total_vectors'],
            size_bytes=vector_result['size_bytes']
        )
        components.append(vector_component)
        total_size += vector_result['size_bytes']
        
        # Clean up temp file
        os.unlink(vector_backup_path)
        
        # 3. Backup active workflow states
        print("[Backup] Capturing workflow states...")
        workflow_result = self.workflow_backup.snapshot_active_workflows(agent_id)
        
        workflow_component = BackupComponent(
            component_type='workflow_state',
            storage_backend='redis',
            snapshot_id=f"wf_{backup_id}",
            checksum=workflow_result['checksum'],
            timestamp=snapshot_time,
            record_count=workflow_result['workflow_count'],
            size_bytes=workflow_result['size_bytes']
        )
        components.append(workflow_component)
        total_size += workflow_result['size_bytes']
        
        self.storage_backend.store(
            key=f"{agent_id}/{backup_id}/workflows.json",
            data=json.dumps(workflow_result, default=str).encode('utf-8')
        )
        
        # 4. Backup agent configuration
        print("[Backup] Capturing configuration snapshot...")
        config_snapshot = self.config_store.export_configuration(agent_id)
        config_json = json.dumps(config_snapshot, default=str).encode('utf-8')
        
        config_component = BackupComponent(
            component_type='config',
            storage_backend='config_store',
            snapshot_id=f"cfg_{backup_id}",
            checksum=hashlib.sha256(config_json).hexdigest(),
            timestamp=snapshot_time,
            size_bytes=len(config_json)
        )
        components.append(config_component)
        total_size += len(config_json)
        
        self.storage_backend.store(
            key=f"{agent_id}/{backup_id}/config.json",
            data=config_json
        )
        
        # Build final manifest
        manifest = BackupManifest(
            agent_id=agent_id,
            backup_id=backup_id,
            created_at=snapshot_time,
            components=components,
            agent_version=self.config_store.get_version(agent_id),
            model_versions={
                'llm': self.config_store.get_model_version('llm'),
                'embedding': self.config_store.get_model_version('embedding')
            },
            total_size_bytes=total_size
        )
        
        # Store manifest as the authoritative recovery index
        manifest_json = manifest.to_json().encode('utf-8')
        self.storage_backend.store(
            key=f"{agent_id}/{backup_id}/manifest.json",
            data=manifest_json
        )
        
        # Update latest pointer for quick recovery reference
        self.storage_backend.store(
            key=f"{agent_id}/latest_backup_id",
            data=backup_id.encode('utf-8')
        )
        
        print(f"[Backup] Complete. Backup ID: {backup_id}, Total size: {total_size / 1024 / 1024:.2f} MB")
        return manifest

Step 6: Recovery — Restoring Agent Memory from Backup

Recovery reconstructs the agent's complete memory state from the backup manifest. The process respects component dependencies and validates integrity at each step.

# recovery_engine.py
import json
import hashlib
from datetime import datetime
from typing import Optional

class AgentRecoveryEngine:
    def __init__(self,
                 conversation_backup,
                 vector_backup,
                 workflow_backup,
                 config_store,
                 storage_backend):
        self.conversation_backup = conversation_backup
        self.vector_backup = vector_backup
        self.workflow_backup = workflow_backup
        self.config_store = config_store
        self.storage_backend = storage_backend
    
    def restore_from_backup(self, agent_id: str, 
                           backup_id: Optional[str] = None,
                           point_in_time: Optional[datetime] = None,
                           dry_run: bool = False) -> Dict:
        """
        Restore agent memory from a specific backup or latest snapshot.
        
        Args:
            agent_id: The agent to restore
            backup_id: Specific backup ID to restore. If None, uses latest.
            point_in_time: For time-travel recovery, finds nearest backup before this time.
            dry_run: Validate backup integrity without actually restoring.
        
        Returns:
            Detailed restoration report with component statuses.
        """
        # Determine which backup to use
        if backup_id is None:
            if point_in_time is not None:
                backup_id = self._find_nearest_backup_before(agent_id, point_in_time)
            else:
                backup_id = self.storage_backend.retrieve(
                    f"{agent_id}/latest_backup_id"
                ).decode('utf-8')
        
        print(f"[Recovery] Restoring agent {agent_id} from backup {backup_id}")
        
        # Fetch and parse manifest
        manifest_data = self.storage_backend.retrieve(
            f"{agent_id}/{backup_id}/manifest.json"
        )
        manifest = BackupManifest.from_json(manifest_data.decode('utf-8'))
        
        recovery_report = {
            'backup_id': backup_id,
            'restore_timestamp': datetime.utcnow().isoformat(),
            'dry_run': dry_run,
            'components': {},
            'overall_status': 'pending'
        }
        
        # Restore components in dependency order
        # 1. Configuration first (other components may depend on config)
        config_component = next(
            c for c in manifest.components if c.component_type == 'config'
        )
        config_data = self.storage_backend.retrieve(
            f"{agent_id}/{backup_id}/config.json"
        )
        
        # Verify checksum
        config_checksum = hashlib.sha256(config_data).hexdigest()
        if config_checksum != config_component.checksum:
            raise IntegrityError(
                f"Config checksum mismatch! Expected {config_component.checksum}, "
                f"got {config_checksum}"
            )
        
        if not dry_run:
            self.config_store.import_configuration(
                agent_id, 
                json.loads(config_data.decode('utf-8'))
            )
        recovery_report['components']['config'] = {'status': 'restored', 'checksum_verified': True}
        
        # 2. Restore vector store
        vector_component = next(
            c for c in manifest.components if c.component_type == 'vector_store'
        )
        vector_backup_data = self.storage_backend.retrieve(
            f"{agent_id}/{backup_id}/vectors.jsonl"
        )
        
        # Verify vector backup checksum
        vector_checksum = hashlib.sha256(vector_backup_data).hexdigest()
        if vector_checksum != vector_component.checksum:
            raise IntegrityError(
                f"Vector store checksum mismatch! Expected {vector_component.checksum}, "
                f"got {vector_checksum}"
            )
        
        if not dry_run:
            # Write to temporary file for streaming restore
            temp_path = f"/tmp/vector_restore_{backup_id}.jsonl"
            with open(temp_path, 'wb') as f:
                f.write(vector_backup_data)
            
            restore_result = self.vector_backup.restore_vectors(
                backup_file_path=temp_path,
                namespace=agent_id,
                verify_checksum=True
            )
            
            import os
            os.unlink(temp_path)
            
            recovery_report['components']['vector_store'] = {
                'status': 'restored',
                'vectors_restored': restore_result['vectors_restored'],
                'checksum_verified': True
            }
        
        # 3. Restore conversation history
        conv_component = next(
            c for c in manifest.components if c.component_type == 'conversation'
        )
        conv_data = self.storage_backend.retrieve(
            f"{agent_id}/{backup_id}/conversations.json"
        )
        
        conv_checksum = hashlib.sha256(conv_data).hexdigest()
        if conv_checksum != conv_component.checksum:
            raise IntegrityError(
                f"Conversation checksum mismatch! Expected {conv_component.checksum}, "
                f"got {conv_checksum}"
            )
        
        if not dry_run:
            conversations = json.loads(conv_data.decode('utf-8'))
            # Bulk insert conversations with conflict handling
            self.conversation_backup.restore_conversations(
                agent_id, conversations['conversations']
            )
        
        recovery_report['components']['conversation'] = {
            'status': 'restored',
            'record_count': conv_component.record_count,
            'checksum_verified': True
        }
        
        # 4. Restore workflow states
        wf_component = next(
            c for c in manifest.components if c.component_type == 'workflow_state'
        )
        wf_data = self.storage_backend.retrieve(
            f"{agent_id}/{backup_id}/workflows.json"
        )
        
        wf_checksum = hashlib.sha256(wf_data).hexdigest()
        if wf_checksum != wf_component.checksum:
            raise IntegrityError(
                f"Workflow checksum mismatch! Expected {wf_component.checksum}, "
                f"got {wf_checksum}"
            )
        
        if not dry_run:
            workflow_backup_data = json.loads(wf_data.decode('utf-8'))
            restore_result = self.workflow_backup.restore_workflows(
                workflow_backup_data, 
                adjust_timers=True  # Compensate for elapsed time since backup
            )
        
        recovery_report['components']['workflow_state'] = {
            'status': 'restored',
            'workflow_count': wf_component.record_count,
            'checksum_verified': True
        }
        
        recovery_report['overall_status'] = 'success'
        
        print(f"[Recovery] Restoration complete. Agent {agent_id} is ready.")
        return recovery_report
    
    def _find_nearest_backup_before(self, agent_id: str, 
                                    point_in_time: datetime) -> str:
        """Find the backup ID closest to (but not after) a point in time."""
        # List all backups for this agent
        backup_listing = self.storage_backend.list_prefix(
            f"{agent_id}/"
        )
        
        # Parse manifests to find timestamps
        backups_with_times = []
        for key in backup_listing:
            if key.endswith('/manifest.json'):
                manifest_data = self.storage_backend.retrieve(key)
                manifest = BackupManifest.from_json(manifest_data.decode('utf-8'))
                if manifest.created_at <= point_in_time:
                    backups_with_times.append((manifest.created_at, manifest.backup_id))
        
        if not backups_with_times:
            raise NoSuitableBackupError(
                f"No backup found before {point_in_time.isoformat()}"
            )
        
        # Return the most recent backup before the point in time
        backups_with_times.sort(key=lambda x: x[0], reverse=True)
        return backups_with_times[0][1]

Step 7: Scheduling Automated Backups

Automated backup scheduling ensures regular snapshots without manual intervention. Use cron-style scheduling with configurable retention policies.

# backup_scheduler.py
import schedule
import time
import threading
from datetime import datetime
from typing import Callable, Dict

class BackupScheduler:
    def __init__(self, orchestrator: AgentBackupOrchestrator, 
                 retention_policy: Dict = None):
        self.orchestrator = orchestrator
        self.retention_policy = retention_policy or {
            'hourly': 24,      # Keep 24 hourly backups
            'daily': 30,       # Keep 30 daily backups
            'weekly': 12,      # Keep 12 weekly backups
            'monthly': 12      # Keep 12 monthly backups
        }
        self.running = False
        self._scheduler_thread = None
    
    def setup_schedule(self, agent_id: str):
        """Configure backup frequency for an agent."""
        # Hourly backups during business hours
        schedule.every().hour.at(":00").do(
            self._scheduled_backup, agent_id=agent_id, frequency='hourly'
        )
        
        # Daily backup at midnight
        schedule.every().day.at("00:00").do(
            self._scheduled_backup, agent_id=agent_id

— Ad —

Google AdSense will appear here after approval

← Back to all articles