OpenHuman 工具级记忆(Tool-Scoped Memory):把“绝不要给 Sarah 发邮件”变成 Agent 必须遵守的硬约束
2026/9/10 2:13:40 网站建设 项目流程

OpenHuman 工具级记忆(Tool-Scoped Memory):把“绝不要给 Sarah 发邮件”变成 Agent 必须遵守的硬约束

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

本文基于 OpenHuman 官方文档 Tool-Scoped Memory 展开,完整讲解 OpenHuman 中“工具作用域记忆层”的设计与实现:它如何为每个工具建立独立的规则命名空间、如何从用户指令和重复失败中自动捕获规则、如何通过三级优先级让 Critical 规则免疫会话内 Token 压缩,以及六个memory.tool_rule_*RPC 方法的参数细节。读完本文,你将能够理解并操作这一持久化、工具级安全规则系统,并能在源码层面验证它的捕获、存储与注入全链路。

什么是工具级记忆:定位与命名空间设计

工具级记忆(tool-scoped memory)层 捕获的是Agent 应当如何使用某个具体工具的“可执行指导”。它有两个明确的分界:

  • 它区别于 Memory Tools 提供的通用recall/store/forget检索能力——后者是面向 Memory Tree 知识库的通用读写;
  • 它也区别于纯统计性质的tool_effectiveness统计命名空间——后者只记录“发生了什么”(调用次数、错误模式),而工具级记忆记录“该怎么做”。

一句话概括它的价值:把用户在对话中随口说出的 “never email Sarah”(绝不要给 Sarah 发邮件),转写成 Agent 在后续每一轮都必须遵守的硬约束。这正是 OpenHuman issue #1400 要求的一等公民级“持久化学习 + 高优先级规则”存储与检索系统。

命名空间隔离

从源码 capture.rs 的模块注释可以看到其隔离原则:每个工具拥有自己的独立命名空间tool-{tool_name},与globalskill-{id}以及仅供统计的tool_effectiveness命名空间完全分开。存储层实现 中的命名空间识别也印证了这一点——常量TOOL_NAMESPACE_PREFIX = "tool-"专门用于识别工具级规则命名空间,而所有写入一律经由tool_memory_namespace()统一构造,保证“trim + 小写”的规范化规则只存在于一处:

// src/openhuman/memory/tool_memory/store.rs /// Namespace prefix every tool-scoped rule namespace carries. const TOOL_NAMESPACE_PREFIX: &str = "tool-";

