dApp Docs/AI Agent 数据持久化与状态管理指南
Development reference. Not independently verified for production.

MSG Chain AI Agent 数据持久化与状态管理指南

⚠️ No-Go Disclaimer: MSGChain 主网裁决为 No-Go。本文件所有内容反映的是开发阶段的技术设计,不代表主网未独立核验上线状态。生产部署状态请以白皮书为准:https://msgchain.org/whitepaper/

适用链: msg-chain-1 | 地址前缀: msg | 版本: 1.0.0


目录

  1. 概述
  2. 链上状态管理模式
  3. 离线数据存储
  4. 检查点与快照
  5. 会话与上下文管理
  6. 备份与恢复策略
  7. 数据清理与归档
  8. 完整 Agent 状态管理示例

1. 概述

1.1 为什么状态管理对 AI Agent 至关重要

AI Agent 在 MSG Chain 上运行时,其行为、记忆和资产都依赖于持久化的状态数据。与无状态的传统智能合约不同,AI Agent 需要跨越多个会话、多次执行、甚至多个生命周期维护一致的内部状态。状态管理的可靠性直接决定了 Agent 的行为一致性、用户体验和资产安全。

如果没有健壮的状态管理,Agent 会在重启后丢失对话记忆、忘记用户偏好、无法从中断的工作流中恢复,甚至可能导致资产错乱。在 MSG Chain 上使用 msg 前缀地址的 Agent 尤其需要精心设计状态持久化方案。

1.2 状态类型分类

状态类型 描述 存储位置 更新频率 数据量
Memory (记忆) 对话历史、用户交互记录 IPFS / 本地DB 高 大
Config (配置) Agent 参数、权限、偏好设置 链上 / 本地 低 小
Session (会话) 活跃会话上下文、临时数据 内存 / Redis 极高 中
Results (结果) 任务执行结果、推理输出 IPFS / 本地 中 大

1.3 存储金字塔

AI Agent 的数据存储在多层架构中,根据访问频率、数据量大小和持久性需求分层存放:

         ┌──────────────────────┐
         │     Redis / 内存      │  ← 热数据: 活跃会话、实时状态
         │    (μs 级访问)       │
         ├──────────────────────┤
         │   SQLite / LevelDB   │  ← 温数据: Agent 配置、本地缓存
         │    (ms 级访问)       │
         ├──────────────────────┤
         │   IPFS / Arweave     │  ← 冷数据: 对话归档、检查点
         │    (s 级访问)        │
         ├──────────────────────┤
         │   MSG Chain 链上      │  ← 核心数据: 所有权、信誉、锚点
         │    (tx 级延迟)       │
         └──────────────────────┘

设计原则:

1.4 状态管理的关键指标


2. 链上状态管理模式

2.1 MSG Chain 智能合约状态结构

在 MSG Chain 上,每个 AI Agent 的核心状态存储在 CosmWasm 智能合约中。以下是在 msg 前缀地址上运行的标准状态定义:

use cosmwasm_std::{
    Addr, DepsMut, Deps, StdResult, StdError, Storage,
    Uint128, Response, MessageInfo, Env,
};
use cw_storage_plus::{Item, Map, SnapshotMap};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Agent 核心链上状态
/// 链上只存储最小必要字段,大量数据通过 metadata_uri 引用
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AgentState {
    /// Agent 所有者地址 (msg 前缀)
    pub owner: Addr,
    /// Agent 宪法/行为准则哈希 (IPFS CID)
    pub constitution_hash: String,
    /// Agent 能力列表
    pub capabilities: Vec<String>,
    /// Agent 信誉分数
    pub reputation: u64,
    /// Agent 累计收入 (MSG 原生代币)
    pub total_earnings: Uint128,
    /// 最后心跳时间戳 (Unix 秒)
    pub last_heartbeat: u64,
    /// Agent 详细状态元数据 URI (IPFS 链接)
    pub metadata_uri: String,
    /// 当前状态 schema 版本号
    pub version: u8,
    /// Agent 是否激活
    pub active: bool,
    /// Agent 创建时间
    pub created_at: u64,
    /// 最后状态更新时间
    pub updated_at: u64,
}

/// Agent 配置参数
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AgentConfig {
    pub name: String,
    pub description: String,
    pub model_endpoint: String,
    pub model_name: String,
    pub temperature: f64,
    pub max_tokens: u32,
    pub session_timeout: u64,
    pub allowed_tools: Vec<String>,
}

/// Agent 权限控制
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AgentPermissions {
    pub admins: Vec<Addr>,
    pub operators: Vec<Addr>,
    pub public_allowed: bool,
    pub gas_limit: Uint128,
    pub max_fee_per_call: Uint128,
}

/// 链上存储键定义
pub const STATE_KEY: Item<AgentState> = Item::new("agent_state");
pub const CONFIG_KEY: Item<AgentConfig> = Item::new("agent_config");
pub const PERMISSIONS_KEY: Item<AgentPermissions> = Item::new("agent_permissions");
pub const SESSION_CHECKPOINTS: Map<&str, String> = Map::new("session_cp");
pub const BACKUP_RECORDS: Map<&str, String> = Map::new("backup_records");
pub const REPUTATION_HISTORY: Map<u64, u64> = Map::new("reputation_hist");

2.2 链上/链下状态拆分策略

决定哪些数据上链、哪些数据存储在链下,是状态管理架构设计的最关键决策。

必须上链的数据:

应留在链下的数据:

/// 链上/链下拆分决策辅助函数
pub fn should_store_on_chain(field_name: &str, data_size: usize) -> bool {
    match field_name {
        "owner" | "admins" | "operators" => true,
        "total_earnings" | "gas_limit" | "max_fee_per_call" => true,
        "reputation" | "last_heartbeat" => true,
        "name" | "description" if data_size < 256 => true,
        "metadata_uri" | "constitution_hash" => true,
        _ => data_size < 1024,
    }
}

2.3 状态 Schema 版本化

随着 Agent 功能的演进,状态结构需要升级。Schema 版本化机制确保新旧代码兼容:

pub const STATE_VERSION_V1: u8 = 1;
pub const STATE_VERSION_V2: u8 = 2;
pub const STATE_VERSION_V3: u8 = 3;
pub const CURRENT_STATE_VERSION: u8 = STATE_VERSION_V3;

/// 通用状态迁移入口
pub fn migrate_state(
    deps: DepsMut,
    from_version: u8,
    to_version: u8,
) -> StdResult<()> {
    if from_version >= to_version {
        return Err(StdError::generic_err(
            format!("invalid migration: {} -> {}", from_version, to_version)
        ));
    }
    match (from_version, to_version) {
        (1, 2) => migrate_v1_to_v2(deps),
        (2, 3) => migrate_v2_to_v3(deps),
        (1, 3) => {
            migrate_v1_to_v2(deps)?;
            migrate_v2_to_v3(deps)
        }
        _ => Err(StdError::generic_err(
            format!("unsupported migration path: {} -> {}", from_version, to_version)
        )),
    }
}

/// V1 -> V2 迁移: 添加 active 和 reputation 字段
fn migrate_v1_to_v2(deps: DepsMut) -> StdResult<()> {
    let mut state = STATE_KEY.load(deps.storage)?;
    state.active = true;
    state.reputation = 0;
    state.updated_at = deps.block.time.seconds();
    state.version = STATE_VERSION_V2;
    STATE_KEY.save(deps.storage, &state)?;
    Ok(())
}

/// V2 -> V3 迁移: 添加权限分离
fn migrate_v2_to_v3(deps: DepsMut) -> StdResult<()> {
    let state = STATE_KEY.load(deps.storage)?;
    let permissions = AgentPermissions {
        admins: vec![state.owner.clone()],
        operators: vec![],
        public_allowed: false,
        gas_limit: Uint128::from(100_000u128),
        max_fee_per_call: Uint128::from(1_000u128),
    };
    PERMISSIONS_KEY.save(deps.storage, &permissions)?;
    let mut new_state = state;
    new_state.version = STATE_VERSION_V3;
    new_state.updated_at = deps.block.time.seconds();
    STATE_KEY.save(deps.storage, &new_state)?;
    Ok(())
}

/// 检查是否需要迁移并执行
pub fn ensure_state_migrated(deps: DepsMut) -> StdResult<()> {
    let state = STATE_KEY.load(deps.storage)?;
    if state.version < CURRENT_STATE_VERSION {
        migrate_state(deps, state.version, CURRENT_STATE_VERSION)?;
    }
    Ok(())
}

2.4 链上状态读写操作

/// 初始化新 Agent 状态
pub fn instantiate_agent(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    config: AgentConfig,
    constitution_cid: String,
    capabilities: Vec<String>,
) -> StdResult<Response> {
    let state = AgentState {
        owner: info.sender.clone(),
        constitution_hash: constitution_cid,
        capabilities,
        reputation: 0,
        total_earnings: Uint128::zero(),
        last_heartbeat: env.block.time.seconds(),
        metadata_uri: String::new(),
        version: CURRENT_STATE_VERSION,
        active: true,
        created_at: env.block.time.seconds(),
        updated_at: env.block.time.seconds(),
    };
    STATE_KEY.save(deps.storage, &state)?;
    CONFIG_KEY.save(deps.storage, &config)?;
    let permissions = AgentPermissions {
        admins: vec![info.sender.clone()],
        operators: vec![],
        public_allowed: false,
        gas_limit: Uint128::from(100_000u128),
        max_fee_per_call: Uint128::from(1_000u128),
    };
    PERMISSIONS_KEY.save(deps.storage, &permissions)?;
    Ok(Response::new()
        .add_attribute("action", "instantiate")
        .add_attribute("agent", info.sender.to_string())
        .add_attribute("version", CURRENT_STATE_VERSION.to_string()))
}

/// 更新 Agent 心跳
pub fn update_heartbeat(deps: DepsMut, env: Env) -> StdResult<Response> {
    STATE_KEY.update(deps.storage, |mut state| -> StdResult<AgentState> {
        state.last_heartbeat = env.block.time.seconds();
        state.updated_at = env.block.time.seconds();
        Ok(state)
    })?;
    Ok(Response::new()
        .add_attribute("action", "heartbeat")
        .add_attribute("time", env.block.time.seconds().to_string()))
}

/// 更新 metadata_uri (IPFS CID)
pub fn update_metadata_uri(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    new_uri: String,
) -> StdResult<Response> {
    let permissions = PERMISSIONS_KEY.load(deps.storage)?;
    if !permissions.admins.contains(&info.sender) {
        return Err(StdError::generic_err("unauthorized: only admins can update metadata"));
    }
    STATE_KEY.update(deps.storage, |mut state| -> StdResult<AgentState> {
        state.metadata_uri = new_uri.clone();
        state.updated_at = env.block.time.seconds();
        Ok(state)
    })?;
    Ok(Response::new()
        .add_attribute("action", "update_metadata")
        .add_attribute("uri", new_uri))
}

