WorkBuddy 工作空间备份与恢复
概述
此技能用于完整备份 WorkBuddy 的工作空间数据(包括工作空间文件和对话记录),并在新设备上恢复。WorkBuddy 的数据分布在三处存储位置,必须成套备份才能完整恢复。
数据结构
WorkBuddy 的一个工作空间涉及三处存储位置(workbuddy.db 里含两张必须一起处理的表):
| 数据 | 位置 | 内容 |
|---|---|---|
| 工作空间文件体 | D:\WYX 2\workbuddy-working space\<空间名>\ |
产物、.workbuddy\memory\ 日志 |
| 对话正文 | C:\Users\<用户名>\.workbuddy\projects\<路径编码>\ |
<uuid>.jsonl 逐条消息 + <uuid>\tool-results\ 工具返回原文 |
| 会话元数据 | C:\Users\<用户名>\.workbuddy\workbuddy.db |
sessions 表:id、cwd、标题、模型、时间戳 |
| 工作空间登记 | C:\Users\<用户名>\.workbuddy\workbuddy.db |
workspaces 表:仅两列 path、last_opened_at——决定左侧工作空间列表,缺了它空间不会出现 |
> 两处数据都在同一个 db,恢复时 sessions 和 workspaces 必须一起写,只写 sessions 会导致会话存在但工作空间不显示。
路径编码规则
projects 下的目录名由工作空间绝对路径编码而来:
- 盘符转小写、去掉冒号
- 反斜杠
\换成连字符- - 保留空格和中文
示例(参与编码的是不带尾斜杠的路径):
D:\WYX 2\workbuddy-working space\课题1
→ d-WYX 2-workbuddy-working space-课题1
关键: 路径编码决定了会话能否在新设备上显示。新机器路径必须一致,或复制成新编码名并修改数据库。
备份流程
前置条件
必须完全退出 WorkBuddy。数据库是 WAL 模式,开着程序复制会丢失最近数据。
步骤 1:确定工作空间列表
# 查看所有工作空间的 cwd
cd "C:/Users/<用户名>/.workbuddy"
python3 -c "
import sqlite3
src = sqlite3.connect('file:workbuddy.db?mode=ro', uri=True)
for r in src.execute('SELECT DISTINCT cwd FROM sessions WHERE cwd IS NOT NULL'):
print(r[0])
"
步骤 2:创建备份目录
DEST="/d/backup/workbuddy-$(date +%Y%m%d)"
mkdir -p "$DEST"
步骤 3:备份工作空间文件
对每个工作空间执行:
# 工作空间1
cp -r "D:/WYX 2/workbuddy-working space/课题1/" "$DEST/课题1/workspace/"
# 工作空间2
cp -r "D:/WYX 2/workbuddy-working space/帕金森病黑质—.../" "$DEST/帕金森病黑质—.../workspace/"
步骤 4:备份会话记录
# 会话目录名 = 工作空间路径编码
cp -r "C:/Users/<用户名>/.workbuddy/projects/d-WYX 2-workbuddy-working space-课题1/" \
"$DEST/课题1/sessions/"
步骤 5:导出会话元数据
import sqlite3, csv, os
src = sqlite3.connect('file:workbuddy.db?mode=ro', uri=True)
sessions_cols = [d[1] for d in src.execute('PRAGMA table_info(sessions)')]
# 按工作空间导出
for ws_name in ['课题1', '帕金森病黑质—...']:
sessions = src.execute(f"SELECT * FROM sessions WHERE cwd LIKE '%{ws_name}%'").fetchall()
csv_path = os.path.join(DEST, ws_name, 'sessions', 'sessions_export.csv')
# 注意:还要导出 workspaces 表,见下方「导出工作空间登记」
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(sessions_cols)
w.writerows(sessions)
步骤 6:导出工作空间登记(workspaces 表,别漏)
# workspaces 表只有两列:path, last_opened_at
ws_cols = [d[1] for d in src.execute('PRAGMA table_info(workspaces)')]
with open(os.path.join(DEST, 'workspaces_export.csv'), 'w', newline='', encoding='utf-8') as f:
w = csv.writer(f)
w.writerow(ws_cols)
w.writerows(src.execute('SELECT * FROM workspaces').fetchall())
恢复流程
前置条件
在新机器上:
1. 安装 WorkBuddy 并登录一次(生成 ~\.workbuddy\ 目录)
2. 完全退出 WorkBuddy
方案 A:路径完全一致(推荐)
如果新机器的用户名和目录结构与原机相同:
步骤 1:恢复工作空间文件
# 从备份复制到原位置
cp -r "<备份>/课题1/workspace/课题1/" "D:/WYX 2/workbuddy-working space/课题1/"
步骤 2:恢复会话记录
# 会话目录必须放在 projects 下,名称为路径编码
cp -r "<备份>/课题1/sessions/d-WYX 2-workbuddy-working space-课题1/" \
"C:/Users/<用户名>/.workbuddy/projects/"
步骤 3:启动验证
- 工作空间出现在左侧列表
- 点击历史会话,能看到完整对话
- 工具调用结果正常显示
方案 B:路径不一致(需要改名)
如果新机器的路径不同,需要调整:
步骤 1:确定新路径
旧: D:\WYX 2\workbuddy-working space\课题1
新: D:\NewPath\workbuddy-working space\课题1
步骤 2:重新编码会话目录名
新编码: d-NewPath-workbuddy-working space-课题1
步骤 3:以新编码名复制 sessions 目录
# 直接以「新编码名」复制到 projects 下,一步完成改名 + 就位
cp -r "<备份>/课题1/sessions/d-WYX 2-workbuddy-working space-课题1/" \
"C:/Users/<用户名>/.workbuddy/projects/d-NewPath-workbuddy-working space-课题1"
> 用 cp 不要用 mv:mv 会把备份包里的目录也改名,
> 下次再恢复或换一台机器时备份就废了。
步骤 4:修改数据库记录
-- 更新 sessions 表的 cwd 字段
UPDATE sessions
SET cwd = 'D:\NewPath\workbuddy-working space\课题1'
WHERE cwd LIKE '%课题1%';
-- 同时登记 workspaces 表,否则左侧列表里看不到这个空间
INSERT OR REPLACE INTO workspaces (path, last_opened_at)
VALUES ('D:\NewPath\workbuddy-working space\课题1', <原 last_opened_at>);
> cwd 一律不带尾部反斜杠。 实测 sessions.cwd 和 workspaces.path 存的值
> 都是 D:\...\课题1 这种形式。写成 '...\课题1\' 会导致
> 与 projects 目录编码不一致,会话加载不到。
步骤 5:改写 jsonl 内部的旧路径(关键,容易漏)
改目录名和数据库只解决了「会话归属」,但 jsonl 每条记录里还自带 cwd 字段,
工具返回原文里也硬编码了旧路径。不改写的话,打开会话会去访问已不存在的旧目录。
OLD = r"c:\Users\OldName\WorkBuddy\空间名" # 旧绝对路径(raw string,避免 \U 转义报错)
NEW = r"D:\NewPath\workbuddy-working space\空间名"
# JSON 里反斜杠是双写的,所以要替换「转义形式」和「原始形式」两种
txt = txt.replace(OLD.replace("\\", "\\\\"), NEW.replace("\\", "\\\\")).replace(OLD, NEW)
只替换工作空间路径本身。不要动 C:\Users\<旧用户名>\.workbuddy\... 这类用户级目录引用——
那是历史文本,属于旧机器的环境,改了反而失真。
步骤 6:用列名映射写库,不要按位置写
CSV 导出的列数/列序可能和当前 db 的 sessions 表不一致(实测:CSV 28 列、表 30 列,
缺 buddy_snapshot_id、buddy_binding_json,且 deleted_at/mode 位置不同)。
所以必须按列名构造记录再 INSERT OR REPLACE:
row = {c: raw.get(c) for c in db_cols} # db_cols 来自 PRAGMA table_info(sessions)
cur.execute("INSERT OR REPLACE INTO sessions ({}) VALUES ({})".format(
", ".join(db_cols), ", ".join("?" * len(db_cols))), [row[c] for c in db_cols])
空字符串要转成 NULL,时间戳等整数字段要 int() 转换。
注意事项
- 退出 WorkBuddy 再操作:WAL 模式下开着程序复制会丢失数据
- 路径编码是关键:会话能否显示取决于目录名与 cwd 字段匹配
- 登录态不需复制:
AppData\Roaming\WorkBuddy的登录态重新登录即可 - 无法退出程序时的折中:恢复任务本身就是由 WorkBuddy 执行的,不可能先退出自己。
此时写库前先把workbuddy.db、-wal、-shm三个文件一起复制到安全目录,
再用sqlite3.connect(db, timeout=30)写入(WAL 模式支持并发连接,多进程写安全)。
写完必须重启 WorkBuddy,因为会话列表通常有内存缓存,不重启看不到新恢复的空间。 - Python 脚本里的 Windows 路径:含
\Users、\WYX这类片段的字符串
必须写成 raw string(r"..."),否则触发unicodeescape语法错误——文档字符串同理。 - jsonl 含敏感信息:工具调用里的 API key 会明文存储,备份包注意保密
- 大目录注意:如果工作空间有大型数据(如 data/results),复制需要时间
快速参考
常用命令
# 查看工作空间会话数
sqlite3 ~/.workbuddy/workbuddy.db "SELECT cwd, COUNT(*) FROM sessions GROUP BY cwd"
# 查看特定工作空间的会话
sqlite3 ~/.workbuddy/workbuddy.db "SELECT id, title FROM sessions WHERE cwd LIKE '%课题1%'"
# 编码路径示例
# D:\WYX 2\workbuddy-working space\课题1 → d-WYX 2-workbuddy-working space-课题1
# D:\WYX\WorkBuddy Space\人格模拟 → d-WYX-WorkBuddy Space-人格模拟
备份清单
- [ ] 工作空间目录(workspace/)
- [ ] 会话记录(sessions/,含
<uuid>.jsonl与<uuid>/tool-results|subagents/) - [ ] 会话元数据(
sessions表导出 CSV) - [ ] 工作空间登记(
workspaces表导出 CSV,最容易被漏掉) - [ ] 用户级配置(skills/、mcp.json、settings.json)
- [ ] 记忆文件(MEMORY.md、SOUL.md 等)
恢复清单
- [ ] 工作空间文件落到目标路径,且与备份源逐项 diff 过文件清单
- [ ] sessions 目录以新路径编码放进了
projects/ - [ ] db 里
sessions.cwd与workspaces.path都改成新路径(无尾斜杠) - [ ] jsonl 内部旧路径已重写(用户级目录引用除外)
- [ ] 写库前已备份
workbuddy.db+-wal+-shm - [ ] 重启 WorkBuddy 后确认:空间出现在列表、历史会话可正常打开