统计信息(tool_effectiveness/tool/{name})与规则(tool-{name}/rule/{id}有意分属不同命名空间——一个跟踪“发生了什么”,另一个跟踪“对此该怎么办”。这种分离让安全规则永远不会被统计数据的读写路径污染。

数据模型:ToolMemoryRule 字段详解

命名空间内的每条条目都是一个ToolMemoryRule结构。官方文档定义的字段语义如下表(完整继承自原文档):

字段用途
id每条规则的稳定 UUID。Upsert 时会复用同一个 id。
tool_name规则适用的工具(如send_emailshell)。
ruleAgent 必须遵守的自然语言指导。
prioritycriticalhighnormal,同时驱动检索与压缩策略。
sourceuser_explicitpost_turnprogrammatic——记录规则来源(provenance)。
tags自由标签(safetypermission等)。
created_at/updated_atRFC3339 时间戳。

结合 store.rs 的put_rule实现,还可以补充几个文档未展开、但实操中重要的行为细节:

  • Upsert 语义tool_namerule均为空时直接报错;id为空时自动通过ToolMemoryRule::generate_id()铸造 UUID。tool_name会先做 trim + 小写化,与命名空间构造使用同一套规范化,因此调用方用原始名称读回时命中的是同一个命名空间。
  • created_at保留:对同一(tool_name, id)的重复 upsert 会保留最初的created_at,只刷新updated_at。实现上用一把进程级互斥锁(rule_mutation_lock)串行化“先 get 再 store”的读改写序列,防止并发 upsert 复活旧的created_at
  • 分类落库:每条规则写入时带上MemoryCategory::Custom("tool_memory")分类,便于在统一记忆后端中与通用记忆区分。
  • 读取容错list_rules中对无法反序列化的损坏行是跳过而非报错——一条坏数据不能把该工具的其他规则从提示词中全部隐藏;同理fetch_rule将坏行视为“不存在”,使其不阻塞后续 upsert 覆盖。

另外有一个特殊哨兵值值得注意:当用户指令中没有任何工具调用可以挂载时,规则会落到__unscoped__工具名下(capture.rs),以便在下一轮由 Agent 重新归位;这类规则不会进入提示词预取,避免把与任何真实工具无关的指导强行钉进任意会话的提示词。

优先级与压缩免疫:Critical / High / Normal 三级

官方文档给出的优先级矩阵如下(完整继承):

优先级存储位置是否抗压缩(compression-resistant)
critical通过ToolMemoryRulesSection钉入系统提示词——系统提示词按会话冻结,绝不会被会话内压缩器改写。
high同一个系统提示词块,排在 critical 之后。——同一机制。
normal存于命名空间内,按需经memory_recall检索。否——像其他命名空间记忆一样可被压缩。

这种“抗压缩”特性是结构性的:critical 与 high 规则寄宿在系统提示词中,而推理后端的 prefix cache 会把系统提示词在整个会话期间冻结。没有任何 Token 压缩路径能悄悄丢掉一条critical规则。

源码层面的两个关键证据:

  1. 预取过滤:store.rs 的rules_for_promptpriority.is_eager()过滤出 Critical + High 规则,先按 critical 优先、同级内按updated_at新者优先排序,然后截断至TOOL_MEMORY_PROMPT_CAP
// src/openhuman/memory/tool_memory/store.rs /// A cap on the **High**-priority remainder only... /// dropping one to fit a budget is the failure this surface exists to prevent. pub const TOOL_MEMORY_PROMPT_CAP: usize = 30;

注意这是一个文档化的权衡:上限主要作用于 High 优先级的剩余部分,一旦 Critical+High 规则的总数超过 30 条,排在队尾的规则(理论上包括 Critical)会被截断——源码注释明确保留了这一与引擎一致的语义,而不是“悄悄改进”成无脑保留全部 Critical。

  1. 提示词区块构造即快照:prompt.rs 中ToolMemoryRulesSection在构造时就把渲染结果冻结为快照字符串,PromptSection::build()直接原样返回、不依赖任何运行时上下文——注释里写得很直白:这是为了保持推理 prefix cache 始终温热。

捕获管线:两条自动路径

捕获钩子 的实现在 ToolMemoryCaptureHook。它实现 harness 的PostTurnHook接口(钩子名为tool_memory_capture),在每一轮对话结束后自动触发两条捕获路径:

路径一:用户指令(User Edicts)→ Critical 规则

用户消息中出现never <动词> <名词>don't <动词> ...do not <动词> ...stop <动词>ing ...这类祈使句式时,会被提升为匹配工具上的Critical规则。extract_user_edicts 的具体匹配逻辑:

  • .、换行、;切分用户消息,逐句检查是否以或包含上述祈使前缀(小写化后匹配);
  • stop一词只在句边界出现时才算祈使句(消息开头、. stop\nstop),避免 “I want to stop working” 这类普通表达误触发;
  • 命中的句子会被截断到MAX_RULE_LEN = 240字符,防止异常输入撑爆命名空间;
  • 工具归位:先尝试pick_tool_for_edict——检查本轮实际调用过的工具名是否作为词出现在指令文本中,再看一组刻意保持精简的常用名词别名表(tool_aliases):
工具名包含用户口语别名
*mail*emailmail
*shell*/*bash*/*exec*shellterminal
*browser*/*web*/*http*browserweb
*slack*slackdm

也就是说email会被映射到名为send_email的工具,shell映射到bash/exec。注释特别说明:这张表故意很小,更复杂的语义抽取“属于 LLM 抽取器的事”。

  • 别名也未命中时:规则落到本轮第一个执行的工具下,让它紧邻相关调用点;若本轮完全没有工具调用,则落到__unscoped__

捕获成功后以ToolMemoryPriority::Critical+ToolMemorySource::UserExplicit+ 标签["user-edict"]落库。

路径二:重复工具失败 → Normal 观察记录

extract_repeated_failures 统计本轮中每个工具的失败次数:同一工具在一个轮次内失败 ≥ 2 次时,才生成一条Normal优先级观察记录;单次瞬时失败被忽略,防止命名空间被噪声填满。记录正文内联总结失败概要,例如:

Tool failed 2 times in one turn (<首次失败的 output_summary>). Consider an alternative approach before retrying.

来源标记为ToolMemorySource::PostTurn、标签["repeated-failure"],这样 Agent 下次考虑使用该工具时就有上下文。

两条路径都刻意保守——只在信号明确时触发,且捕获的规则正文始终回指用户自己的原话,让审阅者能看出到底什么触发了这条规则。

开关:默认开启,可单独关闭

钩子在学习子系统(learning subsystem)开启时默认启用,可用环境变量单独关闭(env 覆盖层 确认了取值解析):

OPENHUMAN_LEARNING_TOOL_MEMORY_CAPTURE_ENABLED=0

从解析代码看,0/false/no/off均被识别为关闭,写入learning.tool_memory_capture_enabled配置项。

工具选择时刻的检索:预取 + 系统提示词钉装

检索侧的设计(完整继承原文档并补充源码印证):

  1. 会话开始时,harness 通过ToolMemoryStore::rules_for_prompt预取所有 Critical 和 High 规则
  2. session builder 将其渲染为## Tool-scoped rules区块(常量TOOL_MEMORY_HEADING),经 ToolMemoryRulesSection 钉入系统提示词;
  3. 因为提示词在会话生命周期内冻结,这些规则在每一轮的工具选择时刻、任何实际工具执行之前都可见。

渲染格式由 render_tool_memory_rules 决定:规则先按tool_name→ 优先级 → 规则文本 → id 排序,然后按工具分组输出。实际进入系统提示词的内容长这样:

## Tool-scoped rules These rules are pinned by the user or by the safety pipeline. Treat every entry as a hard constraint when considering the matching tool — do not override them silently. Lower-priority guidance lives in the `tool-{name}` memory namespace and can be queried via `memory_recall` if needed. ### `send_email` - **[critical]** Never email Sarah at sarah@example.com.

低优先级的指导则被排除在提示词预算之外,Agent 通过调用memory_recall查询tool-{name}命名空间来按需获取。

RPC 接口:memory 命名空间下的六个方法

schemas/tool_memory.rs 注册的FUNCTIONS与原文档表格一一对应,六个方法均暴露于memory命名空间下:

方法用途参数(snake_case JSON)
memory.tool_rule_putUpsert 一条规则。安全关键条目使用priority='critical'tool_name(必填)、rule(必填)、priority(可选,默认normal)、source(可选,默认programmatic)、tags(可选数组)、id(可选,提供则原位 upsert)
memory.tool_rule_get(tool_name, id)取单条规则,不存在时返回 null 而非报错。tool_nameid
memory.tool_rule_list列出某工具的全部规则,按优先级(critical → high → normal)再按updated_at降序排序。tool_name
memory.tool_rule_delete删除一条规则,返回布尔值(规则存在过则为true)。tool_nameid
memory.tool_rules_for_prompt返回渲染好的 Markdown 块 + 结构化规则快照——即 session builder 钉装的内容。tools(可选数组;空或省略时扫描所有已知工具命名空间)
memory.tool_rules_json返回某工具规则的原始 JSON 列表(供 envelope 消费方使用)。tool_name

参数默认值可在 ops/tool_memory.rs 的ToolRulePutParams中确认:prioritysource均带#[serde(default)],省略时分别为normalprogrammatictool_rules_for_prompt的返回结构为{ rendered: string, rules: ToolMemoryRule[] }

所有方法都走与其他 memory RPC 相同的active_memory_client/MemoryGuard管道(ops 层 的tool_memory_guard()统一获取 guard,再经as_tool_memory()路由到工具级记忆家族);JSON 载荷统一 snake_case(priority: "critical"source: "user_explicit")。若当前 memory driver 未宣告ToolMemory能力,handler 会返回memory driver does not support the tool_memory family错误——内嵌驱动总是宣告该能力,因此这条路径仅在 null/兜底绑定时可达。

端到端安全用例:“Never email Sarah”

原文档将该用例作为回归测试覆盖,全链路如下(结合源码路径逐条印证):

  1. 用户说:在某轮调用过send_email的对话中说“Never email Sarah at sarah@example.com.”
  2. 捕获:ToolMemoryCaptureHook 提取指令,将email别名映射到send_email工具,在tool-send_email/rule/{uuid}下写入一条 Critical 规则(来源user_explicit,标签user-edict)。
  3. 下一会话prefetch_tool_memory_rules_blocking拉取全部 Critical 与 High 规则,session builder 把ToolMemoryRulesSection追加进系统提示词。
  4. 生效:Agent 在选择任何工具之前就看到### \send_email`分组下的-[critical]Never email Sarah at sarah@example.com.`,且该规则在任何会话内 Token 压缩中都会存活。

相关覆盖与集成测试位于 src/openhuman/memory/tool_memory/(store_tests.rscapture_tests.rsprompt_tests.rs),RPC 与 schema 层另有 ops 层测试 与 schema 层测试。

小结与延伸阅读

工具级记忆层的三个设计支点值得记住:

  • 命名空间即边界tool-{name}与统计、全局、技能命名空间物理隔离,规则与观测互不污染;
  • 优先级即生命周期:critical/high 借道冻结的系统提示词获得结构性抗压缩能力,normal 留在命名空间内按需memory_recall
  • 捕获即取证:两条自动捕获路径都保守触发、保留用户原话,并可用OPENHUMAN_LEARNING_TOOL_MEMORY_CAPTURE_ENABLED=0单独关闭。

相关文档:

  • Memory Tools —— 通用recallstoreforget记忆工具。
  • Smart Token Compression —— 系统提示词所要防御的压缩机制。

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询