Skip to content

Instantly share code, notes, and snippets.

@parkgogogo
Last active March 11, 2026 11:54
Show Gist options
  • Select an option

  • Save parkgogogo/103fd5ece4703230125a79a59989082f to your computer and use it in GitHub Desktop.

Select an option

Save parkgogogo/103fd5ece4703230125a79a59989082f to your computer and use it in GitHub Desktop.
OpenClaw Reflection 需求文档 - 完整对话存储、可观测性与数据集生成

OpenClaw Reflection 需求文档

项目背景

OpenClaw Reflection 是一个增强 OpenClaw 原生记忆系统的插件。它通过 memory_gatewrite_guardian 两个核心组件,智能地筛选对话中的重要信息并写入对应的记忆文件(MEMORY.md, USER.md, SOUL.md, IDENTITY.md, TOOLS.md)。

当前系统存在一些限制,需要通过本需求文档规划的功能来解决。


需求总览

需求编号 需求名称 优先级 状态
REQ-001 完整对话持久化存储 P0 待实现
REQ-002 memory_gate 行为可观测 P0 待实现
REQ-003 write_guardian 行为审计与回滚 P0 待实现
REQ-004 标注数据集生成 P1 待实现

存储架构(核心变更)

主存储:SQLite —— 支持高效查询、事务和索引

<workspaceDir>/.openclaw-reflection/
├── reflection.db              # SQLite 主数据库
├── reflection.db-wal          # WAL 模式日志文件
├── reflection.db-shm          # WAL 模式共享内存
│
└── exports/                   # 可选的 JSONL 导出目录
    ├── conversations-2026-03-11.jsonl
    ├── dataset-sft-v1.jsonl
    └── ...

为什么选 SQLite

维度 JSONL SQLite
查询速度 需全文件扫描 索引加速,毫秒级
关联查询 无法实现 JOIN 轻松关联多表
更新记录 几乎不可能 UPDATE 原子操作
聚合统计 遍历全部数据 秒级 COUNT/GROUP BY
事务安全 ACID 保证
回滚支持 需手动实现 内置事务回滚
数据完整性 外键约束

SQLite 配置

interface SQLiteStorageConfig {
  enabled: boolean;              // 默认: true
  dbPath: string;                // 默认: ".openclaw-reflection/reflection.db"
  walMode: boolean;              // 默认: true (提升并发写入性能)
  autoVacuum: boolean;           // 默认: true (自动清理碎片)
  busyTimeoutMs: number;         // 默认: 5000 (等待锁的超时时间)
  
  // 性能调优
  cacheSizePages: number;        // 默认: -2000 (2MB 缓存)
  mmapSizeMB: number;            // 默认: 256 (内存映射大小)
  
  // 数据保留策略
  retentionDays?: number;        // 可选: 自动清理 N 天前的数据
  
  // JSONL 导出(可选)
  jsonlExport: {
    enabled: boolean;
    schedule?: string;           // Cron 表达式,如 "0 0 * * *" 每天导出
    retentionDays: number;       // 导出文件保留天数
  };
}

详细需求

REQ-001: 完整对话持久化存储

现状

  • 目前只有 FileLogger 记录的日志(reflection-YYYY-MM-DD.log
  • SessionBufferManager 使用 CircularBuffer,消息会被驱逐
  • 调试用的 debug.json 只保存最新一条消息

目标

将所有对话完整存储到 SQLite,为后续的数据分析和模型训练提供原始数据。

数据库表结构

-- 会话表:记录会话元数据
CREATE TABLE sessions (
    id TEXT PRIMARY KEY,                    -- sessionKey (如 channel:xxx:yyy)
    channel_id TEXT NOT NULL,               -- 频道/渠道标识
    conversation_target TEXT,               -- 对话目标(用户/群组)
    account_id TEXT,                        -- 账号标识
    created_at INTEGER NOT NULL,            -- 创建时间戳(毫秒)
    last_activity_at INTEGER NOT NULL,      -- 最后活动时间
    metadata TEXT,                          -- JSON 格式的额外元数据
    
    INDEX idx_channel (channel_id),
    INDEX idx_last_activity (last_activity_at)
);

-- 对话回合表:记录每一轮对话
CREATE TABLE conversation_turns (
    id TEXT PRIMARY KEY,                    -- ULID
    session_id TEXT NOT NULL,               -- 外键关联 sessions
    turn_number INTEGER NOT NULL,           -- 会话内递增序号
    
    -- 用户消息
    user_message_id TEXT,                   -- 原始消息 ID
    user_message_content TEXT NOT NULL,     -- 消息内容
    user_from TEXT,                         -- 发送者标识
    user_metadata TEXT,                     -- JSON 格式元数据
    
    -- Agent 回复
    agent_message_id TEXT,                  -- 原始消息 ID
    agent_message_content TEXT NOT NULL,    -- 消息内容
    agent_metadata TEXT,                    -- JSON 格式元数据
    
    created_at INTEGER NOT NULL,
    
    UNIQUE(session_id, turn_number),        -- 确保回合号唯一
    FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
    INDEX idx_session_turn (session_id, turn_number),
    INDEX idx_created_at (created_at)
);

-- 对话上下文窗口表:存储每个回合的上下文(用于重现场景)
CREATE TABLE conversation_contexts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    turn_id TEXT NOT NULL,                  -- 外键关联 conversation_turns
    role TEXT NOT NULL CHECK(role IN ('user', 'agent')),
    message TEXT NOT NULL,
    timestamp INTEGER NOT NULL,
    sequence_number INTEGER NOT NULL,       -- 上下文中的顺序
    
    FOREIGN KEY (turn_id) REFERENCES conversation_turns(id) ON DELETE CASCADE,
    INDEX idx_turn_sequence (turn_id, sequence_number)
);

TypeScript 接口

interface SessionRecord {
  id: string;                    // sessionKey
  channelId: string;
  conversationTarget?: string;
  accountId?: string;
  createdAt: number;
  lastActivityAt: number;
  metadata?: Record<string, unknown>;
}

