首页
/ Mem0 OpenCode 插件的 mem0-scope 技能详解:默认记忆作用域(project / session / global)的管理机制与源码实现

Mem0 OpenCode 插件的 mem0-scope 技能详解:默认记忆作用域(project / session / global)的管理机制与源码实现

2026-09-05 21:45:57作者:虞亚竹Luna

在 Mem0(AI Agent 记忆层,仓库路径 mem0/mem0-ts/ 等)的 OpenCode 插件中,mem0-scope 是一个让 Agent 直接查看或切换"默认记忆作用域"的内置技能:记忆工具在未显式传入 scope 参数时,会按该默认作用域决定记忆写到哪个仓库、哪次运行,还是跨该用户的全部项目。本文以 mem0-scope/SKILL.md 为主体,完整梳理三种作用域的语义、~/.mem0/settings.json 中的持久化方式、查看/切换的完整执行流程,并结合 scope.tsopencode-mem0.ts 的源码说明其底层作用机制。

1. mem0-scope 技能是什么

mem0-scope 位于 Mem0 OpenCode 插件的技能目录下,文件为 opencode-skills/mem0-scope/SKILL.md。其 frontmatter 声明如下:

name: mem0-scope
description: Views or changes the default memory scope (project, session, or global) used when saving and searching memories. Use when the user wants to control whether memories are scoped to this repo, this run, or shared across all their projects.

技能解决的问题是:控制"未显式指定 scope 时记忆工具使用哪个作用域"。该默认值持久化在 ~/.mem0/settings.jsondefault_scope 字段中,而插件在每一次记忆操作时都会重新读取该文件,因此修改立即在当前会话生效,无需重启。

从插件入口 opencode-mem0.tsregisterCommands 可以看到其接入方式:插件扫描技能目录,为每个包含 SKILL.md 的子目录注册一个 /mem0-<name> 斜杠命令,命令模板内嵌了插件启动时解析出的身份信息(user_idapp_idsession_idbranch),并从 SKILL.mddescription 行提取命令描述。也就是说,用户在 OpenCode TUI 中输入 /mem0-scope session,触发的正是这份 SKILL.md 所描述的执行流程。

2. 三种作用域:语义与 Mem0 API 参数的映射

SKILL.md 定义了三种作用域,其对应的身份/过滤参数在 scope.ts 的文件头注释与实现中一一对应:

作用域 含义 读(search / get_memories)过滤条件 写(add / delete)身份参数
project(默认) 仅当前仓库 user_id + app_id user_id + app_id
session 仅当前这次运行(会话) user_id + app_id + run_id user_id + app_id + run_id
global 跨该用户的所有项目 user_id + app_id: "*" user_id(丢弃 app_id,记忆对用户级生效)

源码实现只有两个函数,逻辑非常紧凑。读操作使用 scopeSearchFilters

export function scopeSearchFilters(
  scope: Scope,
  userId: string,
  appId: string,
  runId: string,
): Record<string, string> {
  switch (scope) {
    case "session":
      return { user_id: userId, app_id: appId, run_id: runId };
    case "global":
      return { user_id: userId, app_id: "*" };
    case "project":
    default:
      return { user_id: userId, app_id: appId };
  }
}

写操作使用 scopeWriteParams,关键差异在于 global 分支直接丢弃 app_id,使写入的记忆不再绑定具体项目,而是成为该用户范围内的记忆;读时则用通配 app_id: "*" 跨项目检索。这一"读通配、写去绑定"的不对称设计,是 global 作用域能够"跨项目共享记忆"的核心机制。

配套测试 scope.test.ts 逐分支验证了上述映射,其中 global 分支的断言与源码注释一致:

test("global scope spans all the user's projects (matches pi-agent)", () => {
  expect(scopeSearchFilters("global", "u", "app", "run")).toEqual({
    user_id: "u",
    app_id: "*",
  });
  // global writes drop app_id so the memory is user-wide, not project-bound
  expect(scopeWriteParams("global", "u", "app", "run")).toEqual({ user_id: "u" });
});

此外,scope.ts 文件头说明该作用域模型移植自 pi-agent 插件(Mirrors pi-agent/src/memory/scoping.ts),并在仓库的 pi-agent-plugin 集成 中可以看到同源的 src/memory/scoping.ts 实现,两个插件对外暴露方式一致:scope每次工具调用的可选参数,而不是一个有状态的"切换项目"命令;SKILL 层再在其上叠加了一个"持久化默认值"。

