已对照
rohitg00/agentmemory源码核对(npm 最新0.9.26,引擎锁定iii v0.11.2,Apache-2.0)。架构 :NAS 跑一个 全功能 agentmemory 服务器(数据在
/data卷)。
- API(3111):经 Tailscale sidecar 接入 tailnet → 多设备/远程访问,不暴露局域网/公网。
- viewer(3113):发布到 NAS 局域网 → 同一内网用浏览器直接打开。
- 压缩 LLM :走 OpenAI 兼容通道接 DeepSeek
deepseek-v4-flash。- 每台开发机只装 Claude Code 插件 + 轻量 MCP shim,经 tailnet 指向 NAS,共享同一份记忆。
⚠️ 安全须知 :viewer(3113)是免密特权界面 (它代理请求时自动附带你的
AGENTMEMORY_SECRET)。绑到局域网意味着同一内网任何设备都能全权读写你的记忆。家庭内网可接受;公共/办公网请改用 SSH 隧道(见附录 B)。
下面是整体架构图,帮你快速理解各组件之间的关系:
#mermaid-svg-e3TSNShAR81RzPBy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-e3TSNShAR81RzPBy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-e3TSNShAR81RzPBy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-e3TSNShAR81RzPBy .error-icon{fill:#552222;}#mermaid-svg-e3TSNShAR81RzPBy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-e3TSNShAR81RzPBy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-e3TSNShAR81RzPBy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-e3TSNShAR81RzPBy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-e3TSNShAR81RzPBy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-e3TSNShAR81RzPBy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-e3TSNShAR81RzPBy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-e3TSNShAR81RzPBy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-e3TSNShAR81RzPBy .marker.cross{stroke:#333333;}#mermaid-svg-e3TSNShAR81RzPBy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-e3TSNShAR81RzPBy p{margin:0;}#mermaid-svg-e3TSNShAR81RzPBy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-e3TSNShAR81RzPBy .cluster-label text{fill:#333;}#mermaid-svg-e3TSNShAR81RzPBy .cluster-label span{color:#333;}#mermaid-svg-e3TSNShAR81RzPBy .cluster-label span p{background-color:transparent;}#mermaid-svg-e3TSNShAR81RzPBy .label text,#mermaid-svg-e3TSNShAR81RzPBy span{fill:#333;color:#333;}#mermaid-svg-e3TSNShAR81RzPBy .node rect,#mermaid-svg-e3TSNShAR81RzPBy .node circle,#mermaid-svg-e3TSNShAR81RzPBy .node ellipse,#mermaid-svg-e3TSNShAR81RzPBy .node polygon,#mermaid-svg-e3TSNShAR81RzPBy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-e3TSNShAR81RzPBy .rough-node .label text,#mermaid-svg-e3TSNShAR81RzPBy .node .label text,#mermaid-svg-e3TSNShAR81RzPBy .image-shape .label,#mermaid-svg-e3TSNShAR81RzPBy .icon-shape .label{text-anchor:middle;}#mermaid-svg-e3TSNShAR81RzPBy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-e3TSNShAR81RzPBy .rough-node .label,#mermaid-svg-e3TSNShAR81RzPBy .node .label,#mermaid-svg-e3TSNShAR81RzPBy .image-shape .label,#mermaid-svg-e3TSNShAR81RzPBy .icon-shape .label{text-align:center;}#mermaid-svg-e3TSNShAR81RzPBy .node.clickable{cursor:pointer;}#mermaid-svg-e3TSNShAR81RzPBy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-e3TSNShAR81RzPBy .arrowheadPath{fill:#333333;}#mermaid-svg-e3TSNShAR81RzPBy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-e3TSNShAR81RzPBy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-e3TSNShAR81RzPBy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-e3TSNShAR81RzPBy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-e3TSNShAR81RzPBy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-e3TSNShAR81RzPBy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-e3TSNShAR81RzPBy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-e3TSNShAR81RzPBy .cluster text{fill:#333;}#mermaid-svg-e3TSNShAR81RzPBy .cluster span{color:#333;}#mermaid-svg-e3TSNShAR81RzPBy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-e3TSNShAR81RzPBy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-e3TSNShAR81RzPBy rect.text{fill:none;stroke-width:0;}#mermaid-svg-e3TSNShAR81RzPBy .icon-shape,#mermaid-svg-e3TSNShAR81RzPBy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-e3TSNShAR81RzPBy .icon-shape p,#mermaid-svg-e3TSNShAR81RzPBy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-e3TSNShAR81RzPBy .icon-shape .label rect,#mermaid-svg-e3TSNShAR81RzPBy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-e3TSNShAR81RzPBy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-e3TSNShAR81RzPBy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-e3TSNShAR81RzPBy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 云端
NAS 局域网
Tailscale Tailnet
NAS(群晖/任意 Linux)
http://NAS_LAN_IP:3113
OpenAI 兼容
agentmemory 服务器
API(3111) + viewer(3113)
Tailscale sidecar
ts-agentmemory
/data 卷
记忆持久化
开发机 A
Claude Code + MCP shim
开发机 B
Claude Code + MCP shim
浏览器
访问 viewer(3113)
DeepSeek API
deepseek-v4-flash
0. 前置准备
bash
# 在 NAS 上(SSH;需 Docker / Container Manager / Portainer 能跑 compose)
cd /volume1/docker # 群晖示例,换成你的路径
git clone --depth 1 https://github.com/rohitg00/agentmemory.git
Tailscale auth key :登录 https://login.tailscale.com/admin/settings/keys → Auth keys →
Generate auth key,设置:
- Reusable:开(容器重建可再次认证)
- Ephemeral:关(NAS 是常驻节点,别让它离线被清除)
- Expiration:默认 90 天即可(只在首次认证用一次)
- Tags:可不打
生成的 tskey-auth-xxxx 一会儿填进 .env。
收尾 :容器连上后,去 Tailscale 后台 Machines → 找到 agentmemory 节点 → Disable key expiry ,
否则约 180 天后节点会因 node key 过期掉线。
DeepSeek API key (用于压缩):登录 https://platform.deepseek.com → API keys → 创建,得到 sk-xxxx。
(key 必须你自己申请;别人无法提供。)
1.(推荐)定制 Dockerfile:开启本地向量嵌入 + 中文分词
coolify 的 Dockerfile 默认用 --omit=optional,会跳过本地向量模型 ,退化成纯 BM25 关键词检索;
且对中文是整段切词。你写中文、要语义召回,就改一行。
编辑 agentmemory/deploy/coolify/Dockerfile,把那条 npm install ... 改成:
dockerfile
RUN printf '{"name":"agentmemory-deploy","version":"1.0.0","private":true,"overrides":{"iii-sdk":"%s"}}\n' "${III_SDK_VERSION}" > package.json \
&& npm install "@agentmemory/agentmemory@${AGENTMEMORY_VERSION}" --no-fund --no-audit \
&& npm install @xenova/transformers @node-rs/jieba tiny-segmenter --no-fund --no-audit \
&& ln -s /opt/agentmemory/node_modules/.bin/agentmemory /usr/local/bin/agentmemory
(去掉了 --omit=optional,显式装了本地嵌入模型 + 中日韩分词器。)
维护提示:你改过这个文件后不要直接
git pull(会被覆盖)。日常升级只改 compose 里的版本号即可(见第 6 节)。
2. docker-compose.yml
存成 docker-compose.yml,和 clone 出来的 agentmemory/ 目录同级。
yaml
services:
# Tailscale 节点:持有网络栈,agentmemory 共享它
ts-agentmemory:
image: tailscale/tailscale:latest
hostname: agentmemory # → MagicDNS:http://agentmemory:3111
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false # 内核模式(需 /dev/net/tun)
volumes:
- ts-state:/var/lib/tailscale
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
ports:
- "3113:3113" # viewer 发布到 NAS 局域网(端口须发布在网络栈持有者上)
restart: unless-stopped
agentmemory:
build:
context: ./agentmemory/deploy/coolify
dockerfile: Dockerfile
args:
AGENTMEMORY_VERSION: "0.9.26"
III_VERSION: "0.11.2"
III_SDK_VERSION: "0.11.2"
network_mode: service:ts-agentmemory # 与 Tailscale 容器共享网络
depends_on:
- ts-agentmemory
environment:
# ---- 压缩 LLM:DeepSeek(OpenAI 兼容)----
- OPENAI_API_KEY=${OPENAI_API_KEY}
- OPENAI_BASE_URL=${OPENAI_BASE_URL} #https://api.deepseek.com
- OPENAI_MODEL=${OPENAI_MODEL} #deepseek-v4-flash
# ---- viewer 局域网访问 ----
- AGENTMEMORY_VIEWER_HOST=0.0.0.0
- VIEWER_ALLOWED_HOSTS=${NAS_LAN_IP}:3113
- VIEWER_ALLOWED_ORIGINS=http://${NAS_LAN_IP}:3113
volumes:
- agentmemory-data:/data
restart: unless-stopped
volumes:
ts-state:
agentmemory-data:
.env(同目录,务必加入 .gitignore):
env
TS_AUTHKEY=tskey-auth-xxxxxxxx
OPENAI_BASE_URL=sk-xxxxxxxx
OPENAI_API_KEY=https://api.deepseek.com
OPENAI_MODEL=deepseek-v4-flash
NAS_LAN_IP=192.168.1.50 # 换成你 NAS 的局域网 IP
端口说明:API(3111)、stream(3112)被 entrypoint 绑到
0.0.0.0,通过 Tailscale 节点在 tailnet 上可达,不 发布到局域网;viewer(3113)默认绑 loopback,这里用
AGENTMEMORY_VIEWER_HOST=0.0.0.0改绑 + 在
ts-agentmemory上ports: 3113:3113发布到局域网。
下面是容器网络与服务依赖关系图:
#mermaid-svg-H5Pf9kaEuai3Lf79{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-H5Pf9kaEuai3Lf79 .error-icon{fill:#552222;}#mermaid-svg-H5Pf9kaEuai3Lf79 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-H5Pf9kaEuai3Lf79 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .marker.cross{stroke:#333333;}#mermaid-svg-H5Pf9kaEuai3Lf79 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-H5Pf9kaEuai3Lf79 p{margin:0;}#mermaid-svg-H5Pf9kaEuai3Lf79 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster-label text{fill:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster-label span{color:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster-label span p{background-color:transparent;}#mermaid-svg-H5Pf9kaEuai3Lf79 .label text,#mermaid-svg-H5Pf9kaEuai3Lf79 span{fill:#333;color:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .node rect,#mermaid-svg-H5Pf9kaEuai3Lf79 .node circle,#mermaid-svg-H5Pf9kaEuai3Lf79 .node ellipse,#mermaid-svg-H5Pf9kaEuai3Lf79 .node polygon,#mermaid-svg-H5Pf9kaEuai3Lf79 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .rough-node .label text,#mermaid-svg-H5Pf9kaEuai3Lf79 .node .label text,#mermaid-svg-H5Pf9kaEuai3Lf79 .image-shape .label,#mermaid-svg-H5Pf9kaEuai3Lf79 .icon-shape .label{text-anchor:middle;}#mermaid-svg-H5Pf9kaEuai3Lf79 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .rough-node .label,#mermaid-svg-H5Pf9kaEuai3Lf79 .node .label,#mermaid-svg-H5Pf9kaEuai3Lf79 .image-shape .label,#mermaid-svg-H5Pf9kaEuai3Lf79 .icon-shape .label{text-align:center;}#mermaid-svg-H5Pf9kaEuai3Lf79 .node.clickable{cursor:pointer;}#mermaid-svg-H5Pf9kaEuai3Lf79 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .arrowheadPath{fill:#333333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H5Pf9kaEuai3Lf79 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-H5Pf9kaEuai3Lf79 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H5Pf9kaEuai3Lf79 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster text{fill:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 .cluster span{color:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-H5Pf9kaEuai3Lf79 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-H5Pf9kaEuai3Lf79 rect.text{fill:none;stroke-width:0;}#mermaid-svg-H5Pf9kaEuai3Lf79 .icon-shape,#mermaid-svg-H5Pf9kaEuai3Lf79 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-H5Pf9kaEuai3Lf79 .icon-shape p,#mermaid-svg-H5Pf9kaEuai3Lf79 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-H5Pf9kaEuai3Lf79 .icon-shape .label rect,#mermaid-svg-H5Pf9kaEuai3Lf79 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-H5Pf9kaEuai3Lf79 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-H5Pf9kaEuai3Lf79 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-H5Pf9kaEuai3Lf79 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 环境变量
docker-compose.yml 服务栈
共享网络栈
ts-agentmemory
Tailscale sidecar
网络栈持有者
agentmemory
主服务
network_mode: service:ts-agentmemory
ts-state 卷
agentmemory-data 卷
TS_AUTHKEY
DEEPSEEK_API_KEY
NAS_LAN_IP
3. 启动 + 取密钥
bash
docker compose up -d --build
# 首次启动入口会生成一次性密钥,只打印一次,记下来:
docker compose logs agentmemory | grep AGENTMEMORY_SECRET
# 形如 AGENTMEMORY_SECRET=<64位hex>(持久化在 /data/.hmac)
验证:
bash
# API(任意 tailnet 设备)
curl http://agentmemory:3111/agentmemory/health
# viewer(任意局域网设备浏览器)
# http://192.168.1.50:3113
下面是首次启动的完整流程:
DeepSeek Tailscale agentmemory ts-agentmemory docker compose 管理员 DeepSeek Tailscale agentmemory ts-agentmemory docker compose 管理员 #mermaid-svg-PwKr9T8ohNZZ0Y62{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .error-icon{fill:#552222;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .marker.cross{stroke:#333333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PwKr9T8ohNZZ0Y62 p{margin:0;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PwKr9T8ohNZZ0Y62 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PwKr9T8ohNZZ0Y62 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .sequenceNumber{fill:white;}#mermaid-svg-PwKr9T8ohNZZ0Y62 #sequencenumber{fill:#333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .messageText{fill:#333;stroke:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .labelText,#mermaid-svg-PwKr9T8ohNZZ0Y62 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .loopText,#mermaid-svg-PwKr9T8ohNZZ0Y62 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-PwKr9T8ohNZZ0Y62 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .noteText,#mermaid-svg-PwKr9T8ohNZZ0Y62 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actorPopupMenu{position:absolute;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-PwKr9T8ohNZZ0Y62 .actor-man circle,#mermaid-svg-PwKr9T8ohNZZ0Y62 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-PwKr9T8ohNZZ0Y62 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 密钥持久化到 /data/.hmac docker compose up -d --build启动 Tailscale sidecar使用 TS_AUTHKEY 认证加入 tailnet(节点: agentmemory)构建并启动 agentmemory连接 DeepSeek API(验证)连接成功生成 AGENTMEMORY_SECRETdocker compose logs agentmemory | grep SECRETAGENTMEMORY_SECRET=<64位hex>curl http://agentmemory:3111/agentmemory/health{"status":"ok"}
4. 客户端 Tailscale + Claude Code 接入
每台开发机(Mac / Windows / Linux):
bash
# 1) 装并登录 Tailscale,加入同一 tailnet
# https://tailscale.com/download → tailscale up
# 2) 装 Claude Code 插件(注册 hooks + skills + 自动接 MCP)
# 在 Claude Code 里:
# /plugin marketplace add rohitg00/agentmemory
# /plugin install agentmemory
# 3) 设两个环境变量(写进 ~/.zshrc / ~/.bashrc;Windows 用系统环境变量)
export AGENTMEMORY_URL="http://agentmemory:3111" # MagicDNS;或 http://100.x.x.x:3111
export AGENTMEMORY_SECRET="<第3节日志里的密钥>"
# 4) 重启 Claude Code,验证连通 + 鉴权
curl -H "Authorization: Bearer $AGENTMEMORY_SECRET" http://agentmemory:3111/agentmemory/health
hooks 和 MCP shim 都读这两个变量:AGENTMEMORY_URL 决定连哪、AGENTMEMORY_SECRET 以 Bearer 发送。
两台机器配好即共享同一份记忆。日常正常写代码,hooks 自动捕获;需要时用 /recall、/remember、/handoff 等 skill。
5. 压缩 LLM(DeepSeek)说明与验证
agentmemory 的 provider 检测:只要 OPENAI_API_KEY 有真实值,就走 provider: "openai",
model 取 OPENAI_MODEL、baseURL 取 OPENAI_BASE_URL。这条通道官方注释支持
"OpenAI, DeepSeek, SiliconFlow, Azure, vLLM, LM Studio"。所以接 DeepSeek 就是把这三个变量指过去(已在第 2 节配好)。
deepseek-v4-flash是 cost-optimized 档,适合做后台高频压缩(源码里专门有针对贵模型的成本告警)。- base URL 用
https://api.deepseek.com(OpenAI SDK 习惯的https://api.deepseek.com/v1也行)。
验证:
bash
# 变量是否真进了容器
docker compose exec agentmemory printenv | grep -E 'OPENAI_|VIEWER_|TS_'
# provider 是 openai 还是 noop
docker compose exec agentmemory agentmemory doctor
若日志里仍出现 "No LLM provider key found ... using no-op provider",说明 key 没被读到------
检查 .env 的 DEEPSEEK_API_KEY 和 compose 的 environment 引用。
6. 升级
三块各自独立,最常做的是 agentmemory 本身。
6.1 升级 agentmemory(改版本号 + 重建)
bash
cd /volume1/docker
# 1) 先备份(数据唯一不可替代)
curl -s -H "Authorization: Bearer $AGENTMEMORY_SECRET" \
http://agentmemory:3111/agentmemory/export > backup-$(date +%F).json
# 并对 agentmemory-data 卷做 NAS 快照
# 2) 查新版 + 看 release 是否要求改引擎
npm view @agentmemory/agentmemory version
# 3) 改 docker-compose.yml 的 args.AGENTMEMORY_VERSION
# 4) 重建
docker compose build agentmemory && docker compose up -d
docker compose logs --tail=50 agentmemory # 确认无 EPIPE/报错
6.2 铁律:III_VERSION 别乱升
保持 0.11.2。升到 0.11.6+ 会出现 EPIPE 重连循环、保存后搜不到。
只有当 agentmemory 某新版 release notes 明确支持新引擎时,才按它给的版本同步改。
6.3 回滚
把 AGENTMEMORY_VERSION 改回旧值重建即可,数据卷不动。若新版动过状态库结构,
回滚旧二进制可能读不了已迁移数据------这时用第 6.1 步那份 JSON 导出 import 重建。
6.4 Tailscale 边车 / 客户端
bash
docker compose pull ts-agentmemory && docker compose up -d # 边车
客户端插件在 Claude Code 里重装/更新;尽量让服务端与客户端小版本相近。
你的
AGENTMEMORY_SECRET存在/data/.hmac,升级/重建不变,客户端无需重配。
7. 把本地已有记忆迁到 NAS
import 接口要求 { "exportData": <export内容>, "strategy": "merge"|"replace"|"skip" }。
bash
# (本地)先起本地 server:agentmemory 或 npx @agentmemory/agentmemory
curl -s http://127.0.0.1:3111/agentmemory/export > export.json
# 语料大、超时就分页:?maxSessions=10&offset=0 逐批拉
# 包一层
jq '{exportData: ., strategy: "merge"}' export.json > import.json
# 推到 NAS
curl -X POST http://agentmemory:3111/agentmemory/import \
-H "Authorization: Bearer $AGENTMEMORY_SECRET" \
-H "Content-Type: application/json" --data @import.json
merge 合并 / replace 覆盖 / skip 只补新。首次迁移用 merge。
(裸拷贝 ~/.agentmemory 的 SQLite 为备选,要求两端版本一致且停服时操作,优先用 export/import。)
8. 备份 / 维护 / 排错
- 备份 :记忆在
agentmemory-data卷(/data/state_store.db),纳入 NAS 快照;或定期export出 JSON。 - 密钥轮换 :删
/data/.hmac后重启会重新生成并打印。 - 只看到 7 个 MCP 工具 :shim 没连上 server------查
AGENTMEMORY_URL可达、health 通。 - 存了搜不到 / EPIPE :多半 iii 引擎版本不符,保持
III_VERSION=0.11.2。 - viewer 打不开 :确认
AGENTMEMORY_VIEWER_HOST=0.0.0.0且VIEWER_ALLOWED_HOSTS里的host:port
与你浏览器地址栏完全一致(含端口);改了要docker compose up -d重建容器生效。 - provider 显示 noop:DeepSeek key 没传进容器,见第 5 节。
附录 A:Option --- 不用 sidecar,改 NAS 原生 Tailscale
若 NAS 容器拿不到 /dev/net/tun:套件中心装 Tailscale(NAS 得到 100.x.x.x),compose 去掉 sidecar,
agentmemory 直接发布端口:ports: ["100.x.x.x:3111:3111", "192.168.1.50:3113:3113"](API 走 tailnet IP、
viewer 走局域网 IP)。客户端 AGENTMEMORY_URL=http://100.x.x.x:3111。
附录 B:viewer 的三档访问方式(按安全性从高到低)
共同前提:viewer 是免密特权界面 (代理时自动带
AGENTMEMORY_SECRET)。能打开它 = 全权读写记忆,无需令牌。按你的暴露面选最小的一档。三档可按需共存(白名单逗号分隔),但暴露越广风险越大。
B1 --- SSH 隧道(最安全,viewer 不出现在任何网络)
把第 2 节 ts-agentmemory 的 ports: ["3113:3113"] 改成 ["127.0.0.1:3113:3113"](只绑 NAS 本机回环),
并设:
yaml
agentmemory:
environment:
- AGENTMEMORY_VIEWER_HOST=0.0.0.0 # 端口转发到容器内 loopback 收不到,必须 0.0.0.0
- VIEWER_ALLOWED_HOSTS=localhost:3113,127.0.0.1:3113
- VIEWER_ALLOWED_ORIGINS=http://localhost:3113,http://127.0.0.1:3113
然后 ssh -L 3113:127.0.0.1:3113 <用户>@<NAS>,浏览器开 http://localhost:3113。
只有能 SSH 进 NAS 的人能用。
B2 --- 局域网(主文档默认,家庭内网用)
见第 2 节:ports: ["3113:3113"] + VIEWER_ALLOWED_HOSTS=${NAS_LAN_IP}:3113。
同一局域网设备开 http://<NAS局域网IP>:3113。
B3 --- 全域 / tailnet(你的任意 Tailscale 设备、任意地点可访问)
"全域"= 你 tailnet 内任意设备、任意网络位置可达;不是公网。
viewer 已绑 0.0.0.0、与 Tailscale 边车共享网络栈,所以它已在 tailscale 接口监听------
只需把 tailnet 主机加进白名单,无需再发布任何端口:
yaml
agentmemory:
environment:
- AGENTMEMORY_VIEWER_HOST=0.0.0.0
# 可与局域网并存,逗号分隔;host:port 必须与地址栏完全一致
- VIEWER_ALLOWED_HOSTS=${NAS_LAN_IP}:3113,agentmemory:3113,${NAS_TS_IP}:3113
- VIEWER_ALLOWED_ORIGINS=http://${NAS_LAN_IP}:3113,http://agentmemory:3113,http://${NAS_TS_IP}:3113
.env 补一行 NAS_TS_IP=100.x.x.x(NAS 的 Tailscale IP)。docker compose up -d 生效。
任意 tailnet 设备开 http://agentmemory:3113(MagicDNS)或 http://100.x.x.x:3113。
单人 tailnet 到此即可。若你的 tailnet 有别人 ,务必在 Tailscale 后台 Access controls 用 ACL
把 viewer 端口锁到只有你自己:给 NAS 节点打 tag(如 tag:agentmemory),并仅放行你这个用户访问该端口------
jsonc
{
"tagOwners": { "tag:agentmemory": ["autogroup:owner"] },
"acls": [
// 你自己的设备:全放行
{ "action": "accept", "src": ["autogroup:owner"], "dst": ["*:*"] }
// 不要给其他用户/tag 添加指向 tag:agentmemory:3113 的规则,默认即拒绝
]
}
(打 tag 需让 TS_AUTHKEY 带 --advertise-tags=tag:agentmemory,或在后台给该节点手动加 tag。)
不推荐 --- 公网直曝
viewer 无登录,真要从公网开必须在前面套一层带认证的反向代理(basic auth / OAuth)。
鉴于其特权性质,强烈建议改用 B3 的 tailnet 全域访问,而不是把它挂到公网。
附录 C:Headscale(连协调层都自托管)
NAS 的 ts-agentmemory 与各客户端 tailscale up 都加 --login-server=https://你的headscale;
auth key 由 headscale preauthkeys create 签发。数据平面行为不变。