oh-my-pi 的 learn 工具:把一次性调试经验沉淀为长期记忆与可复用技能
2026/9/11 21:27:38 网站建设 项目流程

oh-my-pi 的 learn 工具:把一次性调试经验沉淀为长期记忆与可复用技能

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

导读

在长会话式 Coding Agent 的日常工作中,最难积累的资产不是代码,而是"吃过一次亏才知道"的经验:一个非显而易见的修复、一条项目约定、一套最终跑通的工作流。oh-my-pi(@oh-my-pi/pi-coding-agent)提供的learn工具正是为此设计的——它在一次调用内把可复用的经验写入长期记忆,并在需要时同步铸造或增强一个受管(managed)技能。读完本文,你将掌握learn工具的触发时机、memory/context/skill参数的完整语义、三种记忆后端(local / mnemopi / hindsight)的落盘差异,以及技能命名、隔离与安全边界背后的源码实现原理。

1.learn工具是什么:从"解决完问题"到"沉淀下答案"

learn工具在 oh-my-pi 中扮演"经验捕获器"的角色。它的官方定义(见 learn.md)只有一句话但信息密度极高:

Capture reusable lessons in long-term memory; optionally mint/enhance a managed skill in the same call.

即:捕获可复用的经验到长期记忆;在同一调用中可选地铸造或增强一个受管技能

关键在"reusable"(可复用)一词。learn不是随便记录任何对话,而是要求在解决了一个"很可能再次带来回报"的问题之后使用,文档给出了三类典型场景:

  • 非显而易见的修复(non-obvious fix):比如某个第三方库的坑、某个平台特定的行为,下次遇到同样的错误可以少走弯路;
  • 发现的项目约定(discovered project convention):例如"本仓库的测试必须用bun test而不是jest""提交前需要跑gen-clippy-bazelrc"这类代码里不会明说、但违反就会出问题的规则;
  • 最终跑通的工作流(workflow that worked):一套多步骤的操作序列,例如"升级依赖后必须同时更新 Cargo.lock 与 bun.lock"。

从实现上看,learn是一个标准的 AgentTool,其注册与门控逻辑位于 tools/learn.ts:

static createIf(session: ToolSession): LearnTool | null { if (!session.settings.get("autolearn.enabled")) return null; const backend = session.settings.get("memory.backend"); if (backend !== "hindsight" && backend !== "mnemopi" && backend !== "local") return null; return new LearnTool(session); }

这意味着工具并非总是可用:它要求同时满足两个前提——autolearn.enabled开启,且memory.backend处于localmnemopihindsight三者之一。默认配置下autolearn.enabledfalse(见 settings-schema.ts),因此learn属于"按需启用、零默认足迹"的实验性能力。

2. 调用时机:什么时候该用,什么时候不该用

原文档给出了清晰的使用判据:在解决了一个"很可能再次带来回报"的洞见之后使用。反过来说,learn不该被当作普通日志或聊天记录来用。

判断是否值得捕获,可以自问三个问题:

  1. 这个经验会不会在另一个任务、另一个项目阶段再次出现?不会复用的,不值得占用记忆预算;
  2. 它是否足够具体?"记得多用异步"是模糊的建议,而"在 A 包大于 1GB 时改用流式 API 避免 OOM"才是可执行的教训;
  3. 它是事实还是流程?事实("服务器地址是 x")直接进记忆即可;只有值得固化成SKILL.md的可重复过程,才需要附带skill参数。

原文档最后一句给出了取舍原则,也是全文最核心的纪律:

Capture sparingly, specifically: one strong reusable lesson > several vague ones.

少而精地捕获:一条强而具体的可复用经验,胜过好几条模糊的经验。这不仅是提示词层面的建议,也反映在实现上——本地后端对learned.md的条目数量有硬性上限(见下文第 5 节),模糊、重复的捕获会被去重和淘汰机制自然过滤。

3. 参数详解:memorycontext与可选的skill

learn工具的 JSON Schema 定义在 tools/learn.ts,共三个参数:

参数类型必填语义
memorystring需要记住的、自包含(durable, self-contained)的经验,应说明 what / when / why
contextstring该经验的可选来源上下文(项目、场景、命令等)
skillobject在同一调用中创建或更新一个受管技能

3.1memory:写出"自包含"的经验

memory的约束是self-contained(自包含)——即脱离了当前会话上下文,单独读起来依然成立。原文档要求它涵盖 what / when / why 三个维度:

  • what:发现了什么、怎么做;
  • when:在什么情况下适用;
  • why:为什么这样做是对的(避免后人把结论当成教条)。

例如一条合格的memory长这样:

当修改本仓库 Rust crate 的公共 API 时,必须同步更新Cargo.lockbun.lock,否则 CI 的 bazel 构建会因版本漂移失败(bazel 与 cargo 的依赖解析相互独立)。

3.2context:轻量来源标注

context用于记录这条经验从哪里来(如某个模块、某次事故、某个构建脚本),方便日后回溯。在本地后端中,它会被内联进条目渲染为_(context: ...)_后缀(详见第 5 节)。