interface ConversationTurn {
  id: string;                    // ULID
  sessionId: string;
  turnNumber: number;
  
  userMessage: {
    id?: string;
    content: string;
    from?: string;
    metadata?: Record<string, unknown>;
  };
  
  agentReply: {
    id?: string;
    content: string;
    metadata?: Record<string, unknown>;
  };
  
  createdAt: number;
}

interface ConversationContextItem {
  id: number;
  turnId: string;
  role: "user" | "agent";
  message: string;
  timestamp: number;
  sequenceNumber: number;
}

写入策略

class ConversationStorage {
  // 实时写入会话(UPSERT 更新最后活动时间)
  async upsertSession(session: SessionRecord): Promise<void>;
  
  // 实时写入对话回合
  async insertTurn(turn: ConversationTurn): Promise<void>;
  
  // 批量写入上下文(使用事务批量插入)
  async insertContextBatch(
    turnId: string, 
    context: Array<{ role: "user" | "agent"; message: string; timestamp: number }>
  ): Promise<void>;
  
  // 查询某会话的所有回合
  async getTurnsBySession(
    sessionId: string, 
    options?: { limit?: number; before?: number }
  ): Promise<ConversationTurn[]>;
  
  // 查询某回合的完整上下文
  async getContextByTurn(turnId: string): Promise<ConversationContextItem[]>;
}

REQ-002: memory_gate 行为可观测

现状

  • memory_gate 的决策只通过 FileLogger 记录
  • 无法直观查看某个会话中 memory_gate 的完整决策历史
  • 无法关联决策与原始对话上下文

目标

提供完整的 memory_gate 行为追踪,通过 SQLite 支持高效查询。

数据库表结构

-- memory_gate 决策记录表
CREATE TABLE memory_gate_decisions (
    id TEXT PRIMARY KEY,                    -- ULID
    turn_id TEXT NOT NULL,                  -- 外键关联 conversation_turns
    session_id TEXT NOT NULL,               -- 冗余存储,便于查询
    
    -- 决策结果
    decision TEXT NOT NULL CHECK(decision IN (
        'NO_WRITE', 'UPDATE_MEMORY', 'UPDATE_USER', 
        'UPDATE_SOUL', 'UPDATE_IDENTITY', 'UPDATE_TOOLS'
    )),
    reason TEXT NOT NULL,
    candidate_fact TEXT,                    -- 候选事实(非 NO_WRITE 时必填)
    
    -- LLM 调用信息
    llm_model TEXT NOT NULL,
    llm_latency_ms INTEGER,
    prompt_tokens INTEGER,
    completion_tokens INTEGER,
    raw_response TEXT,                      -- 原始 LLM 响应(调试用)
    
    created_at INTEGER NOT NULL,
    
    FOREIGN KEY (turn_id) REFERENCES conversation_turns(id) ON DELETE CASCADE,
    FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
    INDEX idx_session_time (session_id, created_at),
    INDEX idx_decision (decision),
    INDEX idx_turn (turn_id)
);

-- memory_gate 决策统计表(物化视图或定期更新)
CREATE TABLE memory_gate_stats (
    date TEXT PRIMARY KEY,                  -- YYYY-MM-DD
    session_count INTEGER,
    total_decisions INTEGER,
    no_write_count INTEGER,
    update_memory_count INTEGER,
    update_user_count INTEGER,
    update_soul_count INTEGER,
    update_identity_count INTEGER,
    update_tools_count INTEGER,
    avg_latency_ms INTEGER,
    updated_at INTEGER
);

TypeScript 接口

interface MemoryGateDecisionRecord {
  id: string;
  turnId: string;
  sessionId: string;
  
  // 决策结果
  decision: MemoryDecision;
  reason: string;
  candidateFact?: string;
  
  // LLM 调用信息
  llmCall: {
    model: string;
    latencyMs: number;
    promptTokens?: number;
    completionTokens?: number;
    rawResponse?: string;
  };
  
  createdAt: number;
}

// 查询结果增强版(关联对话内容)
interface MemoryGateDecisionWithContext {
  decision: MemoryGateDecisionRecord;
  turn: ConversationTurn;                   // 对应对话回合
  context: ConversationContextItem[];       // 完整上下文
}

新命令: /reflection gate

/reflection gate [subcommand] [options]

Subcommands:
  list [sessionKey]      列出决策历史
  show <decision-id>     显示单次决策详情(含完整上下文)
  stats                  显示决策统计
  export                 导出决策数据

Options for list:
  --limit, -l            返回最近的 N 条(默认: 10)
  --since, -s            从某个时间开始(ISO 8601)
  --until, -u            到某个时间结束
  --decision, -d         按决策类型过滤
  --session, -S          按会话过滤
  --with-context, -c     包含完整上下文

Options for stats:
  --daily                按天统计
  --weekly               按周统计
  --by-session           按会话统计

Examples:
  /reflection gate list                          # 最近决策
  /reflection gate list channel:xxx:yyy -l 20    # 指定会话
  /reflection gate show <decision-id> -c         # 查看详情+上下文
  /reflection gate stats --daily                 # 每日统计

查询示例

-- 查询某会话最近的 memory_gate 决策(含对话内容)
SELECT 
    d.id,
    d.decision,
    d.reason,
    d.candidate_fact,
    d.llm_latency_ms,
    t.user_message_content,
    t.agent_message_content,
    d.created_at
FROM memory_gate_decisions d
JOIN conversation_turns t ON d.turn_id = t.id
WHERE d.session_id = ?
ORDER BY d.created_at DESC
LIMIT 10;

-- 按决策类型统计最近 7 天
SELECT 
    decision,
    COUNT(*) as count,
    AVG(llm_latency_ms) as avg_latency
FROM memory_gate_decisions
WHERE created_at > ?
GROUP BY decision;

-- 查询候选事实包含特定关键词的决策
SELECT * FROM memory_gate_decisions
WHERE candidate_fact LIKE '%preference%'
ORDER BY created_at DESC;

REQ-003: write_guardian 行为审计与回滚