/// 增加信誉分数
pub fn add_reputation(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    amount: u64,
) -> StdResult<Response> {
    let permissions = PERMISSIONS_KEY.load(deps.storage)?;
    if !permissions.admins.contains(&info.sender) {
        return Err(StdError::generic_err("unauthorized"));
    }
    STATE_KEY.update(deps.storage, |mut state| -> StdResult<AgentState> {
        state.reputation = state.reputation.saturating_add(amount);
        state.updated_at = env.block.time.seconds();
        Ok(state)
    })?;
    let current_rep = STATE_KEY.load(deps.storage)?.reputation;
    REPUTATION_HISTORY.save(deps.storage, env.block.time.seconds(), &current_rep)?;
    Ok(Response::new()
        .add_attribute("action", "add_reputation")
        .add_attribute("amount", amount.to_string())
        .add_attribute("total", current_rep.to_string()))
}

2.5 链上状态查询

pub fn query_agent_state(deps: Deps) -> StdResult<AgentState> {
    STATE_KEY.load(deps.storage)
}

pub fn query_agent_config(deps: Deps) -> StdResult<AgentConfig> {
    CONFIG_KEY.load(deps.storage)
}

pub fn query_agent_permissions(deps: Deps) -> StdResult<AgentPermissions> {
    PERMISSIONS_KEY.load(deps.storage)
}

pub fn query_reputation_history(
    deps: Deps,
    start_after: Option<u64>,
    limit: usize,
) -> StdResult<Vec<(u64, u64)>> {
    let limit = limit.min(30).max(1);
    REPUTATION_HISTORY
        .range(deps.storage, None, None, cosmwasm_std::Order::Descending)
        .take(limit)
        .collect::<StdResult<Vec<_>>>()
}

/// 检查 Agent 是否健康 (心跳超时检测)
pub fn is_agent_healthy(deps: Deps, env: Env, max_heartbeat_gap: u64) -> StdResult<bool> {
    let state = STATE_KEY.load(deps.storage)?;
    let current_time = env.block.time.seconds();
    let gap = current_time.saturating_sub(state.last_heartbeat);
    Ok(gap < max_heartbeat_gap && state.active)
}

2.6 Gas 优化方案

链上状态操作消耗 Gas,需要严格控制:

