首页
/ 深入 Mem0 OpenCode 插件:mem0-search 技能的查询解析、双路记忆召回与紧凑输出规范

深入 Mem0 OpenCode 插件:mem0-search 技能的查询解析、双路记忆召回与紧凑输出规范

2026-09-04 14:17:25作者:柏廷章Berta

在 Mem0 平台的 OpenCode 插件(@mem0/opencode-plugin)中,/mem0-search 是一条面向"快速记忆检索"的斜杠命令:用户给出查询词,Agent 即以并行双路语义搜索当前项目的 Mem0 记忆,并按单行紧凑格式返回结果;若查询词本身是一条记忆 ID 或 [mem0:<hex>] 引用,则直接按 ID 取回单条记忆。本文以 mem0-search 技能定义文件 为主体,完整还原其三步执行流程(查询解析 → 双路搜索 → 紧凑展示),并结合插件入口 opencode-mem0.ts 与范围模块 scope.ts 的源码,说明身份过滤、scope 模型和输出约束背后的实现细节。

技能定位:比 /mem0-tour 更轻量的检索入口

mem0-search 的技能描述(frontmatter description 字段)明确了它的四个典型用途:快速记忆查询、确认某个决策是否已被记录、解析 [mem0:id] 引用、以及在不展开全部分类细节的情况下浏览记忆。文件开头的一句话也点明了定位:"Quick search with compact output. Lighter than /mem0-tour。"

对比同目录下的 mem0-tour 技能:tour 面向"盘点项目全部记忆",先 get_memories 全量拉取(page_size=100),再并行跑三路补充语义搜索,最后按平台 categories 分组、每分类展示最多 5 条;而 search 只针对单个查询词,固定走两路搜索 + 去重 + 单行渲染,开销与输出体量都显著更小。在 OpenCode 插件 README 的组件清单中,/mem0-search/mem0-remember/mem0-tour/mem0-status/mem0-scope/mem0-dream/mem0-forget/mem0-pin/mem0-context-loader 一起构成插件自带的 9 个技能。

该命令在 Claude Code 侧的对应物是 /mem0:peek(见 skills/peek/SKILL.md),两者执行逻辑完全一致,仅命令前缀不同——mem0-plugin 同时维护两套技能目录,一套供 Claude Code/Codex 等走 MCP 的工具使用,一套供 OpenCode 原生工具链使用。

命令如何注册到 OpenCode:从 SKILL.md 到斜杠菜单

技能文件本身只是"提示词模板",真正让它变成可输入的 /mem0-search 的是插件入口的 config 钩子。在 opencode-mem0.ts 的 registerCommands 函数 中,插件遍历 opencode-skills/ 目录下的每个子目录,读取其中的 SKILL.md,用正则 /^description:\s*(.+)$/m 提取 frontmatter 里的 description 作为命令说明,并向 OpenCode 的 config.command 注册一条斜杠命令,其 template 形如:

Load and execute the `mem0-search` skill.

Use the mem0 memory tools (add_memory, search_memories, get_memories, get_memory, ...) as instructed by the skill.

Identity context (resolved at plugin startup):
- user_id: ${userId}
- app_id: ${appId}
- session_id: ${sessionId}
- branch: ${branch}

这里有两点值得注意:

  • 身份上下文由插件启动时解析并注入模板userId 来自 getUserId()(优先 MEM0_USER_ID 环境变量,否则取系统用户名);appId 来自 getProjectId(),解析顺序为 MEM0_APP_ID 环境变量 → git remote get-url origin 的 owner/repo(跨 clone、worktree 稳定)→ git 仓库根目录名 → 当前目录名。技能文件里的 filters={"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]} 中的占位符 <id>/<pid> 就由这段身份上下文填实。
  • shell.env 钩子L384-L395)把 MEM0_USER_IDMEM0_APP_IDMEM0_SESSION_IDMEM0_BRANCH 导出给 shell,供技能脚本读取,避免每条命令都重新跑 git。

执行流程 Step 1:查询解析与 Memory ID 检测

用户输入形如:

/mem0-search auth middleware

若未提供查询词,技能要求 Agent 反问 "What should I search for?"。

真正的关键设计在 Memory ID 检测:当查询词匹配以下任一模式时,不再做语义搜索,而是转为按 ID 直接取回单条记忆——

  • 裸 hex 短 ID:^[a-f0-9]{8}$(8 位十六进制)
  • 完整 UUID:^[a-f0-9]{8}-[a-f0-9-]+$
  • 引用标记:[mem0:<hex>],提取其中的 hex 部分