现状

  • write_guardianWriteGuardianAuditLog 记录写入决策
  • 但无法查看具体的文件变更内容(diff)
  • 无法撤销已执行的写入
  • 无法修改历史决策行为

目标

提供完整的文件变更追踪、审计和回滚能力。

数据库表结构

-- 文件快照表:存储每次写入前的文件状态
CREATE TABLE file_snapshots (
    id TEXT PRIMARY KEY,                    -- ULID
    file_name TEXT NOT NULL CHECK(file_name IN (
        'MEMORY.md', 'USER.md', 'SOUL.md', 'IDENTITY.md', 'TOOLS.md'
    )),
    content TEXT NOT NULL,                  -- 文件完整内容
    content_hash TEXT NOT NULL,             -- SHA256 哈希,用于去重
    created_at INTEGER NOT NULL,
    
    INDEX idx_file_time (file_name, created_at),
    INDEX idx_hash (content_hash)
);

-- write_guardian 审计记录表(增强版)
CREATE TABLE write_guardian_audit (
    id TEXT PRIMARY KEY,                    -- ULID
    decision_id TEXT NOT NULL,              -- 外键关联 memory_gate_decisions
    turn_id TEXT NOT NULL,                  -- 外键关联 conversation_turns
    session_id TEXT NOT NULL,
    
    -- 决策信息
    target_file TEXT NOT NULL,
    candidate_fact TEXT NOT NULL,
    
    -- 文件变更
    before_snapshot_id TEXT,                -- 变更前快照(nullable 表示新建)
    after_snapshot_id TEXT,                 -- 变更后快照
    diff TEXT,                              -- unified diff 格式
    lines_added INTEGER,
    lines_removed INTEGER,
    lines_modified INTEGER,
    
    -- 执行结果
    status TEXT NOT NULL CHECK(status IN (
        'written', 'refused', 'failed', 'skipped', 'rolled_back'
    )),
    reason TEXT,                            -- 拒绝/失败原因
    
    -- LLM 调用信息
    llm_model TEXT,
    llm_latency_ms INTEGER,
    agent_steps INTEGER,
    
    created_at INTEGER NOT NULL,
    
    FOREIGN KEY (decision_id) REFERENCES memory_gate_decisions(id) ON DELETE CASCADE,
    FOREIGN KEY (turn_id) REFERENCES conversation_turns(id) ON DELETE CASCADE,
    FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
    FOREIGN KEY (before_snapshot_id) REFERENCES file_snapshots(id),
    FOREIGN KEY (after_snapshot_id) REFERENCES file_snapshots(id),
    INDEX idx_session_time (session_id, created_at),
    INDEX idx_status (status),
    INDEX idx_target_file (target_file)
);

-- 回滚记录表
CREATE TABLE rollbacks (
    id TEXT PRIMARY KEY,
    audit_id TEXT NOT NULL UNIQUE,          -- 关联的审计记录
    rolled_back_at INTEGER NOT NULL,
    rolled_back_by TEXT NOT NULL,           -- 用户 ID 或 "system"
    reason TEXT NOT NULL,
    
    -- 回滚操作详情
    restored_snapshot_id TEXT NOT NULL,     -- 恢复到的快照
    previous_status TEXT NOT NULL,          -- 回滚前的状态
    
    FOREIGN KEY (audit_id) REFERENCES write_guardian_audit(id),
    FOREIGN KEY (restored_snapshot_id) REFERENCES file_snapshots(id),
    INDEX idx_rolled_back_at (rolled_back_at)
);

-- 行为校正记录表(用于收集训练数据)
CREATE TABLE behavior_corrections (
    id TEXT PRIMARY KEY,
    audit_id TEXT NOT NULL,
    
    -- 原始决策
    original_status TEXT NOT NULL,
    original_candidate_fact TEXT,
    original_target_file TEXT,
    
    -- 校正后
    corrected_status TEXT NOT NULL,
    corrected_candidate_fact TEXT,
    corrected_target_file TEXT,
    correction_reason TEXT NOT NULL,
    
    -- 是否应用到未来相似场景
    apply_to_future BOOLEAN DEFAULT false,
    correction_pattern TEXT,                -- 提取的校正模式(用于自动应用)
    
    corrected_by TEXT NOT NULL,
    corrected_at INTEGER NOT NULL,
    
    FOREIGN KEY (audit_id) REFERENCES write_guardian_audit(id),
    INDEX idx_apply_to_future (apply_to_future)
);

TypeScript 接口

interface FileSnapshot {
  id: string;
  fileName: "MEMORY.md" | "USER.md" | "SOUL.md" | "IDENTITY.md" | "TOOLS.md";
  content: string;
  contentHash: string;
  createdAt: number;
}

interface WriteGuardianAuditRecord {
  id: string;
  decisionId: string;
  turnId: string;
  sessionId: string;
  
  targetFile: string;
  candidateFact: string;
  
  // 文件变更
  fileChange: {
    beforeSnapshotId?: string;
    afterSnapshotId: string;
    diff: string;
    lineChanges: {
      added: number;
      removed: number;
      modified: number;
    };
  };
  
  // 执行结果
  status: "written" | "refused" | "failed" | "skipped" | "rolled_back";
  reason?: string;
  
  // LLM 调用
  llmCall?: {
    model: string;
    latencyMs: number;
    agentSteps: number;
  };
  
  createdAt: number;
}

interface RollbackRecord {
  id: string;
  auditId: string;
  rolledBackAt: number;
  rolledBackBy: string;
  reason: string;
  restoredSnapshotId: string;
  previousStatus: string;
}

interface BehaviorCorrection {
  id: string;
  auditId: string;
  originalStatus: string;
  originalCandidateFact?: string;
  originalTargetFile?: string;
  correctedStatus: string;
  correctedCandidateFact?: string;
  correctedTargetFile?: string;
  correctionReason: string;
  applyToFuture: boolean;
  correctionPattern?: string;
  correctedBy: string;
  correctedAt: number;
}

回滚机制实现

