nas1112/self-evolve:任务结束后自动复盘,把经验写进记忆和技能

前言

DSH 里跑一个长任务,经常是连着十几次工具调用,方向早就偏了,但每一步单独看都「合理」;同一条 shell 命令换着写法反复失败,也没换思路;好不容易走通的多步骤流程,下次同类任务又从头摸一遍。self-evolve(维护者 NAS1112,社区目录分类:记忆)就是为这件事做的:它会在任务完整结束后复盘一次,把真正值得记的经验写进本地记忆或可复用技能,右下角的桌宠播报这一次进化。不是每次对话结束都写总结,也不是中途插话。

这是什么

一句话定位:DSH 插件,Host 半 + Client 半。Host 半负责在任务结束时做触发判断并落盘,Client 半负责会话页右下角的桌宠动画和卡片。

它的三条硬约束,README 里写得很明确:

  • 不中途打断:复盘只在任务结束时发生,对话流里一个字不多;
  • 默认不写:只有「会让未来的你做得不一样」的经验才落盘,一次最多一条;
  • 数据全在本地:记忆进 AGENTS.md,技能进 ~/.agents/skills/,你手写的内容永远不会被覆盖。

维护者是 NAS1112。README 的 package.json 里版本是 0.5.2,INSTALL.md 里的压缩包还写着 self-evolve-0.3.0.zip,两处不一致,装的时候以实际下载的目录为准。

触发条件:长度不等于价值

依据是 DESIGN.md。核心判断是「一个正常的开发任务轻松就能跑过 15 步」,如果按步数触发,结果就是每轮都来一次复盘、记忆里堆满噪声。所以「跑了多少步」默认不参与触发,只有三种情形在任务结束时合并评估一次:

信号 默认阈值
用户明确要求记住 命中即触发(记住 / 记一下 / 以后都 / 我习惯 / 我偏好 / remember that / from now on)
确实出过问题 任意工具失败 2 次;bash / shell 连续失败峰值达 3 次
走通了复杂流程 25 步 且 4 种工具

几个细节:

  1. 失败判定覆盖 isError、[exit code: N](N≠0)、[timed out after ...]、[killed by signal: ...];edit 因文件变更失败同样算。
  2. 链路计数在用户发新消息时清零,阈值衡量的是「这一轮」;同一个 turn 用 turn 号做幂等键,只触发一次。
  3. 用户说「记住 X」时偏好类会直接指向 scope: "global",仓库约定仍留在项目级。
  4. 想恢复按步数触发,把 triggerOnLengthOnly 设为 true,满 chainSteps 步也会触发。
  5. ask_user_question、exit_plan_mode 之后的 turn 结束属于中途停下来等用户,插件会跳过触发,等任务真正走完的 turn 再进化。

触发信号本身不产生任何消息。每步在 agent/pre-step 评估一次,条件满足就把该 agent 标成「本轮已就绪」,协议正文通过 ctx.systemPrompt.section() 写进系统提示、就绪状态通过 ctx.systemPrompt.context() 写进按步刷新的动态上下文——只有模型能看到,用户界面上不会出现多出来的话或消息。DESIGN.md 里还记了这件事的来历:早先用 agent.steer() 在 turn 结束时追加一步专门复盘,那一步的回复成了「本轮最后一条助手消息」,真正的结论被挤进折叠的「已调用工具」分组,等于把答案藏起来了。

写什么、写多少

协议基调是默认什么都不写,并明确列出不要记录的内容:这个任务做成了什么、报错原文、记忆里已有道理的另一种说法。没有合格经验就直接结束本轮,不向用户解释协议或标记。

「一次最多一条」不只写在协议里,工具侧也硬性限制(maxRecordsPerTurn,默认 1),超出的调用直接返回 skipped,不落盘。