3.3skill:把流程固化为SKILL.md

skill是可选参数,且文档明确限定:只为"值得固化为SKILL.md的可重复过程"提供,而不是为事实提供。一条事实("公司代理端口是 8080")用memory就够了;一套"设置序列 / 调试配方 / 项目专属工作流"才需要技能化。

skill对象包含四个字段:

字段类型说明
action"create" \| "update"新建或覆盖更新
namestringkebab-case 技能名(小写字母、数字、连字符)
descriptionstring一行说明"何时使用该技能",用于技能发现
bodystringSKILL.md正文(不含 frontmatter

注意body的约束:frontmatter 由系统根据namedescription自动生成,调用方只需提供纯 Markdown 正文。这一设计保证了机器生成的技能文件格式一致,也避免了调用方注入任意 frontmatter 字段。

4. 一次调用的完整执行链路

从 tools/learn.ts 的实现看,LearnTool.execute分两个阶段:

第一阶段:持久化经验到长期记忆(必然执行)。根据当前memory.backend走不同分支:

  • mnemopi(本地 SQLite 后端):调用state.rememberScoped,以importance: 0.8source: "coding-agent-learn"memoryType: "fact"写入,并携带session_idcwdcontext等元数据;若后端未初始化或写入失败(返回空 id),工具会显式抛错而不是静默丢弃;
  • local(文件后端):调用localBackend.savesaveLearnedLesson管道(详见第 5 节);若清洗后内容为空(stored === 0),同样抛错;
  • hindsight(远程记忆服务):调用state.enqueueRetain将经验排队交给后台保留管道,返回信息为 "Lesson queued for retention"。

第二阶段:可选地铸造/增强受管技能(失败不致命,但会如实报告)。当调用带skill时,先做两道前置校验:

  1. 名称清洗sanitizeSkillName校验名称是否符合^[a-z0-9][a-z0-9-]{0,63}$(小写字母/数字/连字符,1~64 字符),非法名称直接拒绝;
  2. 作者技能冲突检查isNameClaimedByAuthoredSkill检查该名称是否已被用户手写的技能占用。由于受管技能在发现时的优先级低于作者技能,若强行创建同名受管技能,写出的文件永远不会被呈现——工具此时会返回错误,提示"换个名字"而不是谎报成功(tools/learn.ts)。

随后调用共享原语writeManagedSkill完成写入,并按action返回 "Created" 或 "Updated" 的确认信息。若技能写入失败,错误消息会同时说明"经验已存储/排队,但技能未能写入",保证调用方不会误以为整个操作失败。

此外,LearnTool的审批分级值得注意(tools/learn.ts):skill载荷、或后端为local时,审批级别为write;否则为read。这是因为纯远程后端的记忆写入是"排队"性质的轻操作,而写文件(技能或learned.md)需要显式写权限。

5. 本地后端:learned.md的存储与读回机制

memory.backendlocal时,经验被写入项目记忆根目录下的learned.md文件(memories/index.ts)。该文件刻意与后台汇总产物(memory_summary.mdMEMORY.mdskills/)分离,保证汇总管线永远不会覆盖手动捕获的经验

5.1 写入时的规范化管道

每条经验落盘前要经过 normalizeLearnedText 的三步处理:

  1. 注入中和(neutralizeInjection):剔除控制/格式字符、尖括号(</skills><system-directive>)、反引号和~~~围栏。因为learned.md的内容会原样渲染进后续会话的系统提示词,必须防止经验文本里夹带提示词注入载荷;
  2. 密钥脱敏(redactSecrets):替换疑似 token(含ghp_等供应商令牌前缀)为[REDACTED]。顺序上有讲究——先中和再脱敏,避免分隔符被剥离后把 token 重新拼起来绕过正则;
  3. 长度封顶(boundChars)memory内容上限2000 字符context上限400 字符,截断时会处理末位未配对的 Unicode 高代理项,避免产生损坏字符。

5.2 文件级约束

  • 最新在前 + 精确去重:同一经验重复捕获不会产生重复条目;
  • 100 条上限MAX_LEARNED_LESSONS):超出时淘汰最旧的条目,控制文件按条数增长;
  • 并发安全learnedWriteChains按文件路径串行化"读-改-写",同一轮次内并行调用(如多个子代理同时learn)不会互相覆盖(memories/index.ts);
  • 保留手工结构:对已经存在头部、散文、分节标题的learned.md,追加操作只触碰-开头的列表条目,新条目进入第一个列表段的头部,标题与正文的相对位置不变——你可以放心手工维护这个文件。

测试用例对这些行为有完整覆盖,例如 autolearn-learn-local.test.ts 验证了空白归一化与 context 内联、L47-L64 验证了密钥脱敏、L74-L83 验证了 100 条上限、L261-L298 验证了手工结构保持与字节幂等性。

5.3 读回与注入预算