class WriteGuardianRollback {
  private db: Database;
  private fileUtils: FileUtils;
  
  /**
   * 回滚指定的 write_guardian 操作
   */
  async rollback(
    auditId: string,
    options: {
      reason: string;
      requestedBy: string;
      createBackup?: boolean;
    }
  ): Promise<RollbackResult> {
    return this.db.transaction(async (trx) => {
      // 1. 获取审计记录
      const audit = await trx
        .select('*')
        .from('write_guardian_audit')
        .where('id', auditId)
        .first();
      
      if (!audit) {
        throw new Error(`Audit record ${auditId} not found`);
      }
      
      if (audit.status === 'rolled_back') {
        throw new Error('This operation has already been rolled back');
      }
      
      if (audit.status !== 'written') {
        throw new Error(`Cannot rollback operation with status: ${audit.status}`);
      }
      
      // 2. 获取恢复目标快照
      const targetSnapshot = audit.before_snapshot_id 
        ? await trx.select('*').from('file_snapshots').where('id', audit.before_snapshot_id).first()
        : null; // 表示文件不存在(新建后回滚 = 删除)
      
      // 3. 可选:创建当前状态的备份
      if (options.createBackup !== false) {
        const currentContent = await this.fileUtils.readFile(audit.target_file);
        await this.createBackup(audit.target_file, currentContent);
      }
      
      // 4. 执行回滚
      if (targetSnapshot) {
        await this.fileUtils.writeFile(audit.target_file, targetSnapshot.content);
      } else {
        await this.fileUtils.deleteFile(audit.target_file);
      }
      
      // 5. 更新审计记录状态
      await trx
        .update('write_guardian_audit')
        .set({ status: 'rolled_back' })
        .where('id', auditId);
      
      // 6. 记录回滚操作
      const rollbackId = ulid();
      await trx.insert('rollbacks').values({
        id: rollbackId,
        audit_id: auditId,
        rolled_back_at: Date.now(),
        rolled_back_by: options.requestedBy,
        reason: options.reason,
        restored_snapshot_id: audit.before_snapshot_id,
        previous_status: audit.status
      });
      
      return {
        rollbackId,
        success: true,
        restoredTo: targetSnapshot?.created_at || null
      };
    });
  }
  
  /**
   * 批量回滚某个会话的所有写入操作
   */
  async rollbackSession(
    sessionKey: string,
    options: {
      reason: string;
      requestedBy: string;
      since?: number;
      until?: number;
    }
  ): Promise<BatchRollbackResult>;
  
  /**
   * 使用新的 candidate fact 重新执行某次操作
   */
  async retry(
    auditId: string,
    newCandidateFact?: string,
    newTargetFile?: string
  ): Promise<WriteResult>;
}

新命令: /reflection guardian

/reflection guardian [subcommand] [options]

Subcommands:
  list [sessionKey]          列出 write_guardian 操作历史
  show <audit-id>            显示单次操作的详细信息(含 diff)
  diff <audit-id>            显示文件变更的 diff
  rollback <audit-id>        回滚到变更前的状态
  history <file-name>        查看某文件的所有变更历史
  correct <audit-id>         标记需要校正,并提供正确决策
  export [options]           导出审计数据

Options for list:
  --limit, -l                限制结果数量(默认: 10)
  --file, -f                 按目标文件过滤(MEMORY.md, USER.md, 等)
  --status, -s               按状态过滤(written, refused, rolled_back)
  --since                    时间范围开始
  --until                    时间范围结束
  --with-diff, -d            显示 diff 摘要

Options for history:
  --file, -f                 文件名(必需)
  --limit, -l                限制历史版本数量

Options for correct:
  --status                   校正后的状态(written 或 refused)
  --fact                     校正后的 candidate fact
  --file                     校正后的目标文件
  --reason                   校正原因
  --apply-to-future          是否应用到未来相似场景

Options for export:
  --format                   导出格式: json | csv | markdown
  --output, -o               输出文件路径
  --include-content          包含完整文件内容

Examples:
  /reflection guardian list                          # 最近操作
  /reflection guardian list -f USER.md -s written    # USER.md 的成功写入
  /reflection guardian show <audit-id>               # 查看详情
  /reflection guardian diff <audit-id>               # 查看 diff
  /reflection guardian rollback <audit-id>           # 回滚操作
  /reflection guardian history -f MEMORY.md -l 20    # 查看文件历史
  /reflection guardian correct <audit-id> --status refused --reason "不应写入"

REQ-004: 标注数据集生成

背景

通过 Reflection 插件校正决策的过程,实际上是在产生高质量的监督微调数据(SFT data)。这些数据可以用于:

  • 微调 memory_gate 模型
  • 微调 write_guardian 模型
  • 构建领域特定的记忆系统评估基准

目标

自动收集和生成高质量的标注数据集,存储在 SQLite 中便于查询和导出。

数据库表结构

-- 数据集样本表
CREATE TABLE dataset_samples (
    id TEXT PRIMARY KEY,
    sample_type TEXT NOT NULL CHECK(sample_type IN (
        'memory_gate_positive',      -- memory_gate 正确决策(最终被写入)
        'memory_gate_negative',      -- memory_gate 错误决策(被 guardian 拒绝或校正)
        'write_guardian_decision',   -- write_guardian 决策样本
        'annotated_turn'             -- 完整标注回合
    )),
    
    -- 关联信息
    turn_id TEXT,
    decision_id TEXT,
    audit_id TEXT,
    session_id TEXT NOT NULL,
    
    -- 样本内容(JSON 格式,根据类型存储不同结构)
    content TEXT NOT NULL,
    
    -- 质量评分(自动计算)
    quality_score REAL,             -- 0-1 之间的质量分数
    quality_factors TEXT,           -- JSON 格式评分因子详情
    
    -- 标注状态
    annotation_status TEXT DEFAULT 'pending' CHECK(annotation_status IN (
        'pending', 'approved', 'rejected', 'needs_review', 'auto_approved'
    )),
    annotated_by TEXT,
    annotated_at INTEGER,
    annotation_notes TEXT,
    
    -- 隐私检查
    privacy_checked BOOLEAN DEFAULT false,
    contains_pii BOOLEAN DEFAULT false,
    pii_redacted_content TEXT,      -- 脱敏后的内容
    
    -- 样本来源
    collection_strategy TEXT,       -- 收集策略(如 auto_positive, manual_correction)
    
    created_at INTEGER NOT NULL,
    
    FOREIGN KEY (turn_id) REFERENCES conversation_turns(id),
    FOREIGN KEY (decision_id) REFERENCES memory_gate_decisions(id),
    FOREIGN KEY (audit_id) REFERENCES write_guardian_audit(id),
    FOREIGN KEY (session_id) REFERENCES sessions(id),
    INDEX idx_type_status (sample_type, annotation_status),
    INDEX idx_quality (quality_score),
    INDEX idx_session (session_id)
);