去重和合并交给判定模型(可选)。给了 key 时,它除了判断值不值得写,还会回答 merge_v1(这条是不是在重说已有条目)、relation_v1(是不是在更新某条,跨档也能命中并取代)、stale_v1(这次写入让哪条旧条目的前提不再成立,标记「前提已过期」)。判定时会把已有条目的标题 + 正文前 160 字 + 所在档位交给模型(最多 12 条),所以换个说法不再堆一条。不配 key 时判定降级为直接写入(fail-open),插件照常工作。

数据落在哪

内容 路径
项目记忆 <工作区>/AGENTS.md(<!-- self-evolve:begin --> 托管区间内)
全局记忆 $DSH_HOME/AGENTS.md
本插件写的技能 ~/.agents/skills/<name>/SKILL.md
长经验详情 $DSH_HOME/self-evolve/details/
判定流水 $DSH_HOME/self-evolve/judgements.jsonl
最近一次沉淀 $DSH_HOME/self-evolve/last-record.json

插件只改自己那一段托管区间,不碰你手写的内容;自己写的 SKILL.md(没有归属标记)不会被接管。自建技能写入时会带一行 <!-- self-evolve:skill -->,老文件按正文里的托管标题兜底识别——这也是「累计自建技能」的口径,目录里你自己写的其他技能不参与计数。

安装与启用

官方没有 dsh plugin add 这类命令,走 link: 方式挂到某个 profile。下面以 profile 目录 $DSH_HOME/profiles/desktop 为例,其它 profile 换路径即可。

1、把插件目录解压到固定位置:

unzip self-evolve-0.3.0.zip -d ~/plugins
# 得到 ~/plugins/self-evolve/