读回时(buildMemoryToolDeveloperInstructions),learned.md的条目会与memory_summary.md合并注入系统提示词,且两者共享summaryInjectionTokenLimit预算(memories/index.ts):先按 token 预算截断汇总,剩余预算才分配给经验列表。若汇总已耗尽预算,经验条目会被丢弃而非撑爆上下文——这正是"少而精"原则在系统层面的强制保证。读回时还会再次执行中和与脱敏,因此手工编辑过的learned.md即使夹带危险内容,也不会泄漏进提示词(autolearn-learn-local.test.ts)。

6. 受管技能:隔离目录、命名与安全边界

learnskill参数写入的是受管技能(managed skill),其全部文件操作被限制在独立目录~/.omp/agent/managed-skills,与用户手写技能目录~/.omp/agent/skills严格隔离。原文档强调了两条铁律:

Managed skills: isolated~/.omp/agent/managed-skills; surfaced as normal skills next session; NEVER touch user-authored skills.

即:受管技能下一次会话会像普通技能一样被发现和呈现;但 Agent永远不得触碰用户手写的技能。这一隔离体现在 autolearn/managed-skills.ts 的多层防护上:

  • 提供者标记:受管技能被标记为omp-managed提供者,与作者技能区分;
  • 名称白名单SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/,禁止..、斜杠、空名和大写,从源头杜绝路径逃逸;
  • 描述消毒sanitizeManagedDescription写入和读取两个方向都剥离控制字符、<>、反引号与~~~,防止机器生成的描述在未来的会话中破坏<skills>列表结构;
  • frontmatter 自动生成toSkillFrontmatter用 YAML 序列化name与消毒后的description,形成标准---\n...\n---头部;
  • 64KB 大小上限MAX_MANAGED_SKILL_BYTES = 64_000,按最终文件 UTF-8 字节数(含 frontmatter)校验,防止一次生成把技能文件写爆;
  • 写入防伪create使用wx标志(O_CREAT|O_EXCL)原子创建、文件已存在即失败;update使用O_NOFOLLOW打开,且拒绝符号链接文件与硬链接数 >1 的文件——防止把写入重定向到用户技能或其他文件(managed-skills.ts);
  • 根目录防符号链接:写入前lstat检查managed-skills根与技能子目录不是符号链接,杜绝"合法名称经符号链接写到目录外";
  • 同名单次串行化serializeSkillMutation让同一技能名的多次变更(如同一轮里 create 与 update 并发)按提交顺序执行,不同技能名仍可并行。

6.1createupdate的语义差异

action行为失败条件
create原子新建SKILL.md技能已存在(EEXIST 显式报错)
update覆盖正文(frontmatter 由 name/description 重新生成)技能不存在

两个动作都要求descriptionbody非空(空的 description 会被发现扫描静默丢弃,工具因此会在写入前直接拒绝)。这与manage_skill工具(manage-skill.md)共享同一套writeManagedSkill原语,行为完全一致,manage_skill还额外支持delete

7. 与 Auto-Learn 体系的关系及配置

learn是 oh-my-pi Auto-Learn(实验性)体系中的一个工具。会话停止后,系统会根据 autolearn-guidance.md 的指引提示 Agent 捕获经验——learn是其中的"手动、即时"通道,manage_skill则是"构建可复用技能库"的通道。

相关配置项集中在 settings-schema.ts:

配置类型默认说明
autolearn.enabledbooleanfalse总开关;关闭时learn/manage_skill均不可用(createIf返回 null)
autolearn.autoContinuebooleanfalse停止时自动运行一次私有捕获回合(额外消耗 token);关闭则仅保留常驻 Auto-Learn 提示
autolearn.minToolCallsnumber5触发捕获提示所需的最少工具调用次数(仅配置文件可设)
memory.backendenumoffoff/local/hindsight/mnemopi/sharpshooter;仅前三者支持learn

一个实用的组合是:memory.backend: "local"+autolearn.enabled: true。此时无需任何外部服务,经验会落到项目记忆根的learned.md,技能落到~/.omp/agent/managed-skills,全程本地文件、零网络依赖,也便于直接查看和手工维护。

8. 最佳实践清单

综合原文档与实现,使用learn工具时应遵循以下纪律:

  1. 只在洞见可能再次变现时调用——非显而易见修复、项目约定、跑通的工作流;
  2. 一条强经验胜过多条弱经验——memory写自包含(what/when/why),宁缺毋滥;
  3. 事实进memory,流程进skill——只有可重复过程才值得SKILL.md化;
  4. skill.name用 kebab-case(小写字母/数字/连字符,1~64 字符),description写清"何时使用",body不带 frontmatter;
  5. 不要与用户手写技能重名——受管技能永远无法覆盖作者技能,冲突时换个名字;
  6. local后端可放心查看与手工编辑learned.md——读回时会再次消毒,结构也能被保留,但内容最终会渲染进未来会话的提示词,注意别写入不需要长期保留的信息。

掌握了这几点,你就拥有了让 Coding Agent "越用越懂你的项目"的完整闭环:解决问题 →learn捕获 → 跨会话注入 → 技能化复用。

【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi

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

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

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

立即咨询