通用记忆同步·跨智能体(v1.0.0)
一、价值与定位
用户在多台设备、甚至多个智能体(WorkBuddy / Claude / Cursor / Codex 等)之间切换时,个人规则、习惯、记忆存储在各智能体的本地记忆目录下,互不连通。本 Skill 把个人记忆当作可携带资产:以本地备份为主——打包为 zip,由用户自行留存与传输。
- 不绑定任何特定云端知识库或网盘,零外部依赖。
- 跨智能体开箱即用:关键改进是文件映射表按检测到的智能体自动选文件名——WorkBuddy 用
MEMORY.md、Claude 用CLAUDE.md、Cursor 用.cursorrules,不再写死单一客户端文件名。 - 备份完成后,用户可把 zip 自行存到个人常用的库(如个人网盘、云笔记、邮件附件等,由用户自选),换设备/换智能体时取出即可恢复。
- Skill 本身也打包在内,形成完整随身包,换机不断档。
- AI 实际执行打包、校验、恢复、备份;用户只需确认范围和路径。
> 本 Skill 是执行层:AI 会调用本地脚本完成文件操作,不自动外发任何数据。
二、触发词
- "本地备份个人记忆" / "导出记忆包" / "打包我的个人规则" / "换机同步记忆" / "备份我的记忆" / "跨设备同步记忆"
- "恢复个人记忆" / "导入记忆包" / "换机恢复记忆" / "把记忆同步到 Claude/Cursor"
三、配置与自动探测
3.1 默认探测表(常见智能体)
AI 首先检查本节是否有用户显式配置的 MEMORY_DIR / SKILL_DIR;若无,则按以下顺序自动探测(取第一个存在且含记忆文件的目录):
| 智能体 | 记忆目录 MEMORY_DIR |
技能目录 SKILL_DIR |
|---|---|---|
| WorkBuddy | ~/.workbuddy |
~/.workbuddy/skills/universal-memory-sync |
| Claude | ~/.claude |
~/.claude/skills/universal-memory-sync |
| Cursor | ~/.cursor |
~/.cursor/skills/universal-memory-sync |
| 通用 | 查阅该智能体文档,定位其"用户级记忆目录" | 该智能体文档指定的技能目录 |
> 探测到多个候选时,列出候选并询问用户使用哪一个;探测失败时,停止并提示用户按 3.2 手动配置。
3.2 手动配置(可选)
如果自动探测不满足,在 Skill 文件顶部或执行前显式声明:
MEMORY_DIR = "<你的智能体用户级记忆目录>" # 例:~/.claude
SKILL_DIR = "<本 Skill 的安装目录>" # 例:~/.claude/skills/universal-memory-sync
EXPORT_DIR = "~/Desktop" # 默认导出目录,可由用户指定
# 可选:显式指定记忆文件名列表(覆盖 3.3 默认映射)
MEMORY_FILES = ["CLAUDE.md"]
3.3 同步文件映射表(按智能体自动取文件名)
这是本技能的核心通用化改进:记忆文件名因智能体而异,不再写死 MEMORY.md。
| 智能体 | 记忆文件名(打包/恢复时使用,自动探测优先) |
|---|---|
| WorkBuddy | MEMORY.md / SOUL.md / USER.md |
| Claude | CLAUDE.md(Claude Code 记忆文件;可选 MEMORY.md) |
| Cursor | .cursorrules(项目级)或 MEMORY.md(用户级,依配置) |
| 通用 | 用户显式提供文件名列表,或默认 MEMORY.md |
> AI 探测到智能体后,自动套用对应文件名;识别失败时请用户确认文件名列表,不写死默认。
> 默认三件全同步(若该智能体只有 1 个记忆文件则只同步存在的);如需收窄或扩展,按用户指示。
四、执行前确认清单(首次运行必做)
AI 在动手前必须向用户确认以下项,确认后再执行:
- 同步范围:自动识别的文件全同步 / 仅核心记忆文件 / 自定义(列出额外文件)。
- 导出/导入路径:默认桌面,可由用户指定。
- 是否打包本 Skill 自身:推荐是,形成完整随身包。
- 隐私提示:zip 含个人敏感信息,请妥善保管。
导入场景额外确认:
5. 覆盖确认:导入会覆盖新设备已有记忆文件,AI 会先自动备份原文件到带时间戳的 zip,再请用户确认是否继续。
五、导出(自动打包)
5.1 前置校验(自动执行)
AI 执行以下检查,任一失败立即按 7.2 给出友好提示:
MEMORY_DIR存在且可读。- 至少有一个待同步文件存在且非空。
EXPORT_DIR存在或可创建,且可写。- 磁盘剩余空间大于待打包文件总大小的 2 倍(预留 zip 膨胀空间)。
5.2 执行脚本(AI 必须实际运行)
AI 使用本地 Python 3 运行以下逻辑(macOS/Linux 可用 python3 <<'PYEOF';Windows 可用 PowerShell 配合 python3 -c 或先写入临时 .py 再执行):
import os, zipfile, shutil, json, tempfile
from pathlib import Path
from datetime import datetime
# 各智能体的记忆文件名映射(核心通用化点)
AGENT_FILE_MAP = {
"workbuddy": ["MEMORY.md", "SOUL.md", "USER.md"],
"claude": ["CLAUDE.md", "MEMORY.md"],
"cursor": [".cursorrules", "MEMORY.md"],
"generic": ["MEMORY.md"],
}
def get_core_files(agent, override=None):
"""返回当前智能体应同步的记忆文件名列表。override 为用户显式指定。"""
if override:
return list(override)
return AGENT_FILE_MAP.get(agent.lower(), AGENT_FILE_MAP["generic"])
def detect_agent(memory_dir):
"""简单探测目录特征,返回 agent 键。可扩展。"""
md = Path(memory_dir).expanduser()
if (md / "MEMORY.md").exists() or (md / "SOUL.md").exists():
# WorkBuddy 同时有 SOUL.md;但 Claude 也可能有 MEMORY.md,需结合目录名
if "claude" in str(md).lower():
return "claude"
if "cursor" in str(md).lower():
return "cursor"
return "workbuddy"
if (md / "CLAUDE.md").exists():
return "claude"
if (md / ".cursorrules").exists():
return "cursor"
return "generic"
def export_pack(memory_dir, skill_dir, export_dir, agent="generic",
extra_files=None, include_skill=True, override_files=None):
memory_dir = Path(memory_dir).expanduser().resolve()
skill_dir = Path(skill_dir).expanduser().resolve() if skill_dir else None
export_dir = Path(export_dir).expanduser().resolve()
extra_files = extra_files or []
if not memory_dir.exists():
raise FileNotFoundError(f"记忆目录不存在:{memory_dir}")
if not os.access(memory_dir, os.R_OK):
raise PermissionError(f"记忆目录无读取权限:{memory_dir}")
core_files = get_core_files(agent, override_files)
files_to_pack = []
missing = []
for name in core_files:
p = memory_dir / name
if p.exists():
if p.stat().st_size > 0:
files_to_pack.append((p, f"memory/{name}"))
else:
missing.append(f"{name}(空文件)")
else:
missing.append(f"{name}(不存在)")
for f in extra_files:
p = Path(f).expanduser().resolve()
if p.exists() and p.stat().st_size > 0:
files_to_pack.append((p, f"memory/{p.name}"))
else:
missing.append(f"{f}(不存在或为空)")
if not files_to_pack:
raise ValueError("没有可打包的非空记忆文件。" + ";".join(missing))
total_size = sum(f[0].stat().st_size for f in files_to_pack)
if include_skill and skill_dir and skill_dir.exists():
total_size += sum(p.stat().st_size for p in skill_dir.rglob("*") if p.is_file())
if shutil.disk_usage(export_dir.parent if not export_dir.exists() else export_dir).free < total_size * 2:
raise OSError(f"导出目录剩余空间不足,需要约 {total_size*2/1024:.1f} KB")
export_dir.mkdir(parents=True, exist_ok=True)
date_str = datetime.now().strftime("%Y%m%d")
zip_path = export_dir / f"个人记忆包_{date_str}.zip"
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for src, arcname in files_to_pack:
zf.write(src, arcname)
if include_skill and skill_dir and skill_dir.exists():
for sub in skill_dir.rglob("*"):
if sub.is_file():
zf.write(sub, f"skill/{sub.relative_to(skill_dir)}")
meta = {
"created": datetime.now().isoformat(),
"memory_dir": str(memory_dir),
"agent": agent,
"files": [arcname for _, arcname in files_to_pack],
"include_skill": include_skill and skill_dir and skill_dir.exists()
}
zf.writestr("meta.json", json.dumps(meta, ensure_ascii=False, indent=2))
with zipfile.ZipFile(zip_path, 'r') as zf:
bad = zf.testzip()
if bad:
raise zipfile.BadZipFile(f"zip 校验失败:{bad}")
if "meta.json" not in zf.namelist():
raise zipfile.BadZipFile("zip 缺少元数据")
return zip_path, files_to_pack, missing
5.3 失败重试
打包/校验失败时,自动重试最多 3 次,间隔 1–2 秒退避。重试前提示"正在重试(第 n 次)…"。重试仍失败 → 按 7.2 给出错误提示,并明确标注失败。
5.4 导出成功报告示例
✅ 已打包个人记忆包(检测到智能体:Claude)
路径:~/Desktop/个人记忆包_20260721.zip
内含:CLAUDE.md、本 Skill 目录
体积:约 36 KB,完整性校验通过
缺失/为空:MEMORY.md(当前智能体未使用)
下一步:可将该 zip 自行存到个人网盘 / 邮件,换设备时取出恢复
六、导入(自动恢复)
6.1 前置校验(自动执行)
- 用户提供或 AI 自动选择最新的 zip 文件存在且可读。
- zip 通过完整性校验且含
meta.json。 - 目标
MEMORY_DIR存在或可创建,且可写。 - 若目标已有记忆文件,先自动备份到
${MEMORY_DIR}/个人记忆_备份_YYYYMMDD_HHMMSS.zip,再提示用户确认覆盖。
6.2 执行脚本(AI 必须实际运行)
import os, zipfile, shutil, json
from pathlib import Path
from datetime import datetime
AGENT_FILE_MAP = {
"workbuddy": ["MEMORY.md", "SOUL.md", "USER.md"],
"claude": ["CLAUDE.md", "MEMORY.md"],
"cursor": [".cursorrules", "MEMORY.md"],
"generic": ["MEMORY.md"],
}
def import_pack(zip_path, memory_dir, skill_dir, agent="generic", override_files=None):
zip_path = Path(zip_path).expanduser().resolve()
memory_dir = Path(memory_dir).expanduser().resolve()
skill_dir = Path(skill_dir).expanduser().resolve() if skill_dir else None
if not zip_path.exists():
raise FileNotFoundError(f"zip 文件不存在:{zip_path}")
with zipfile.ZipFile(zip_path, 'r') as zf:
bad = zf.testzip()
if bad:
raise zipfile.BadZipFile(f"zip 校验失败:{bad}")
if "meta.json" not in zf.namelist():
raise zipfile.BadZipFile("zip 缺少元数据,可能不是本 Skill 生成的包")
meta = json.loads(zf.read("meta.json").decode('utf-8'))
core_files = AGENT_FILE_MAP.get(agent, AGENT_FILE_MAP["generic"]) if not override_files else override_files
memory_dir.mkdir(parents=True, exist_ok=True)
existing = [memory_dir / name for name in core_files if (memory_dir / name).exists()]
if existing:
backup_name = f"个人记忆_备份_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
backup_path = memory_dir / backup_name
with zipfile.ZipFile(backup_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for f in existing:
zf.write(f, f.name)
with zipfile.ZipFile(zip_path, 'r') as zf:
for name in zf.namelist():
if name.startswith("memory/"):
target = memory_dir / Path(name).name
zf.extract(name, memory_dir)
extracted = memory_dir / name
extracted.replace(target)
(memory_dir / "memory").rmdir() if (memory_dir / "memory").exists() else None
if skill_dir and any(n.startswith("skill/") for n in zf.namelist()):
skill_dir.mkdir(parents=True, exist_ok=True)
for name in zf.namelist():
if name.startswith("skill/"):
relative = Path(name).relative_to("skill")
target = skill_dir / relative
target.parent.mkdir(parents=True, exist_ok=True)
zf.extract(name, skill_dir)
extracted = skill_dir / name
extracted.replace(target)
(skill_dir / "skill").rmdir() if (skill_dir / "skill").exists() else None
restored = [memory_dir / Path(f).name for f in meta.get("files", [])]
for f in restored:
if not f.exists() or f.stat().st_size == 0:
raise FileNotFoundError(f"恢复后文件缺失或为空:{f}")
return restored, meta
6.3 失败重试
解压/覆盖/校验失败时,自动重试最多 3 次,间隔 1–2 秒退避。若目标文件被占用,提示用户关闭占用程序后重试。
6.4 导入成功报告示例
✅ 已恢复个人记忆(目标智能体:Claude)
已写入:新设备 ~/.claude/CLAUDE.md
已备份原文件:~/.claude/个人记忆_备份_20260721_154820.zip
已安装:本 Skill 目录(重启客户端生效)
校验:文件存在、非空、元数据完整
请重启 / 刷新智能体客户端使 Skill 生效
七、异常处理与运行稳定性
7.1 自动重试
- 导出/导入的关键步骤(zip 写入、完整性校验、解压覆盖)启用自动重试:最多 3 次,间隔 1–2 秒退避。
- 重试前提示用户"正在重试(第 n 次)…"。
- 重试范围仅限本地瞬时失败(文件被占用、磁盘临时繁忙)。涉及用户侧网络传输(网盘/邮件)不自动重试。
- 重试仍失败 → 按 7.2 给出友好提示,并明确标注失败。
7.2 错误类型 → 友好提示对照表
操作失败时,禁止仅说"请手动处理"。必须给出明确原因 + 可操作下一步:
| 错误场景 | 给用户的话(友好、具体) | 下一步 |
|---|---|---|
MEMORY_DIR 不存在 |
"还没找到记忆目录,可能路径没配置好" | 按第三节配置 MEMORY_DIR 后重试 |
| 待同步文件为空/不存在 | "没有可打包的内容,记忆文件是空的" | 确认是否已在智能体里写过记忆/规则 |
| zip 写入失败 | "打包没成功,可能是磁盘满了或没有写入权限" | 检查磁盘空间、换一个导出路径 |
| 完整性校验不通过 | "生成的 zip 不完整,可能写一半被打断" | 删除残包重新运行,或换导出路径 |
| 解压/覆盖失败 | "恢复没完成,可能目标文件被别的程序占用" | 关闭占用程序(如编辑器)后重试 |
| 空间不足 | "磁盘空间不够打包/恢复" | 清理磁盘或换目标路径 |
| 权限不足 | "没有权限读取/写入目标目录" | 检查目录权限,或换一个路径 |
| 用户取消确认 | "已停止,未做任何改动" | 随时可重新发起 |
> 红线重申:失败必须明确标注失败,绝不暗示 / 谎报成功。
八、自动校验清单(导出/导入后必做)
- zip 文件存在:
zip_path.exists()为真。 - zip 非空:大小 > 0。
- zip 完整性:
ZipFile.testzip()返回None。 - 含 meta.json:便于导入时识别与校验(含 agent 标记)。
- 关键文件非空:恢复后对应智能体记忆文件存在且大小 > 0。
- Skill 目录完整(若打包):恢复后 SKILL.md 存在。
以上任一失败,按 7.2 报错,不报告成功。
九、禁止项(红线)
- 禁止未成功却报成功;禁止让用户"手动复制粘贴"。
- 禁止未经确认覆盖本地记忆(首次运行必须确认,导入前必须自动备份原文件并再次确认)。
- 本地通道:禁止自动外发 zip;禁止解压覆盖无关文件;zip 含个人信息,提醒妥善保管。
- 隐私:备份/导出前须声明内容含敏感个人信息。
- 禁止删除用户原始记忆文件;导入时只能覆盖,且必须先备份。
- 跨智能体:禁止写死单一客户端文件名(如只认
MEMORY.md);必须按检测到的智能体取对应文件名。
十、FAQ 常见疑问
Q1:在 Claude 上用,会自动认 CLAUDE.md 吗?
会。本版按检测到的智能体自动选文件名:WorkBuddy→MEMORY.md/SOUL.md/USER.md,Claude→CLAUDE.md,Cursor→.cursorrules。旧版(personal-memory-sync)只认 MEMORY.md,Claude 用户需手动改;本版开箱即用。
Q2:两台设备记忆冲突以谁为准?
以"最后一次导入方"为准。避免多设备同时改后再各自同步;冲突时先让 AI 导出当前设备完整包,再决定保留哪边。
Q3:导入会不会覆盖新设备已有的记忆?
会覆盖,但 AI 会先把已有记忆自动备份到 ${MEMORY_DIR}/个人记忆_备份_时间戳.zip。导入前会提示你确认,备份完再覆盖。
Q4:换设备后 Skill 没生效?
解压到 SKILL_DIR 后必须重启/刷新智能体客户端,Skill 才会被加载。这是最常见原因。
Q5:zip 体积过大或过小怎么办?
过大:检查是否误打包了工作区或非记忆文件;过小(接近 0):文件可能为空,回到 Q1 排查。正常应在几 KB ~ 几百 KB。
Q6:个人网盘 / 邮件怎么选?
都行,由你自主管控。网盘便于多设备取用,邮件适合点对点传;Skill 不做自动外发,传输由你完成。
Q7:AI 探测不到我的记忆目录或文件名?
按第三节手动配置 MEMORY_DIR / SKILL_DIR,或用 3.2 的 MEMORY_FILES 显式指定文件名列表。
十一、红蓝对抗优化记录(v1.0.0)
红军:主干流程
- 旧版 personal-memory-sync 写死
MEMORY.md,Claude/Cursor 用户需手动改文件名,非开箱即用。 - 改为按智能体自动取文件名:内置
AGENT_FILE_MAP+detect_agent(),导出/导入均按当前智能体用正确文件名。 - 其余流程(自动探测、前置校验、自动打包/解压、完整性校验、失败重试、导入前自动备份)完整保留。
蓝军:挑刺与加固
| 漏洞 | 风险 | 加固方式 |
|---|---|---|
| 自动探测路径可能误判 | 打包错目录 | 探测到多个候选时列出让用户确认;探测失败停止并提示手动配置 |
| 文件名写死单一客户端 | 跨智能体不可用 | 引入 AGENT_FILE_MAP,按探测到的 agent 选文件名;支持 override_files 显式指定 |
| 自动打包含敏感信息 | 隐私泄露 | 导出前列出清单让用户确认;zip 提醒妥善保管 |
| 导入覆盖丢失已有记忆 | 数据丢失 | 导入前自动备份原文件到时间戳 zip,再请用户确认覆盖 |
| 跨平台路径差异 | Windows/Mac/Linux 不兼容 | 使用 Python pathlib.Path.expanduser(),脚本跨平台 |
| 文件被占用 | 解压失败 | 自动重试 3 次;失败提示关闭占用程序 |
| 失败仍报成功 | 信任崩塌 | 自动校验清单全通过才报成功;任一失败明确标注失败 |
结果
- 功能不变(仍为零外部依赖的本地 zip 备份)。
- 核心改进:跨智能体开箱即用,记忆文件名不再写死。
- 保留并强化确认、备份、红线、隐私声明。
- 原版
personal-memory-sync保留不删,用户按需选用。
十二、参考(已验证)
- 本地打包迁移实践:zip 完整性校验、以名称定位、关键章节回读校验、传输边界由用户自主管控。
- v1.0.0 核心升级:跨智能体文件名自动映射(
AGENT_FILE_MAP+detect_agent),其余沿用已验证的自动执行流程。