claude-mem Server(Beta):Postgres + BullMQ 可部署运行时——架构、环境校验与生产部署指南
本篇基于仓库内的 server 部署文档,讲解 claude-mem 13 的 Beta 版服务器运行时:一个以 Postgres 为规范存储、以 BullMQ(Valkey)为队列、以 API Key 鉴权为边界的可部署服务。它用 claude-mem-server + claude-mem-worker 两个角色取代了旧的本地 claude-mem worker,解决的核心问题是:长时间运行的 AI 生成任务如何不阻塞 HTTP 请求、如何水平扩缩、以及重启后如何不丢任务。读完本文,你将掌握该运行时的完整环境变量契约、Compose 部署流程、API Key 生命周期管理,以及端到端验证脚本背后的设计依据。
架构总览:Postgres 规范存储 + Valkey 队列 + 双进程角色
原文档给出了如下的部署架构图:
+-------------------+
| Hooks / SDK / MCP|
| (clients) |
+---------+---------+
| HTTPS / Bearer API key
v
+-----------------+ +----+---------+ +-------------------+
| Postgres |<-+ claude-mem- +-->+ Valkey |
| (canonical | | server | | (BullMQ queue, |
| storage: | | --daemon | | noeviction, |
| events, | | HTTP only, | | appendonly yes) |
| observations, | | no generation | +---------+---------+
| jobs, sessions,| +-------+-------+ ^
| api_keys) | | enqueue |
+--------^--------+ v |
| +-----------------+ |
+----------+ claude-mem- +-------------+
read | worker (Nx) | consume jobs
write | server worker | call provider
| start |
+-----------------+
各组件的职责分工如下:
- Hooks / SDK / MCP 客户端:所有请求方(Claude Code hooks、SDK、MCP 工具)通过 HTTPS 携带 Bearer API Key 访问 HTTP 服务;
- Postgres:规范存储(canonical storage),持久化 events、observations、jobs、sessions、api_keys 等全部领域数据;
- Valkey:BullMQ 队列后端,配置
noeviction+appendonly yes(AOF 持久化),保证已入队的生成任务在进程重启后仍然存活; - claude-mem-server:纯 HTTP 运行时,只负责接收事件、写入 Postgres、向队列投递生成任务,自身不做任何 AI 生成;
- claude-mem-worker (Nx):BullMQ 消费端,从 Valkey 轮询任务、调用模型 provider、把生成结果写回 Postgres。
HTTP 服务与生成 worker 来自同一个镜像、同一份代码库,但被拆成独立进程/容器,文档给出的理由有三条:
- 长时间运行的 provider 调用不会阻塞 HTTP 响应;
- 生成能力可以水平扩展:
docker compose up -d --scale claude-mem-worker=4; - 重启 HTTP 服务不会丢失已入队的生成工作——任务活在 Valkey 中并由 AOF 持久化。
从源码结构看,这个拆分与文档一一对应。队列侧由 ActiveServerQueueManager.ts 管理,消费侧由 ActiveServerGenerationWorkerManager.ts 挂载 BullMQ Worker;两者监听的队列名在 jobs/types.ts 中集中定义:
export const SERVER_JOB_QUEUE_NAMES: Record<ServerGenerationJobKind, string> = {
event: 'server_beta_generate_event',
summary: 'server_beta_generate_summary'
};
export const SERVER_JOB_KIND_PREFIX: Record<ServerGenerationJobKind, string> = {
event: 'evt',
summary: 'sum'
};
即 worker 实际挂载的正是文档所称的 event 与 summary 两条队列(lane),任务 ID 前缀 evt_ / sum_ 与原文档中幂等 ID 的形式 evt_<sha256> / sum_<sha256> 完全一致。
另外需要明确一点:旧的 claude-mem worker 运行时在 Docker 中不会被启动。容器入口始终运行 bun server-service.cjs --daemon(或 worker start),永远不会运行 worker-service.cjs。这在 entrypoint.sh 中可以直接得到验证:入口脚本根据 CLAUDE_MEM_CONTAINER_MODE 选择角色——server(默认)执行 server-service.cjs --daemon,worker 先 unset CLAUDE_MEM_GENERATION_DISABLED 再执行 server-service.cjs worker start,另有 shell 模式用于进入容器做工具操作。
必需环境变量与启动期强校验
服务端在启动时运行 validateServerBetaEnv(),任何必需变量缺失或非法都会直接拒绝启动。文档给出的完整校验契约如下:
| 变量 | 必需性 | 说明 |
|---|---|---|
CLAUDE_MEM_RUNTIME |
Docker | Docker 内必须为 server-beta(否则告警)。 |
CLAUDE_MEM_QUEUE_ENGINE |
Docker | 必须为 bullmq。Docker 内拒绝进程内队列。 |
CLAUDE_MEM_SERVER_DATABASE_URL |
始终 | Postgres 连接串,启动期快速失败。 |
CLAUDE_MEM_REDIS_URL |
bullmq | 队列引擎为 bullmq 时必需。 |
CLAUDE_MEM_AUTH_MODE |
始终 | Docker 内不允许为 local-dev。 |
CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS |
Docker | Docker 内不允许为 1/true。 |
CLAUDE_MEM_GENERATION_DISABLED |
可选 | 当 HTTP 服务与独立 worker 分离部署时,在 HTTP 服务上设为 true。 |
CLAUDE_MEM_SERVER_PROVIDER |
Worker | claude、gemini、openrouter 三选一,仅 worker 需要。 |
ANTHROPIC_API_KEY(或等价变量) |
Worker | 所选 provider 的凭据。 |
本地开发仅在 Docker 之外可以使用 SQLite + local-dev 鉴权旁路;可部署模式必须满足上表。这套校验在仓库中有两处可执行的证据:
- docker-compose.yml 头部注释明确列出启动校验清单,并对
POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB使用了${VAR:?VAR is required}语法——缺少任一机密,Compose 直接拒绝启动; - E2E 脚本 e2e-server-docker.sh 专门起一个一次性容器、注入
CLAUDE_MEM_AUTH_MODE=local-dev+CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS=1,断言进程以非零码退出且 stderr 含local-dev is not allowed in Docker。
从源码结构看,Docker 环境识别是双保险的:entrypoint 显式导出 CLAUDE_MEM_DOCKER=1,注释说明运行时同时会自动探测 /.dockerenv,用于校验器判断“是否处于容器内”。
生成 worker 模式:claude-mem server worker start
同一镜像通过下面的命令运行生成 worker 角色:
claude-mem server worker start
该进程的行为契约是:
- 使用与 HTTP 服务相同的配置连接 Postgres 和 Valkey;
- 将 BullMQ Worker 挂载到
event与summary两条队列; - 不打开任何 HTTP 监听端口;
- 前台阻塞运行,适合
docker run、kubectl run、systemd 等托管方式; - 即使继承了共享 Compose 文件里的
CLAUDE_MEM_GENERATION_DISABLED=true也会强制开启生成——因为 worker 本身就是生成进程。
最后一条在 entrypoint.sh 中有直接实现:worker 分支在进入 server-service.cjs worker start 之前执行 unset CLAUDE_MEM_GENERATION_DISABLED。
在 Compose 中,这一角色对应 claude-mem-worker 服务,可水平扩展:
docker compose up -d --scale claude-mem-worker=4
并发安全由两层保证:BullMQ 保证同一时刻只有一个 worker 处理某个任务;ProviderObservationGenerator.process 内部的 provider 调用对任务 ID(evt_<sha256> / sum_<sha256>)是幂等的,因此重试不会重复产生 observations。任务载荷结构在 jobs/types.ts 中定义:event 任务携带 agent_event_id,summary 任务携带 server_session_id,并且 Zod schema 在入队边界同步校验载荷,缺少 team_id、project_id 或 generation_job_id 这类编程错误会在入队时即被拒绝,而不是生成一个 worker 无法审计的坏任务。
CLI 侧的入口在 npx-cli/commands/server.ts,帮助文本列出了完整的 server 子命令面:start, stop, restart, status, api-key create|list|revoke, keys rotate, worker start, jobs status|failed|retry|cancel——即 worker 与任务队列的日常运维都收敛在这一个 CLI 命名空间下。
生产鉴权:一次性明文、按次查库的 API Key 体系
生产部署统一使用:
CLAUDE_MEM_AUTH_MODE=api-key
API Key 通过 CLI 创建:
claude-mem server api-key create \
--name "ci" \
--scope memories:read,memories:write
关键设计点:
- 明文只展示一次;Postgres 的
api_keys.key_hash列只保存 SHA-256 哈希; - 撤销命令为
claude-mem server api-key revoke <id>; - 撤销在每次请求时都生效:鉴权中间件
requirePostgresServerAuth每次调用都按哈希回表查询该行,不存在可被污染的内存缓存。
requirePostgresServerAuth 的实现在 middleware/postgres-auth.ts 中,是 Postgres 存储模式下所有受保护路由的鉴权入口。E2E 脚本还演示了两种典型 scope 的划分:全权限 key(events:write,sessions:write,observations:read,jobs:read,memories:read,memories:write)与只读 key(observations:read,jobs:read,memories:read),撤销只读 key 后,后续读写请求应返回 401/403(见 e2e-server-docker.sh)。
原文档特别用引用块强调了一条安全边界:
不要在 Docker 内启用
CLAUDE_MEM_AUTH_MODE=local-dev。 loopback 旁路依赖请求来自 HTTP 监听端的127.0.0.1,这在容器内不是有意义的边界。启动校验器会拒绝该组合并以非零退出码退出。
此外,docker-compose.yml 头部注释还说明了一套并行的计费选择:API-Key 模式下生成走计费的 provider API key(高 observation 量下成本可能显著);也可将生成 provider 指向 Claude 订阅/Pro 会话以避免按 token 计费,此时 HTTP 侧的 bearer API key 契约保持不变。
Compose 栈:四个服务与可复制的部署方式
docker-compose.yml 提供了四个服务:
postgres(postgres:17-alpine):规范存储;schema 在启动时由bootstrapServerBetaPostgresSchema()引导(这是一个幂等的进程内迁移运行器,无需外部扩展)。凭据无默认值,缺失即拒绝启动,可用.env文件或内联POSTGRES_USER=... POSTGRES_PASSWORD=... docker compose up提供;valkey(valkey/valkey:8-alpine):BullMQ 队列后端,启动命令即文档所述三条关键配置——--appendonly yes(AOF 持久化)、--appendfsync everysec、--maxmemory-policy noeviction(BullMQ 硬性要求);claude-mem-server:HTTP 运行时,显式设置CLAUDE_MEM_GENERATION_DISABLED=true,因此该容器内不挂载 BullMQ Worker;监听0.0.0.0:37877,健康检查为curl http://127.0.0.1:37877/healthz;claude-mem-worker:生成 worker,水平扩展单位;provider 配置全部集中在它上面(CLAUDE_MEM_SERVER_PROVIDER默认claude,以及ANTHROPIC_API_KEY/GEMINI_API_KEY/OPENROUTER_API_KEY)。
启动与销毁:
docker compose up -d --build
docker compose down -v
(-v 会连同数据卷一起清除,等价于抹掉全部数据。)
文件头部注释还给出了几条值得注意的运维约定:
- 该文件不可未经修改部署到任何公网可达环境(包括可做横向移动的 VPN 后 staging),因为 Postgres 凭据以必需环境变量方式提供,需自行改为机密管理;
- 所有长驻服务声明
restart: unless-stopped,崩溃容器(OOM、panic、瞬时依赖故障)会自动拉起; CLAUDE_MEM_REDIS_URL有${CLAUDE_MEM_REDIS_URL:-redis://valkey:6379}回退默认,可指向外部 Redis;- 文件内预留了注释掉的
CREDENTIALS_FILE挂载块,支持把 provider/API 机密以只读文件形式挂进容器,而不内联到环境变量里; - server 与 worker 均声明了
postgres-data/valkey-data/claude-mem-data三个命名卷的归属,claude-mem-data挂载到/data/claude-mem。
角色选择的开关是 CLAUDE_MEM_CONTAINER_MODE:server(默认,server-service.cjs --daemon)、worker(生成 worker)、shell(进入容器执行工具),完整分支逻辑见 entrypoint.sh。
端到端测试:可执行的行为契约
scripts/e2e-server-docker.sh 拉起完整栈并做全量断言,任何断言失败都会以非零码退出。它验证的内容与原文档一致,且脚本注释补充了细节:
POST /v1/events?wait=true经由队列完整生成一条 observation;- 流程中途
docker compose restart claude-mem-server claude-mem-worker后,任务与 observation 不丢失(队列持久性验证); - 撤销 API Key 后,后续读/写被拒绝(401/403);
- 任何容器内都没有
worker-service.cjs进程运行——脚本通过pgrep -af 'worker-service\.cjs'在两个容器内分别检查; - Docker 内
CLAUDE_MEM_AUTH_MODE=local-dev被拒绝(含对错误信息的精确匹配)。
测试主体逻辑在 docker/e2e/server-e2e.mjs,以独立的 server-e2e Compose 服务运行,分 phase1(功能路径:事件提交、生成、查询)与 phase2(重启后的持久化与已撤销 key 检查)两个阶段注入环境变量执行。API Key 的创建与撤销则直接 exec 进 server 容器,用 bun server-service.cjs server api-key create|revoke 完成——注释特别指出这条子树由 Postgres 支撑,而非旧的 SQLite worker-service 树。
适用前提与小结
适用前提需要说清楚:这是 Beta 运行时,面向可部署(deployable)场景;本地开发仍应走 SQLite + local-dev 旁路的轻量路径(仅限 Docker 之外)。可部署模式的最小可行配置是——Postgres 17 与 Valkey 8(或兼容 Redis)实例、CLAUDE_MEM_RUNTIME=server-beta、CLAUDE_MEM_QUEUE_ENGINE=bullmq、CLAUDE_MEM_AUTH_MODE=api-key,外加 server/worker 各自所需的 CLAUDE_MEM_SERVER_DATABASE_URL、CLAUDE_MEM_REDIS_URL 与 worker 侧的 provider 凭据。
整体设计上,这一运行时值得参考的取舍有三点:用“同镜像双角色”把 AI 生成从 HTTP 关键路径中剥离,换来延迟隔离与独立扩缩;把任务状态外置到 AOF 持久化的 Valkey,使“重启不丢任务”成为可被 E2E 断言验证的性质;用“明文一次展示 + 每次请求按哈希查库”把 API Key 撤销做成无缓存、无宽限期的强一致行为。这三者分别对应文档中架构图、Compose 拆分和鉴权三个小节,也都能在仓库的 entrypoint、Compose 定义、队列类型 与 鉴权中间件 中找到一一对应的实现证据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00