3. 持久化机制:~/.mem0/settings.jsondefault_scope

技能的默认值读写全部围绕 ~/.mem0/settings.json 展开,插件侧的实现见 opencode-mem0.ts 的 loadSettings / loadDefaultScope

/** Read & parse `~/.mem0/settings.json`, returning {} when missing/invalid. */
function loadSettings() {
  try {
    const settingsPath = join(homedir(), ".mem0", "settings.json");
    if (!existsSync(settingsPath)) return {};
    return JSON.parse(readFileSync(settingsPath, "utf8"));
  } catch {
  }
  return {};
}

/**
 * The user's persisted default memory scope (set via the `mem0-scope` skill).
 * Read fresh so a scope change takes effect on the next memory operation without
 * restarting OpenCode. Defaults to "project".
 */
function loadDefaultScope(): Scope {
  return resolveDefaultScope(loadSettings());
}

几个值得注意的实现事实:

  • 文件不存在或 JSON 解析失败一律按 {} 处理,即回落到 project 默认作用域,不会报错中断。
  • 每次操作即时重读(源码注释明确写了 "Read fresh so a scope change takes effect on the next memory operation"),这正是 SKILL.md 中"修改立即在当前会话生效"承诺的底层依据。
  • 无效取值会被规范化:asScope / resolveDefaultScope 只接受 "session""global",其余任何值(包括数字、乱码、undefined)都归一为 "project"scope.test.tsdefault_scope: "nonsense"default_scope: 42 等边界情况都有断言。

settings 文件中除 default_scope 外还可存在其他字段(如 auto_savesearch_limitglobal_search),因此技能明确要求"读取—修改—写回"时保留全部既有键。

4. 执行流程:查看模式与切换模式

SKILL.md 将流程分为意图判定、查看模式、切换模式三步。

4.1 意图判定

检查用户消息中是否出现目标作用域词:projectsessionglobal(同时接受同义表达:"repo"→project、"run"→session、"all"/"everywhere"→global)。无目标词进入查看模式,有目标词进入切换模式。

4.2 查看模式:显示当前作用域

第一步是用一条 shell 命令从 settings 中读出当前默认作用域(命令直接来自 SKILL.md,可原样使用):

_S="$HOME/.mem0/settings.json"
[ -f "$_S" ] && grep -o '"default_scope"[[:space:]]*:[[:space:]]*"[a-z]*"' "$_S" | grep -o '[a-z]*"$' | tr -d '"' || echo "project"

若命令无输出,说明 default_scope 未设置,作用域即为默认的 project

第二步(可选)是统计当前作用域内的记忆数量:调用 get_memories,传入 scope="<当前作用域>"page_size=1,从响应的 count(或结果长度)中取总数。

第三步按插件导出的身份信息展示(SKILL.md 强调"不要重新 shell git",身份由插件在启动时解析并通过环境注入)。插件的 shell.env 钩子 正是这些变量的来源:

"shell.env": async (_input, output) => {
  if (output?.env) {
    output.env.MEM0_USER_ID = userId;
    output.env.MEM0_APP_ID = appId;
    output.env.MEM0_SESSION_ID = sessionId;
    output.env.MEM0_BRANCH = branch;
    output.env.MEM0_GLOBAL_SEARCH = globalSearch ? "true" : "false";
  }
}

查看模式的输出模板(纯文本,非 Markdown):

Mem0 memory scope

Current default scope: <current>

  project  - this repo only (user + app_id)        <marker if active>
  session  - this run only (adds run_id)            <marker if active>
  global   - all your projects (app_id = *)         <marker if active>

User:    ${MEM0_USER_ID}
Project: ${MEM0_APP_ID}
Session: ${MEM0_SESSION_ID}

To change: /mem0-scope session    (or project / global)

在当前作用域旁标注 [active];若第二步取到了计数,则追加一行 Memories in scope: <N>