目录里应当有 index.js(Host)、client.js(Client)、package.json、cordis.patch.yml、assets/pet/*.gif、locale/。

2、编辑 profiles/desktop/package.json,把 @local/self-evolve 以 link: 方式加进 dependencies,并放进 dsh.profile.bundles(这一行是必须的,插件才随 profile 加载):

{
  "dependencies": {
    "@local/self-evolve": "link:/Users/you/plugins/self-evolve"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@local/self-evolve"
      ]
    }
  }
}

3、安装依赖或直接建软链:

cd "$DSH_HOME/profiles/desktop"
pnpm install          # 有 pnpm 走这条
# 没有 pnpm 就直接建链:
ln -sfn ~/plugins/self-evolve node_modules/@local/self-evolve

4、重载:重启 dsh web,然后刷新页面。

可选配置。profiles/desktop/cordis.patch.yml 里的覆盖段:

- id: self-evolve
  name: "@local/self-evolve"
  config:
    shellFailureThreshold: 3
    toolFailureThreshold: 2
    triggerOnLengthOnly: false
    chainSteps: 15
    scope: auto
    workflowMinSteps: 25
    workflowMinDistinctTools: 4
    maxRecordsPerTurn: 1
    decision:
      enabled: true
      shadow: true
      model: doubao-seed-2-1-lite-260915
      baseURL: https://ark.cn-beijing.volces.com/api/v1
      apiKey: ''            # 留空 = 用界面里配的 key,或读环境变量
      apiKeyEnv: A_API_KEY
      timeoutMs: 900
      worthThreshold: 0.5
      scopeMinConfidence: 0.6
  disabled: false

不加这段也能跑,走内置默认值。判定模型(火山方舟 System One)不是必需的:不配 key 时判定返回 degraded,插件照常写入。要开判定就在经验库 ⚙ 页里填 key,或写进 apiKey,也可以放环境变量。

5、验证:

设置 → 插件:列表里出现 self-evolve
页面右下角出现桌宠
点桌宠 → 打开经验库,四个页面(记忆 / 技能 / 判断日志 / 设置)都能进

命令行探一下路由:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H 'x-self-evolve: 1' \
  "http://127.0.0.1:62100/api/self-evolve/pet/idle.gif"
# 期望 200

6、回归测试(可选,插件需要正在运行):

cd self-evolve/test
pnpm install
pnpm test

test-routes.mjs 默认打 http://127.0.0.1:62100;端口变了用 SELF_EVOLVE_BASE=http://127.0.0.1:<端口> 指定。

日常用法

装好后不需要做任何事。平时它完全隐身,只在任务结束时于右下角弹一次小卡:

  1. 点桌宠打开经验库:看记忆 / 技能、应用或丢弃技能提案、就地编辑删除、用「孟婆汤」批量清空。
  2. 对模型说「记住……」会直接触发一次沉淀,不必等失败或长链路。
  3. 模型侧提供三个工具:self_evolve_record、self_evolve_propose、self_evolve_status。self_evolve_propose 的目标从「只允许自建技能」放宽到技能目录下的任何技能,包括你自己写的;模型只能提交提案,落盘必须由人在经验库里点「应用」,且改写里不许悄悄加归属标记。

界面形态上有几点值得知道:右下角桌宠常驻,右上角紫色角标显示本会话累计进化次数;同一会话第 2 次进化起只以小卡形式自动出现,不打断阅读,点小卡升级为整屏台账;dock 卡片有三个阶段——沉淀中(burst)→ 完成瞬间(flash)→ 已落定(settled);台账显示最近一次沉淀的触发原因、判定值、写入落点与本会话历史。第 1 次进化不会用整屏卡片打断正在流式输出的回答说答案被盖住了——动画时机由 running 门控,本轮还在跑时什么都不显示。

适用场景与注意

适合这类做法:任务完成后才复盘、默认不写只写有用的、经验落在本地仓库或家目录而不是云端、想要一个不打断对话的提醒形态。如果你更倾向于每次都手动总结、或把经验统一收在集中服务里,这套东西的落点可能不合用。

几个必须知道的边界:

  • 以当前 dsh 进程权限运行。插件能读写工作区和家目录,安装前应当检查源码与许可证(README 的 package.json 标的是 MIT)再决定是否挂进 profile。
  • 全局写入依赖审批通道。如果当前组合没有可用的审批回答者(approval answerer),越界写入会 fail closed 并返回 FS_SANDBOX_DENIED;此时只能把 scope 切回 workspace,或显式把文件策略设为 danger-full-access。
  • 「新工作流」是启发式判断(步数 + 工具种类),不做语义理解。一次长链路但确实是同一件事的重复劳动,可能被判成工作流;提示文案里已经要求模型在「一次性流程」时跳过并说明,所以误判的代价是一次无害的自我说明。
  • 计数按 agent 存在内存里(WeakMap),进程重启或会话切换后从零开始。这是「本轮是否跑偏」的指标,设计上不需要持久化。
  • 复盘发生在 agent/turn-stopping,用户中途取消本轮则这一轮不会复盘——符合「任务完成后才进化」的语义,但被取消的工作不会沉淀经验。
  • 技能被写坏(frontmatter 不合法)时会被 dsh-skill-filesystem 忽略并在日志里告警;本插件只追加合法内容,不会修改已有 frontmatter。
  • 以 link: 方式安装时,改完 index.js 未必立即热生效。验证前先调 self_evolve_status,确认 environment 字段存在——没有该字段说明跑的还是旧代码,需要在插件页把该插件关掉再打开。
  • Client 半(动画)的加载时机与 Host 半不同:dsh.client 的变更要重新安装 bundle 并刷新页面才生效。只重载插件不足以让新注册的 slot 出现。

触发阈值、进化动画的实现细节、记忆两档与去重合并、经验库、自我优化提案、判定模型、配置全表、设计取舍与验证方法,都在仓库的 DESIGN.md 里。

结尾

self-evolve 做的事情很窄:任务结束后复盘一次,把一条真正有用的经验写进本地记忆或技能,然后用桌宠告诉你这次进化是什么。它不改写对话流程,不覆盖你手写的内容,也不把经验送到别处去。如果这正是你缺的那块,可以从这里看起:

  • 目录页:https://www.skillhub.cn/plugins/NAS1112/self-evolve
  • GitHub:https://github.com/NAS1112/self-evolve
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