前言¶
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 種工具 |
幾個細節:
- 失敗判定覆蓋
isError、[exit code: N](N≠0)、[timed out after ...]、[killed by signal: ...];edit因文件變更失敗同樣算。 - 鏈路計數在用戶發新消息時清零,閾值衡量的是「這一輪」;同一個 turn 用 turn 號做冪等鍵,只觸發一次。
- 用戶說「記住 X」時偏好類會直接指向
scope: "global",倉庫約定仍留在項目級。 - 想恢復按步數觸發,把
triggerOnLengthOnly設爲true,滿chainSteps步也會觸發。 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:<端口> 指定。
日常用法¶
裝好後不需要做任何事。平時它完全隱身,只在任務結束時於右下角彈一次小卡:
- 點桌寵打開經驗庫:看記憶 / 技能、應用或丟棄技能提案、就地編輯刪除、用「孟婆湯」批量清空。
- 對模型說「記住……」會直接觸發一次沉澱,不必等失敗或長鏈路。
- 模型側提供三個工具:
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