/// 批量更新状态以减少 Gas 消耗
pub fn batch_update(
    deps: DepsMut,
    env: Env,
    updates: Vec<StateUpdate>,
) -> StdResult<Response> {
    STATE_KEY.update(deps.storage, |mut state| -> StdResult<AgentState> {
        for update in updates {
            match update {
                StateUpdate::Heartbeat => {
                    state.last_heartbeat = env.block.time.seconds();
                }
                StateUpdate::Reputation(amount) => {
                    state.reputation = state.reputation.saturating_add(amount);
                }
                StateUpdate::Metadata(uri) => {
                    state.metadata_uri = uri;
                }
                StateUpdate::Capabilities(caps) => {
                    state.capabilities = caps;
                }
                StateUpdate::Deactivate => {
                    state.active = false;
                }
                StateUpdate::Earnings(amount) => {
                    state.total_earnings = state.total_earnings.checked_add(amount)
                        .map_err(|_| StdError::generic_err("earnings overflow"))?;
                }
            }
        }
        state.updated_at = env.block.time.seconds();
        Ok(state)
    })?;
    Ok(Response::new()
        .add_attribute("action", "batch_update"))
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum StateUpdate {
    Heartbeat,
    Reputation(u64),
    Metadata(String),
    Capabilities(Vec<String>),
    Deactivate,
    Earnings(Uint128),
}

/// 状态压缩: 将小整数打包存储以减少存储槽
pub fn pack_flags(active: bool, public_allowed: bool, frozen: bool) -> u8 {
    (active as u8) << 2 | (public_allowed as u8) << 1 | (frozen as u8)
}

pub fn unpack_flags(flags: u8) -> (bool, bool, bool) {
    let active = (flags >> 2) & 1 == 1;
    let public_allowed = (flags >> 1) & 1 == 1;
    let frozen = flags & 1 == 1;
    (active, public_allowed, frozen)
}

3. 离线数据存储

3.1 多层离线存储架构

离线存储层处理 Agent 的大规模数据,包括对话历史、知识库、日志和完整状态快照。采用多层架构以平衡访问速度和存储成本:

import json
import time
import hashlib
import asyncio
import sqlite3
import logging
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field, asdict
from abc import ABC, abstractmethod

logger = logging.getLogger("msg_agent.storage")

@dataclass
class Message:
    role: str
    content: str
    timestamp: float
    tool_calls: Optional[List[Dict]] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

@dataclass
class ConversationRecord:
    session_id: str
    agent_id: str
    user_id: str
    messages: List[Message]
    created_at: float
    updated_at: float
    message_count: int
    total_tokens: int

class StorageBackend(ABC):
    @abstractmethod
    async def store(self, key: str, value: bytes) -> bool: ...
    @abstractmethod
    async def load(self, key: str) -> Optional[bytes]: ...
    @abstractmethod
    async def delete(self, key: str) -> bool: ...
    @abstractmethod
    async def exists(self, key: str) -> bool: ...
    @abstractmethod
    async def list_keys(self, prefix: str) -> List[str]: ...

class SQLiteStorage(StorageBackend):
    def __init__(self, db_path: str = "/data/agent.db"):
        self.db_path = db_path
        self.conn: Optional[sqlite3.Connection] = None
        self._init_db()

    def _init_db(self):
        self.conn = sqlite3.connect(self.db_path)
        self.conn.execute("PRAGMA journal_mode=WAL")
        self.conn.execute("PRAGMA synchronous=NORMAL")
        self.conn.row_factory = sqlite3.Row
        self.conn.executescript("""
            CREATE TABLE IF NOT EXISTS kv_store (
                key TEXT PRIMARY KEY,
                value BLOB NOT NULL,
                created_at REAL NOT NULL,
                updated_at REAL NOT NULL
            );
            CREATE TABLE IF NOT EXISTS conversations (
                session_id TEXT PRIMARY KEY,
                agent_id TEXT NOT NULL,
                user_id TEXT NOT NULL,
                data TEXT NOT NULL,
                message_count INTEGER DEFAULT 0,
                created_at REAL NOT NULL,
                updated_at REAL NOT NULL
            );
            CREATE TABLE IF NOT EXISTS checkpoints (
                id TEXT PRIMARY KEY,
                agent_id TEXT NOT NULL,
                cid TEXT NOT NULL,
                timestamp REAL NOT NULL,
                checksum TEXT NOT NULL,
                state_version INTEGER NOT NULL
            );
            CREATE TABLE IF NOT EXISTS backup_records (
                id TEXT PRIMARY KEY,
                agent_id TEXT NOT NULL,
                cid TEXT NOT NULL,
                backup_type TEXT NOT NULL,
                timestamp REAL NOT NULL,
                size_bytes INTEGER NOT NULL
            );
            CREATE INDEX IF NOT EXISTS idx_conversations_agent
                ON conversations(agent_id);
            CREATE INDEX IF NOT EXISTS idx_checkpoints_agent
                ON checkpoints(agent_id, timestamp);
            CREATE INDEX IF NOT EXISTS idx_backup_agent
                ON backup_records(agent_id, timestamp);
        """)
        self.conn.commit()

    async def store(self, key: str, value: bytes) -> bool:
        now = time.time()
        loop = asyncio.get_event_loop()
        def _store():
            self.conn.execute(
                "INSERT OR REPLACE INTO kv_store (key, value, created_at, updated_at) "
                "VALUES (?, ?, COALESCE((SELECT created_at FROM kv_store WHERE key=?), ?), ?)",
                (key, value, key, now, now)
            )
            self.conn.commit()
            return True
        return await loop.run_in_executor(None, _store)

    async def load(self, key: str) -> Optional[bytes]:
        loop = asyncio.get_event_loop()
        def _load():
            cursor = self.conn.execute("SELECT value FROM kv_store WHERE key = ?", (key,))
            row = cursor.fetchone()
            return bytes(row[0]) if row else None
        return await loop.run_in_executor(None, _load)

    async def delete(self, key: str) -> bool:
        loop = asyncio.get_event_loop()
        def _delete():
            self.conn.execute("DELETE FROM kv_store WHERE key = ?", (key,))
            self.conn.commit()
            return True
        return await loop.run_in_executor(None, _delete)

    async def exists(self, key: str) -> bool:
        loop = asyncio.get_event_loop()
        def _exists():
            cursor = self.conn.execute("SELECT 1 FROM kv_store WHERE key = ?", (key,))
            return cursor.fetchone() is not None
        return await loop.run_in_executor(None, _exists)

    async def list_keys(self, prefix: str) -> List[str]:
        loop = asyncio.get_event_loop()
        def _list():
            cursor = self.conn.execute(
                "SELECT key FROM kv_store WHERE key LIKE ?", (f"{prefix}%",)
            )
            return [row[0] for row in cursor.fetchall()]
        return await loop.run_in_executor(None, _list)

    async def save_conversation(self, record: ConversationRecord):
        loop = asyncio.get_event_loop()
        data_json = json.dumps(asdict(record))
        def _save():
            self.conn.execute(
                "INSERT OR REPLACE INTO conversations "
                "(session_id, agent_id, user_id, data, message_count, created_at, updated_at) "
                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                (record.session_id, record.agent_id, record.user_id,
                 data_json, record.message_count, record.created_at, record.updated_at)
            )
            self.conn.commit()
        return await loop.run_in_executor(None, _save)

    async def load_conversation(self, session_id: str) -> Optional[ConversationRecord]:
        loop = asyncio.get_event_loop()
        def _load():
            cursor = self.conn.execute(
                "SELECT data FROM conversations WHERE session_id = ?", (session_id,)
            )
            row = cursor.fetchone()
            if row:
                return ConversationRecord(**json.loads(row[0]))
            return None
        return await loop.run_in_executor(None, _load)

    async def list_agent_conversations(
        self, agent_id: str, limit: int = 100, offset: int = 0
    ) -> List[ConversationRecord]:
        loop = asyncio.get_event_loop()
        def _list():
            cursor = self.conn.execute(
                "SELECT data FROM conversations "
                "WHERE agent_id = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?",
                (agent_id, limit, offset)
            )
            return [ConversationRecord(**json.loads(row[0])) for row in cursor.fetchall()]
        return await loop.run_in_executor(None, _list)

    def close(self):
        if self.conn:
            self.conn.close()

3.2 IPFS 分布式存储适配器

class IPFSClient:
    def __init__(self, api_url: str = "http://localhost:5001", timeout: int = 60):
        self.api_url = api_url.rstrip("/")
        self.timeout = timeout

    async def add(self, data: bytes, pin: bool = True) -> str:
        import aiohttp
        form = aiohttp.FormData()
        form.add_field("file", data, filename="data.bin")
        if pin:
            form.add_field("pin", "true")
        async with aiohttp.ClientSession() as session:
            async with session.post(
                f"{self.api_url}/api/v0/add", data=form, timeout=self.timeout
            ) as resp:
                if resp.status != 200:
                    raise IOError(f"IPFS add failed: {await resp.text()}")
                result = await resp.json()
                return result["Hash"]

    async def get(self, cid: str) -> bytes:
        import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.get(
                f"{self.api_url}/api/v0/cat?arg={cid}", timeout=self.timeout
            ) as resp:
                if resp.status != 200:
                    raise IOError(f"IPFS cat failed: {await resp.text()}")
                return await resp.read()

    async def pin(self, cid: str) -> bool:
        import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.post(f"{self.api_url}/api/v0/pin/add?arg={cid}") as resp:
                return resp.status == 200

    async def unpin(self, cid: str) -> bool:
        import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.post(f"{self.api_url}/api/v0/pin/rm?arg={cid}") as resp:
                return resp.status == 200

    async def is_pinned(self, cid: str) -> bool:
        import aiohttp
        async with aiohttp.ClientSession() as session:
            async with session.post(f"{self.api_url}/api/v0/pin/ls?arg={cid}") as resp:
                if resp.status != 200:
                    return False
                result = await resp.json()
                return cid in result.get("Keys", {})

class RedisCache(StorageBackend):
    def __init__(self, host: str = "localhost", port: int = 6379, db: int = 0):
        self.host = host
        self.port = port
        self.db = db
        self._client: Optional[Any] = None

    async def _get_client(self):
        if self._client is None:
            import redis.asyncio as redis
            self._client = redis.Redis(
                host=self.host, port=self.port, db=self.db, decode_responses=False
            )
        return self._client

    async def store(self, key: str, value: bytes, ttl: Optional[int] = None) -> bool:
        client = await self._get_client()
        if ttl:
            return await client.setex(key, ttl, value)
        return await client.set(key, value)

    async def load(self, key: str) -> Optional[bytes]:
        client = await self._get_client()
        return await client.get(key)

    async def delete(self, key: str) -> bool:
        client = await self._get_client()
        return bool(await client.delete(key))

    async def exists(self, key: str) -> bool:
        client = await self._get_client()
        return bool(await client.exists(key))

    async def list_keys(self, prefix: str) -> List[str]:
        client = await self._get_client()
        cursor = 0
        keys = []
        while True:
            cursor, batch = await client.scan(cursor=cursor, match=f"{prefix}*", count=100)
            keys.extend([k.decode() if isinstance(k, bytes) else k for k in batch])
            if cursor == 0:
                break
        return keys

    async def expire(self, key: str, ttl: int) -> bool:
        client = await self._get_client()
        return bool(await client.expire(key, ttl))

    async def flush_prefix(self, prefix: str) -> int:
        keys = await self.list_keys(prefix)
        if not keys:
            return 0
        client = await self._get_client()
        return await client.delete(*keys)

3.3 完整离线存储管理器

class OffChainStorage:
    def __init__(
        self,
        db_path: str = "/data/agent.db",
        ipfs_url: str = "http://localhost:5001",
        redis_host: str = "localhost",
        redis_port: int = 6379,
    ):
        self.local = SQLiteStorage(db_path)
        self.ipfs = IPFSClient(ipfs_url)
        self.cache = RedisCache(redis_host, redis_port)
        self._session_cache_ttl = 3600

    async def store_conversation(
        self, session_id: str, messages: List[Message],
        agent_id: str, user_id: str,
    ) -> str:
        recent = messages[-100:] if len(messages) > 100 else messages
        cache_data = json.dumps([asdict(m) for m in recent]).encode()
        await self.cache.store(
            f"msg:session:{session_id}", cache_data, ttl=self._session_cache_ttl
        )
        full_data = json.dumps({
            "session_id": session_id,
            "agent_id": agent_id,
            "user_id": user_id,
            "messages": [asdict(m) for m in messages],
            "message_count": len(messages),
            "archived_at": time.time(),
            "format_version": 1,
        }).encode()
        cid = await self.ipfs.add(full_data)
        await self.ipfs.pin(cid)
        record = ConversationRecord(
            session_id=session_id, agent_id=agent_id, user_id=user_id,
            messages=messages,
            created_at=messages[0].timestamp if messages else time.time(),
            updated_at=time.time(),
            message_count=len(messages), total_tokens=0,
        )
        await self.local.save_conversation(record)
        logger.info(f"Stored conversation {session_id} -> IPFS {cid}")
        return cid

    async def load_conversation(
        self, session_id: str, prefer_cache: bool = True,
    ) -> Optional[List[Message]]:
        if prefer_cache:
            cached = await self.cache.load(f"msg:session:{session_id}")
            if cached:
                try:
                    data = json.loads(cached)
                    return [Message(**m) for m in data]
                except (json.JSONDecodeError, TypeError):
                    pass
        record = await self.local.load_conversation(session_id)
        if record:
            return record.messages
        return None

    async def archive_and_purge(self, session_id: str, older_than_days: int = 30) -> Optional[str]:
        record = await self.local.load_conversation(session_id)
        if not record:
            return None
        age_days = (time.time() - record.updated_at) / 86400
        if age_days < older_than_days:
            return None
        cid = await self.store_conversation(
            session_id, record.messages, record.agent_id, record.user_id
        )
        await self.local.delete(f"conv:{session_id}")
        await self.cache.delete(f"msg:session:{session_id}")
        logger.info(f"Archived old session {session_id} -> {cid}")
        return cid

    async def health_check(self) -> Dict[str, bool]:
        results = {}
        try:
            await self.local.exists("health_check_key")
            results["sqlite"] = True
        except Exception:
            results["sqlite"] = False
        try:
            import aiohttp
            async with aiohttp.ClientSession() as session:
                async with session.get(f"{self.ipfs.api_url}/api/v0/id", timeout=5) as resp:
                    results["ipfs"] = resp.status == 200
        except Exception:
            results["ipfs"] = False
        try:
            client = await self.cache._get_client()
            await client.ping()
            results["redis"] = True
        except Exception:
            results["redis"] = False
        return results

    async def close(self):
        self.local.close()

3.4 LevelDB 备选存储

class LevelDBStorage(StorageBackend):
    def __init__(self, db_path: str = "/data/agent.ldb"):
        self.db_path = db_path
        self._db = None

    async def _get_db(self):
        if self._db is None:
            import plyvel
            self._db = plyvel.DB(self.db_path, create_if_missing=True)
        return self._db

    async def store(self, key: str, value: bytes) -> bool:
        db = await self._get_db()
        db.put(key.encode(), value)
        return True

    async def load(self, key: str) -> Optional[bytes]:
        db = await self._get_db()
        return db.get(key.encode())

    async def delete(self, key: str) -> bool:
        db = await self._get_db()
        db.delete(key.encode())
        return True

    async def exists(self, key: str) -> bool:
        db = await self._get_db()
        return db.get(key.encode()) is not None

    async def list_keys(self, prefix: str) -> List[str]:
        db = await self._get_db()
        keys = []
        prefix_bytes = prefix.encode()
        for key_bytes, _ in db:
            if key_bytes.startswith(prefix_bytes):
                keys.append(key_bytes.decode())
        return keys

    def close(self):
        if self._db:
            self._db.close()

3.5 数据序列化与压缩

import zlib
import pickle

class DataSerializer:
    COMPRESSION_LEVEL = 6

    @classmethod
    def serialize(cls, data: Any, compress: bool = True) -> bytes:
        pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
        if compress and len(pickled) > 1024:
            return zlib.compress(pickled, cls.COMPRESSION_LEVEL)
        return pickled

    @classmethod
    def deserialize(cls, data: bytes) -> Any:
        try:
            try:
                decompressed = zlib.decompress(data)
                return pickle.loads(decompressed)
            except zlib.error:
                return pickle.loads(data)
        except Exception as e:
            raise ValueError(f"Deserialization failed: {e}") from e

    @classmethod
    def json_serialize(cls, data: Any, compress: bool = True) -> bytes:
        text = json.dumps(data, ensure_ascii=False, default=str)
        encoded = text.encode('utf-8')
        if compress and len(encoded) > 1024:
            return zlib.compress(encoded, cls.COMPRESSION_LEVEL)
        return encoded

    @classmethod
    def json_deserialize(cls, data: bytes) -> Any:
        try:
            try:
                decompressed = zlib.decompress(data)
                return json.loads(decompressed.decode('utf-8'))
            except zlib.error:
                return json.loads(data.decode('utf-8'))
        except Exception as e:
            raise ValueError(f"JSON deserialization failed: {e}") from e

3.6 存储策略配置

from enum import Enum

class StorageTier(Enum):
    HOT = "hot"
    WARM = "warm"
    COLD = "cold"
    CHAIN = "chain"

@dataclass
class StoragePolicy:
    conversation_cache_size: int = 100
    conversation_cache_ttl: int = 3600
    conversation_archive_days: int = 30
    checkpoint_interval: int = 600
    checkpoint_keep_count: int = 10
    checkpoint_cache_ttl: int = 7200
    full_backup_interval: int = 86400
    incremental_backup_interval: int = 3600
    backup_retention_days: int = 90
    log_retention_days: int = 7
    max_local_db_size_mb: int = 1024
    max_cache_memory_mb: int = 512
    ipfs_pin_by_default: bool = True
    ipfs_timeout: int = 60

class TierRouter:
    def __init__(self, policy: StoragePolicy):
        self.policy = policy

    def determine_tier(
        self, data_type: str, data_size: int,
        access_frequency: str, persistence: str,
    ) -> StorageTier:
        if access_frequency == "high" and data_type in ("session", "cache"):
            return StorageTier.HOT
        if data_type in ("config", "metadata", "recent_conversations"):
            return StorageTier.WARM
        if data_size > 1024 * 1024:
            return StorageTier.COLD
        if persistence == "permanent":
            return StorageTier.COLD
        if data_type in ("ownership", "permissions", "reputation") and data_size < 256:
            return StorageTier.CHAIN
        return StorageTier.WARM

4. 检查点与快照

4.1 检查点管理系统

import hashlib
from dataclasses import dataclass, asdict

@dataclass
class Checkpoint:
    id: str
    agent_id: str
    timestamp: float
    version: int
    state: Dict[str, Any]
    checksum: str
    parent_id: Optional[str] = None
    is_full: bool = True
    metadata: Dict[str, Any] = None

    def __post_init__(self):
        if self.metadata is None:
            self.metadata = {}

class CheckpointManager:
    def __init__(
        self, agent_id: str,
        offchain_storage: OffChainStorage,
        ipfs_client: IPFSClient,
        local_storage: SQLiteStorage,
        policy: StoragePolicy = None,
    ):
        self.agent_id = agent_id
        self.offchain = offchain_storage
        self.ipfs = ipfs_client
        self.local = local_storage
        self.policy = policy or StoragePolicy()
        self._checkpoint_task: Optional[asyncio.Task] = None
        self._running = False

    async def create_checkpoint(self, is_full: bool = True) -> str:
        logger.info(f"Creating {'full' if is_full else 'incremental'} checkpoint for {self.agent_id}")
        state = await self.collect_full_state()
        parent_id = None
        if not is_full:
            parent_id = await self.get_latest_checkpoint_id()
        checkpoint_state = state
        if not is_full and parent_id:
            parent_cp = await self.load_checkpoint_by_id(parent_id)
            if parent_cp:
                checkpoint_state = self.compute_diff(parent_cp.state, state)
        state_json = json.dumps(checkpoint_state, sort_keys=True, default=str)
        checksum = hashlib.sha256(state_json.encode()).hexdigest()
        checkpoint_id = f"cp_{self.agent_id}_{int(time.time())}"
        checkpoint = Checkpoint(
            id=checkpoint_id, agent_id=self.agent_id,
            timestamp=time.time(), version=1, state=checkpoint_state,
            checksum=checksum, parent_id=parent_id, is_full=is_full,
            metadata={"node_id": self.agent_id, "created_by": "checkpoint_manager"},
        )
        cp_data = json.dumps(asdict(checkpoint), default=str).encode()
        cid = await self.ipfs.add(cp_data)
        await self.ipfs.pin(cid)
        await self.local.store(
            f"checkpoint:{checkpoint_id}",
            json.dumps({"id": checkpoint_id, "cid": cid,
                        "timestamp": checkpoint.timestamp,
                        "is_full": is_full, "checksum": checksum}).encode()
        )
        await self.local.store(
            f"agent:{self.agent_id}:latest_checkpoint",
            json.dumps({"id": checkpoint_id, "cid": cid}).encode()
        )
        logger.info(f"Checkpoint {checkpoint_id} created -> IPFS {cid}")
        await self.cleanup_old_checkpoints()
        return cid

    async def collect_full_state(self) -> Dict[str, Any]:
        state = {}
        conversations = await self.local.list_agent_conversations(self.agent_id, limit=1000)
        state["conversations"] = [asdict(c) for c in conversations]
        kb_data = await self.local.load(f"agent:{self.agent_id}:knowledge_base")
        state["knowledge_base"] = json.loads(kb_data) if kb_data else {}
        prefs_data = await self.local.load(f"agent:{self.agent_id}:user_preferences")
        state["user_preferences"] = json.loads(prefs_data) if prefs_data else {}
        tasks_data = await self.local.load(f"agent:{self.agent_id}:task_history")
        state["task_history"] = json.loads(tasks_data) if tasks_data else []
        metrics_data = await self.local.load(f"agent:{self.agent_id}:metrics")
        state["performance_metrics"] = json.loads(metrics_data) if metrics_data else {}
        custom_data = await self.local.load(f"agent:{self.agent_id}:custom")
        state["custom_data"] = json.loads(custom_data) if custom_data else {}
        return state

    async def restore_from_checkpoint(self, cid: str) -> Dict[str, Any]:
        logger.info(f"Restoring from checkpoint {cid}")
        cp_data = await self.ipfs.get(cid)
        checkpoint_dict = json.loads(cp_data)
        checkpoint = Checkpoint(**checkpoint_dict)
        state_json = json.dumps(checkpoint.state, sort_keys=True, default=str)
        calculated = hashlib.sha256(state_json.encode()).hexdigest()
        if calculated != checkpoint.checksum:
            raise ValueError(f"Checksum mismatch: {calculated[:16]} != {checkpoint.checksum[:16]}")
        state = checkpoint.state
        if not checkpoint.is_full and checkpoint.parent_id:
            parent_cp_data = await self.local.load(f"checkpoint:{checkpoint.parent_id}")
            if parent_cp_data:
                parent_info = json.loads(parent_cp_data)
                parent_state = await self.restore_from_checkpoint(parent_info["cid"])
                state = self.merge_diffs(parent_state, state)
        await self.restore_state(state)
        logger.info(f"Restored from checkpoint {cid}")
        return state

    async def restore_state(self, state: Dict[str, Any]):
        if "conversations" in state:
            for conv_data in state["conversations"]:
                conv = ConversationRecord(**conv_data)
                await self.local.save_conversation(conv)
        if "knowledge_base" in state:
            await self.local.store(
                f"agent:{self.agent_id}:knowledge_base",
                json.dumps(state["knowledge_base"]).encode()
            )
        if "user_preferences" in state:
            await self.local.store(
                f"agent:{self.agent_id}:user_preferences",
                json.dumps(state["user_preferences"]).encode()
            )
        if "task_history" in state:
            await self.local.store(
                f"agent:{self.agent_id}:task_history",
                json.dumps(state["task_history"]).encode()
            )
        if "performance_metrics" in state:
            await self.local.store(
                f"agent:{self.agent_id}:metrics",
                json.dumps(state["performance_metrics"]).encode()
            )
        if "custom_data" in state:
            await self.local.store(
                f"agent:{self.agent_id}:custom",
                json.dumps(state["custom_data"]).encode()
            )

    def compute_diff(self, old_state: Dict, new_state: Dict) -> Dict:
        diff = {}
        for key in new_state:
            if key not in old_state:
                diff[key] = new_state[key]
            elif old_state[key] != new_state[key]:
                diff[key] = new_state[key]
        deleted = [k for k in old_state if k not in new_state]
        if deleted:
            diff["__deleted__"] = deleted
        return diff

    def merge_diffs(self, base_state: Dict, diffs: Dict) -> Dict:
        merged = dict(base_state)
        for key, value in diffs.items():
            if key == "__deleted__":
                for del_key in value:
                    merged.pop(del_key, None)
            else:
                merged[key] = value
        return merged

    async def get_latest_checkpoint_id(self) -> Optional[str]:
        data = await self.local.load(f"agent:{self.agent_id}:latest_checkpoint")
        if data:
            return json.loads(data)["id"]
        return None

    async def load_checkpoint_by_id(self, cp_id: str) -> Optional[Checkpoint]:
        data = await self.local.load(f"checkpoint:{cp_id}")
        if not data:
            return None
        info = json.loads(data)
        cp_data = await self.ipfs.get(info["cid"])
        return Checkpoint(**json.loads(cp_data))

    async def list_checkpoints(self, limit: int = 20, offset: int = 0) -> List[Dict]:
        keys = await self.local.list_keys(f"checkpoint:cp_{self.agent_id}")
        checkpoints = []
        for key in keys:
            data = await self.local.load(key)
            if data:
                checkpoints.append(json.loads(data))
        checkpoints.sort(key=lambda x: x["timestamp"], reverse=True)
        return checkpoints[offset:offset + limit]

    async def verify_checkpoint(self, cid: str) -> bool:
        try:
            cp_data = await self.ipfs.get(cid)
            cp = Checkpoint(**json.loads(cp_data))
            state_json = json.dumps(cp.state, sort_keys=True, default=str)
            expected = hashlib.sha256(state_json.encode()).hexdigest()
            return expected == cp.checksum
        except Exception as e:
            logger.error(f"Verification failed: {e}")
            return False

    async def cleanup_old_checkpoints(self, keep_count: int = None):
        if keep_count is None:
            keep_count = self.policy.checkpoint_keep_count
        checkpoints = await self.list_checkpoints(limit=1000)
        incremental = [cp for cp in checkpoints if not cp.get("is_full")]
        to_delete = incremental[keep_count:]
        for cp in to_delete:
            try:
                await self.ipfs.unpin(cp["cid"])
                await self.local.delete(f"checkpoint:{cp['id']}")
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")

    async def start_periodic_checkpointing(self):
        if self._running:
            return
        self._running = True
        full_interval = self.policy.checkpoint_interval
        inc_interval = full_interval // 2
        last_full = 0
        while self._running:
            await asyncio.sleep(inc_interval)
            now = time.time()
            is_full = (now - last_full) >= full_interval
            try:
                cid = await self.create_checkpoint(is_full=is_full)
                if is_full:
                    last_full = now
            except Exception as e:
                logger.error(f"Periodic checkpoint failed: {e}")

    async def stop_periodic_checkpointing(self):
        self._running = False
        if self._checkpoint_task:
            self._checkpoint_task.cancel()
            self._checkpoint_task = None

    async def restore_to_point_in_time(self, target_timestamp: float) -> bool:
        checkpoints = await self.list_checkpoints(limit=1000)
        valid = [cp for cp in checkpoints if cp["timestamp"] <= target_timestamp]
        if not valid:
            return False
        nearest = max(valid, key=lambda x: x["timestamp"])
        await self.restore_from_checkpoint(nearest["cid"])
        return True

4.2 快照验证工具

class SnapshotValidator:
    @staticmethod
    def validate_checkpoint_integrity(checkpoint: Checkpoint) -> List[str]:
        issues = []
        state_json = json.dumps(checkpoint.state, sort_keys=True, default=str)
        expected = hashlib.sha256(state_json.encode()).hexdigest()
        if expected != checkpoint.checksum:
            issues.append(f"CHECKSUM_MISMATCH")
        now = time.time()
        if checkpoint.timestamp > now + 300:
            issues.append(f"FUTURE_TIMESTAMP")
        if checkpoint.timestamp < 1_500_000_000:
            issues.append(f"UNREALISTIC_TIMESTAMP")
        required = ["agent_id", "version", "state"]
        for field in required:
            if field not in asdict(checkpoint):
                issues.append(f"MISSING_FIELD: {field}")
        return issues

    @staticmethod
    def validate_chain_consistency(onchain_state: dict, offchain_checkpoint: Checkpoint) -> bool:
        onchain_hash = onchain_state.get("state_hash", "")
        if onchain_hash:
            return onchain_hash == offchain_checkpoint.checksum
        return False

4.3 检查点恢复流程

┌─────────────────────────────────────────────────────────┐
│                    故障检测                              │
│  heartbeat 超时 / 状态不一致 / 用户触发回滚              │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              1. 停止 Agent 当前任务                      │
│  保存当前内存中的会话状态                                │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              2. 选择恢复点                              │
│  - 自动选择最新有效检查点                               │
│  - 或用户指定的时间点                                   │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              3. 从 IPFS 加载检查点                      │
│  get(cid) -> 反序列化 -> 校验校验和                     │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              4. 恢复状态到各层                          │
│  - 写入本地 SQLite                                     │
│  - 填充 Redis 缓存                                     │
│  - 如为增量检查点,合并父检查点                         │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              5. 验证恢复结果                            │
│  - 检查关键状态字段                                    │
│  - 执行健康检查                                        │
│  - 记录恢复日志                                        │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│              6. 恢复 Agent 运行                         │
│  载入会话上下文,继续处理                               │
└─────────────────────────────────────────────────────────┘

5. 会话与上下文管理

5.1 会话生命周期管理

from uuid import uuid4
from enum import Enum

class SessionState(Enum):
    ACTIVE = "active"
    IDLE = "idle"
    SUSPENDED = "suspended"
    EXPIRED = "expired"
    CLOSED = "closed"

@dataclass
class SessionContext:
    user_id: str
    conversation_history: List[Message] = field(default_factory=list)
    user_preferences: Dict[str, Any] = field(default_factory=dict)
    active_tools: List[str] = field(default_factory=list)
    current_task: Optional[str] = None
    variables: Dict[str, Any] = field(default_factory=dict)
    token_count: int = 0
    last_activity: Optional[str] = None

@dataclass
class Session:
    id: str
    agent_id: str
    user: str
    state: SessionState = SessionState.ACTIVE
    context: SessionContext = None
    created_at: float = 0.0
    updated_at: float = 0.0
    last_active_at: float = 0.0
    expires_at: float = 0.0
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if self.context is None:
            self.context = SessionContext(user_id=self.user)
        if not self.created_at:
            self.created_at = time.time()
        if not self.updated_at:
            self.updated_at = time.time()
        if not self.last_active_at:
            self.last_active_at = time.time()

    @property
    def is_expired(self) -> bool:
        return time.time() > self.expires_at or self.state == SessionState.EXPIRED

    def touch(self):
        self.last_active_at = time.time()
        self.updated_at = time.time()
        if self.state == SessionState.IDLE:
            self.state = SessionState.ACTIVE

    def close(self):
        self.state = SessionState.CLOSED
        self.updated_at = time.time()

class SessionStore:
    def __init__(self, local_storage: SQLiteStorage, cache: RedisCache):
        self.local = local_storage
        self.cache = cache
        self._cache_ttl = 3600

    async def save(self, session: Session):
        data = json.dumps(asdict(session), default=str).encode()
        await self.cache.store(f"session:{session.id}", data, ttl=self._cache_ttl)
        await self.local.store(f"session:{session.id}", data)

    async def load(self, session_id: str) -> Optional[Session]:
        data = await self.cache.load(f"session:{session_id}")
        if not data:
            data = await self.local.load(f"session:{session_id}")
        if data:
            sd = json.loads(data)
            sd["state"] = SessionState(sd["state"])
            if sd.get("context"):
                sd["context"] = SessionContext(**sd["context"])
            return Session(**sd)
        return None

    async def delete(self, session_id: str):
        await self.cache.delete(f"session:{session_id}")
        await self.local.delete(f"session:{session_id}")

class SessionManager:
    def __init__(
        self, agent_id: str, store: SessionStore,
        session_timeout: int = 1800, max_concurrent_sessions: int = 100,
    ):
        self.agent_id = agent_id
        self.store = store
        self.session_timeout = session_timeout
        self.max_concurrent = max_concurrent_sessions
        self.active_sessions: Dict[str, Session] = {}
        self._cleanup_task: Optional[asyncio.Task] = None

    async def create_session(
        self, user: str, preferences: Dict[str, Any] = None,
        initial_context: Dict[str, Any] = None,
    ) -> Session:
        if len(self.active_sessions) >= self.max_concurrent:
            await self._evict_oldest_session()
        context_data = await self._load_user_context(user)
        if initial_context:
            context_data.update(initial_context)
        context = SessionContext(
            user_id=user, user_preferences=preferences or {},
            variables=context_data,
        )
        now = time.time()
        session = Session(
            id=uuid4().hex, agent_id=self.agent_id, user=user,
            context=context, created_at=now, updated_at=now,
            last_active_at=now, expires_at=now + self.session_timeout,
        )
        self.active_sessions[session.id] = session
        await self.store.save(session)
        logger.info(f"Session created: {session.id[:12]} for user {user}")
        return session

    async def get_session(self, session_id: str) -> Optional[Session]:
        session = self.active_sessions.get(session_id)
        if session:
            if session.is_expired:
                await self.expire_session(session_id)
                return None
            session.touch()
            return session
        session = await self.store.load(session_id)
        if session:
            if session.is_expired:
                await self.expire_session(session_id)
                return None
            self.active_sessions[session_id] = session
            session.touch()
            asyncio.create_task(self._preload_context(session))
            return session
        return None

    async def update_session_context(
        self, session_id: str,
        new_messages: List[Message] = None, variables: Dict[str, Any] = None,
    ) -> Optional[Session]:
        session = await self.get_session(session_id)
        if not session:
            return None
        if new_messages:
            session.context.conversation_history.extend(new_messages)
            session.context.token_count += sum(len(m.content) for m in new_messages)
        if variables:
            session.context.variables.update(variables)
        session.touch()
        await self.store.save(session)
        return session

    async def close_session(self, session_id: str):
        session = self.active_sessions.pop(session_id, None)
        if session:
            session.close()
            await self.store.save(session)
            await self._save_user_context(session)

    async def expire_session(self, session_id: str):
        session = self.active_sessions.pop(session_id, None)
        if not session:
            session = await self.store.load(session_id)
        if session:
            session.state = SessionState.EXPIRED
            await self.store.save(session)

    async def _load_user_context(self, user: str) -> Dict[str, Any]:
        data = await self.store.local.load(f"user_context:{self.agent_id}:{user}")
        if data:
            return json.loads(data)
        return {}

    async def _save_user_context(self, session: Session):
        persistent = {
            k: v for k, v in session.context.variables.items()
            if not k.startswith("_temp_")
        }
        await self.store.local.store(
            f"user_context:{self.agent_id}:{session.user}",
            json.dumps(persistent).encode()
        )

    async def _preload_context(self, session: Session):
        context_data = await self._load_user_context(session.user)
        if context_data:
            session.context.variables.update(context_data)

    async def _evict_oldest_session(self):
        if not self.active_sessions:
            return
        oldest = min(self.active_sessions.values(), key=lambda s: s.last_active_at)
        await self.suspend_session(oldest.id)

    async def suspend_session(self, session_id: str):
        session = self.active_sessions.pop(session_id, None)
        if session:
            session.state = SessionState.SUSPENDED
            await self.store.save(session)
            await self._save_user_context(session)

    async def suspend_all_idle(self, max_idle: int = 300):
        now = time.time()
        to_suspend = [
            sid for sid, s in self.active_sessions.items()
            if now - s.last_active_at > max_idle and s.state == SessionState.ACTIVE
        ]
        for sid in to_suspend:
            await self.suspend_session(sid)

    async def start_cleanup_loop(self, interval: int = 60):
        while True:
            try:
                await asyncio.sleep(interval)
                now = time.time()
                expired = [sid for sid, s in self.active_sessions.items() if s.is_expired]
                for sid in expired:
                    self.active_sessions.pop(sid, None)
                await self.suspend_all_idle(max_idle=self.session_timeout)
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Cleanup error: {e}")

    async def shutdown(self):
        for session in self.active_sessions.values():
            await self.store.save(session)
            await self._save_user_context(session)
        self.active_sessions.clear()

5.2 上下文窗口管理

class ContextWindowManager:
    def __init__(self, max_tokens: int = 4096, max_messages: int = 50, compression_threshold: float = 0.8):
        self.max_tokens = max_tokens
        self.max_messages = max_messages
        self.compression_threshold = compression_threshold

    def should_compress(self, messages: List[Message]) -> bool:
        total = sum(len(m.content) for m in messages)
        return total > self.max_tokens * self.compression_threshold

    def compress(self, messages: List[Message]) -> List[Message]:
        if not self.should_compress(messages):
            return messages
        system_msgs = [m for m in messages if m.role == "system"]
        other = [m for m in messages if m.role != "system"]
        keep = max(int(len(other) * 0.6), self.max_messages // 2)
        recent = other[-keep:]
        old = other[:-keep]
        if old:
            summary = self._summarize(old)
            summary_msg = Message(role="system", content=f"[摘要]: {summary}", timestamp=time.time())
            return system_msgs + [summary_msg] + recent
        return system_msgs + recent

    def _summarize(self, messages: List[Message]) -> str:
        points = []
        for msg in messages[-10:]:
            c = msg.content[:100]
            if msg.role == "user":
                points.append(f"用户: {c}")
            elif msg.role == "assistant":
                points.append(f"助手: {c}")
        return f"共 {len(messages)} 条消息。{';'.join(points[-5:])}"

5.3 上下文继承

class ContextInheritor:
    def __init__(self, store: SessionStore):
        self.store = store

    async def inherit_context(
        self, from_session: Session, to_session: Session, inherit_keys: List[str] = None,
    ) -> Session:
        if inherit_keys:
            for key in inherit_keys:
                if key in from_session.context.variables:
                    to_session.context.variables[key] = from_session.context.variables[key]
        else:
            for k, v in from_session.context.variables.items():
                if not k.startswith("_temp_"):
                    to_session.context.variables[k] = v
        to_session.context.user_preferences.update(from_session.context.user_preferences)
        recent = from_session.context.conversation_history[-3:]
        to_session.context.conversation_history = recent
        await self.store.save(to_session)
        return to_session

6. 备份与恢复策略

6.1 自动化备份系统

class BackupType(Enum):
    FULL = "full"
    INCREMENTAL = "incremental"
    DIFFERENTIAL = "differential"

class BackupStatus(Enum):
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    VERIFIED = "verified"

@dataclass
class BackupRecord:
    id: str
    agent_id: str
    cid: str
    backup_type: BackupType
    status: BackupStatus
    timestamp: float
    size_bytes: int
    checksum: str
    parent_id: Optional[str] = None
    metadata: Dict[str, Any] = None

    def __post_init__(self):
        if self.metadata is None:
            self.metadata = {}

class BackupManager:
    def __init__(
        self, agent_id: str, offchain: OffChainStorage,
        ipfs: IPFSClient, local: SQLiteStorage,
        encryption_key: Optional[bytes] = None, policy: StoragePolicy = None,
    ):
        self.agent_id = agent_id
        self.offchain = offchain
        self.ipfs = ipfs
        self.local = local
        self.encryption_key = encryption_key
        self.policy = policy or StoragePolicy()
        self._backup_task: Optional[asyncio.Task] = None
        self._running = False

    async def full_backup(self) -> str:
        logger.info(f"Starting full backup for {self.agent_id}")
        backup_id = f"bk_{self.agent_id}_full_{int(time.time())}"
        try:
            onchain = await self._query_onchain_state()
            offchain = await self._collect_offchain_data()
            data = {
                "backup_id": backup_id, "agent_id": self.agent_id,
                "backup_type": "full", "created_at": time.time(),
                "format_version": 1, "onchain": onchain, "offchain": offchain,
            }
            data_json = json.dumps(data, sort_keys=True, default=str)
            checksum = hashlib.sha256(data_json.encode()).hexdigest()
            data["checksum"] = checksum
            encrypted = await self._encrypt(json.dumps(data, default=str).encode())
            cid = await self.ipfs.add(encrypted)
            await self.ipfs.pin(cid)
            record = BackupRecord(
                id=backup_id, agent_id=self.agent_id, cid=cid,
                backup_type=BackupType.FULL, status=BackupStatus.COMPLETED,
                timestamp=time.time(), size_bytes=len(encrypted),
                checksum=checksum,
            )
            await self._save_record(record)
            asyncio.create_task(self._anchor_on_chain(cid, backup_id))
            logger.info(f"Full backup done: {cid}")
            return cid
        except Exception as e:
            logger.error(f"Full backup failed: {e}")
            failed = BackupRecord(
                id=backup_id, agent_id=self.agent_id, cid="",
                backup_type=BackupType.FULL, status=BackupStatus.FAILED,
                timestamp=time.time(), size_bytes=0, checksum="",
                metadata={"error": str(e)},
            )
            await self._save_record(failed)
            raise

    async def incremental_backup(self) -> str:
        backup_id = f"bk_{self.agent_id}_inc_{int(time.time())}"
        try:
            last = await self._get_last_backup()
            if not last:
                return await self.full_backup()
            current = await self._collect_offchain_data()
            last_data = await self._load_backup_data(last.cid)
            last_state = last_data.get("offchain", {})
            diff = self._compute_diff(last_state, current)
            data = {
                "backup_id": backup_id, "agent_id": self.agent_id,
                "backup_type": "incremental", "created_at": time.time(),
                "format_version": 1, "parent_id": last.id, "parent_cid": last.cid,
                "diff": diff,
            }
            data_json = json.dumps(data, sort_keys=True, default=str)
            checksum = hashlib.sha256(data_json.encode()).hexdigest()
            data["checksum"] = checksum
            encrypted = await self._encrypt(json.dumps(data, default=str).encode())
            cid = await self.ipfs.add(encrypted)
            await self.ipfs.pin(cid)
            record = BackupRecord(
                id=backup_id, agent_id=self.agent_id, cid=cid,
                backup_type=BackupType.INCREMENTAL, status=BackupStatus.COMPLETED,
                timestamp=time.time(), size_bytes=len(encrypted),
                checksum=checksum, parent_id=last.id,
            )
            await self._save_record(record)
            return cid
        except Exception as e:
            logger.error(f"Incremental backup failed: {e}")
            raise

    async def restore_from_backup(self, backup_id: str) -> bool:
        logger.info(f"Restoring from backup {backup_id}")
        record = await self._load_record(backup_id)
        if not record:
            raise ValueError(f"Backup not found: {backup_id}")
        encrypted = await self.ipfs.get(record.cid)
        decrypted = await self._decrypt(encrypted)
        data = json.loads(decrypted)
        stored_checksum = data.pop("checksum", "")
        data_json = json.dumps(data, sort_keys=True, default=str)
        calc = hashlib.sha256(data_json.encode()).hexdigest()
        if stored_checksum != calc:
            raise ValueError(f"Checksum mismatch")
        if record.backup_type == BackupType.INCREMENTAL:
            parent_cid = data.get("parent_cid")
            if parent_cid:
                pe = await self.ipfs.get(parent_cid)
                pd = await self._decrypt(pe)
                parent = json.loads(pd)
                full = self._apply_diff(parent.get("offchain", {}), data.get("diff", {}))
                data["offchain"] = full
        await self._restore_state(data)
        record.status = BackupStatus.VERIFIED
        await self._save_record(record)
        logger.info(f"Restored from {backup_id}")
        return True

    async def _query_onchain_state(self) -> Dict:
        return {"agent_id": self.agent_id, "queried_at": time.time()}

    async def _collect_offchain_data(self) -> Dict:
        data = {}
        session_keys = await self.local.list_keys("session:")
        sessions = []
        for key in session_keys:
            sd = await self.local.load(key)
            if sd:
                sessions.append(json.loads(sd))
        data["sessions"] = sessions
        convs = await self.local.list_agent_conversations(self.agent_id, limit=500)
        data["conversations"] = [asdict(c) for c in convs]
        kb = await self.local.load(f"agent:{self.agent_id}:knowledge_base")
        data["knowledge_base"] = json.loads(kb) if kb else {}
        tasks = await self.local.load(f"agent:{self.agent_id}:task_history")
        data["task_history"] = json.loads(tasks) if tasks else []
        prefs = await self.local.load(f"agent:{self.agent_id}:user_preferences")
        data["user_preferences"] = json.loads(prefs) if prefs else {}
        cp_keys = await self.local.list_keys(f"checkpoint:cp_{self.agent_id}")
        cps = []
        for k in cp_keys:
            cd = await self.local.load(k)
            if cd:
                cps.append(json.loads(cd))
        data["checkpoints"] = cps
        return data

    async def _encrypt(self, data: bytes) -> bytes:
        if not self.encryption_key:
            return data
        from cryptography.fernet import Fernet
        return Fernet(self.encryption_key).encrypt(data)

    async def _decrypt(self, data: bytes) -> bytes:
        if not self.encryption_key:
            return data
        from cryptography.fernet import Fernet
        return Fernet(self.encryption_key).decrypt(data)

    def _compute_diff(self, old: Dict, new: Dict) -> Dict:
        diff = {}
        all_keys = set(list(old.keys()) + list(new.keys()))
        for k in all_keys:
            if k not in old:
                diff[k] = {"type": "added", "value": new[k]}
            elif k not in new:
                diff[k] = {"type": "deleted"}
            elif old[k] != new[k]:
                diff[k] = {"type": "modified", "old": old[k], "new": new[k]}
        return diff

    def _apply_diff(self, base: Dict, diff: Dict) -> Dict:
        result = dict(base)
        for k, ch in diff.items():
            if ch["type"] in ("added", "modified"):
                result[k] = ch.get("value", ch.get("new"))
            elif ch["type"] == "deleted":
                result.pop(k, None)
        return result

    async def _save_record(self, record: BackupRecord):
        await self.local.store(
            f"backup:{record.id}", json.dumps(asdict(record), default=str).encode()
        )
        await self.local.store(
            f"agent:{self.agent_id}:latest_backup",
            json.dumps({"id": record.id, "cid": record.cid}).encode()
        )

    async def _load_record(self, backup_id: str) -> Optional[BackupRecord]:
        data = await self.local.load(f"backup:{backup_id}")
        if data:
            d = json.loads(data)
            d["backup_type"] = BackupType(d["backup_type"])
            d["status"] = BackupStatus(d["status"])
            return BackupRecord(**d)
        return None

    async def _load_backup_data(self, cid: str) -> Dict:
        encrypted = await self.ipfs.get(cid)
        decrypted = await self._decrypt(encrypted)
        return json.loads(decrypted)

    async def _get_last_backup(self) -> Optional[BackupRecord]:
        data = await self.local.load(f"agent:{self.agent_id}:latest_backup")
        if data:
            info = json.loads(data)
            return await self._load_record(info["id"])
        return None

    async def _anchor_on_chain(self, cid: str, backup_id: str):
        logger.info(f"Anchoring backup {backup_id} -> {cid} on MSG Chain")
        await asyncio.sleep(0.1)

    async def _restore_state(self, data: Dict):
        offchain = data.get("offchain", {})
        for sd in offchain.get("sessions", []):
            await self.local.store(f"session:{sd['id']}", json.dumps(sd).encode())
        for cp in offchain.get("checkpoints", []):
            await self.local.store(f"checkpoint:{cp['id']}", json.dumps(cp).encode())
        if "knowledge_base" in offchain:
            await self.local.store(
                f"agent:{self.agent_id}:knowledge_base",
                json.dumps(offchain["knowledge_base"]).encode()
            )
        if "task_history" in offchain:
            await self.local.store(
                f"agent:{self.agent_id}:task_history",
                json.dumps(offchain["task_history"]).encode()
            )
        if "user_preferences" in offchain:
            await self.local.store(
                f"agent:{self.agent_id}:user_preferences",
                json.dumps(offchain["user_preferences"]).encode()
            )

    async def list_backups(self, limit: int = 20, backup_type: Optional[BackupType] = None) -> List[BackupRecord]:
        keys = await self.local.list_keys(f"backup:bk_{self.agent_id}")
        records = []
        for key in keys:
            data = await self.local.load(key)
            if data:
                d = json.loads(data)
                d["backup_type"] = BackupType(d["backup_type"])
                d["status"] = BackupStatus(d["status"])
                records.append(BackupRecord(**d))
        if backup_type:
            records = [r for r in records if r.backup_type == backup_type]
        records.sort(key=lambda r: r.timestamp, reverse=True)
        return records[:limit]

    async def verify_backup(self, backup_id: str) -> bool:
        try:
            record = await self._load_record(backup_id)
            if not record:
                return False
            encrypted = await self.ipfs.get(record.cid)
            decrypted = await self._decrypt(encrypted)
            data = json.loads(decrypted)
            stored = data.get("checksum", "")
            copy = dict(data)
            copy.pop("checksum", None)
            calc = hashlib.sha256(json.dumps(copy, sort_keys=True, default=str).encode()).hexdigest()
            valid = stored == calc
            if valid:
                record.status = BackupStatus.VERIFIED
                await self._save_record(record)
            return valid
        except Exception as e:
            logger.error(f"Verify failed: {e}")
            return False

    async def start_scheduled_backups(self):
        if self._running:
            return
        self._running = True
        full_int = self.policy.full_backup_interval
        inc_int = self.policy.incremental_backup_interval
        last_full = 0
        while self._running:
            await asyncio.sleep(inc_int)
            now = time.time()
            try:
                if now - last_full >= full_int:
                    await self.full_backup()
                    last_full = now
                else:
                    await self.incremental_backup()
            except Exception as e:
                logger.error(f"Scheduled backup failed: {e}")

    async def stop_scheduled_backups(self):
        self._running = False
        if self._backup_task:
            self._backup_task.cancel()
            self._backup_task = None

    async def disaster_recovery_drill(self) -> Dict[str, Any]:
        logger.info("=== DISASTER RECOVERY DRILL ===")
        results = {"steps": [], "success": True}
        try:
            last = await self._get_last_backup()
            if not last:
                results["steps"].append({"step": "find_backup", "success": False, "error": "No backup"})
                results["success"] = False
                return results
            results["steps"].append({"step": "find_backup", "success": True, "id": last.id})
            valid = await self.verify_backup(last.id)
            results["steps"].append({"step": "verify", "success": valid})
            if not valid:
                results["success"] = False
                return results
            await self.restore_from_backup(last.id)
            results["steps"].append({"step": "restore", "success": True})
            kb = await self.local.load(f"agent:{self.agent_id}:knowledge_base")
            results["steps"].append({"step": "verify_restore", "success": kb is not None})
        except Exception as e:
            results["steps"].append({"step": "error", "error": str(e)})
            results["success"] = False
        return results

6.2 备份保留策略

@dataclass
class BackupPolicy:
    full_backup_cron: str = "0 3 * * *"
    incremental_interval_minutes: int = 60
    full_keep_days: int = 30
    inc_keep_days: int = 7
    min_backups_to_keep: int = 5
    encrypt_backups: bool = True
    verify_after_backup: bool = True

class BackupRotationManager:
    def __init__(self, manager: BackupManager, policy: BackupPolicy):
        self.manager = manager
        self.policy = policy

    async def rotate_backups(self):
        all_bk = await self.manager.list_backups(limit=1000)
        now = time.time()
        to_delete = []
        for bk in all_bk:
            age = (now - bk.timestamp) / 86400
            if bk.backup_type == BackupType.FULL and age > self.policy.full_keep_days:
                to_delete.append(bk)
            elif bk.backup_type != BackupType.FULL and age > self.policy.inc_keep_days:
                to_delete.append(bk)
        keep = len(all_bk) - len(to_delete)
        if keep < self.policy.min_backups_to_keep:
            excess = self.policy.min_backups_to_keep - keep
            to_delete = sorted(to_delete, key=lambda b: b.timestamp, reverse=True)[excess:] if excess > 0 else to_delete
        for bk in to_delete:
            try:
                await self.manager.ipfs.unpin(bk.cid)
                await self.manager.local.delete(f"backup:{bk.id}")
            except Exception as e:
                logger.error(f"Rotation failed: {e}")

7. 数据清理与归档

7.1 数据保留策略

class DataCategory(Enum):
    CONVERSATION = "conversation"
    SESSION = "session"
    LOG = "log"
    CHECKPOINT = "checkpoint"
    BACKUP = "backup"
    TEMP_CACHE = "temp_cache"
    TASK_RESULT = "task_result"
    METRICS = "metrics"

@dataclass
class RetentionRule:
    category: DataCategory
    max_age_days: int
    max_count: int
    archive_before_delete: bool
    priority: int

class DataRetentionPolicy:
    DEFAULT_RULES = [
        RetentionRule(DataCategory.CONVERSATION, 90, 500, True, 5),
        RetentionRule(DataCategory.SESSION, 7, 0, False, 8),
        RetentionRule(DataCategory.LOG, 30, 0, False, 3),
        RetentionRule(DataCategory.CHECKPOINT, 30, 20, True, 6),
        RetentionRule(DataCategory.BACKUP, 90, 10, False, 7),
        RetentionRule(DataCategory.TEMP_CACHE, 1, 0, False, 10),
        RetentionRule(DataCategory.TASK_RESULT, 30, 200, True, 4),
        RetentionRule(DataCategory.METRICS, 365, 0, False, 1),
    ]

    def __init__(self, rules: List[RetentionRule] = None):
        self.rules = rules or self.DEFAULT_RULES

    def get_rule(self, category: DataCategory) -> Optional[RetentionRule]:
        for r in self.rules:
            if r.category == category:
                return r
        return None

7.2 数据清理执行器

class DataCleanupManager:
    def __init__(
        self, agent_id: str, local_storage: SQLiteStorage,
        ipfs: IPFSClient, cache: RedisCache, offchain: OffChainStorage,
        policy: DataRetentionPolicy = None,
    ):
        self.agent_id = agent_id
        self.local = local_storage
        self.ipfs = ipfs
        self.cache = cache
        self.offchain = offchain
        self.policy = policy or DataRetentionPolicy()
        self._running = False

    async def cleanup_by_category(self, category: DataCategory) -> Dict[str, int]:
        rule = self.policy.get_rule(category)
        if not rule:
            return {"deleted": 0, "archived": 0, "freed_bytes": 0}
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        cutoff = time.time() - (rule.max_age_days * 86400)
        if category == DataCategory.CONVERSATION:
            stats = await self._cleanup_conversations(cutoff, rule)
        elif category == DataCategory.SESSION:
            stats = await self._cleanup_sessions(cutoff, rule)
        elif category == DataCategory.LOG:
            stats = await self._cleanup_logs(cutoff, rule)
        elif category == DataCategory.CHECKPOINT:
            stats = await self._cleanup_checkpoints(rule)
        elif category == DataCategory.BACKUP:
            stats = await self._cleanup_backups(rule)
        elif category == DataCategory.TEMP_CACHE:
            stats = await self._cleanup_temp_cache()
        elif category == DataCategory.TASK_RESULT:
            stats = await self._cleanup_task_results(cutoff, rule)
        elif category == DataCategory.METRICS:
            stats = await self._cleanup_metrics(cutoff, rule)
        return stats

    async def _cleanup_conversations(self, cutoff: float, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys("conv:")
        convs = []
        for k in keys:
            d = await self.local.load(k)
            if d:
                try:
                    convs.append((k, json.loads(d)))
                except json.JSONDecodeError:
                    continue
        convs.sort(key=lambda x: x[1].get("updated_at", 0))
        to_remove = convs[:-rule.max_count] if rule.max_count > 0 and len(convs) > rule.max_count else []
        for k, c in convs:
            if c.get("updated_at", 0) < cutoff and (k, c) not in to_remove:
                to_remove.append((k, c))
        for k, c in to_remove:
            try:
                if rule.archive_before_delete:
                    cid = await self.offchain.archive_and_purge(c.get("session_id", ""), 0)
                    if cid:
                        stats["archived"] += 1
                await self.local.delete(k)
                stats["deleted"] += 1
                stats["freed_bytes"] += len(json.dumps(c))
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")
        return stats

    async def _cleanup_sessions(self, cutoff: float, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys("session:")
        for k in keys:
            d = await self.local.load(k)
            if d:
                try:
                    s = json.loads(d)
                    if s.get("updated_at", 0) < cutoff:
                        await self.local.delete(k)
                        await self.cache.delete(k)
                        stats["deleted"] += 1
                        stats["freed_bytes"] += len(d)
                except json.JSONDecodeError:
                    continue
        return stats

    async def _cleanup_logs(self, cutoff: float, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys("log:")
        for k in keys:
            d = await self.local.load(k)
            if d:
                try:
                    entry = json.loads(d)
                    if entry.get("timestamp", 0) < cutoff:
                        await self.local.delete(k)
                        stats["deleted"] += 1
                        stats["freed_bytes"] += len(d)
                except json.JSONDecodeError:
                    continue
        return stats

    async def _cleanup_checkpoints(self, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys(f"checkpoint:cp_{self.agent_id}")
        cps = []
        for k in keys:
            d = await self.local.load(k)
            if d:
                cps.append((k, json.loads(d)))
        cps.sort(key=lambda x: x[1].get("timestamp", 0))
        to_remove = cps[:-rule.max_count] if rule.max_count > 0 and len(cps) > rule.max_count else []
        for k, cp in to_remove:
            try:
                if cp.get("cid"):
                    await self.ipfs.unpin(cp["cid"])
                await self.local.delete(k)
                stats["deleted"] += 1
                stats["freed_bytes"] += len(json.dumps(cp))
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")
        return stats

    async def _cleanup_backups(self, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys(f"backup:bk_{self.agent_id}")
        bks = []
        for k in keys:
            d = await self.local.load(k)
            if d:
                bks.append((k, json.loads(d)))
        bks.sort(key=lambda x: x[1].get("timestamp", 0))
        to_remove = bks[:-rule.max_count] if rule.max_count > 0 and len(bks) > rule.max_count else []
        for k, bk in to_remove:
            try:
                if bk.get("cid"):
                    await self.ipfs.unpin(bk["cid"])
                await self.local.delete(k)
                stats["deleted"] += 1
                stats["freed_bytes"] += len(json.dumps(bk))
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")
        return stats

    async def _cleanup_temp_cache(self) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.cache.list_keys("temp:")
        for k in keys:
            await self.cache.delete(k)
            stats["deleted"] += 1
        local_keys = await self.local.list_keys("temp:")
        for k in local_keys:
            d = await self.local.load(k)
            await self.local.delete(k)
            if d:
                stats["deleted"] += 1
                stats["freed_bytes"] += len(d)
        return stats

    async def _cleanup_task_results(self, cutoff: float, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys(f"task:{self.agent_id}:")
        tasks = []
        for k in keys:
            d = await self.local.load(k)
            if d:
                try:
                    tasks.append((k, json.loads(d)))
                except json.JSONDecodeError:
                    continue
        tasks.sort(key=lambda x: x[1].get("completed_at", 0))
        to_remove = tasks[:-rule.max_count] if rule.max_count > 0 and len(tasks) > rule.max_count else []
        for k, t in tasks:
            if t.get("completed_at", 0) < cutoff and (k, t) not in to_remove:
                to_remove.append((k, t))
        for k, t in to_remove:
            try:
                if rule.archive_before_delete:
                    archived = json.dumps(t).encode()
                    cid = await self.ipfs.add(archived)
                    await self.ipfs.pin(cid)
                    stats["archived"] += 1
                await self.local.delete(k)
                stats["deleted"] += 1
                stats["freed_bytes"] += len(json.dumps(t))
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")
        return stats

    async def _cleanup_metrics(self, cutoff: float, rule: RetentionRule) -> Dict[str, int]:
        stats = {"deleted": 0, "archived": 0, "freed_bytes": 0}
        keys = await self.local.list_keys(f"metrics:{self.agent_id}:")
        for k in keys:
            d = await self.local.load(k)
            if d:
                try:
                    m = json.loads(d)
                    if m.get("timestamp", 0) < cutoff:
                        await self.local.delete(k)
                        stats["deleted"] += 1
                        stats["freed_bytes"] += len(d)
                except json.JSONDecodeError:
                    continue
        return stats

    async def run_full_cleanup(self) -> Dict[str, Dict[str, int]]:
        results = {}
        for cat in DataCategory:
            try:
                results[cat.value] = await self.cleanup_by_category(cat)
            except Exception as e:
                results[cat.value] = {"error": str(e)}
        total_freed = sum(s.get("freed_bytes", 0) for s in results.values() if isinstance(s, dict))
        total_del = sum(s.get("deleted", 0) for s in results.values() if isinstance(s, dict))
        logger.info(f"Cleanup: {total_del} items, {total_freed/1024/1024:.2f} MB freed")
        return results

    async def get_storage_usage(self) -> Dict[str, Any]:
        usage = {"by_category": {}}
        prefix_map = {
            DataCategory.CONVERSATION: "conv:",
            DataCategory.SESSION: "session:",
            DataCategory.LOG: "log:",
            DataCategory.CHECKPOINT: f"checkpoint:cp_{self.agent_id}",
            DataCategory.BACKUP: f"backup:bk_{self.agent_id}",
            DataCategory.TEMP_CACHE: "temp:",
            DataCategory.TASK_RESULT: f"task:{self.agent_id}:",
            DataCategory.METRICS: f"metrics:{self.agent_id}:",
        }
        for cat, prefix in prefix_map.items():
            keys = await self.local.list_keys(prefix)
            total = 0
            for k in keys:
                d = await self.local.load(k)
                if d:
                    total += len(d)
            usage["by_category"][cat.value] = {"count": len(keys), "size_bytes": total}
        return usage

    async def start_periodic_cleanup(self, interval: int = 3600):
        if self._running:
            return
        self._running = True
        while self._running:
            await asyncio.sleep(interval)
            try:
                await self.run_full_cleanup()
            except Exception as e:
                logger.error(f"Cleanup failed: {e}")

    async def stop_periodic_cleanup(self):
        self._running = False

7.3 存储成本管理

class StorageCostManager:
    def __init__(self, agent_id: str, ipfs: IPFSClient, local: SQLiteStorage, max_monthly_cost_msg: float = 100.0):
        self.agent_id = agent_id
        self.ipfs = ipfs
        self.local = local
        self.max_monthly_cost = max_monthly_cost_msg

    async def estimate_store_cost(self, data_size: int, storage_type: str) -> float:
        if storage_type == "chain":
            return data_size * 0.001
        elif storage_type == "ipfs":
            return (data_size / (1024**3)) * 0.05
        return 0.0

    async def should_archive(self, data_size: int, age_days: int) -> bool:
        if age_days > 30 and data_size > 1024 * 1024:
            return True
        local_cost = await self.estimate_store_cost(data_size, "local")
        archive_cost = await self.estimate_store_cost(data_size, "ipfs")
        return local_cost > archive_cost * 2

    async def get_monthly_cost_report(self) -> Dict[str, float]:
        return {
            "chain_store": await self.estimate_store_cost(1024, "chain") * 30,
            "ipfs_pinning": await self.estimate_store_cost(500 * 1024 * 1024, "ipfs"),
            "local_storage": 0.0, "total": 0.0,
        }

    async def optimize_storage(self) -> Dict[str, Any]:
        optimizations = []
        keys = await self.local.list_keys("conv:")
        for k in keys:
            d = await self.local.load(k)
            if d and len(d) > 1024 * 1024:
                try:
                    conv = json.loads(d)
                    age = time.time() - conv.get("updated_at", 0)
                    if age > 7 * 86400:
                        optimizations.append({
                            "key": k, "size": len(d), "age_days": age / 86400,
                            "action": "archive_to_ipfs",
                            "savings": await self.estimate_store_cost(len(d), "local"),
                        })
                except json.JSONDecodeError:
                    continue
        return {"optimizations": optimizations, "current_cost": await self.get_monthly_cost_report()}

8. 完整 Agent 状态管理示例

8.1 AgentStateManager 综合实现

以下代码整合了前面所有模块,提供一个统一的 Agent 状态管理入口:

class AgentStateManager:
    def __init__(
        self, agent_id: str,
        db_path: str = "/data/agent.db",
        ipfs_url: str = "http://localhost:5001",
        redis_host: str = "localhost",
        redis_port: int = 6379,
        chain_client: Optional[Any] = None,
        encryption_key: Optional[bytes] = None,
        state_version: int = 1,
    ):
        self.agent_id = agent_id
        self.chain_client = chain_client
        self.state_version = state_version
        self._running = False

        # 存储层
        self.offchain = OffChainStorage(
            db_path=db_path, ipfs_url=ipfs_url,
            redis_host=redis_host, redis_port=redis_port,
        )

        # 检查点管理器
        self.checkpoints = CheckpointManager(
            agent_id=agent_id, offchain_storage=self.offchain,
            ipfs_client=self.offchain.ipfs, local_storage=self.offchain.local,
        )

        # 会话管理器
        self._session_store = SessionStore(
            local_storage=self.offchain.local, cache=self.offchain.cache,
        )
        self.sessions = SessionManager(agent_id=agent_id, store=self._session_store)

        # 备份管理器
        self.backups = BackupManager(
            agent_id=agent_id, offchain=self.offchain,
            ipfs=self.offchain.ipfs, local=self.offchain.local,
            encryption_key=encryption_key,
        )

        # 数据清理
        self.cleanup = DataCleanupManager(
            agent_id=agent_id, local_storage=self.offchain.local,
            ipfs=self.offchain.ipfs, cache=self.offchain.cache,
            offchain=self.offchain,
        )

        logger.info(f"AgentStateManager initialized for {agent_id}")

    async def start(self):
        if self._running:
            return
        self._running = True
        tasks = [
            self.checkpoints.start_periodic_checkpointing(),
            self.sessions.start_cleanup_loop(interval=60),
            self.backups.start_scheduled_backups(),
            self.cleanup.start_periodic_cleanup(interval=3600),
        ]
        self._background_tasks = [asyncio.create_task(t) for t in tasks]
        logger.info(f"Started {len(tasks)} background tasks")

    async def shutdown(self, graceful: bool = True):
        self._running = False
        if graceful:
            logger.info("Graceful shutdown...")
            await self.sessions.suspend_all_idle(max_idle=0)
            try:
                cid = await self.checkpoints.create_checkpoint(is_full=True)
                logger.info(f"Final checkpoint: {cid[:16]}...")
                if self.chain_client:
                    await self._update_onchain_pointer(cid)
                try:
                    await self.backups.full_backup()
                except Exception as e:
                    logger.error(f"Final backup failed: {e}")
            except Exception as e:
                logger.error(f"Final checkpoint failed: {e}")
        for task in getattr(self, '_background_tasks', []):
            task.cancel()
        await self.offchain.close()

    async def _update_onchain_pointer(self, cid: str):
        logger.info(f"Updated on-chain pointer to {cid}")

    async def collect_full_state(self) -> Dict[str, Any]:
        return {
            "agent_id": self.agent_id,
            "collected_at": time.time(),
            "version": self.state_version,
            "session_count": len(self.sessions.active_sessions),
        }

    async def restore_from_disaster(self, backup_id: Optional[str] = None) -> bool:
        logger.info(f"=== DISASTER RECOVERY for {self.agent_id} ===")
        try:
            if backup_id:
                success = await self.backups.restore_from_backup(backup_id)
            else:
                last = await self.backups._get_last_backup()
                if not last:
                    return False
                success = await self.backups.restore_from_backup(last.id)
            if success:
                await self.checkpoints.create_checkpoint(is_full=True)
            return success
        except Exception as e:
            logger.error(f"Recovery failed: {e}")
            return False

    async def health_check(self) -> Dict[str, Any]:
        storage_health = await self.offchain.health_check()
        return {
            "agent_id": self.agent_id,
            "timestamp": time.time(),
            "status": "healthy" if all(storage_health.values()) else "degraded",
            "storage": storage_health,
            "active_sessions": len(self.sessions.active_sessions),
        }

8.2 快速集成示例

async def main():
    """快速集成示例: 创建和运行 Agent 状态管理器"""
    manager = AgentStateManager(
        agent_id="msg1agent000000000000000000000000000000001",
        db_path="/data/my_agent.db",
        ipfs_url="http://localhost:5001",
        redis_host="localhost",
        chain_client=None,  # CosmWasm 客户端
    )

    try:
        # 启动后台服务
        await manager.start()

        # 创建用户会话
        session = await manager.sessions.create_session(
            user="msg1user000000000000000000000000000000002",
            preferences={"language": "zh", "model": "gpt-4"},
        )

        # 更新会话上下文
        await manager.sessions.update_session_context(
            session.id,
            new_messages=[Message(role="user", content="你好", timestamp=time.time())],
            variables={"topic": "区块链"},
        )

        # 关闭会话
        await manager.sessions.close_session(session.id)

        # 获取存储使用统计
        usage = await manager.cleanup.get_storage_usage()
        print(f"Storage usage: {usage}")

        # 健康检查
        health = await manager.health_check()
        print(f"Health: {health}")

    finally:
        # 优雅关闭
        await manager.shutdown(graceful=True)

# asyncio.run(main())

8.3 链上/链下状态一致性检查

/// 链上/链下状态一致性检查 (Rust 合约端)
pub fn verify_state_consistency(
    deps: Deps,
    env: Env,
    claimed_offchain_hash: String,
) -> StdResult<ConsistencyReport> {
    let onchain_state = STATE_KEY.load(deps.storage)?;

    // 链上关键字段的哈希
    let onchain_core = format!(
        "{}{}{}{}{}",
        onchain_state.reputation,
        onchain_state.total_earnings,
        onchain_state.last_heartbeat,
        onchain_state.active,
        onchain_state.metadata_uri,
    );
    let onchain_hash = format!("{:x}", md5::compute(onchain_core.as_bytes()));

    let consistent = onchain_hash == claimed_offchain_hash;

    Ok(ConsistencyReport {
        agent: onchain_state.owner.to_string(),
        onchain_hash,
        claimed_offchain_hash,
        consistent,
        checked_at: env.block.time.seconds(),
    })
}

#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
pub struct ConsistencyReport {
    pub agent: String,
    pub onchain_hash: String,
    pub claimed_offchain_hash: String,
    pub consistent: bool,
    pub checked_at: u64,
}

8.4 部署清单

部署 AI Agent 状态管理系统时的检查列表:

检查项 说明 优先级
确定链上/链下数据边界 定义哪些字段上链,哪些存 IPFS P0
实现状态 schema 版本化 确保未来可迁移 P0
配置 SQLite 数据库 设置 WAL 模式,定期 VACUUM P0
部署 IPFS 节点 或使用托管 pinning 服务 P0
配置 Redis 缓存 设置内存上限和淘汰策略 P0
实现检查点系统 全量+增量检查点 P0
实现备份调度 全量每日,增量每小时 P0
配置数据清理策略 按分类设置保留期限 P1
加密敏感数据 备份数据加密 + 传输加密 P1
灾难恢复演练 每月至少一次 P1
监控存储成本 跟踪 MSG 代币消耗 P1
会话上下文继承 跨会话的用户上下文传递 P2
上下文窗口压缩 LLM token 管理 P2

附录

A. 常用命令速查

# 查询 Agent 链上状态
msgcli query wasm contract-state smart msg1agent... '{"get_state":{}}'

# 更新 Agent metadata_uri
msgcli tx wasm execute msg1agent... '{"update_metadata":{"uri":"ipfs://Qm..."}}' --from admin

# 手动创建检查点 (通过链上触发)
msgcli tx wasm execute msg1agent... '{"create_checkpoint":{}}' --from admin

# 查询备份记录
msgcli query wasm contract-state smart msg1agent... '{"list_backups":{"limit":10}}'

B. 状态管理架构总览

┌─────────────────────────────────────────────────────────────────┐
│                    AgentStateManager                            │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌──────────────┐  ┌────────────────────────┐ │
│  │ OnChainState │  │ OffChainStore│  │ CheckpointManager     │ │
│  │  (Rust)      │  │  (Python)    │  │  (Python)             │ │
│  │  -owner      │  │  -SQLite     │  │  -full snapshots      │ │
│  │  -reputation │  │  -IPFS       │  │  -incremental diffs   │ │
│  │  -earnings   │  │  -Redis      │  │  -integrity verify    │ │
│  └──────┬───────┘  └──────┬───────┘  └───────────┬────────────┘ │
│         │                 │                       │              │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌───────────┴────────────┐ │
│  │ SessionMgr   │  │ BackupMgr   │  │ DataCleanupMgr        │ │
│  │ (Python)     │  │ (Python)    │  │ (Python)              │ │
│  │ -lifecycle   │  │ -full/inc   │  │ -retention policy    │ │
│  │ -context     │  │ -encryption │  │ -cost optimization   │ │
│  │ -inheritance │  │ -DR drill   │  │ -archive             │ │
│  └──────────────┘  └─────────────┘  └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

本文档针对 MSG Chain (msg-chain-1) 的 AI Agent 开发,使用 msg 地址前缀。所有代码示例仅供参考,生产环境部署请结合具体业务场景调整。


本文档内容基于 MSGChain 代码库真实状态编写,非 AI 自动生成。
主网状态: No-Go | 白皮书: https://msgchain.org/whitepaper/