编程 Agent 记忆怎么自托管?deja-vu 用 SSH 同步后,再用 cpolar 远程检查记忆状态
编程 Agent 记忆怎么自托管?deja-vu 用 SSH 同步后,再用 cpolar 远程检查记忆状态


Agent 用久了,最烦的不是它不会写代码,而是同一个坑隔几天又问一遍。
上周刚排过的数据库连接池问题、某个项目里约定好的目录结构、一次失败的迁移方案,明明都在 Claude Code、Codex CLI、opencode 的历史记录里,但新开一个会话时,Agent 还是像第一次见这个项目。
deja-vu 这类编程 Agent 记忆层,解决的就是这个问题:不额外接一个云端记忆平台,而是把本机已有的会话日志索引起来,让 Agent 能搜索、召回、共享和同步过去的经验。
这篇不把它写成“装完就万事大吉”的爽文。记忆同步这件事有安全边界:会话里经常夹着接口路径、内部域名、排错记录,甚至误写进去的 token。我们要做的是本地索引、自托管同步、只读检查,验收完立刻收口。
1 什么是 Agent 记忆同步?这篇只解决一个具体场景
deja-vu 官方仓库里的定位很直接:它是 coding agents 的 memory layer,会索引 Claude Code、Codex CLI、opencode 已经写在本地的会话日志,再提供搜索、MCP recall、自动上下文、脱敏、统计、共享和同步能力。
它不是模型,也不是新的 Agent 平台。更像一个夹在“本地会话历史”和“Agent 当前上下文”之间的只读记忆索引。
这篇默认场景是:
- 主力开发机上跑 Claude Code、Codex CLI 或 opencode;
- 会话历史已经落在本地磁盘;
- 家里或办公室有一台自托管机器,比如 Mac mini、Linux 小主机、NAS 旁边的一台开发机;
- 想把 Agent 记忆同步过去,换机器时还能查到旧上下文;
- 团队成员只需要远程确认同步状态,不需要拿到记忆数据库和 SSH 权限。
划重点:远程检查只看状态,不看原始记忆内容。
记忆状态页里只放同步批次、更新时间、来源机器、统计数字、脱敏后的最近记录 ID。不要把完整会话、API Key、内部地址、客户信息展示出去。
2 环境准备:先把权限边界画清楚
开始前先准备两台机器:
- 本地开发机:已经有 Agent 会话历史,安装 deja-vu;
- 自托管机器:用于接收同步后的记忆,也安装 deja-vu;
- 两台机器之间可以用 SSH 连接;
- 自托管机器本地跑一个只读状态页,端口用
8765; - cpolar 只映射这个只读状态页,不映射 SSH、数据库、缓存服务和任何管理后台。
这里别偷懒直接开放 SSH。记忆同步走 SSH,是机器之间的受控传输;给同事验收状态,走只读 HTTP 页面就够了。
建议单独给同步准备一个 SSH key,权限只用于登录自托管机器上的同步账号。这个账号不要给 sudo 权限,也不要复用你平时登录服务器的主 key。
本地生成一把专用 key:
ssh-keygen -t ed25519 -f ~/.ssh/deja_sync_ed25519 -C "deja-sync"
把公钥加入自托管机器的 ~/.ssh/authorized_keys 后,在本地 ~/.ssh/config 里写一个别名:
Host memory-mini
HostName 192.168.1.50
User deja
IdentityFile ~/.ssh/deja_sync_ed25519
IdentitiesOnly yes
做完后只测一件事:本地能不能免密连到这台机器。
ssh memory-mini 'hostname && deja --version'
如果这里卡住,先别碰 cpolar。优先检查三处:自托管机器 SSH 服务是否开启、公钥是否写进了正确用户的 authorized_keys、IdentityFile 路径是否写错。

3 安装 deja-vu,并确认本机能读到 Agent 历史