检测到 ID 后的处理链是:

  1. 直接调用 get_memory(<id>)(若只有 8 位短 ID,则把它当作完整 UUID 的前缀去尝试);
  2. 若找到,跳过 Step 2,直接按 Step 3 的格式展示这一条结果;
  3. 若未找到,回落到普通搜索,把该 ID 字符串当作查询文本。

这个设计之所以成立,是因为插件生态里到处都在输出 [mem0:<short_id>] 引用。例如 Python 侧预取钩子的公共格式化函数 format_results_for_context 对每条记忆都会生成 - [<type>] <text> [mem0:<前8位ID>] 行;dreamhealthmemory-reviewer 等技能的输出也大量使用 [mem0:<id>] 标注待处理记忆。用户看到某条自动注入的记忆后,把 [mem0:a3f8b2c1] 原样丢给 /mem0-search,即可精确回查该条记忆全文——这就是技能描述中 "resolving [mem0:id] citations" 的用意。

get_memory 工具本身在 opencode-mem0.ts 的 tool 定义 中实现为对 mem0ai SDK 的 mem0.get(args.id) 的一次调用,返回单条记忆的完整内容(memory 文本 + metadata + 平台字段)。

执行流程 Step 2:双路并行搜索

未命中 ID 时,技能要求运行 两个并行的 search_memories 调用

query filters top_k rerank
Broad(宽泛) 用户原始查询词 {"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}]} 10 true
Targeted(定向) 用户原始查询词 {"AND": [{"user_id": "<id>"}, {"app_id": "<pid>"}, {"metadata": {"type": "decision"}}]} 5 true

两条路的区别只在第三条 AND 子句:定向路额外约束 metadata.type = "decision",专门捞"决策"类记忆(例如"Auth module uses JWT with RS256 keys"),因为开发者问得最多的往往就是"我们当时为什么这么定"。两路合并去重后,既覆盖了相关度排序的宽泛召回,又保证决策类记忆不被淹没。

对照同目录 mem0-tour 的补充搜索 可以看到一个刻意相反的约定:tour 明确警告"不要在这些调用里按 metadata.type 过滤,平台自动分配的 categories 可能不含显式 metadata.type"——那是"全量盘点"场景;而 search 的定向路只针对单一 decision 类型做补充召回,主召回由 Broad 路兜底,因此不会因部分记忆缺 metadata.type 而整体漏检。

关于 rerank=true:插件 Python 侧钩子辅助库 _search.py 的注释说明了平台 REST 搜索端点(POST /v3/memories/search/)的行为——省略 rerank 时结果按原始向量相似度排序,最相关的一条可能落在 top_k 窗口之外;显式开启 rerank 有约 150–200ms 的额外开销,钩子路径默认开启并允许用 MEM0_RERANK=0/false/no/off 关闭。技能在展示型搜索中固定要求 rerank=true,是为了让少量结果(10+5 条)的排序质量最大化。需要注意,OpenCode 原生 search_memories 工具的参数 schema(L466-L490)暴露的是 queryfilterslimit/top_kscope 等参数,rerank 能力由平台搜索端处理,技能指令是面向"搜索语义"的约定而非工具字面参数。

从源码结构看,search_memories 工具的 execute 实现为:topK = args.limit ?? args.top_k ?? 10(默认 10,与技能 Broad 路的 top_k=10 对齐),过滤条件经 readScopeFilters 解析后调用 mem0.search(query, {filters, topK})

执行流程 Step 3:去重与紧凑单行渲染

两路结果先按 memory ID 去重,再按下述格式渲染:

## mem0 search: "<query>" (<N> results)

1. [decision] Auth module uses JWT with RS256 keys (2025-05-15) [mem0:a3f8b2c1]
2. [anti_pattern] Don't use symmetric HS256 — leaked in env (2025-05-10) [mem0:7e2d9f4a]
3. [convention] All middleware in src/middleware/ (2025-05-08) [mem0:c4d5e6f7]

单行格式约定为:

<序号>. [<type>] <内容, 截断到 80 字符> (<日期>) [mem0:<短ID>]

其中 [<type>] 取自记忆的 metadata 类型或平台分类(示例中的 decision / anti_pattern / convention),[mem0:<短ID>] 取 ID 前 8 位——与 mem0-tour 的分组映射表一致,decision 对应平台分类 architecture decisions / architecture_decisions / decision 等。这个短 ID 又构成了下一轮 /mem0-search 的输入,形成"列表 → 精确回查"的闭环。

无结果时的输出被固定为:

No memories matching "<query>" for project <project_id>.

源码纵深:过滤条件如何被插件解析

技能文件里的 filters={"AND": [...]} 与插件工具侧的过滤解析逻辑是同一套语义。resolveFilters 的合并规则是:

  • 若调用方已传 filters,优先保留其中的 AND 子句,检查是否已包含 user_id/app_id/agent_id,缺哪个补哪个,最终仍收敛为 {AND: [...]} 形态;
  • 未传 filters 且开启了 global_search~/.mem0/settings.jsonglobal_search: true,覆盖所有用户)时,使用 {OR: [{user_id: "*"}]}
  • 传了 agent_id 时按 {AND: [{agent_id}, {app_id}]} 收敛;
  • 其余情况默认 {AND: [{user_id: <当前用户>}, {app_id: <当前项目>}]}——即技能 Step 2 中 Broad 路 filters 的原型。

