跳到主要内容

巧妙之处、边界与代码地图

本章讲什么: 把前四章散落的「精华」收拢成一张可带走的清单,诚实标出项目会在哪崩,和兄弟项目比一比取舍,最后给一张按符号名导航的代码地图。

1. 巧妙之处(可借鉴的技术)

零 LLM 是默认,LLM 是 opt-in。 整个项目最核心的成本哲学:捕获/压缩/取代/召回/衰减都不需要 LLM——合成压缩用正则(compress-synthetic.ts:76)、取代判定用 Jaccard(remember.ts:77)。只有想要更丰富的语义摘要(AUTO_COMPRESS)和 4 层巩固(CONSOLIDATION_ENABLED)才花 token。妙在:基础记忆能力对用户「免费」,贵的能力按需付费。

RRF 的动态权重归一化。 融合时把缺席流的权重归零再重新归一(hybrid-search.ts:194-206),让 BM25-only 和三路全开两种部署都不用改配置就排序合理。这是「优雅降级」落到排序公式里的写法。

对称的维度守卫。 向量维度不匹配会静默毁掉搜索(跨维余弦=0)。项目在写入侧(vectorIndexAddGuarded,search.ts:94)和加载侧(逐条 validateDimensions 否则拒绝启动,src/index.ts:405-447)都设了守卫——两头堵,而不是出了问题才查。

fire-and-forget hook 的正确退出。 纯遥测 hook 发出 fetch 不 await,再用 setTimeout(...).unref() 强制退出(AGENTS.md 详述)——否则 Node 事件循环会一直等 fetch settle,fire-and-forget 形同虚设。这是个容易写错的细节,项目把它写进了贡献规范。

删除要同步刷盘。 内存索引的删除如果只在内存里,硬退出后持久化快照会「复活」被删条目。所以 mem::forgetflushIndexSave() 同步刷(search.ts:63-74,remember.ts:243),而 add 走 debounce 异步刷——按操作的「能否丢失」区别对待。

Float32Array 序列化的 byteOffset 陷阱。 显式传 byteOffset+byteLength 防 Node Buffer pool 制造幽灵维度(vector-index.ts:8-21),修了四个连号 issue。值得任何做向量持久化的人抄走。

2. 边界与局限(诚实)

  • 向量是暴力 KNN,没有 ANN 索引。 VectorIndex.search(vector-index.ts:49)对每条算余弦,O(N)。语料到几十万条时单次向量查询会线性变慢——没有 HNSW/IVF。索引也整个驻内存,大语料吃 RAM。
  • 取代判定对语义改写不敏感。 Jaccard 是词集合交并比(schema.ts:94);「用 yarn」和「采用 Yarn 包管理器」词重叠低,可能判不出是同一件事,留下两条并存记忆。
  • 合成压缩信息密度低。 默认路径的 facts/concepts 为空、narrative 截断到 400 字(compress-synthetic.ts),confidence 只有 0.3。不开 LLM 压缩时召回到的观察偏「流水账」。
  • 召回侧有大 scope 全扫的隐患。 mem::context(context.ts:135)和取代判定都 kv.list 整个 scope。KV 注释里多处提到 75K+ 节点的图谱已撞过 iii 调用超时(schema.ts:18-39),靠预计算快照绕过——说明全扫在大体量下是真实瓶颈。
  • 强一致性不保证。 跨函数的多步写(写观察 + 更新 session + 建索引)只在单会话 keyed-mutex 内串行,没有跨 scope 事务;部分失败靠 try/catch + 重启重建兜底,不是 ACID。

3. 横向对比(同 shelf 兄弟)

memory-context 这一格关注「agent 怎么把上下文存下来、找回来」。agentmemory 的取舍位置:

维度agentmemory 的选择取舍
存储iii-engine 内置 SQLite KV,零外部 DB装得轻,但受单引擎调用超时/单机内存约束
检索BM25+向量+图谱 RRF 融合,自实现三路兜底召回好,但向量是暴力 KNN 不扩展
捕获hook 全自动 + 零 LLM 默认零摩擦零成本,但默认观察信息密度低
记忆模型显式版本化 + 4 层巩固(LLM opt-in)模型完整,但巩固/语义层依赖 LLM
集成面REST + MCP + 12 hooks,跨 10+ agent集成广,但一致性维护负担重(AGENTS.md 列了 8 处要同步)

对比基线:很多「agent memory」方案直接挂外部向量库(Pinecone/Chroma)+ 纯向量召回。agentmemory 反过来——自带轻量存储 + 多路融合 + 零 LLM 默认,换来「装一个 npm 包就能跑、不烧 token」,代价是检索在超大语料下的可扩展性。

4. 代码地图(导航索引)

按符号名 grep 比按行号稳。下表是读源码的跳转表:

主题文件符号名
Worker 启动 / 全部函数注册src/index.tsmain
捕获主入口src/functions/observe.tsregisterObserveFunction
零 LLM 合成压缩src/functions/compress-synthetic.tsbuildSyntheticCompressioninferType
去重(SHA-256 + TTL)src/functions/dedup.tsDedupMapcomputeHash
密钥脱敏src/functions/privacy.tsstripPrivateData
BM25 倒排索引src/state/search-index.tsSearchIndexsearch
向量暴力 KNNsrc/state/vector-index.tsVectorIndexcosineSimilarity
三路融合检索 / RRFsrc/state/hybrid-search.tsHybridSearchtripleStreamSearchdiversifyBySession
检索函数入口 / 重建索引src/functions/search.tsregisterSearchFunctionrebuildIndexvectorIndexAddGuarded
上下文注入(召回拼装)src/functions/context.tsregisterContextFunction
显式记忆 / 取代 / 遗忘src/functions/remember.tsregisterRememberFunctionmem::forget
4 层巩固 / 衰减src/functions/consolidation-pipeline.tsregisterConsolidationPipelineFunctionapplyDecay
KV 状态层src/state/kv.tsStateKV
KV scope 定义 / id 生成src/state/schema.tsKVgenerateIdfingerprintIdjaccardSimilarity
并发锁src/state/keyed-mutex.tswithKeyedLock
REST 端点注册src/triggers/api.tsregisterApiTriggers
事件 / state / durable triggersrc/triggers/events.tsregisterEventTriggers
MCP 工具 → 函数映射src/mcp/server.tsregisterMcpEndpoints
MCP 工具定义src/mcp/tools-registry.tsCORE_TOOLSgetAllTools
上下文注入 hooksrc/hooks/session-start.tsmain

5. 一句话收束

agentmemory 的精华是「把 agent 记忆做成零摩擦、零默认成本的本地服务」:hook 自动捕获、正则压缩、三路融合召回、显式记忆版本化——贵的(LLM 巩固、丰富压缩)全是 opt-in。它在「装得轻、不烧 token」上做到了极致,代价是向量检索不扩展、超大语料会撞引擎超时。要它的轻便就接受它的天花板。