-- 数据集导出记录表
CREATE TABLE dataset_exports (
    id TEXT PRIMARY KEY,
    export_name TEXT NOT NULL,
    export_format TEXT NOT NULL CHECK(export_format IN (
        'alpaca', 'sharegpt', 'openai', 'raw', 'csv'
    )),
    
    -- 导出参数
    sample_types TEXT NOT NULL,     -- JSON 数组,如 ["memory_gate_positive", "annotated_turn"]
    filters TEXT,                   -- JSON 格式的过滤条件
    
    -- 统计信息
    total_samples INTEGER,
    approved_samples INTEGER,
    rejected_samples INTEGER,
    avg_quality_score REAL,
    
    -- 文件信息
    file_path TEXT NOT NULL,
    file_size_bytes INTEGER,
    checksum TEXT,
    
    exported_by TEXT,
    exported_at INTEGER NOT NULL,
    
    INDEX idx_exported_at (exported_at)
);

-- 人工审核队列视图
CREATE VIEW pending_review_samples AS
SELECT 
    s.*,
    t.user_message_content,
    t.agent_message_content,
    d.decision as memory_gate_decision,
    a.status as write_guardian_status
FROM dataset_samples s
LEFT JOIN conversation_turns t ON s.turn_id = t.id
LEFT JOIN memory_gate_decisions d ON s.decision_id = d.id
LEFT JOIN write_guardian_audit a ON s.audit_id = a.id
WHERE s.annotation_status = 'needs_review'
ORDER BY s.quality_score DESC;

样本内容结构

// Type 1: memory_gate 正样本
interface MemoryGatePositiveSample {
  type: "memory_gate_positive";
  conversation: Array<{ role: "user" | "agent"; message: string }>;
  correctDecision: MemoryDecision;
  correctFact: string;
  evidence: {
    wasWritten: boolean;
    userFeedback?: "confirmed" | "corrected";
  };
}

// Type 2: memory_gate 负样本
interface MemoryGateNegativeSample {
  type: "memory_gate_negative";
  conversation: Array<{ role: "user" | "agent"; message: string }>;
  incorrectDecision: MemoryDecision;
  incorrectFact: string;
  correctDecision: "NO_WRITE" | MemoryDecision;
  rejectionReason: string;
}

// Type 3: write_guardian 决策样本
interface WriteGuardianSample {
  type: "write_guardian_decision";
  candidateFact: string;
  targetFile: string;
  currentFileContent: string;
  decision: "written" | "refused";
  userCorrection?: {
    originalDecision: string;
    correctedDecision: string;
    reason: string;
  };
}

// Type 4: 完整标注回合
interface AnnotatedTurn {
  type: "annotated_turn";
  conversationId: string;
  turnNumber: number;
  context: Array<{ role: "user" | "agent"; message: string }>;
  
  memoryGate: {
    input: MemoryGateInput;
    output: MemoryGateOutput;
    label: "correct" | "incorrect" | "corrected";
    correctedOutput?: MemoryGateOutput;
  };
  
  writeGuardian: {
    input: {
      candidateFact: string;
      targetFile: string;
      currentContent: string;
    };
    output: WriteGuardianWriteResult;
    label: "correct" | "incorrect" | "corrected";
    correctedOutput?: WriteGuardianWriteResult;
  };
}

// 数据库中的统一格式
interface DatasetSampleRecord {
  id: string;
  sampleType: "memory_gate_positive" | "memory_gate_negative" | "write_guardian_decision" | "annotated_turn";
  turnId?: string;
  decisionId?: string;
  auditId?: string;
  sessionId: string;
  content: MemoryGatePositiveSample | MemoryGateNegativeSample | WriteGuardianSample | AnnotatedTurn;
  qualityScore?: number;
  qualityFactors?: {
    conversationClarity: number;
    decisionConfidence: number;
    factVerifiability: number;
  };
  annotationStatus: "pending" | "approved" | "rejected" | "needs_review" | "auto_approved";
  privacyChecked: boolean;
  containsPii: boolean;
  piiRedactedContent?: string;
  collectionStrategy: string;
  createdAt: number;
}

自动收集策略

interface DatasetCollectionStrategy {
  // 自动标记正样本的条件
  positiveCriteria: {
    // write_guardian 成功写入
    wasWritten: boolean;
    // 用户没有提出异议(通过反应或后续消息判断)
    noUserObjection: boolean;
    // N 天后仍然有效(未被回滚)
    notRolledBackWithinDays: number;
    // 质量分数阈值
    minQualityScore: number;
  };
  
  // 自动标记负样本的条件
  negativeCriteria: {
    // memory_gate 提议写入但被 write_guardian 拒绝
    gateProposedWrite: boolean;
    guardianRefused: boolean;
    // 或者用户后续手动要求记录类似内容
    userLaterRequestedSimilar: boolean;
    // 被人工校正标记为错误
    hasBehaviorCorrection: boolean;
  };
  
  // 需要人工审核的条件
  manualReviewCriteria: {
    // 决策置信度低(如 LLM 响应犹豫)
    lowConfidence: boolean;
    // 涉及敏感信息
    containsSensitiveInfo: boolean;
    // 用户明确反馈
    userFeedbackReceived: boolean;
    // 质量分数中等(不确定是否合格)
    mediumQualityScore: boolean;
  };
  