在此基础上,scope.ts 定义了三种调用级 scope,search_memories 等工具可通过 scope 参数覆盖默认过滤(L17-L32 的 scopeSearchFilters):

scope 读过滤 语义
project(默认) user_id + app_id 仅本仓库
session user_id + app_id + run_id 仅本次会话
global user_id + app_id: "*" 该用户所有项目

默认 scope 可由 /mem0-scope 技能 修改,持久化在 ~/.mem0/settings.jsondefault_scope 字段;插件在每次记忆操作时重新读取该设置(loadDefaultScope),所以切换立即生效、无需重启。技能里固定的 user_id + app_id AND 过滤对应的是默认的 project scope;若用户默认 scope 已改为 sessionglobalreadScopeFilters 会在未显式传 scope/filters 时优先采用持久化默认值。

另外值得了解的是自动注入路径:插件的 chat.message 钩子在每条用户消息(≥10 字符)上都会自动跑一次 mem0.search(top_k=5,同样的 user/app AND 过滤,先经 redact() 用正则抹掉 sk-/m0-/AKIA/ghp_ 等密钥模式),把命中记忆以 "Relevant memories" 注入系统上下文。也就是说 /mem0-search 是用户主动发起的、面向终端展示的检索;钩子自动注入则是后台静默召回,两者共用同一套过滤与搜索语义。

输出约束:OpenCode TUI 不使用 Markdown

技能文件末尾的 "Output formatting" 一节是一条硬性约束:

IMPORTANT: Do NOT use markdown in your output. OpenCode TUI renders text verbatim — markdown like bold, ## headers, and | table | syntax appears as raw characters. Use plain text with indentation for structure. Use dashes for lists. Use spaces to align columns instead of markdown tables.

由于 OpenCode 的 TUI 按原文渲染文本,**bold**## 标题、| 表格会原样显示为字符垃圾,因此技能要求结果用纯文本 + 缩进组织、用破折号代替列表符号、用空格对齐代替 Markdown 表格。这也解释了上面示例中 ## mem0 search: 这种写法是"示意标题",Agent 实际输出时应以纯文本呈现。该约定在 mem0-plugin 的所有 OpenCode 技能(tour、scope、search 等)中重复出现,是同一渲染约束下的统一规范。

使用前提与验证方式

该技能随插件安装即得,前置条件只有两条:

  1. 环境中已设置 MEM0_API_KEYm0- 开头的 Mem0 平台密钥,如 export MEM0_API_KEY="m0-your-key");未设置时插件入口会记录错误日志并直接不注册任何工具(opencode-mem0.ts L262-L277);

  2. 已安装插件并重启 OpenCode:

    opencode plugin @mem0/opencode-plugin
    

    该命令把插件写入 ~/.config/opencode/opencode.json,插件通过自身的 config 钩子注册记忆工具、钩子与技能路径,无需单独配置 MCP server。安装后重启会话,输入 /mem0-search 出现在斜杠菜单中即为注册成功。

    验证可执行一次实际查询,例如 /mem0-search auth middleware;若项目尚无记忆,应看到 No memories matching "auth middleware" for project <project_id>. 的空态输出——这本身也证明了搜索链路(API key、身份解析、过滤条件)已贯通。

小结

mem0-search 技能的完整行为链条可以概括为:插件在启动时解析 user/app/branch 身份并注册 /mem0-search 斜杠命令 → 收到查询后先用 8 位 hex / UUID / [mem0:<hex>] 三种模式做 ID 检测,命中则 get_memory 直达、未命中回落搜索 → 否则并行执行 Broad(top_k=10)与 Targeted(metadata.type=decision, top_k=5)两路 rerank 搜索 → 按 ID 去重后以 <序号>. [<type>] <80字符内容> (<日期>) [mem0:<短ID>] 的纯文本单行输出。技能文件(mem0-search/SKILL.md)定义了行为契约,opencode-mem0.ts 提供 search_memories/get_memory 等原生工具与过滤解析,scope.ts 提供 project/session/global 三级范围模型,三者共同构成 OpenCode 中"轻量、可回查、可引用"的记忆检索入口。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384