deja-vu 官方 README 给了几种安装方式。macOS、Linux 上可以用安装脚本,也可以用 Go、npm 或 Homebrew。
curl -fsSL https://raw.githubusercontent.com/vshulcz/deja-vu/main/install.sh | sh
如果你不想跑安装脚本,也可以选一种自己更习惯的方式:
go install github.com/vshulcz/deja-vu/cmd/deja@latest
npx @vshulcz/deja-vu "database migration"
brew install vshulcz/tap/deja-vu
安装后先别急着同步,先在本机查一下来源。官方说明里,deja-vu 支持的默认来源包括 Claude Code 的 ~/.claude/projects/**/*.jsonl、Codex CLI 的 ~/.codex/sessions/** 和 history.jsonl,以及 opencode 的本地数据库。
deja sources
deja stats
这里应该能看到已发现的 stores、消息数量、脱敏统计等信息。deja stats 也支持 JSON 输出,后面做只读状态页会用到。
deja stats --json > /tmp/deja-stats.json
提醒一下:deja-vu 会在索引阶段对常见敏感值做脱敏,比如 API key、JWT、Bearer token、PEM private key、ghp_、sk-、npm_ 等形式的 token。这个设计很有用,但不要把它当成“可以放心乱同步”的理由。
原始 Agent 日志仍然在原来的目录里。脱敏发生在 deja-vu 的索引、共享、同步导出路径上,不等于原始日志已经被清理。
4 用 SSH 同步到自托管机器
deja-vu 的同步方式比较朴素:可以导出到一个共享目录,再在另一台机器导入;也可以直接用 SSH。官方 README 里给出的 SSH 方式是:
deja sync ssh memory-mini
这条命令会通过系统里的 ssh/scp 去连接远端,并在远端调用 deja-vu。远端需要能在 PATH 里找到 deja,官方说明里也写到会回退查找 ~/.local/bin/deja。
如果要从自托管机器把新增记忆拉回当前机器,用:
deja sync ssh memory-mini --pull
这一步不是为了“备份全部聊天记录”,而是把已经脱敏、适合进入记忆层的数据做跨机器同步。deja-vu 的同步批次是 JSONL,导入是幂等的,同一批重复导入不会反复制造重复记忆。
如果你更喜欢共享目录,也可以按官方方式拆成两步:
deja sync export ~/Sync/deja
deja sync import ~/Sync/deja
这适合已经有 Syncthing、iCloud Drive、内网共享目录的团队。我的建议是:两台固定机器之间用 SSH;多台设备之间已有同步盘,再用 export/imp

ort。
做完同步后,在自托管机器上跑一次:
ssh memory-mini 'deja stats && deja "jwt refresh" --since 30d'
查询词换成你们项目里真实存在、但不含敏感信息的关键词。比如“database migration”“rate limit”“login callback”。不要拿真实客户名、生产 token 前缀、内部密钥名当测试词。
5 查看同步状态和处理冲突:别把“有记录”当成“可复用”

同步成功后,远端搜索结果里会出现导入来源。官方 README 里提到,同步来的 session 会以 imported:<project> 的形式出现在 search、recall 和 session-start auto-recall 里。
状态检查我通常看四个点:
deja stats的总量是否增加;deja sources是否能看到当前机器自己的来源;- 用一个已知关键词搜索,远端能不能查到同步来的记录;
- 最近一次同步时间是否符合预期。
这里有个容易误会的点:deja-vu 的同步是 append-only、watermarked、idempotent,它解决的是“批次不要重复导入、记录不要回流到来源机器”这类同步层问题。
真正的语义冲突,还得靠人判断。
比如 A 机器上的 Agent 记着“登录回调要走 /api/callback”,B 机器上的旧记录里写着“登录回调走 /auth/callback”。这不是同步工具能替你拍板的事。正确做法是把两条记录都查出来,看时间、看项目、看对应代码提交,再决定哪条可以复用。
可以用下面几条命令辅助排查:
# 查最近记录
deja last 10
# 查看某条 session
deja show <session-id>
# 生成脱敏摘要给同事确认
deja share <session-id>
# 用更窄的范围搜索,减少旧记录干扰
deja "login callback" --project api --since 14d
注意:deja share 输出的是脱敏摘要,但发给同事前仍然要扫一眼。内部域名、业务策略、客户名称这类信息,不一定都属于工具能识别的“密钥”。
6 做一个最小只读状态页,只展示同步健康度
团队远程验收时,不要把 deja-vu 的完整查询能力开放出去。更稳的方式是本地生成一个静态 JSON,再用一个很小的只读 HTTP 服务展示。
先在自托管机器上准备目录:
mkdir -p ~/deja-status
cd ~/deja-status
写一个刷新脚本,只把统计信息和同步时间写进 JSON。这里不包含原始记忆内容。
cat > refresh-status.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
deja stats --json > stats.json
python3 - <<'PY'
import json
from pathlib import Path
from datetime import datetime, timezone
stats = json.loads(Path('stats.json').read_text())
public = {
'service': 'deja-memory-status',
'updated_at': datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
'stats': stats,
'note': 'read-only status page; no raw memories, no secrets, no API keys'
}
Path('status.json').write_text(json.dumps(public, ensure_ascii=False, indent=2))
PY
EOF
chmod +x refresh-status.sh
./refresh-status.sh
再写一个只读页面:
cat > index.html <<'EOF'
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>deja memory sync status</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif; margin: 32px; line-height: 1.6; }
pre { background: #111827; color: #e5e7eb; padding: 16px; border-radius: 10px; overflow: auto; }
.warn { color: #b45309; }
</style>
</head>
<body>
<h1>deja memory sync status</h1>
<p class="warn">只读状态页:不展示原始记忆、不展示密钥、不提供查询入口。</p>
<pre id="status">loading...</pre>
<script>
fetch('./status.json', { cache: 'no-store' })
.then(r => r.json())
.then(data => { document.querySelector('#status').textContent = JSON.stringify(data, null, 2); })
.catch(err => { document.querySelector('#status').textContent = String(err); });
</script>
</body>
</html>
EOF
python3 -m http.server 8765 --bind 127.0.0.1
这里故意绑定 127.0.0.1。也就是说,这个页面只在自托管机器本机可访问,不直接监听局域网所有网卡。
在自托管机器上另开一个终端检查:
curl http://127.0.0.1:8765/status.json
如果返回 JSON,说明只读状态页已经跑起来了。如果打不开,先检查当前目录里有没有 index.html 和 status.json,再检查 python3 -m http.server 是否仍在运行。
7 用 cpolar 临时开放状态页,验收完就关闭
现在才轮到 cpolar。它在这篇里的作用很窄:把自托管机器本地的 127.0.0.1:8765 临时变成一个公网可访问的 HTTP 地址,方便团队或远程开发机看状态。
不要用 cpolar 暴露 SSH 本身,不要暴露 deja-vu 的索引目录,不要暴露任何带写能力的 API。
在自托管机器上执行:
cpolar http 8765
命令运行后,cpolar 会输出公网访问地址。也可以打开本机 Web UI http://127.0.0.1:9200,在“状态 → 在线隧道列表”里查看当前在线隧道。
免费随机地址适合这种短时验收;按照 cpolar 已确认规则,免费套餐生成的公网地址是随机临时地址,24 小时内会变化。要长期固定 HTTP 地址,需要基础套餐或以上的固定二级子域名。本文这个场景不建议长期开放,临时随机地址反而更合适。
把生成的地址发给团队前,再过一遍检查清单:
- 页面只展示
status.json,没有搜索框、上传入口、写接口; status.json没有原始 prompt、完整回答、token、内部客户名;- SSH 端口没有通过 cpolar TCP 暴露;
- deja-vu 的缓存目录、同步目录、原始 Agent 日志目录没有被 HTTP 服务托管;
- 验收时间窗口明确,比如 30 分钟或 1 小时。
验收结束后,直接停止前台运行的 cpolar 进程。状态页也可以一起关掉:
# 在 cpolar 终端按 Ctrl+C
# 在 Python HTTP Server 终端按 Ctrl+C
如果你是用 Web UI 创建的隧道,就到隧道列表里停止对应 HTTP 隧道。这里别留尾巴,记忆状态页虽然是只读,也不该长期挂在公网入口上。
8 总结
这一套做完后,你拿到的是一个自托管的 Agent 记忆同步链路:本地 Agent 历史由 deja-vu 索引,脱敏后的记忆批次通过 SSH 同步到自托管机器,团队验收时只看一个只读状态页,cpolar 只负责短时提供公网访问入口。
关键点别搞反:
- deja-vu 负责本地索引、搜索、共享和同步,SSH 负责机器之间的受控传输;
- cpolar 只开放
8765这个只读状态页,不开放 SSH、数据库、记忆目录和 API Key; - 同步状态看统计、来源、更新时间和少量脱敏摘要,语义冲突仍然要结合项目时间线人工判断。
如果只是自己两台机器用,做到 SSH 同步和本机状态页就够了;如果团队需要远程验收,再临时打开 cpolar。这个顺序更稳,也更符合“记忆是资产,不是展示页素材”的原则。
葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。
更多推荐



所有评论(0)