  // 自动批准的条件(无需人工审核)
  autoApproveCriteria: {
    // 被明确回滚且用户校正过
    hasConfirmedCorrection: boolean;
    // 高分且无疑虑
    highQualityScore: boolean;
    // 重复模式(之前已批准过类似样本)
    matchesApprovedPattern: boolean;
  };
}

class DatasetCollector {
  // 从最近的对话中自动收集样本
  async collectSamples(options: {
    since?: number;
    until?: number;
    sessionId?: string;
    strategy?: DatasetCollectionStrategy;
  }): Promise<CollectionResult>;
  
  // 计算样本质量分数
  private calculateQualityScore(
    sample: DatasetSampleRecord
  ): { score: number; factors: QualityFactors };
  
  // 隐私检查和脱敏
  private async checkPrivacy(content: unknown): Promise<{
    containsPii: boolean;
    redactedContent?: string;
  }>;
  
  // 根据收集策略判断样本类型
  private classifySample(
    turn: ConversationTurn,
    decision: MemoryGateDecisionRecord,
    audit?: WriteGuardianAuditRecord
  ): DatasetSampleRecord["sampleType"] | null;
}

新命令: /reflection dataset

/reflection dataset [subcommand] [options]

Subcommands:
  collect                    从最近的对话中收集样本
  list                       列出已收集的样本
  review                     进入交互式标注模式
  approve <sample-id>        批准样本
  reject <sample-id>         拒绝样本
  export                     导出数据集
  stats                      显示数据集统计
  quality                    质量分析报告

Options for collect:
  --since                    收集起始时间
  --until                    收集结束时间
  --session, -s              指定会话
  --type, -t                 样本类型: all | positive | negative
  --auto-approve             自动批准符合条件的样本
  --dry-run                  预览但不实际收集

Options for list:
  --status                   按标注状态过滤
  --type                     按样本类型过滤
  --quality-above            质量分数下限
  --limit, -l                限制数量
  --needs-review             只显示待审核的

Options for export:
  --format                   导出格式:
                               - alpaca (Alpaca 格式)
                               - sharegpt (ShareGPT 格式)
                               - openai (OpenAI fine-tuning 格式)
                               - raw (原始 JSONL)
                               - csv (CSV 格式)
  --output, -o               输出文件路径
  --train-split              训练集比例(默认: 0.8)
  --include-pii              包含敏感信息(默认: false)
  --only-approved            只导出已批准的样本
  --min-quality              最低质量分数

Options for stats:
  --daily                    按天统计
  --by-type                  按类型统计
  --by-status                按标注状态统计
  --quality-distribution     质量分数分布

Examples:
  /reflection dataset collect --since 2026-03-01 --auto-approve
  /reflection dataset list --needs-review
  /reflection dataset review                              # 交互式审核
  /reflection dataset approve <sample-id> --notes "高质量样本"
  /reflection dataset export --format alpaca -o ./sft-data.jsonl
  /reflection dataset export --format openai --train-split 0.9 --only-approved
  /reflection dataset stats --quality-distribution

导出格式示例

Alpaca 格式:

{
  "instruction": "Based on the following conversation, decide if this should be written to memory and which file it belongs to.",
  "input": "User: I prefer morning check-ins\nAgent: I'll remember that you prefer morning check-ins.",
  "output": "Decision: UPDATE_USER\nFact: prefers morning check-ins\nReason: This is a stable user preference about collaboration cadence."
}

ShareGPT 格式:

{
  "conversations": [
    {
      "from": "human",
      "value": "I prefer morning check-ins"
    },
    {
      "from": "gpt",
      "value": "I'll remember that.",
      "memory_gate_decision": "UPDATE_USER",
      "candidate_fact": "prefers morning check-ins"
    }
  ]
}

架构设计

新增组件

┌─────────────────────────────────────────────────────────────────────────┐
│                          OpenClaw Reflection                            │
├─────────────────────────────────────────────────────────────────────────┤
│   Existing Components              │   New Components (This PRD)        │
│                                    │                                    │
│   ┌──────────────────────┐        │   ┌──────────────────────────────┐ │
│   │  SessionBufferManager │──────▶│   │  SQLiteStorageManager        │ │
│   └──────────────────────┘        │   │  - SQLite 连接管理            │ │
│            │                      │   │  - 事务封装                   │ │
│            ▼                      │   │  - 连接池                     │ │
│   ┌──────────────────────┐        │   └──────────────────────────────┘ │
│   │  MemoryGateAnalyzer  │──────▶│   ┌──────────────────────────────┐ │
│   │  (memory_gate)       │        │   │  ConversationRepository      │ │
│   └──────────────────────┘        │   │  - 会话/对话 CRUD             │ │
│            │                      │   │  - 上下文查询                 │ │
│            ▼                      │   └──────────────────────────────┘ │
│   ┌──────────────────────┐        │   ┌──────────────────────────────┐ │
│   │  WriteGuardian       │──────▶│   │  MemoryGateRepository        │ │
│   │  (write_guardian)    │        │   │  - 决策记录 CRUD              │ │
│   └──────────────────────┘        │   │  - 统计分析                   │ │
│                                   │   └──────────────────────────────┘ │
│                                   │   ┌──────────────────────────────┐ │
│                                   │   │  WriteGuardianRepository     │ │
│                                   │   │  - 审计记录 CRUD              │ │
│                                   │   │  - 文件快照管理               │ │
│                                   │   │  - 回滚操作                   │ │
│                                   │   └──────────────────────────────┘ │
│                                   │   ┌──────────────────────────────┐ │
│                                   │   │  DatasetRepository           │ │
│                                   │   │  - 样本 CRUD                  │ │
│                                   │   │  - 导出管理                   │ │
│                                   │   │  - 质量评分                   │ │
│                                   │   └──────────────────────────────┘ │
│                                   │   ┌──────────────────────────────┐ │
│                                   │   │  JSONLExporter               │ │
│                                   │   │  - 定期导出                   │ │
│                                   │   │  - 格式转换                   │ │
│                                   │   └──────────────────────────────┘ │
└───────────────────────────────────┴─────────────────────────────────────┘

