构建 OpenClaw 插件:会话历史记录器

详细介绍如何开发一个 OpenClaw 插件来实现会话历史记录功能。

本文摘要

详细介绍如何开发一个 OpenClaw 插件来实现会话历史记录功能。

OpenClaw 是一个强大的 AI agent 框架,支持自定义插件扩展功能。本文将介绍如何构建一个实用插件:会话历史记录器,将对话记录持久化到 SQLite,便于后续分析和搜索。

为什么需要这个插件?

默认情况下,AI agent 对话存在于内存中,最终会被压缩或丢失。为了调试、分析或知识提取,你需要持久化存储。会话记录器插件在压缩发生前捕获每条消息。

插件架构

OpenClaw 插件使用基于钩子的架构。我们需要的关键钩子:

// 注册钩子
api.on("before_compaction", async (event, ctx) => {
  // 压缩前保存所有记录
  await autoImport(event, ctx, "before_compaction");
});

api.on("after_compaction", async (event, ctx) => {
  // 导入压缩期间生成的新记录
  await autoImport(event, ctx, "after_compaction");
});

api.on("agent_end", async (event, ctx) => {
  // agent 结束时的最终捕获
  await autoImport(event, ctx, "agent_end");
});

增量导入策略

简单的方法是每次重新导入整个记录。相反,我们跟踪每个会话已导入的行数:

// 跟踪每个会话的已导入行数
const lineCounts = {};

function getImportedLines(db, sessionId) {
  if (!lineCounts[sessionId]) {
    const row = db.prepare(
      "SELECT COUNT(*) as cnt FROM transcript_entries WHERE session_id = ?"
    ).get(sessionId);
    lineCounts[sessionId] = row ? row.cnt : 0;
  }
  return lineCounts[sessionId];
}

这样,每次钩子调用只处理新条目——对于长时间运行的会话效率更高。

SQLite 表结构

简单但有效:

CREATE TABLE IF NOT EXISTS transcript_entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  session_id TEXT NOT NULL,
  role TEXT NOT NULL,
  kind TEXT DEFAULT "message",
  content TEXT,
  model TEXT,
  timestamp TEXT,
  created_at TEXT DEFAULT (datetime("now"))
);

CREATE INDEX IF NOT EXISTS idx_session_id
  ON transcript_entries(session_id);
CREATE INDEX IF NOT EXISTS idx_timestamp
  ON transcript_entries(timestamp);

关键经验

  • 钩子时机很重要before_compaction 至关重要,因为这是在内容被摘要之前捕获完整对话的最后机会。
  • 增量导入 防止重复数据,减少每次钩子触发时的 I/O。
  • 良好的日志 — 当零条记录被导入时,记录路径和现有记录数以便快速诊断问题。
  • agent_end 钩子 作为安全网,捕获不触发压缩的会话。

这个插件已在生产环境运行数天,可靠地捕获对话历史,即使经历多次压缩周期。SQLite 数据库使得用标准 SQL 查询历史对话变得简单。

0 0 投票数
文章评级
订阅评论
提醒
guest

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理

0 评论
最多投票
最新 最旧
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x