TencentDB-Agent-Memory 数据迁移实战:v2 → v3 租户隔离表结构升级与 L2/L3 文件重定位
【免费下载链接】TencentDB-Agent-MemoryTencentDB Agent Memory is a team-level memory hub for AI Agents — turning conversations, docs, and code into four reusable memory assets (Chat Memory, Skill, LLM-Wiki, Code-Graph) that are governed, shared, and equipped across agents and frameworks.项目地址: https://gitcode.com/GitHub_Trending/te/TencentDB-Agent-Memory
本指南完整讲解 TencentDB-Agent-Memory 的 MemoryCore 数据面从 v1.x/v0.x 升级到 v2.0.0+(数据格式 v3)时使用的官方迁移脚本v2-to-v3-migrate.py:包括何时需要迁移、三条核心命令与全部参数、vectors.db六张表的 DDL 级变更细节、L2/L3 文件的 scoped 路径重定位规则,以及脚本的备份、dry-run 与幂等机制。读完你将掌握如何安全地把存量记忆数据升级到支持租户隔离的 v3 格式,并能在升级新版 Gateway 前独立完成迁移演练与故障恢复。
迁移背景:为什么 v3 数据格式需要一次显式升级
MemoryCore 的本地数据默认写入~/.memory-tencentdb/memory-tdai(对应环境变量TDAI_DATA_DIR,见 MemoryCore/README_CN.md)。当用户从旧版本(v1.x 或 v0.x)升级到 v2.0.0+ 时,数据面的表结构和文件布局发生了破坏性变更,因此必须先运行迁移脚本把存量数据升级到 v3 格式,再启动新版 Gateway。全新安装的新版 Gateway 会自动创建 v3 格式数据,不需要执行迁移。
迁移脚本的源码位于 MemoryCore/scripts/migrate-v2-to-v3/v2-to-v3-migrate.py,官方中文说明见 MemoryCore/scripts/migrate-v2-to-v3/README_CN.md。
受影响的数据包括两类:
| 数据对象 | 变更内容 |
|---|---|
vectors.db | 新增team_id、task_id、user_id、agent_id、version等租户隔离字段,并新增审计表与技能相关表 |
scene_blocks/、persona.md、.metadata/ | L2/L3 文件迁移到profiles/子目录下的 scoped 路径 |
⚠️ 迁移前请务必备份整个数据目录,避免意外数据丢失(脚本虽然默认会自动备份vectors.db,但文件部分的迁移同样需要整体备份兜底)。
这一表结构变更是"三维租户隔离"(three-dim tenancy isolation,即 team / agent / user 维度)落地到数据面存储的直接结果。在 SQLite 存储实现 中,l1_records、l0_conversations的表定义注释明确标注了 "user_id / agent_id added for three-dim tenancy isolation",且 FTS 全文索引行中也镜像写入隔离字段,保证召回(recall)后的过滤能跨租户正确隔离。
前置条件与数据目录结构
运行迁移脚本只需满足两点:
- Python 3.8+(脚本仅使用
argparse、os、shutil、sqlite3、sys、time等标准库,无第三方依赖); - 数据目录路径,默认数据目录为
~/.memory-tencentdb/memory-tdai/,该目录下必须存在vectors.db(脚本会先检查文件存在性,不存在则报错退出)。
一个典型的数据目录包含:vectors.db(SQLite 主数据库,含 L0 原始对话、L1 结构化记忆的元数据表与向量表)、scene_blocks/(L2 场景块文件)、persona.md(L3 画像文件)、.metadata/(元数据目录),以及后续迁移生成/使用的profiles/(v3 scoped 路径根目录)。
命令用法与参数说明
迁移脚本提供三条核心用法,官方文档推荐先 dry-run 检查、确认无误后再真正执行:
# 1. 先 dry-run 检查,不实际修改数据 python v2-to-v3-migrate.py /path/to/memory-tdai --dry-run # 2. 确认无误后执行迁移 python v2-to-v3-migrate.py /path/to/memory-tdai # 3. 仅迁移数据库(跳过 L2/L3 文件) python v2-to-v3-migrate.py /path/to/memory-tdai --db-only参数说明:
| 参数 | 说明 |
|---|---|
/path/to/memory-tdai | 数据目录路径,必填,目录下需包含vectors.db |
--dry-run | 仅检查,不实际修改数据库(以只读模式连接) |
--db-only | 仅迁移vectors.db表结构,跳过 L2/L3 文件 |
--no-backup | 跳过自动备份(默认会自动创建.bak.{timestamp}文件) |
在仓库 README 中,两种典型场景(Hermes 与 OpenClaw 插件)的完整示例为(需在 MemoryCore 目录下执行):
# Hermes 场景 python scripts/migrate-v2-to-v3/v2-to-v3-migrate.py ~/.memory-tencentdb/memory-tdai --dry-run python scripts/migrate-v2-to-v3/v2-to-v3-migrate.py ~/.memory-tencentdb/memory-tdai # OpenClaw 场景 python scripts/migrate-v2-to-v3/v2-to-v3-migrate.py ~/.memory-tencentdb/memory-tdai --dry-run python scripts/migrate-v2-to-v3/v2-to-v3-migrate.py ~/.memory-tencentdb/memory-tdaidry-run 模式的行为可以从源码得到精确印证(v2-to-v3-migrate.py#L397-L428):脚本以sqlite3.connect("file:{db_path}?mode=ro", uri=True)的只读 URI 模式打开数据库,遍历l1_records、l0_conversations、l1_fts、l0_fts、memory_audit、skills六张表,打印各自的列数与行数及列名清单;若未指定--db-only,还会检查scene_blocks、.metadata、persona.md三个文件/目录的源与目标存在状态,最后打印DRY-RUN 完成,未做任何修改。因此 dry-run 是安全的预演手段,可用于核对存量数据的规模与结构。
迁移内容一:vectors.db数据库表结构升级
数据库迁移共覆盖六张表,官方文档的变更总览如下:
| 表 | 变更 |
|---|---|
l1_records | 新增team_id、task_id、user_id、agent_id、version字段 |
l0_conversations | 新增team_id、task_id、user_id、agent_id字段 |
l1_fts/l0_fts | 重建 FTS5 索引,增加租户隔离列 |
memory_audit | 新增审计表 |
skills | 新增技能表 |
skill_fts | 新增技能全文索引表 |
l1_records:五个隔离字段 + 存量数据回填 + 复合索引
迁移函数migrate_l1_records(v2-to-v3-migrate.py#L180-L218)先记录迁移前的记录数,然后通过safe_alter(对duplicate column name错误做幂等吞并,见 L168-L177)依次添加字段:
| 字段 | 列定义 | 存量数据回填默认值 |
|---|---|---|
team_id | TEXT DEFAULT '' | default |
task_id | TEXT DEFAULT '' | ''(空串) |
user_id | TEXT NOT NULL DEFAULT 'default' | default |
agent_id | TEXT NOT NULL DEFAULT 'default' | default |
version | INTEGER NOT NULL DEFAULT 0 | 0 |
回填使用UPDATE ... WHERE 列 = '' OR 列 IS NULL的形式,保证老数据全部落入默认租户域;同时把空session_id统一置为default。随后创建 5 个面向隔离查询的复合索引:idx_l1_task_updated(task_id + updated_time)、idx_l1_team_agent_updated(team_id + agent_id + updated_time)、idx_l1_user_agent_session、idx_l1_user_updated、idx_l1_agent_updated。
对照新版 Gateway 的建表语句(sqlite.ts#L609-L630),v3 目标结构中的列默认值与迁移脚本完全一致:team_id TEXT DEFAULT 'default'、user_id TEXT NOT NULL DEFAULT 'default'、agent_id TEXT NOT NULL DEFAULT 'default'、task_id TEXT DEFAULT ''、version INTEGER NOT NULL DEFAULT 0。值得说明的是,新版运行时同样内置了在线迁移逻辑(sqlite.ts#L632-L643),会对缺列的旧库执行幂等ALTER TABLE ADD COLUMN;迁移脚本的意义在于在启动新版 Gateway 之前,把数据库与文件布局一次性升级到位。
l0_conversations:四个隔离字段
migrate_l0_conversations(v2-to-v3-migrate.py#L221-L256)为原始对话表添加team_id、task_id、user_id、agent_id四列(不含version),同样执行默认值回填与 5 个复合索引创建(idx_l0_task、idx_l0_team_agent、idx_l0_user_agent_session、idx_l0_user_recorded、idx_l0_agent_recorded)。
l1_fts / l0_fts:FTS5 不支持 ALTER,必须 DROP 后重建
FTS5 虚拟表不支持ALTER TABLE ADD COLUMN,因此增加租户隔离列只能采用"删除旧表 → 建新表 → 从数据表全量重建索引"的路径。rebuild_fts函数(v2-to-v3-migrate.py#L259-L293)实现了这一逻辑:
- 通过
sqlite_master检查旧 FTS 表是否存在; - 存在时用
PRAGMA table_info读取旧列名,若新列集合已是旧列集合的子集则判定"已包含所有新列,跳过重建"(幂等关键); - 否则
DROP TABLE IF EXISTS后按新 DDL 建表,再INSERT INTO ... SELECT ... FROM 数据表全量重建,并打印重建行数。
新版 L1 FTS 表结构(v2-to-v3-migrate.py#L121-L141)共 17 列,其中正文列content参与分词索引,其余content_original、record_id、type、priority、scene_name、session_key、session_id、team_id、task_id、user_id、agent_id、version、timestamp_str、timestamp_start、timestamp_end、metadata_json全部标记为UNINDEXED(仅存储不索引,用于召回后过滤)。L0 FTS(L146-L161)则包含message_text、message_text_original、record_id、session_key、session_id及四个隔离列与role、recorded_at、timestamp。
这一"FTS5 无法 ALTER、只能 DROP 重建"的技术约束在运行时实现中同样成立:新版 Gateway 的 FTS 版本检查逻辑(sqlite.ts#L3100-L3150)按content_original(v2 标记)、user_id/agent_id(v3 标记)、version(v4 标记)、task_id(v5 标记)逐级判断 FTS 版本,一旦发现落后就DROP TABLE IF EXISTS l1_fts/l0_fts并触发全量重建,与迁移脚本的rebuild_fts思路一致。
memory_audit:全新审计表
MEMORY_AUDIT_DDL(v2-to-v3-migrate.py#L49-L63)创建审计表,关键设计点:
layer TEXT NOT NULL CHECK (layer IN ('L1','L2','L3'))—— 记录被审计的记忆层级;action TEXT NOT NULL CHECK (action IN ('update','delete'))—— 仅审计更新与删除两类写操作;- 携带完整的隔离维度
team_id、agent_id、user_id、task_id与version、updated_at_ms、request_id; - 配套 3 个索引:
idx_memory_audit_record(record_id + updated_at_ms)、idx_memory_audit_isolation(四元组隔离查询)、idx_memory_audit_time(时间范围扫描)。
审计表的消费端同样存在于新版存储实现中——sqlite.ts#L3308 附近可见INSERT OR REPLACE INTO memory_audit的写入口及对应的审计查询语句,迁移脚本负责把这张表"凭空"建立起来,使旧库升级后立即具备审计能力。
skills 与 skill_fts:全新技能存储
SKILLS_DDL(v2-to-v3-migrate.py#L71-L93)创建技能主表,采用"单表多行多版本"模型:每行是(skill_id, version)的一个不可变快照,is_head标记当前生效版本,manifest_json、storage_dir、status、metadata_json等列承载技能元数据与内容存储位置。配套 6 个索引中最关键的是部分唯一索引:
CREATE UNIQUE INDEX IF NOT EXISTS uniq_skills_team_agent_name_head ON skills(team_id, owner_agent_id, name) WHERE is_head=1 AND status='active';它保证"同一团队、同一拥有者 Agent 下,处于 active 状态的 head 版本技能名唯一"。SKILL_FTS_DDL(L104-L116)创建技能全文索引,name、description、content参与索引,隔离维度列全部UNINDEXED,分词器为unicode61 remove_diacritics 1。
这两张表的 DDL 与新版运行时的 Skill 存储 DDL 常量(SKILLS_DDL见 L21-L65,SKILL_FTS_DDL见 L71-L83)逐字对应,迁移脚本相当于把运行时依赖的表结构提前预置进存量数据库。
迁移内容二:L2/L3 文件迁移到 scoped 路径
L2(场景)/L3(画像)文件在 v3 中不再直接位于数据目录根部,而是迁入带 URL 编码作用域标识的profiles/子目录:
| 源路径 | 目标路径 |
|---|---|
{data_dir}/scene_blocks/ | {data_dir}/profiles/team%3Adefault%7Cagent%3Adefault/scene_blocks/ |
{data_dir}/persona.md | {data_dir}/profiles/team%3Adefault%7Cagent%3Adefault/persona.md |
{data_dir}/.metadata/ | {data_dir}/profiles/team%3Adefault%7Cagent%3Adefault/.metadata/ |
目标目录名team%3Adefault%7Cagent%3Adefault是team:default|agent:default的 URL 编码形式(:→%3A,|→%7C),即"默认团队 × 默认 Agent"的 scoped 路径——这正是 v3 将 L2/L3 文件从"全局单份"改造为"按租户作用域隔离存放"的体现。这一设计在运行时得到了印证:召回侧通过profiles/${encodeURIComponent(profileScope)}/构造作用域存储路径(见 MemoryCore/src/core/hooks/auto-recall.ts#L173),而persona.md、scene_blocks/的读取逻辑(如 persona-trigger.ts)依旧按dataDir下的相对路径工作,二者通过 scoped 目录衔接。
migrate_l2_l3_files(v2-to-v3-migrate.py#L316-L361)的实现要点:
- 复制而非移动:目录用
shutil.copytree递归复制,文件用shutil.copy2复制(保留元数据),源文件永远不会被删除,这是迁移失败可回退的保证; - 目标已存在则跳过:目标目录/文件已存在时打印"已存在,跳过",配合数据库侧的幂等判断,保证脚本可重复执行;
- 若某源路径不存在(如从未生成过
persona.md),打印"不存在,跳过",不报错。
脚本的工程化细节:备份、WAL 与执行时序
main函数(v2-to-v3-migrate.py#L364-L495)展示了完整的安全执行时序:
- 路径校验:
vectors.db不存在直接sys.exit(1),避免对错误目录操作; - dry-run 分支:只读连接检查后直接返回,绝不写库;
- 自动备份(除非
--no-backup):以 UTC 时间戳命名vectors.db.bak.{YYYYMMDD_HHMMSS},shutil.copy2复制; - WAL checkpoint:先执行
PRAGMA wal_checkpoint(TRUNCATE);将 WAL 日志落盘并截断,确保后续直接连接能看到全部已提交数据; - 正式迁移:
PRAGMA journal_mode = WAL;开启 WAL 后依次执行 L1 → L0 → FTS 重建 → 新建表,最后commit(); - 文件迁移:未指定
--db-only时执行 L2/L3 文件复制; - 耗时统计:打印
迁移完成! 耗时: X.XXs。
脚本头部注释还明确列出了刻意不处理的对象,理解这些边界可避免迁移后产生困惑:
skill_vec:vec0 虚拟表,其维度依赖运行时 embedding dimensions 参数,由 v3 服务启动时自动创建(仅当dimensions > 0时创建,对应 skill-store-ddl.ts#L92-L97 中SKILL_VEC_DDL_TEMPLATE的__DIM__占位符机制);metadata.db:独立数据库,由管控面(控制面)创建和维护;l1_vec/l0_vec/embedding_meta:表结构无变更,无需处理。
常见问题
Q: 迁移失败了怎么办?
脚本默认在迁移前自动备份vectors.db(生成vectors.db.bak.{timestamp}文件);L2/L3 文件采用复制而非移动,源文件不会被删除。如果迁移失败,直接用备份文件替换vectors.db、删除或保留重复的profiles/目录即可恢复原状。更稳妥的做法是迁移前手动整体备份整个数据目录。
Q: 可以重复执行吗?
可以。脚本是幂等的:safe_alter对已存在列打印"字段已存在,跳过"并吞掉duplicate column name错误;rebuild_fts在旧 FTS 已包含全部新列时跳过重建;migrate_l2_l3_files对已存在的目标文件/目录跳过复制。重复执行不会产生重复数据或报错。
Q: 全新安装需要跑迁移吗?
不需要。迁移脚本仅用于从旧版(v1.x)升级到新版(v2.0.0+)的存量用户。全新安装的新版 Gateway 会按 v3 格式自动创建表结构与profiles/目录布局(包括运行时内置的 FTS 在线迁移逻辑,sqlite.ts#L3162 附近的rebuildFtsIndex会在检测到数据时自动全量重建索引)。
迁移执行核对清单
完成升级前建议按以下顺序操作:
- 备份整个数据目录(含
vectors.db、scene_blocks/、persona.md、.metadata/); - 运行
python v2-to-v3-migrate.py <data_dir> --dry-run,核对六张表的列/行数与三个 L2/L3 文件的源/目标状态; - 执行正式迁移
python v2-to-v3-migrate.py <data_dir>,确认输出包含"备份""WAL checkpoint""各表迁移完成""迁移完成! 耗时"等关键日志; - 抽查结果:
sqlite3 vectors.db "PRAGMA table_info(l1_records)"应包含五个新字段;ls profiles/team%3Adefault%7Cagent%3Adefault/应看到scene_blocks/、persona.md、.metadata/; - 再启动新版 Gateway,随后即可正常读取 v3 格式的存量记忆数据。
整个迁移链路以官方文档为主体、以 迁移脚本 的源码细节和 SQLite 存储实现 的运行时行为为佐证:一次成功的 v2 → v3 迁移,本质上就是把租户隔离能力"下沉"到每一行记录、每一个 FTS 索引项和每一份 L2/L3 文件路径中,让升级后的 MemoryCore 在团队级多 Agent 共享场景下具备可隔离、可审计、可检索的完整数据底座。
【免费下载链接】TencentDB-Agent-MemoryTencentDB Agent Memory is a team-level memory hub for AI Agents — turning conversations, docs, and code into four reusable memory assets (Chat Memory, Skill, LLM-Wiki, Code-Graph) that are governed, shared, and equipped across agents and frameworks.项目地址: https://gitcode.com/GitHub_Trending/te/TencentDB-Agent-Memory
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考