数据流

┌─────────────────────────────────────────────────────────────────────────┐
│                              数据流图                                    │
└─────────────────────────────────────────────────────────────────────────┘

┌──────────────┐
│   用户消息    │
└──────┬───────┘
       │
       ▼
┌──────────────────────┐     ┌─────────────────────────────────────┐
│  SessionBufferManager │────▶│  SQLiteStorageManager               │
│  (保留,用于缓冲)      │     │  - upsertSession()                  │
└──────────────────────┘     │  - insertTurn()                     │
       │                     │  - insertContextBatch()              │
       ▼                     └─────────────────────────────────────┘
┌──────────────────────┐                    │
│  MemoryGateAnalyzer  │                    │
│  (分析决策)           │                    │
└──────────┬───────────┘                    │
           │                                │
    ┌──────┴──────┐                         │
    ▼             ▼                         ▼
┌─────────┐  ┌──────────────────────────────────────────────────────┐
│ NO_WRITE │  │ UPDATE_*                                             │
└─────────┘  │                                                      │
             │  ┌────────────────────────────────────────────────┐  │
             └─▶│  MemoryGateRepository                            │  │
                │  - insertDecision()                              │  │
                └────────────────────────────────────────────────┘  │
                              │                                      │
                              ▼                                      │
                ┌──────────────────────────┐                        │
                │  WriteGuardian            │                        │
                │  (执行写入)                │                        │
                └──────────┬───────────────┘                        │
                           │                                        │
               ┌───────────┼───────────┐                           │
               ▼           ▼           ▼                           │
          ┌────────┐  ┌────────┐  ┌────────┐                       │
          │ written │  │ refused │  │ failed │                       │
          └────┬───┘  └────┬───┘  └────┬───┘                       │
               │           │           │                           │
               ▼           ▼           ▼                           │
    ┌──────────────────────────────────────────────────────┐      │
    │  WriteGuardianRepository                              │◀─────┘
    │  - insertAudit()                                      │
    │  - insertSnapshot()                                   │
    │  - rollback()                                         │
    └──────────────────────────────────────────────────────┘
               │
               ▼
    ┌──────────────────────────────────────────────────────┐
    │  DatasetRepository (自动收集)                         │
    │  - collectFromDecision()                             │
    │  - calculateQualityScore()                           │
    │  - checkPrivacy()                                    │
    └──────────────────────────────────────────────────────┘

完整 ER 图

┌─────────────────┐       ┌─────────────────────────┐       ┌─────────────────────────┐
│    sessions     │       │   conversation_turns    │       │ conversation_contexts   │
├─────────────────┤       ├─────────────────────────┤       ├─────────────────────────┤
│ id (PK)         │◀──────│ session_id (FK)         │       │ turn_id (FK)            │
│ channel_id      │       │ id (PK)                 │◀──────│ id (PK)                 │
│ conversation_   │       │ turn_number             │       │ role                    │
│   target        │       │ user_message_content    │       │ message                 │
│ account_id      │       │ user_message_id         │       │ timestamp               │
│ created_at      │       │ agent_message_content   │       │ sequence_number         │
│ last_activity_at│       │ agent_message_id        │       └─────────────────────────┘
│ metadata        │       │ created_at              │
└─────────────────┘       └─────────────────────────┘
             │                       │
             │                       │
             ▼                       ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                              memory_gate_decisions                                       │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ id (PK)                                                                                 │
│ turn_id (FK) ───────────────────────────────────────────────────────────────────────────┘
│ session_id (FK)                                                                         │
│ decision                                                                                │
│ reason                                                                                  │
│ candidate_fact                                                                          │
│ llm_model, llm_latency_ms, prompt_tokens, completion_tokens, raw_response               │
│ created_at                                                                              │
└─────────────────────────────────────────────────────────────────────────────────────────┘
             │
             │
             ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                            write_guardian_audit                                          │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ id (PK)                                                                                 │
│ decision_id (FK) ───────────────────────────────────────────────────────────────────────┘
│ turn_id (FK)                                                                            │
│ session_id (FK)                                                                         │
│ target_file                                                                             │
│ candidate_fact                                                                          │
│ before_snapshot_id (FK) ─────────┐                                                      │
│ after_snapshot_id (FK) ──────────┼──────┐                                               │
│ diff, lines_added/removed/modified│      │                                               │
│ status, reason                                                                          │
│ llm_model, llm_latency_ms, agent_steps                                                  │
│ created_at                                                                              │
└─────────────────────────────────────────────────────────────────────────────────────────┘
             │                              │                    │
             │                              ▼                    ▼
             │                    ┌─────────────────┐    ┌─────────────────┐
             │                    │ file_snapshots  │    │ file_snapshots  │
             │                    │ (before)        │    │ (after)         │
             │                    ├─────────────────┤    ├─────────────────┤
             │                    │ id (PK)         │    │ id (PK)         │
             │                    │ file_name       │    │ file_name       │
             │                    │ content         │    │ content         │
             │                    │ content_hash    │    │ content_hash    │
             │                    │ created_at      │    │ created_at      │
             │                    └─────────────────┘    └─────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                               rollbacks                                                  │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ id (PK)                                                                                 │