关于输出格式,SKILL.md 末尾有一条针对 OpenCode TUI 的硬性约束:输出中不要使用任何 Markdown(加粗、## 标题、| 表格 | 在 TUI 中会被原样渲染为字符),用缩进组织结构、用短横线做列表、用空格对齐列。

4.3 切换模式:设置新的作用域

  1. 校验:目标必须是 project / session / global 之一,否则展示三个选项后终止;

  2. 读取:用 Read 工具读 ~/.mem0/settings.json(文件可能尚不存在,按 {} 处理);

  3. 写回:用 Write 工具写回,保留所有既有键,只把 "default_scope" 设为目标值;JSON 使用 2 空格缩进并带末尾换行。明确提示不得丢弃 global_searchdreamauto_save 等已存在的字段。示例结果文件:

    {
      "auto_save": true,
      "search_limit": 10,
      "default_scope": "global"
    }
    
  4. 确认,输出格式为:

    Default memory scope changed: <old> -> <new>
    
    <一行效果说明,见下>
    Applies immediately to memory tools in this session.
    
    To revert: /mem0-scope <old>
    

    各作用域对应的效果说明行:

    • project → "New memories and searches are limited to this repo."
    • session → "New memories and searches are limited to this run (this conversation)."
    • global → "New memories and searches span all your projects. delete_all_memories still needs an explicit scope=global to delete user-wide."

5. 默认作用域在工具调用链中的优先级

理解 /mem0-scope 的实际影响,要看插件如何为每个记忆工具解析作用域。readScopeFilters 给出了读操作的完整优先级:

// Resolve read filters for the memory tools. Precedence: an explicit `scope`
// arg wins; then explicit `filters`/`agent_id`; otherwise fall back to the
// user's persisted default scope (read fresh so /mem0-scope applies at once).
function readScopeFilters(args: any): any {
  if (args.scope) return scopeSearchFilters(asScope(args.scope), userId, appId, sessionId);
  if (args.filters || args.agent_id) return resolveFilters(args, globalSearch, userId, appId);
  const ds = loadDefaultScope();
  return ds === "project"
    ? resolveFilters(args, globalSearch, userId, appId)
    : scopeSearchFilters(ds, userId, appId, sessionId);
}

可以归纳为三级优先:显式 scope 参数 > 显式 filters/agent_id > 持久化默认作用域。写路径同理,add_memory 的执行逻辑args.scope ? asScope(args.scope) : loadDefaultScope() 后交给 scopeWriteParams 展开为身份参数。因此 SKILL.md 的第一条 Notes 成立:"这里只改默认值,任何记忆工具调用仍可通过显式 scope 覆盖单次行为"。

所有记忆工具(add_memorysearch_memoriesget_memoriesdelete_all_memories 等)的 schema 中都声明了同样的可选 scope 参数,描述文案与 SCOPE_GUIDANCE 保持一致:"global" 仅在用户明确要求跨项目检索/操作时使用,例如 add_memory 的 scope 参数

scope: tool.schema.string().optional()
  .describe('Write scope: "project" (this repo, default), "session" (this run), or "global" (user-wide, all projects). Use "global" only when explicitly asked.')

6. 安全边界:删除保护与 global_search 的区分

SKILL.md 的 Notes 部分给出了两条重要的安全约束,均与源码吻合:

  1. delete_all_memories 刻意忽略默认作用域。用户级全量删除必须显式传 scope="global"delete_all_memories 工具实现 中只在 args.scope 存在时才按该 scope 展开参数(否则不展开),且其工具描述为 "Delete ALL memories in the given scope. Destructive and irreversible -- only use when the user explicitly asks to wipe their memory."。其设计意图如 SKILL.md 所述:改变默认作用域永远不可能把一次常规清理变成跨项目误删。
  2. global 作用域与 global_search 设置是两个独立概念。前者是"当前用户的所有项目"(app_id="*",身份仍是当前 user_id);后者是 settings 中的另一个布尔开关(all users 级别的检索,见 loadGlobalSearch),切换默认作用域时不得触碰该字段。

7. 适用前提与限制

  • 本文讨论的技能与实现位于 OpenCode 插件目录 integrations/mem0-plugin/.opencode-plugin/,仅适用于通过 OpenCode 加载 Mem0 插件的场景;同仓库的 Claude/Cursor/Codex 等钩子(hooks/.cursor-plugin/ 等)走的是另一套机制。
  • 作用域解析与持久化的单元测试用 bun 运行(scope.test.ts 导入 bun:test);插件目录 package.json 管理其依赖。
  • session 作用域依赖插件启动时解析出的 session_id(即 MEM0_SESSION_ID),跨会话不可见;global 作用域只跨"当前用户"的项目,与 global_search 的全用户检索不同。
  • 作用域模型移植自 pi-agent 插件(见 scope.ts 文件头注释),两者通过 scope 作为每次调用的工具参数来暴露作用域,行为保持对齐。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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