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

编程 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 路径是否写错。

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

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

deja-vu 将 Claude Code、Codex CLI 和 opencode 本地会话历史索引为可召回记忆的示意图

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

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

ort。

做完同步后,在自托管机器上跑一次:

ssh memory-mini 'deja stats && deja "jwt refresh" --since 30d'

查询词换成你们项目里真实存在、但不含敏感信息的关键词。比如“database migration”“rate limit”“login callback”。不要拿真实客户名、生产 token 前缀、内部密钥名当测试词。

5 查看同步状态和处理冲突:别把“有记录”当成“可复用”

SSH 同步到自托管机器后通过 cpolar 暴露只读状态页进行远程验收的安全边界示意图

同步成功后,远端搜索结果里会出现导入来源。官方 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。这个顺序更稳,也更符合“记忆是资产,不是展示页素材”的原则。

Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务,一站式满足开发者需求,帮助企业提升开发效率并创新开发模式。

更多推荐