│ audit_id (FK) ──────────────────────────────────────────────────────────────────────────┘
│ rolled_back_at                                                                          │
│ rolled_back_by                                                                          │
│ reason                                                                                  │
│ restored_snapshot_id (FK)                                                               │
│ previous_status                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                            behavior_corrections                                          │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ id (PK)                                                                                 │
│ audit_id (FK) ──────────────────────────────────────────────────────────────────────────┘
│ original_status, original_candidate_fact, original_target_file                          │
│ corrected_status, corrected_candidate_fact, corrected_target_file                       │
│ correction_reason, apply_to_future, correction_pattern                                  │
│ corrected_by, corrected_at                                                              │
└─────────────────────────────────────────────────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                             dataset_samples                                              │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ id (PK)                                                                                 │
│ sample_type                                                                             │
│ turn_id (FK)                                                                            │
│ decision_id (FK) ───────────────────────────────────────────────────────────────────────┘
│ audit_id (FK)                                                                           │
│ session_id (FK)                                                                         │
│ content (JSON)                                                                          │
│ quality_score, quality_factors                                                          │
│ annotation_status, annotated_by, annotated_at, annotation_notes                         │
│ privacy_checked, contains_pii, pii_redacted_content                                     │
│ collection_strategy, created_at                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘

实施建议

阶段一:基础存储(REQ-001)

  1. 引入 SQLite 依赖(better-sqlite3sqlite3
  2. 创建数据库初始化模块(schema migration)
  3. 实现 SQLiteStorageManagerConversationRepository
  4. 修改 SessionBufferManager 将数据写入 SQLite
  5. 确保向后兼容(现有日志功能保留)

阶段二:可观测性(REQ-002, REQ-003)

  1. 创建 MemoryGateRepositoryWriteGuardianRepository
  2. 实现文件快照机制
  3. 实现回滚功能
  4. 添加新的 /reflection 子命令
  5. 创建数据库视图和索引优化查询

阶段三:数据集(REQ-004)

  1. 实现 DatasetRepository
  2. 实现自动收集策略
  3. 实现质量评分算法
  4. 实现隐私检查和脱敏
  5. 添加数据集导出功能

完整配置示例

{
  "enabled": true,
  "config": {
    "workspaceDir": "/path/to/workspace",
    
    // SQLite 存储配置
    "storage": {
      "type": "sqlite",
      "sqlite": {
        "dbPath": ".openclaw-reflection/reflection.db",
        "walMode": true,
        "autoVacuum": true,
        "busyTimeoutMs": 5000,
        "cacheSizePages": -2000,
        "mmapSizeMB": 256
      },
      "retentionDays": 365,
      
      // 可选的 JSONL 导出
      "jsonlExport": {
        "enabled": true,
        "schedule": "0 0 * * *",
        "retentionDays": 30
      }
    },
    
    // memory_gate 审计配置
    "memoryGateAuditing": {
      "enabled": true,
      "storeRawLLMResponse": true,
      "enableStats": true
    },
    
    // write_guardian 审计配置
    "writeGuardianAuditing": {
      "enabled": true,
      "storeFileSnapshots": true,
      "enableRollback": true,
      "maxSnapshotsPerFile": 100
    },
    
    // 数据集收集配置
    "datasetCollection": {
      "enabled": true,
      "autoCollect": true,
      "positiveCriteria": {
        "wasWritten": true,
        "noUserObjection": true,
        "notRolledBackWithinDays": 7,
        "minQualityScore": 0.7
      },
      "negativeCriteria": {
        "gateProposedWrite": true,
        "guardianRefused": true,
        "hasBehaviorCorrection": true
      },
      "privacyFiltering": {
        "enabled": true,
        "redactPatterns": [
          "api[_-]?key",
          "password",
          "token",
          "secret",
          "-----BEGIN.*-----"
        ]
      },
      "qualityScoring": {
        "enabled": true,
        "weights": {
          "conversationClarity": 0.4,
          "decisionConfidence": 0.35,
          "factVerifiability": 0.25
        }
      }
    },
    
    // LLM 配置
    "llm": {
      "baseURL": "https://openrouter.ai/api/v1",
      "apiKey": "YOUR_API_KEY",
      "model": "x-ai/grok-4.1-fast"
    },
    
    // 原有配置
    "memoryGate": {
      "enabled": true,
      "windowSize": 10
    },
    "consolidation": {
      "enabled": false,
      "schedule": "0 2 * * *"
    }
  }
}

依赖项

{
  "dependencies": {
    "better-sqlite3": "^9.4.0",
    "ulid": "^2.3.0"
  }
}
  • better-sqlite3: 高性能 SQLite 驱动,支持同步 API 和 WAL 模式
  • ulid: 生成按时间排序的唯一 ID

待讨论问题

  1. 数据库迁移策略

    • 如何管理 schema 变更?
    • 是否需要 ORM(如 Drizzle ORM)?
  2. 数据保留策略

    • 是否需要自动清理旧数据?
    • 归档策略(热数据 SQLite / 冷数据 JSONL)?
  3. 并发写入

    • 多个 OpenClaw 实例共享同一数据库?
    • WAL 模式是否足够?
  4. 隐私合规

    • 敏感信息自动检测的准确率要求?
    • 用户级别的 opt-out 机制?

附录

A. 数据库初始化 SQL

-- 启用 WAL 模式
PRAGMA journal_mode = WAL;

-- 启用外键约束
PRAGMA foreign_keys = ON;

-- 自动清理碎片
PRAGMA auto_vacuum = INCREMENTAL;

-- 创建所有表(见上文)
-- ...

-- 创建优化索引
CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(last_activity_at);
CREATE INDEX IF NOT EXISTS idx_turns_session_created ON conversation_turns(session_id, created_at);
CREATE INDEX IF NOT EXISTS idx_decisions_session_created ON memory_gate_decisions(session_id, created_at);
CREATE INDEX IF NOT EXISTS idx_audit_session_created ON write_guardian_audit(session_id, created_at);
CREATE INDEX IF NOT EXISTS idx_samples_type_status ON dataset_samples(sample_type, annotation_status);

B. 相关文件路径

组件 路径
Logger src/logger.ts
SessionBufferManager src/session-manager.ts
MemoryGateAnalyzer src/memory-gate/analyzer.ts
WriteGuardian src/write-guardian/index.ts
WriteGuardianAuditLog src/write-guardian/audit-log.ts
MessageHandler src/message-handler.ts
Config src/config.ts

文档版本: 2.0 (SQLite 版) 更新日期: 2026-03-11 作者: Lia for Sirocco

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment