claude-mem:把基于 Postgres 的 server 运行时封装为可嵌入的 cmem-sdk,以及 `server-beta` → `server` 改名全路径
本文基于 claude-mem 仓库中的实施计划 plans/2026-05-25-cmem-sdk-and-server-rename.md 展开:它定义了 claude-mem/sdk 这一可导入库形态的完整设计——剥离 HTTP/daemon/Redis 外壳后,如何在纯 Node 进程内完成"事件捕获 → AI 压缩生成 → Chroma 语义检索 + FTS 兜底"全链路,并给出了从 server-beta 到 server 的改名方案与持久化值兼容策略。读完本文,你可以掌握该 SDK 的分层架构、每个 Phase 的具体 API 契约、禁用依赖清单与验证方法,并能对照当前仓库源码确认哪些部分已经落地。
一、执行决策:SDK 不是新系统,是既有 server 运行时的"去壳"组合
计划开篇给出了一条硬性定性:
cmem-sdk is not a new system.
它就是把既有的进程内 server 运行时(src/server/* + src/storage/postgres/* + 既有的 src/services/sync/* Chroma 引擎)暴露为一个可导入的库,并剥掉 HTTP/daemon/Redis 外壳。SDK 需要的一切能力都已存在,且核心部分本来就是无 daemon 的;SDK 的工作是"组合 + 打包",外加一次谨慎的改名。
计划给出的目标对象图如下(消费方应用视角):
consumer app
└─ import { createCmemClient } from 'claude-mem/sdk'
├─ Postgres (pg) ← system of record (capture, observations, sessions, jobs) [src/storage/postgres/*]
├─ in-process generation ← provider.generate() (fetch) → parseAgentXml → processGeneratedResponse [src/server/generation/*]
├─ Chroma (REQUIRED) ← semantic index over the SAME observations, via uvx chroma-mcp subprocess [src/services/sync/*]
└─ search ← Chroma semantic (primary). Postgres FTS exists only as a runtime safety net when Chroma transiently fails — it is NOT a feature toggle. [src/storage/postgres/observations.ts]
这里有三个必须理解的约束:
- Chroma 不是可选的。 计划明确:没有语义检索的 claude-mem 是坏的——observations 无法以用户真实的方式被检索。因此
createCmemClient(...)在构造时必须初始化并验证 Chroma;若uvx chroma-mcp子进程起不来,构造函数直接 reject。Postgres FTS 路径只保留为一个运行时安全网(镜像 SearchManager 中"Chroma 瞬死时降级"的韧性模式),使用时大声打日志,且不作为用户可配置模式暴露。 - 必须排除在外的一切: Express、BullMQ、ioredis/Redis、better-auth、HTTP 路由、daemon/pidfile 生命周期、worker 的
bun:sqlite存储、Claude Code 子进程生成路径。这些全是可复用核心外面包的"壳"。 - 要研究的接线中枢是
createServerService()(计划写作时为createServerBetaService(),改名后即 src/server/runtime/create-server-service.ts 中的createServerService)。它已经构建出 SDK 想要的完整对象图(pool → schema bootstrap → repositories),再挂上 SDK 不要的部分(HTTP 服务、队列管理器、生成 worker)。SDK 复现的是"图",不是"服务"。
对照当前仓库源码可以看到该中枢的实际结构:createServerService(options) 依次执行环境校验 → loadServerMode()(启动期加载 code 模式,加载失败立即抛出)→ 取 pool(options.pool ?? getSharedPostgresPool({requireDatabaseUrl:true}))→ initializePostgres(pool)(幂等 bootstrap)→ 构建 queueManager 与 generationWorkerManager → 组装 ServerServiceGraph 并 new ServerService({graph})。见 create-server-service.ts。计划指出 SDK 要复制的正是"pool → bootstrap → repositories"这一段,而丢弃 queueManager、generationWorkerManager、ServerService 外壳。
术语决策(继承并强制执行)
- 领域对象叫 observation,永不叫 "memory"。保留
observations、observation_sources、PostgresObservationRepository、/v1/observations;/v1/memories与memory_*MCP 工具只是别名。 - 运行时叫
server(本计划起),永不叫 "server-beta";worker仍是遗留 SQLite 运行时。 - 公开客户端叫
CmemClient,由createCmemClient(...)构造,从claude-mem/sdk导入。
二、Phase 0:文档发现——先读透接线中枢,再谈实现
计划要求在执行前先完整阅读源码并固定一份"允许使用的 API"清单,禁止凭印象发明或扩展 API。以下是该清单中与 SDK 直接相关的部分(均摘自计划的 verbatim 签名,路径为当前仓库实际位置):
连接 / 启动(src/storage/postgres/):
parsePostgresConfig(options?): PostgresConfig | null(config.ts)——只读CLAUDE_MEM_SERVER_DATABASE_URL这一个连接变量,外加 pool/SSL 调优参数;createPostgresPool(config: PostgresConfig): PostgresPool与getSharedPostgresPool(options?: {requireDatabaseUrl?})(pool.ts);bootstrapServerPostgresSchema(client): Promise<void>(schema.ts)——幂等的进程内迁移 runner,不装扩展、不用 pgvector。observations表 DDL 含content TEXT、content_search TSVECTOR GENERATED ALWAYS、embedding JSONB(可空且从未被读取),GIN 索引用于 FTS;createPostgresStorageRepositories(client): PostgresStorageRepositories(index.ts)——一次返回全部 repository;withPostgresTransaction<T>(pool, fn);PostgresQueryable = { query(text, values?) },pg.Pool或pg.PoolClient都满足该接口。
捕获:
new IngestEventsService({ pool, resolveEventQueue: () => null })后调ingestOne(input, { generate })(IngestEventsService)——resolveEventQueue返回null使 BullMQ 入队变成 no-op(enqueueState='queued_only'),这是 SDK 不碰 Redis 的关键;PostgresAgentEventsRepository.create(input: CreatePostgresAgentEventInput)(agent-events.ts)。
生成(进程内,无 BullMQ):
new ClaudeObservationProvider({ apiKey, model? })(或 Gemini/OpenRouter 兄弟实现)——构造函数接受{apiKey, model?, maxOutputTokens?, fetchImpl?},用纯fetch请求https://api.anthropic.com/v1/messages,不依赖@anthropic-ai/claude-agent-sdk、不起子进程;PostgresObservationGenerationJobRepository.transitionStatus({ id, projectId, teamId, status:'processing', lockedBy })——状态机只允许合法迁移:queued→processing→completed;queued→completed直接抛错;provider.generate({ job, events, project }, signal?)(ServerGenerationContext={job, events, project:{projectId,teamId,serverSessionId,projectName}},结果为{rawText, modelId?, providerLabel, tokensUsed?});processGeneratedResponse({ pool, job, rawText, modelId, providerLabel, ... })与processSessionSummaryResponse(...)(processGeneratedResponse.ts)——包一层withPostgresTransaction,经parseAgentXml解析,写入observations+observation_sources,完成 job。从源码结构看,该路径从不写embedding列——语义索引完全交给 Chroma;parseAgentXml(raw, correlationId?)(src/sdk/parser.ts)——注意:它内部调用ModeManager.getInstance().getActiveMode()且无回退,没有激活模式会抛错,因此 SDK 必须在构造客户端时初始化模式(见 Phase 5)。
检索:
PostgresObservationRepository.search({ projectId, teamId, query, limit? })(observations.ts)——FTS 通过websearch_to_tsquery+ts_rank实现,这是 Postgres 侧唯一的搜索手段;getByIdForScope({ id, projectId, teamId })/listByProject(...)。
Chroma(必需的语义层,复用而非分叉):
ChromaMcpManager.getInstance()+callTool('chroma_add_documents' | 'chroma_query_documents' | 'chroma_create_collection' | 'chroma_delete_documents', args)(ChromaMcpManager)——单例,负责拉起uvx chroma-mcp子进程,本地 all-MiniLM 嵌入,不需要任何 API key;new ChromaSync(project)+queryChroma(...)+close()(ChromaSync);collection 命名沿用cm__<project>约定;其中addDocuments(ChromaDocument[])这一"文档层"是存储无关的可复用接缝,而syncObservation(observationId:number, ...)是 SQLite 形状(整型 id),SDK 不得复用它处理 Postgres 的 UUID。
反模式清单(计划存在的原因:这些坑已经踩过)
- 不要"建混合体""适配""迁移""分叉"任何东西——每个引擎都已存在,SDK 是胶水 + 打包;任务描述里出现 "new system" 或 "reimplement" 即是错的。
- 不要加 pgvector /
vector列 / embeddings API 调用。Postgres 语义检索不存在也不在范围内,语义检索由既有 Chroma 引擎交付;FTS 是 Postgres 侧搜索。 - 不要把 Express、BullMQ、ioredis、better-auth、React、
bun:sqlite拉进 SDK bundle,用构建期 import guard 强制执行。 - 不要调
transitionStatus(queued → completed)——它抛错;必须先queued → processing。 - 不要在无激活模式时调
parseAgentXml。 - 不要盲目字符串替换改名——持久化值(迁移表名、job 枚举字符串、用户 settings.json 键、
CLAUDE_MEM_RUNTIME取值)需要双向兼容;只有代码标识符可以随意改。 - 不要只靠 grep 碎片拼全局认知——完整读接线中枢与组合根。
三、Phase 1:改名 server-beta → server(基础 + 回归修复)
改名放在 SDK 之前,原因有二:SDK 建立在 server 运行时之上,应该带着干净的命名出厂;且它本身就可独立交付——单独修复"静默回退"回归。
1a. 修复回归(价值最高、改动最小)
问题根源在运行时选择器:此前 CLAUDE_MEM_RUNTIME 必须精确等于 'server-beta',否则 hook 会静默回退到 worker,且回退不易察觉。修复要求:
- 运行时选择器同时接受
'server'(规范值)与'server-beta'(向后兼容); - 服务端环境校验接受两种字面量;
- 设置键新增
CLAUDE_MEM_SERVER_{URL,API_KEY,PROJECT_ID},先读新键、再回退旧键(CLAUDE_MEM_SERVER_BETA_*),保证既有settings.json继续工作。
验证标准:CLAUDE_MEM_RUNTIME=server + CLAUDE_MEM_SERVER_DATABASE_URL 已设时,hook 解析到 server 运行时而非 worker;并补一个对 'server' 与 'server-beta' 都断言 selectRuntime()==='server' 的单测。
这一部分在当前仓库中已经落地,可直接对照阅读:runtime-selector.ts 中 selectRuntime() 对两个字面量都返回 'server',并有注释"Phase 1a (cmem-sdk rename): the canonical runtime value is 'server'";buildServerContext 用 pickFirstNonEmpty 先读 CLAUDE_MEM_SERVER_URL 再回退 CLAUDE_MEM_SERVER_BETA_URL(API key、project id 同理);对应单测在 tests/hooks/runtime-selector.test.ts。服务端侧的 validateServerEnv 同样接受 server 与 server-beta 两种取值(见该文件 L93 注释)。
1b. 代码标识符改名(安全、机械)
约 80 个 ServerBeta* / serverBeta* / SERVER_BETA_* 代码符号(类、类型、接口、变量、非持久化常量)改为 Server* / server* / SERVER_*,例如:
| 旧名 | 新名 |
|---|---|
ServerBetaService |
ServerService |
createServerBetaService |
createServerService |
ServerBetaClient |
ServerClient |
ActiveServerBetaQueueManager |
ActiveServerQueueManager |
ServerBetaServiceGraph |
ServerServiceGraph |
bootstrapServerBetaPostgresSchema |
bootstrapServerPostgresSchema |
SERVER_BETA_POSTGRES_SCHEMA_VERSION |
SERVER_POSTGRES_SCHEMA_VERSION |
验证:npm run typecheck 通过;rg -i 'serverbeta' 在代码标识符中为 0。当前仓库可以确认这一步已执行:src/server/runtime/ 目录下已只有 ServerService.ts、ActiveServerQueueManager.ts、ActiveServerGenerationWorkerManager.ts、create-server-service.ts 等新命名文件,bootstrapServerPostgresSchema、SERVER_POSTGRES_SCHEMA_VERSION 也已是新名。
1c. 文件与构建/分派目标改名
源文件整体重命名(create-server-service.ts、ServerService.ts、server-client.ts、server-bootstrap.ts、ActiveServer*.ts、scripts/e2e-server-docker.sh、docs/server-*.md),构建目标 server-beta-service → server-service(产物 plugin/scripts/server-service.cjs),worker-service 中的 CLI 分派函数改名为 runServerServiceCli 并查找 server-service.cjs;server <cmd> 子命令名保持不变。验证:npm run build 产出 plugin/scripts/server-service.cjs,claude-mem server status 分派正确。当前仓库中 package.json 的 e2e:server:docker 脚本已指向 bash scripts/e2e-server-docker.sh,与计划一致;并保留"若已安装插件缓存中仍有 server-beta-service.cjs 则回退查找"的升级期保护。
1d. 持久化值——向后兼容(危险区)
这是改名中最容易把线上数据弄坏的部分,计划逐项给出策略:
- 迁移表
server_beta_schema_migrations:在bootstrapServerPostgresSchema顶部加一条幂等、带保护的ALTER TABLE IF EXISTS server_beta_schema_migrations RENAME TO server_schema_migrations;,再CREATE ... IF NOT EXISTS,并更新读取语句。(零风险替代方案:物理表名不动,只改 TS 常量。) - job 的
job_type/source_type枚举字符串(server_beta_generate_event、server_beta_generate_summary、server_beta_generate_event_batch、server_beta_reindex、server_beta_observation_request):写入时发server_*,读取/匹配时两种都接受,配一个小的 normalize 助手。 - settings 键 / runtime 值:由 1a 的"先新后旧"读取覆盖;安装器今后写新键 +
CLAUDE_MEM_RUNTIME=server。
验证:打开改名前的既有 Postgres 库,bootstrap 干净跑通、迁移行保留、无重复表;带旧键的既有 settings.json 仍能解析到 server 运行时;排队的旧式 server_beta_generate_event job 仍能被处理。反模式护栏:绝不 DROP 或重建有数据的表;绝不在不做双接受的情况下改列里存历史枚举值。
当前仓库印证了该阶段的中间状态:create-server-service.ts L198-L202 中 ServerServiceGraph.runtime 仍写死持久化字面量 'server-beta',注释明确"Phase 1d will migrate this value. The TS identifiers above are now Server*; the wire/storage value remains 'server-beta' for back-compat";而 initializePostgres 仍在读 server_beta_schema_migrations 表。也就是说,代码标识符已新、存储值仍旧,正是计划刻意选择的兼容姿态。
四、Phase 2:SDK 包骨架 + 构建 + export 接线
计划写作时,package.json 曾留下一个"空槽":exports["."] → dist/index.js 与 exports["./sdk"] → dist/sdk/index.js,但从未有 src/index.ts、src/sdk/index.ts,也没有构建步骤产出它们(构建脚本 scripts/build-hooks.js 只产出 dist/npx-cli + dist/opencode-plugin,且已有 bun:sqlite import guard 先例)。当前仓库快照中,package.json 的 exports 仅有 "./modes/*",src/index.ts 与 src/sdk/index.ts 均不存在,src/sdk/ 下只有既有的 parser.ts、prompts.ts、hardened-options.ts、output-classifier.ts——即该导出槽仍处于"占位未兑现"状态,这正是计划要补齐的内容:
- 新建
src/sdk/index.ts作为公开入口(重导出createCmemClient、CmemClient与公开类型);既有的parser.ts/prompts.ts留在原处内部复用; - 新建
src/index.ts(.导出),重导出 SDK 面,保持最小; - 增加真正产出 JS +
.d.ts的构建:方案 A 用tsconfig.sdk.json(继承根配置、rootDir: src、outDir: dist、declaration: true、types: ["node"]——去掉"bun",include 仅限 SDK 传递依赖源);方案 B 引入tsup(devDep),entries 为src/index.ts+src/sdk/index.ts,format: esm、dts: true、platform: node。新增"build:sdk"脚本并接入build与prepublishOnly; - 核对
package.json的exports映射并确认files字段携带dist。
验证清单:
npm run build产出dist/sdk/index.js和dist/sdk/index.d.ts;- 在干净的 node 项目里
import { createCmemClient } from 'claude-mem/sdk'能解析且类型可加载; - Import guard:构建/测试步骤 grep SDK bundle(或其解析后的 import 图),一旦出现
express、bullmq、ioredis、better-auth、react、bun:sqlite即失败。
反模式护栏:不要对全仓库做 tsc 产物输出(会拖进 worker 与 bun:sqlite);SDK 构建只圈定自己的入口。不要给 SDK 加 @anthropic-ai/claude-agent-sdk 依赖——server 生成 provider 用 fetch。
五、Phase 3–5:连接与租户、捕获 API、进程内生成 API
Phase 3:核心——连接、schema bootstrap、repositories、tenancy
从 create-server-service 的组合段复制对象图(去掉 service/queue/worker 部分):
createCmemClient(options),options = { databaseUrl?, pool?, teamId?, projectId?, provider?, chroma?: ChromaOptions }。注意:chroma 只用于调参(collection 前缀、MCP 路径等),不能用于禁用——不存在 enabled: false 开关。
- Pool:
options.pool ?? createPostgresPool(parsePostgresConfig({ env: { CLAUDE_MEM_SERVER_DATABASE_URL: options.databaseUrl ?? process.env... } })!),或直接用getSharedPostgresPool; await bootstrapServerPostgresSchema(pool)(幂等);repos = createPostgresStorageRepositories(pool);- Chroma 必需:
chromaSync = new ChromaSync(projectId);await chromaSync.ensureReady()(或首次addDocuments/queryChroma调用)。uvx chroma-mcp起不来时,createCmemClient以清晰错误 REJECT——SDK 绝不返回一个半残的客户端。
Tenancy 自举:Postgres 每次调用都要求 teamId + projectId,而 ProjectsRepository 没有按名查找(只有 getByIdForTeam)。因此:
- 若
options.teamId/projectId已给 → 直接用; - 否则
ensureDefaults():创建一次默认 team(teams.create({name:'default'}))+ 默认 project(projects.create({teamId, name: options.projectName ?? 'default'})),并把 ID 持久化到 SDK 本地状态文件(如$CLAUDE_MEM_DATA_DIR/sdk-tenant.json),后续运行复用。文档要注明:生产消费者应显式传 ID。
反模式护栏:不要要求 Redis/bullmq 环境变量(validateServerEnv 的 Docker 检查是给 HTTP 容器的,SDK 永不调用它);不要给 repository 发明 getProjectByName——持久化 ID 即可。
Phase 4:捕获 API
client.capture(event)/client.captureBatch(events)包装new IngestEventsService({ pool, resolveEventQueue: () => null }).ingestOne(input, { generate: false })——写一条agent_event+ 一行queued状态的 generation-job outbox,无 Redis;- SDK 友好事件形 →
CreatePostgresAgentEventInput的映射:{ projectId, teamId, serverSessionId?, sourceAdapter, sourceEventId?, eventType, payload, occurredAt }; - 可选经
PostgresServerSessionsRepository暴露client.startSession()/endSession()做分组。
验证:capture(...) 之后,该租户恰好一行 agent_events + 一行 observation_generation_jobs(状态 queued),且未尝试任何 Redis 连接。
Phase 5:生成/压缩 API(进程内,无 worker)
复现 ProviderObservationGenerator 中"内联核心"部分(ProviderObservationGenerator.ts),跳过 BullMQ 仪式段:
- Provider:
options.provider→ 实例化ClaudeObservationProvider({apiKey, model?})(或 Gemini/OpenRouter),或复用buildServerGenerationProviderFromEnv()的 env 驱动逻辑——当前仓库中该函数支持claude/anthropic(读ANTHROPIC_API_KEY或CLAUDE_MEM_ANTHROPIC_API_KEY)、gemini、openrouter(支持可选 base URL)三种,见 create-server-service.ts L256-L282; - 为
parseAgentXml保证激活模式:构造客户端时用默认模式初始化ModeManager(或用 prompt-builder 的回退语义); client.generate(jobOrEventId)的四步:job = transitionStatus({ id, projectId, teamId, status:'processing', lockedBy:'sdk' })(强制queued→processing);- 加载 events(
agentEvents.getByIdForScope/listByProject)+ project(projects.getByIdForTeam); result = await provider.generate({ job, events, project:{ projectId, teamId, serverSessionId, projectName } });await processGeneratedResponse({ pool, job, rawText: result.rawText, modelId: result.modelId, providerLabel: result.providerLabel, sourceAdapter:'sdk' })。
- 便捷方法:
client.captureAndGenerate(event)= Phase 4 + 5 的顺序组合。
验证:captureAndGenerate(...) 产出一行 observations,其 metadata 携带 {title,subtitle,facts,narrative,concepts,files_*},job 终结于 completed,observation_sources 把它关联回源 agent_event。
六、Phase 6:搜索——Chroma 语义为主 + FTS 运行时安全网 + context
再次强调:Chroma 是必需的。下面的纯 FTS 分支只用于镜像 SearchManager 的"捕获异常、降级一次"行为,应对 Chroma 瞬死——它不是特性开关,不是可配置关闭的路径,并且会发出响亮的 logger.error,让坏状态可见。
实现要点:
client.search({ query, limit })镜像SearchManager.search的分支逻辑,但作用于 Postgres:- 默认路径 →
queryChroma(query, limit, whereFilter)→ 返回按语义距离排序的 observation UUID → 经observations.getByIdForScope/批量水合; - 空查询路径 →
PostgresObservationRepository.listByProject(...)(没有语义意图可表达); - 仅在 Chroma 运行时失败时(不是配置问题):回退
PostgresObservationRepository.search({projectId, teamId, query, limit})(FTS),记logger.error('CHROMA', 'semantic search failed; returning degraded FTS results — investigate uvx chroma-mcp', err),并在响应中带{ degraded: true },让调用方自行决定重试或让本方请求失败。
- 默认路径 →
client.context({ query, limit })= 执行search后results.map(o => o.content).join('\n\n')(镜像服务端 V1 路由的 context 端点语义)。- Chroma↔Postgres 胶水(全计划中唯一真正的新代码,保持最小):复用存储无关的文档层,不要复用 SQLite 形状的
syncObservation——在 observation 落库时(Phase 5)即时索引:构造ChromaDocument { id: observation.id /*UUID 字符串*/, document: observation.content, metadata: { projectId, teamId, kind, serverSessionId } },走既有chroma_add_documents路径(经ChromaMcpManager.callTool,或把addDocuments从private重构成可复用接缝);collection 名沿用cm__<projectId>约定。写时同步意味着不涉及 SQLite 的 backfill/watermark 路径(ChromaSyncState/整型 ID 留在 SQLite 侧)。
验证矩阵:
- 构造期 Chroma 必需:故意让
uvx/chroma-mcp 不可用,createCmemClient(...)必须 REJECT(不存在静默 FTS-only 模式); - 快乐路径:
createCmemClient+captureAndGenerate+search('semantic query')返回按语义距离排序、已水合的 Postgres observations; - 运行时失败路径:成功
search后杀掉 chroma-mcp,再search——结果带{ degraded: true }返回,发出logger.error('CHROMA', …),而随后的一次冷启动createCmemClient应 REJECT; context(...)返回{ observations, context }(内容以\n\n连接),且底层search降级时同样上浮{ degraded: true }。
反模式护栏:不加 pgvector;不复用整型 id 的 syncObservation;不要求 embeddings API key(Chroma 本地嵌入);不加 chroma.enabled = false 选项(那会重新引入被明确拒绝的"静默坏掉"状态)。
七、Phase 7–9:公开门面、测试与最终验证
Phase 7(门面与类型):CmemClient 统一暴露 capture、captureBatch、generate、captureAndGenerate、search、context、startSession、endSession、close()(关 pool + 关 Chroma);公开类型保持小而稳,重导出 PostgresObservation、捕获输入类型、检索/上下文类型及相关 src/core/schemas 的 Zod 类型。close() 必须 await chromaSync?.close(),且仅当 SDK 自己创建的 pool 才执行 closePostgresPool。验证:一个端到端测试在 Postgres 测试库上跑通 createCmemClient → captureAndGenerate → search → context → close。反模式:无 HTTP 服务、无 pidfile、无 process.exit、无 daemon。
Phase 8(测试 + 无 worker 示例应用 + 文档):
- 对 Postgres 测试库做单元/集成测试(复用改名后的
scripts/e2e-server-docker.shdocker 脚手架),覆盖:schema bootstrap 幂等、capture、内联生成、FTS 搜索、Chroma 降级、tenancy 自举; examples/sdk-node/——一个纯 Node(不是 Bun)脚本,导入claude-mem/sdk,指向CLAUDE_MEM_SERVER_DATABASE_URL,在没有 worker/daemon 运行的条件下跑 capture → generate → search。这是"标题要求"的实证;- 文档:在
docs/public/加一页 "Using claude-mem in your app (SDK)" 并更新导航。
验证:npm test 全绿;示例在 node 下运行(无 Bun),打印生成的 observations 与检索命中,期间没有任何 worker 进程存活。
Phase 9(最终验证):
- 改名完整且安全:
rg -i 'server[-_]?beta'只剩刻意保留的持久化字面量(记录在 1d)与 changelog/历史计划文件;npm run typecheck+npm test全绿;CLAUDE_MEM_RUNTIME=server能到达 Postgres(1a 的回归测试); - SDK 无禁用依赖:自动 guard 确认
claude-mem/sdk的 import 图不含express、bullmq、ioredis、better-auth、react、bun:sqlite、@anthropic-ai/claude-agent-sdk; - 导出为真:
dist/index.js、dist/index.d.ts、dist/sdk/index.js、dist/sdk/index.d.ts构建后全部存在,且可从外部项目解析; - 无发明 API:grep SDK 确认没有
pgvector/vector(/embedding写入;生成走fetchprovider 而非 agent SDK;parseAgentXml总在激活模式下被调用; - 标题要求达成:Phase 8 示例证明"纯 Node、进程内、无 HTTP worker"下的完整 capture → 压缩 → 语义+FTS 检索链路。
八、遗留问题与修正日志:计划本身如何被纠偏
计划尾部保留了两个未决执行决策与一条重要修正记录,对读者理解"计划文档如何演进"很有价值:
- tsup vs
tsconfig.sdk.json(Phase 2):执行时二选一——tsup 一步出 JS+dts,tsconfig 方案避免新增 devDep; - Chroma
addDocuments的暴露方式(Phase 6):把private addDocuments重构成可复用接缝,还是 SDK 直接调ChromaMcpManager.callTool('chroma_add_documents')。倾向选择"保持 chroma-mcp 协议单一路径的最小改动"。
修正日志(2026-05-29)记录了这次计划最关键的自我纠偏:原稿把 Chroma 表述为"可选",这是错的——没有语义检索的 claude-mem 就是坏的。修正后:架构图与执行决策标注 Chroma 为 REQUIRED;createCmemClient 选项去掉布尔关闭开关,ChromaOptions 只负责调参;Phase 6 默认路径是 Chroma,FTS 仅在瞬态失败时作为运行时安全网出现(上浮 { degraded: true } 并打 logger.error),不是特性开关;验证新增"Chroma 不可用时构造函数必须 REJECT";反模式新增"禁止 chroma.enabled = false 选项"。
九、小结:如何读这份计划与当前仓库
把 plans/2026-05-25-cmem-sdk-and-server-rename.md 当作两份文档来读是最高效的:
- 改名工程(Phase 1):当前仓库已完成 1a/1b/1c——runtime-selector.ts 双字面量接受、新键优先旧键回退、
src/server/runtime/与src/services/hooks/server-client.ts、server-bootstrap.ts等新文件均已就位;1d 处于计划预期的中间态:createServerService的 graph 里持久化字面量仍为'server-beta'(见 create-server-service.ts L198-L202),server_beta_schema_migrations表名亦被刻意保留。理解"TS 标识符已新、存储值仍旧"的双轨状态,是看懂该仓库 server 侧代码的关键。 - SDK 化工程(Phase 2–9):当前快照中
src/sdk/只有既有的parser.ts/prompts.ts(已被计划列为复用件),尚无src/sdk/index.ts公开入口,package.json也暂未兑现././sdk导出——也就是说,SDK 的"导出槽"仍是计划要兑现的核心目标,读者可对照 Phase 2 的验证清单跟踪其后续落地。
整体来看,这份计划的方法论价值在于:先用 Phase 0 的 verbatim API 清单与反模式清单锁死边界,再按"回归修复 → 机械改名 → 危险持久化值"的风险递增顺序推进改名,最后以"对象图复制 + 最小胶水"的方式把既有 Postgres 运行时产品化为库——每一步都有明确的验证命令与反模式护栏,是一套可直接借鉴的"存量运行时 SDK 化"实施模板。
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