dsh-continual-evolve 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -409
- package/README.zh.md +84 -272
- package/lib/apply.js +7 -1
- package/lib/auto.d.ts +17 -0
- package/lib/auto.js +5 -2
- package/lib/benchmark-command.js +2 -0
- package/lib/command.d.ts +3 -0
- package/lib/command.js +40 -2
- package/lib/fate.d.ts +2 -1
- package/lib/fate.js +5 -4
- package/lib/index.d.ts +22 -0
- package/lib/index.js +19 -1
- package/lib/inject.d.ts +16 -1
- package/lib/inject.js +55 -14
- package/lib/promotion.d.ts +62 -0
- package/lib/promotion.js +102 -0
- package/lib/service.js +2 -1
- package/lib/skill-render.d.ts +9 -1
- package/lib/skill-render.js +40 -2
- package/lib/state.js +6 -1
- package/lib/usage.d.ts +17 -4
- package/lib/usage.js +41 -10
- package/lib/wrapup-command.d.ts +2 -1
- package/lib/wrapup-command.js +4 -3
- package/lib/wrapup.d.ts +15 -6
- package/lib/wrapup.js +45 -6
- package/package.json +9 -7
package/README.zh.md
CHANGED
|
@@ -7,313 +7,125 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
10
|
+
[]()
|
|
12
11
|
|
|
13
12
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的持续自进化插件:一套**版本化、可审计、可回滚**的 harness 状态层——提示词补充、记忆、技能、子代理规格——从会话轨迹中沉淀而来。
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## 背景
|
|
18
|
-
|
|
19
|
-
这个项目始于一个研究问题:*harness 能自我改进吗?生产级版本长什么样?* 三条证据线塑造了答案:
|
|
20
|
-
|
|
21
|
-
- **penguin-harness** 证明了概念(benchmark → 评估 → 优化 → 接受/回滚),但**代码层零强制**——所有保证都是提示词契约。它的研究报告(`docs/research/`)成了本项目的硬化清单。
|
|
22
|
-
- **prime-agent `/refine`** 证明了工程形态:版本化 harness 条目、原子持久化、乐观并发、逆操作回滚。本包是在 DSH 插件表面上对该形态的原创实现。
|
|
23
|
-
- 学术工作(Self-Harness、AHE、HarnessOpt-Bench)提供了纪律:冻结评估运行时、代码所有聚合、非退化接受。
|
|
24
|
-
|
|
25
|
-
结果:**模型提议,代码保证。** 每一项机械化安全属性(schema 校验、快照、版本、审计、接受决策)都由代码强制——从不要求模型自觉守规矩。
|
|
14
|
+
**模型提议,代码保证。** 每一项机械化安全属性——schema 校验、原子写入、快照、版本、审计、接受决策——都由代码强制,从不依赖提示词自觉。
|
|
26
15
|
|
|
27
16
|
## 为什么
|
|
28
17
|
|
|
29
|
-
Agent
|
|
30
|
-
|
|
31
|
-
- **版本化条目**:按 `prompt` / `memory` / `skill` / `subagent` 分键,每条带来源与版本
|
|
32
|
-
- **证据链**:每次进化追加一条携带 `trigger / changes / evidence / outcome` 的事件
|
|
33
|
-
- **确定性回滚**:逆操作由已应用的结果生成——不需要 LLM 再猜
|
|
34
|
-
- **代码强制安全**,而非提示词纪律:schema 校验、原子写、损坏降级、乐观并发、基础系统提示词不可变
|
|
35
|
-
- **局部(会话内)与全局(跨会话)双作用域**,带合并语义
|
|
36
|
-
|
|
37
|
-
## 设计来源
|
|
38
|
-
|
|
39
|
-
受三方面工作启发(见 [`docs/design.md`](docs/design.md)):
|
|
18
|
+
Agent 在每个会话里积累可复用经验(重复失败、持久事实、可复用流程),下个会话就忘掉。本插件把这些经验变成一等公民的持久状态:
|
|
40
19
|
|
|
41
|
-
- **
|
|
42
|
-
-
|
|
43
|
-
-
|
|
20
|
+
- **local 会话级 / global 跨会话** 双作用域与合并语义——配合机械化晋升守卫,只有可携带、有分量、非重复的知识才能进全局
|
|
21
|
+
- **确定性回滚**:逆操作编辑由已应用结果生成——不靠 LLM 重新猜测
|
|
22
|
+
- **benchmark 闭环**:候选沉淀先经冻结用例 + 独立评分者评估再接受(rubric 加密落盘)
|
|
44
23
|
|
|
45
|
-
##
|
|
24
|
+
## 工作原理
|
|
46
25
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| 包管理 | pnpm(DSH 生态标准) |
|
|
53
|
-
| 构建 | `tsc` → `lib/`(main `lib/index.js`,types `lib/index.d.ts`) |
|
|
54
|
-
| 测试 | Vitest |
|
|
55
|
-
| Lint | oxlint(DSH 官方仓库惯例) |
|
|
56
|
-
| License | MIT |
|
|
57
|
-
|
|
58
|
-
## 项目结构
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
dsh-continual-evolve/
|
|
62
|
-
├── package.json # exports / files / engines / scripts + dsh.bundle manifest
|
|
63
|
-
├── cordis.patch.yml # bundle patch(dsh plugin add 安装即激活)
|
|
64
|
-
├── tsconfig.json / .oxlintrc.json / .editorconfig / .gitignore
|
|
65
|
-
├── LICENSE / README.md / README.zh.md
|
|
66
|
-
├── docs/
|
|
67
|
-
│ ├── design.md # 完整设计文档(含硬化对照表)
|
|
68
|
-
│ └── research/ # penguin 研究报告 + prime-agent 参考源码
|
|
69
|
-
├── src/
|
|
70
|
-
│ ├── index.ts # cordis 插件入口(服务挂载 + 接线)
|
|
71
|
-
│ ├── types.ts # HarnessState / 条目 / 编辑 / 结果类型
|
|
72
|
-
│ ├── state.ts # 原子持久化、损坏降级、合并、乐观并发
|
|
73
|
-
│ ├── validate.ts # 代码强制编辑校验(基础提示词不可改、skill 契约)
|
|
74
|
-
│ ├── apply.ts # 逐条应用 + 乐观锁
|
|
75
|
-
│ ├── rollback.ts # 确定性逆操作回滚
|
|
76
|
-
│ ├── plan.ts # 提案 JSON 解析(截断诊断)
|
|
77
|
-
│ ├── tool.ts # evolve_* 模型工具(5 个)
|
|
78
|
-
│ ├── command.ts # /evolve 命令分发器 + 共享工具
|
|
79
|
-
│ ├── goal-command.ts # /evolve goal 子命令处理
|
|
80
|
-
│ ├── mount-command.ts # /evolve mount + unmount 子命令处理
|
|
81
|
-
│ ├── benchmark-command.ts # /evolve benchmark 子命令处理
|
|
82
|
-
│ ├── wrapup-command.ts # /evolve wrapup 子命令处理
|
|
83
|
-
│ ├── planner.ts # ctx.llm 规划器
|
|
84
|
-
│ ├── llm-text.ts # 统一流式文本助手(BlockAssembler + finish 检查)
|
|
85
|
-
│ ├── render.ts # 有界提示词渲染
|
|
86
|
-
│ ├── inject.ts # 动态系统提示词段(prompt 补充 + 委派规格,打分排序注入)
|
|
87
|
-
│ ├── source.ts # 轨迹引用(沉淀条目的 sessionId + 事件 seq)
|
|
88
|
-
│ ├── auto.ts # 自动 review 门禁(回合/压缩触发 + 审计,global 感知视图,local 归宿阶段)
|
|
89
|
-
│ ├── fate.ts # 门禁 local 归宿维度——自动提议 local 条目提升/归档(先征询、带冷却)
|
|
90
|
-
│ ├── notify.ts # 门禁可见性——approved 自动沉淀后发送可见通知
|
|
91
|
-
│ ├── goal.ts # goal 驱动的进化轮次(/evolve goal)
|
|
92
|
-
│ ├── review.ts # 门禁 LLM 判断(拒绝 global 已覆盖主题的 local 重复沉淀)
|
|
93
|
-
│ ├── approval.ts # 全局写入人工审批
|
|
94
|
-
│ ├── skill.ts # 技能物化($DSH_HOME/skills/)
|
|
95
|
-
│ ├── skill-render.ts # 共享技能渲染(skillNameOf + renderSkillMarkdown,打破循环依赖)
|
|
96
|
-
│ ├── skillquality.ts # 自进化环中的技能标准(skill-creator 模板读取 + frontmatter 代码校验)
|
|
97
|
-
│ ├── mount.ts # 技能热挂载插件(loader.create + 启动恢复)
|
|
98
|
-
│ ├── benchmark.ts # benchmark 存储 + CellScore 类型(含运行时实证字段)
|
|
99
|
-
│ ├── rubric.ts # rubric ACL(AES-256-GCM 密文信封,自动生成本地密钥)
|
|
100
|
-
│ ├── logfile.ts # 插件自带文件日志(JSONL exporter + 轮转)
|
|
101
|
-
│ ├── score.ts # 代码所有聚合 + 接受规则
|
|
102
|
-
│ ├── evaluate.ts # 两段式评估执行器(执行者产证据 → 独立评审者评分)+ 失败格协议 + 运行时实证校验
|
|
103
|
-
│ ├── pool.ts # 评估运行的有界并发工作池
|
|
104
|
-
│ ├── store.ts # store 布局 + 快照 + 结果历史
|
|
105
|
-
│ ├── service.ts # 进化引擎(onApplied 钩子)
|
|
106
|
-
│ ├── usage.ts # 条目注入使用率追踪(持久计数、陈旧检测)
|
|
107
|
-
│ ├── failures.ts # 失败签名聚合(门禁 + benchmark 失败按类统计,/evolve failures)
|
|
108
|
-
│ └── wrapup.ts # 会话收尾生命周期(提升/拆解提升到 global、带守卫的归档;共享 proposal 构造器;陈旧信号)
|
|
109
|
-
└── test/ # 28 个文件,401 个测试
|
|
110
|
-
```
|
|
26
|
+
1. **沉淀**——模型经 `evolve_add` 创建条目,或自动 review 门禁从会话轨迹提议(回合间隔 + 压缩检查点)。
|
|
27
|
+
2. **守卫**——代码强制校验:编辑 schema、blast-radius 与作用域一致性、晋升政策(项目专属标记 / 过薄内容 / 近似重复检测保持全局库干净)。
|
|
28
|
+
3. **审批**——全局写入需明确人工批准;local 归宿提议先征询后落地。
|
|
29
|
+
4. **应用与注入**——原子应用带快照与审计事件。prompt 补充与委派规格注入系统提示词(封顶、按相关性排序、空 store 零 token);memory/skill 以目录索引出现。
|
|
30
|
+
5. **验证与回滚**——benchmark 用冻结用例为候选打分;被拒候选确定性回滚。
|
|
111
31
|
|
|
112
32
|
## 安装
|
|
113
33
|
|
|
114
34
|
```bash
|
|
115
|
-
# 从 npm
|
|
116
|
-
dsh plugin
|
|
35
|
+
# 从 npm(安装即激活——自带 bundle patch)
|
|
36
|
+
dsh plugin add dsh-continual-evolve
|
|
117
37
|
|
|
118
|
-
#
|
|
119
|
-
dsh plugin
|
|
38
|
+
# 或从源码(首次 GitHub 安装需批准 allowBuilds 构建步骤)
|
|
39
|
+
dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
120
40
|
```
|
|
121
41
|
|
|
122
|
-
|
|
42
|
+
安装或更新后重启 `dsh web`。
|
|
123
43
|
|
|
124
|
-
##
|
|
44
|
+
## 使用
|
|
125
45
|
|
|
126
|
-
|
|
127
|
-
/evolve 帮助 + 当前局部 store
|
|
128
|
-
/evolve list [global] 列出条目
|
|
129
|
-
/evolve history 已应用的 refinement(回滚用 id)
|
|
130
|
-
/evolve rollback <id> 确定性回滚某个 refinement
|
|
131
|
-
/evolve plan [msg] LLM 规划器
|
|
132
|
-
/evolve wrapup 评估本会话 local 条目:可复用的提升到 global(需审批),
|
|
133
|
-
会话特有的一次性条目归档
|
|
134
|
-
/evolve archive <id> 归档条目——不再注入(数据保留,可恢复)
|
|
135
|
-
/evolve unarchive <id> 恢复已归档条目
|
|
136
|
-
/evolve log [tail N] [session <id>] 查看最近插件日志(默认 50 行;可加会话过滤)
|
|
137
|
-
/evolve failures 失败聚合统计(门禁 + benchmark 失败按类计数——D1 观察层)
|
|
138
|
-
/evolve export <path> 备份局部 store 为 JSON
|
|
139
|
-
/evolve import <path> 从导出文件恢复 store
|
|
140
|
-
/evolve mount <skillId> 热挂载 skill 条目为实时 cordis 插件(工具:skill_<name>)
|
|
141
|
-
/evolve mount list 列出热挂载插件(重启自动恢复)
|
|
142
|
-
/evolve unmount <id> 移除热挂载插件
|
|
143
|
-
/evolve goal 查看进化 goal(轮次驱动自动 review)
|
|
144
|
-
/evolve goal <objective> 创建/更新进化 goal——active 时 review 门禁每轮触发
|
|
145
|
-
/evolve goal done 完成进化 goal
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
模型工具:`evolve_list`、`evolve_add`、`evolve_update`、`evolve_delete`、`evolve_rollback`。
|
|
149
|
-
|
|
150
|
-
## 记忆层
|
|
151
|
-
|
|
152
|
-
在持久 store 之外,四项增强让注入的记忆在条目增多时依然"懂你"(对照 Mem0 / Letta / Zep / LangMem 的差距分析;不引入外部服务——全部是纯函数):
|
|
153
|
-
|
|
154
|
-
- **打分排序注入**——某类条目超过 6 条封顶时,注入块不再固定取前 6 条:先按与 agent 最近直接用户消息的相关度打分(关键词/BM25 级别,标题命中权重 2×),再按新鲜度排序(`updated_at`,30 天半衰期),让"最新 + 最相关"的条目填满封顶。空 store 零 token 行为不变。
|
|
155
|
-
- **轨迹引用**——每条新沉淀条目都会记录 `metadata.sourceSession` + `metadata.sourceSeqs`,指向它蒸馏自的直接用户消息(DSH 会话是事件溯源、seq 连续,引用可展开回持久会话日志)。列表显示 `src=<sessionId>:<seqs>`;旧条目不迁移也不报错。
|
|
156
|
-
- **归档**——`/evolve archive <id>` 让条目不再注入(`metadata.archivedAt`,数据保留、与快照/回滚兼容),`/evolve unarchive <id>` 恢复。归档条目在 `evolve_list` 中标记 `[archived]`,注入跳过,溢出计数不含它们。
|
|
157
|
-
- **会话收尾**——否则会话结束时的 local 条目会变成孤岛(后续会话永远看不到)。`/evolve wrapup` 给它们一个归宿:先机械审计——**全局覆盖只看标题相似**(裸同 id 但标题迥异**不算**覆盖;实际命中的全局标题会展示给分类器,让它对照真实内容判断)——再由模型逐条分类为 `promote` / `archive` / `keep`。提升把可复用条目写入 global store——**经人工审批门禁**,保留轨迹引用并追加 `sourcedFromLocal=<session>:<id>` 反向回引;本地副本随后盖 `promotedTo` 戳退出注入,永不再被提议。**拆解提升(A 形)**:混合条目(持久事实 + 会话快照)可整体归档、同时带一个清洗过的 `promote` 子对象——只有持久部分落进 global,快照留在归档里。**对称归档守卫**:未被全局覆盖、且源自真实用户消息的归档,先征求用户确认才隐藏内容(防过度归档与防过度写入获得同等保护);操作性条目仍静默归档。一切仍走快照/版本/可回滚。
|
|
158
|
-
- **门禁 local 归宿(自动收尾)**——同一套 wrap-up 机制现在以内置节奏(`fateIntervalTurns`)跑在自动 review 门禁里:local 条目在会话进行中就能获得归宿,不必等手动 `/evolve wrapup`。每次到期的门禁运行都会审计候选条目、由分类器分类、再经同一套确定性守卫划分;任何治理动作落地前**先征询用户**(一个弹窗覆盖提升、拆解提升与需确认归档——consultSkillEdits 模式,带拒绝冷却)。被全局覆盖或操作性条目仍静默归档;压缩时刻门禁绝不弹窗:只做静默归档,治理动作以审计记录推迟并指向 `/evolve wrapup`。每次 fate 决策都落进 `reviews.jsonl`(`approved` / `declined` / `deferred` / `assessed` / `failed`),已执行动作通过后续通知可见。应用写入与 wrapup 命令逐字节一致(共享 proposal 构造器)。
|
|
159
|
-
- **global 感知门禁**——自动 review 门禁与规划器评审的是合并后的 global + local 状态,每条条目标注真实 scope;global 已覆盖的主题会被 declined,不再重复沉淀为 local 条目。
|
|
46
|
+
会话内命令:
|
|
160
47
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
/
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
前台终端想要实时输出时,可(可选)在 profile 加官方 `@deepseek-ai/cordis-plugin-logger-console` 插件;文件日志始终是默认存在的基础。
|
|
181
|
-
|
|
182
|
-
## benchmark 驱动验证(Phase 3)
|
|
183
|
-
|
|
184
|
-
```
|
|
185
|
-
/evolve benchmark new <title> [runs] 创建 benchmark(runs = 每个 case 重复次数,默认 1)
|
|
186
|
-
/evolve benchmark add-case <bid> <title> <statement> <rubric>
|
|
187
|
-
/evolve benchmark list 列出 benchmark
|
|
188
|
-
/evolve benchmark status <bid> 查看计分板 + 决策
|
|
189
|
-
/evolve benchmark reset <bid> 清空计分板(重跑参考线)
|
|
190
|
-
/evolve benchmark run <bid> 评估当前状态 → 参考线
|
|
191
|
-
/evolve benchmark run <bid> candidate <refinementId> 评估进化后状态 → 决策
|
|
192
|
-
/evolve benchmark casecheck <bid> 质量门禁检查所有 case
|
|
193
|
-
/evolve benchmark pilot <bid> <cid> 单次 pilot 运行(校准用)
|
|
194
|
-
/evolve benchmark freeze <bid> <cid> 冻结 case 为正式基线
|
|
195
|
-
/evolve benchmark meta <bid> <cid> <field> <value> 设置 case 元数据(capability/distinguisher/shortcuts)
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
闭环:冻结参考分 → 进化候选(`/evolve plan`)→ 用同一 case × run 矩阵复测进化后状态 → **代码所有**的接受规则只在总体均值严格提高且无 case 退化时保留候选(Self-Harness 风格)。
|
|
199
|
-
|
|
200
|
-
**评估者/评分者分离(两段式,差距 A1)**——每个 case × run 单元是一对全新子代理:
|
|
201
|
-
1. **执行者**用工具完成任务并记录**具体证据**(做了什么、查到了什么)——它**永远看不到 rubric**,被测 agent 无法朝评分标准优化、也无法自评;
|
|
202
|
-
2. **独立评审者**严格按 rubric 给证据打分(唯一接收解密 rubric 的分支),消除"自产自审"偏差。
|
|
203
|
-
|
|
204
|
-
每个 cell 记录执行者会话 id——分数可下钻回产生该证据的确切会话轨迹(Trace 证据指针,差距 A4)。
|
|
205
|
-
|
|
206
|
-
**失败格协议(差距 A2)**——无法产出分数的单元(rubric 解密失败、执行者/评审者崩溃、协议错误)记为**失败格**,**绝不是 0 分**:聚合从所有均值中排除失败格并计数(`/evolve benchmark status` 显示 `(N failed)`),接受规则在失败格超过 `maxFailedCells`(默认 0)时拒绝整轮,而不是把 0 平均进均值。
|
|
207
|
-
|
|
208
|
-
聚合与决策都在 `src/score.ts`。rubric 隔离靠构造(规划器的提示词永远不含 rubric 文件、执行者分支永不解密);拒绝会记录进 scoreboard 并自动回滚该 refinement(`autoRollbackOnReject`,默认开)。
|
|
209
|
-
|
|
210
|
-
开箱即用的种子 case 在 [`examples/`](examples/)——复制粘贴 statement 和 rubric 即可在一分钟内上手。
|
|
211
|
-
|
|
212
|
-
### 真实运行记录(ACCEPT)
|
|
213
|
-
|
|
214
|
-
一次真实的 `dsh web` 会话,一个 case、一个候选——第一次真正的接受:
|
|
215
|
-
|
|
216
|
-
| 步骤 | 命令 | 结果 |
|
|
217
|
-
|---|---|---|
|
|
218
|
-
| 参考线 | `/evolve benchmark run lint_convention` | **90**——评估子代理真的 grep 了 harness store,报告"lint/ruff/eslint/mypy 在所有条目中零出现" |
|
|
219
|
-
| 进化候选 | `/evolve plan 记住:写代码前必须先运行适用的 lint 检查` | 创建 `memory:convention_lint_before_code` |
|
|
220
|
-
| 复测 | `/evolve benchmark run lint_convention candidate <id>` | **100**——评估器跑 `evolve_list` 命中记忆并逐字引用 |
|
|
221
|
-
| 决策 | — | `overall: 90 → 100` · `lint_knowledge: 90 → 100` · **DECISION: ACCEPTED** |
|
|
222
|
-
|
|
223
|
-
执行者评的不是模型常识,而是**实际检查被测 harness 状态**(grep、`evolve_list`)并记录产出,再由独立评审者按 rubric 评分——所以 harness 的改动会真实地反映在分数上。同一会话早些时候还产生过诚实的 `REJECTED` 决策(0→0 占位符 case、100→100 满分基线无法超越)。
|
|
48
|
+
| 命令 | 效果 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `/evolve` | 帮助 + 当前 local store |
|
|
51
|
+
| `/evolve list · history · rollback <id>` | 查看与回滚(加 `global` 操作跨会话库) |
|
|
52
|
+
| `/evolve plan [msg]` | 对 store 运行 LLM 规划器 |
|
|
53
|
+
| `/evolve wrapup` | 收尾本会话 local 条目:晋升 / 归档 / 保留 |
|
|
54
|
+
| `/evolve archive · unarchive · demote <id>` | 从注入中隐藏(数据保留可恢复)——`demote` 针对全局噪声 |
|
|
55
|
+
| `/evolve failures` | 失败类聚合(门禁 + benchmark) |
|
|
56
|
+
| `/evolve log [tail N] [session <id>]` | 插件日志 |
|
|
57
|
+
| `/evolve export · import <path>` | 备份 / 恢复 store |
|
|
58
|
+
| `/evolve mount · unmount <skillId>` | 把可执行技能热挂载为 live 插件 |
|
|
59
|
+
| `/evolve goal [objective · done · block]` | 回合驱动的自进化目标 |
|
|
60
|
+
| `/evolve benchmark …` | 用例生命周期、运行、接受决策 |
|
|
61
|
+
|
|
62
|
+
模型工具:`evolve_list / add / update / delete / rollback`。
|
|
63
|
+
|
|
64
|
+
注入形态:prompt 补充与委派规格带内容注入(每 kind ≤6 条 × 180 字符,按相关性排序)。memory/skill 以目录索引出现(`[kind:id] 标题`,15 行封顶 + 折叠计数行)——全文经 `evolve_list` 获取。空 store = 零注入 token。
|
|
224
65
|
|
|
225
66
|
## 配置
|
|
226
67
|
|
|
227
|
-
| 键 |
|
|
68
|
+
| 键 | 默认 | 含义 |
|
|
228
69
|
|---|---|---|
|
|
229
|
-
| `baseDir` | 解析后的 DSH home | `evolve/`
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
-
| `
|
|
242
|
-
| `
|
|
243
|
-
| `
|
|
244
|
-
| `
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
|
|
248
|
-
|
|
70
|
+
| `baseDir` | 解析后的 DSH home | `evolve/` 存储根目录 |
|
|
71
|
+
| `autoReview` | `false` | 启用自动 review 门禁 |
|
|
72
|
+
| `reviewIntervalTurns` | `6` | 回合间隔路径的门禁节奏 |
|
|
73
|
+
| `maxReviewInputChars` | `40000` | 交给门禁的轨迹切片 |
|
|
74
|
+
| `reviewBudgetTokens` | `4096` | 门禁调用输出预算 |
|
|
75
|
+
| `notifyOnAutoReview` | `true` | 门禁应用后发可见跟进通知 |
|
|
76
|
+
| `requireGlobalApproval` | `true` | 全局写入需明确批准 |
|
|
77
|
+
| `localFate` | `true` | 门禁审计本地条目并提议晋升/归档(先征询,绝不静默) |
|
|
78
|
+
| `fateIntervalTurns` | 跟随 `reviewIntervalTurns` | 归宿评估的最小回合间隔 |
|
|
79
|
+
| `goalBlockedWrapupTurns` | `3` | 连续阻塞目标的门禁轮数触发一次归宿评估(`0` 关闭) |
|
|
80
|
+
| `promotionBlockPatterns` | POSIX 路径、session id、`~/.dsh` | 内容命中即判定项目专属,永不晋升全局 |
|
|
81
|
+
| `promotionMinChars` | `100` | 低于此长度的整体晋升留在本地 |
|
|
82
|
+
| `injectionDirectoryLines` | `15` | 每次构建的目录行数上限,超出折叠为计数行 |
|
|
83
|
+
| `sectionOrder` | `118` | 系统提示词 section 顺序 |
|
|
84
|
+
| `skillsDir` | `<dshHome>/skills` | 技能条目物化为 SKILL.md 的根目录 |
|
|
85
|
+
| `rubricKey` | 自动生成本地密钥文件 | benchmark rubric 的 AES-256-GCM 口令(`DSH_EVOLVE_RUBRIC_KEY` 可覆盖) |
|
|
86
|
+
| `logToFile` / `logLevel` / `logMaxBytes` | `true` / `1` / 5 MiB | 插件自带 JSONL 文件日志带轮转 |
|
|
87
|
+
| `autoRollbackOnReject` | `true` | benchmark 拒绝后自动确定性回滚 |
|
|
88
|
+
| `reviewModel` | agent 自身 | 门禁可选更便宜的模型(`"provider/model"`) |
|
|
89
|
+
|
|
90
|
+
profile patch 示例:
|
|
249
91
|
|
|
250
92
|
```yaml
|
|
251
|
-
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
autoReview: true
|
|
256
|
-
reviewIntervalTurns: 6
|
|
93
|
+
- id: continual-evolve
|
|
94
|
+
config:
|
|
95
|
+
autoReview: true
|
|
96
|
+
reviewIntervalTurns: 6
|
|
257
97
|
```
|
|
258
98
|
|
|
259
99
|
## 开发
|
|
260
100
|
|
|
261
101
|
```bash
|
|
262
|
-
pnpm install
|
|
263
|
-
pnpm
|
|
264
|
-
pnpm
|
|
265
|
-
pnpm
|
|
266
|
-
pnpm lint # oxlint src test
|
|
102
|
+
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
103
|
+
pnpm test # vitest(527 例)
|
|
104
|
+
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
105
|
+
pnpm lint # oxlint src test
|
|
267
106
|
```
|
|
268
107
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
对照 prime-agent `/refine` 与 penguin-harness 的差距与下一步实施项(P0+P1+P2+P3 已交付:评估者/评分者分离、失败格协议、运行时实证校验+材料漂移检测、使用率统计、自动衰减、case 生命周期+质检、条目目录视图、review 模型分离、blast-radius 标注、耗时追踪、evolve_complete 事件、种子 benchmark;D1 观察层 + D3 goal-blocked 触发已交付;剩余:跨进程同步按需实现 + D1/D2 完整工程化待实验数据):[`docs/gap-analysis.md`](docs/gap-analysis.md)。
|
|
272
|
-
|
|
273
|
-
## 路线图
|
|
108
|
+
目录结构:
|
|
274
109
|
|
|
275
|
-
|
|
110
|
+
```
|
|
111
|
+
├── src/ # 引擎、工具、命令、门禁、fate、benchmark、usage…
|
|
112
|
+
├── test/ # vitest 测试套件(33 个文件)
|
|
113
|
+
├── lib/ # 构建产物(tsc)
|
|
114
|
+
├── docs/
|
|
115
|
+
│ ├── design.md # 完整设计文档(硬化矩阵)
|
|
116
|
+
│ ├── FAQ.md # 真实踩坑记录
|
|
117
|
+
│ ├── gap-analysis.md # 对照 prime-agent /refine + penguin-harness
|
|
118
|
+
│ ├── experiment-bootstrap.md
|
|
119
|
+
│ ├── archive/ # 已完结的一次性报告
|
|
120
|
+
│ └── research/ # penguin 报告 + prime-agent 注释源码
|
|
121
|
+
├── examples/README.md # 种子 benchmark 用例
|
|
122
|
+
└── .agents/ # AI 协作层(AGENTS.md、技能、ADR 笔记)
|
|
123
|
+
```
|
|
276
124
|
|
|
277
|
-
|
|
278
|
-
- **2026-08 维护期增强(完成)**:
|
|
279
|
-
- **记忆层**——排序注入(相关度 + 新鲜度打分填满每类封顶)、轨迹引用(`metadata.sourceSession` + `sourceSeqs`,显示为 `src=session:seqs`)、归档/恢复(`/evolve archive <id>`,注入跳过归档条目)、global 感知门禁(拒绝 global 已覆盖主题的 local 重复沉淀)
|
|
280
|
-
- **每安装独立 rubric 密钥**——自动生成本地密钥文件(`<dshHome>/evolve/rubric.key`,0600);不再有全世界公开的 dev 键
|
|
281
|
-
- **插件自带文件日志**——所有 cordis 日志消息写入 `<dshHome>/evolve/plugin.log`(JSONL、0600、自动轮转),`/evolve log` 查看;与启动方式无关、无需安装额外组件
|
|
282
|
-
- **轨迹接地规划**——`/evolve plan`(及所有规划调用,含门禁 refine 步骤)现在读取会话轨迹:从调用方会话日志提取最近直接用户消息,作为 `<session_trajectory>` 块喂给规划器,提案以用户真实说过的话为依据(显式 `trajectory` 覆盖;空轨迹省略、零成本)
|
|
283
|
-
- **门禁提议归档**——过时条目是一等 refine 目标:规划器可输出 `action: "archive"`(仅需 kind + id),代码经正常 apply 通道盖 `metadata.archivedAt` 戳——快照、版本 +1、审计事件、以及恢复归档前状态的确定性回滚逆编辑。归档隐藏于注入但绝不删除;重复归档被拒绝;基础系统提示词保持不可变
|
|
284
|
-
- **benchmark 拒绝自动回滚**——接受闭环已闭合:代码所有决策拒绝候选时,refinement 经与 `/evolve rollback` 相同的引擎路径自动撤销(确定性逆编辑、快照 + 审计;`autoRollbackOnReject` 配置,默认开)。失败时给出手动回滚提示而不是抛错
|
|
285
|
-
- **日志按会话过滤**——`/evolve log [tail N] [session <id>]` 只保留提及指定会话 id 的行(精确 token 匹配,取自渲染消息与原始 args);门禁记录的行现在携带会话 id
|
|
286
|
-
- **自进化环中的技能标准**——规划器与门禁现在按 skill-creator/skill-audit 标准(作者蒸馏自官方 deepseek-harness 11 技能)创作与评审技能条目:每次规划注入 `template.md` 事实(内置精华版兜底)为 `<skill_quality_standard>`;apply 代码强制 frontmatter 机械规则(禁止遮蔽 `---`、禁止越界资源引用);物化后的 SKILL.md 复检,悬空资源引用记日志;
|
|
287
|
-
- **guidance 技能形态 + 用户治理创建**——第二种技能形态(无 python reference 的 SKILL.md 文档技能)让反复出现的流程可以被提议为技能;门禁把每次自动创建的技能先交给用户决定(固化/不固化)再落地,带拒绝冷却——技能在治理下生长,绝不静默写入
|
|
288
|
-
- **2026-08-17 收尾 wave(完成)**:
|
|
289
|
-
- **`/evolve wrapup`**——会话结束时 local 条目有了真正的归宿:先机械审计(local 候选 + 全局覆盖检测;**覆盖只看标题相似**——裸同 id 但标题迥异**不算**覆盖,真正命中的全局标题会展示给分类器)→ LLM 分类(`promote` / `archive` / `keep` + A 形拆解提升:混合条目整体归档、同时提升清洗出的持久子对象)→ 应用时刻确定性守卫复检(promote 永不写出全局重复;对称归档守卫要求用户确认后才隐藏未被覆盖、源自真实对话的条目;清洗标题撞全局主题的拆解降级为普通归档)→ 所有全局 create 走一个人工审批门
|
|
290
|
-
- **门禁 local 归宿维度**——wrap-up 机制现在以内置节奏(`fateIntervalTurns`,压缩时刻无条件)跑在自动 review 门禁里:local 条目在会话进行中被审计、分类、划分;治理动作先征询(一个弹窗、拒绝冷却),被覆盖/操作性条目静默归档,压缩时刻只做静默归档并以审计记录推迟治理动作;每次决策落进 `reviews.jsonl`,已执行动作发后续通知。应用写入与 wrapup 命令共享构造器(逐字节一致)
|
|
291
|
-
- **2026-08-19 研究项先导(完成)**:
|
|
292
|
-
- **goal-blocked 触发收尾(D3)**——goal 连续 `goalBlockedWrapupTurns` 次门禁运行(默认 3)处于 `blocked` 时触发一次 local 归宿评估,把卡住的原因沉淀下来再继续;连胜在任意非 blocked 运行与每次触发后被重置,被拒提案走正常 fate 冷却(绝不打扰);`goalBlockedWrapupTurns: 0` 关闭
|
|
293
|
-
- **失败签名聚合(D1 观察层)**——`/evolve failures` 将门禁失败记录与 benchmark 失败格按确定性失败类(`rubric-decrypt` / `executor` / `reviewer` / `material-drift` / `gate` / `max-tokens` 等)统计——未来 failure-signature Refiner 的底层数据
|
|
294
|
-
- **bootstrap 加速实验脚手架(D2)**——[`docs/experiment-bootstrap.md`](docs/experiment-bootstrap.md) 设计 ≤3 轮对照实验(固定 reference → 沉淀 harness → 候选评估)验证"被提高的 harness 加速下一跑";`scripts/benchmark-trend.sh` 从 scoreboard 提取每轮趋势表(overall / totalDurationMs / failed / case-hash 一致性)
|
|
295
|
-
- **2026-08-17 差距 P0(完成)**:
|
|
296
|
-
- **评估者/评分者分离**——benchmark 评估改为两段式(差距 A1):执行者完成任务并记录具体证据、**永远看不到 rubric**;独立评审者按 rubric 给证据评分(唯一解密 rubric 的分支)。被测 agent 无法朝评分标准优化、也无法自评
|
|
297
|
-
- **失败格协议**——cell 带 `status: ok|failed`(差距 A2):失败格从所有均值中排除并计数,接受规则在失败格超过 `maxFailedCells`(默认 0)时拒绝整轮,而不是把 0 平均进均值。scoreboard status/run 展示失败数与逐格原因
|
|
298
|
-
- **Trace 证据指针**——每个 cell 记录执行者会话 id(差距 A4),分数可下钻回产生它的确切会话轨迹
|
|
299
|
-
- **2026-08-18 差距 P1(完成)**:
|
|
300
|
-
- **运行时实证校验(A3)**——cell 现在记录宿主写入的实际 `provider`、`model` 和 `caseHash`(statement + rubric 的 SHA-256 前缀);参考线与候选运行之间的材料变化会被检出并把受影响候选格重标为失败(version_changed 语义,`score.flagMaterialDrift`),材料漂移的轮次绝不可能被接受
|
|
301
|
-
- **条目使用率统计(B1)**——注入计数持久追踪(`<baseDir>/evolve/usage.json`);`evolve_list` 展示使用次数;`zeroUsageEntries()` 筛选从未注入的 local 条目作为归档候选
|
|
302
|
-
- **自动陈旧检测(B2)**——零注入且低新鲜度的条目标记为 `stale`;LLM 分类器被指示优先归档陈旧条目
|
|
303
|
-
- **2026-08-18 差距 P2(完成)**:
|
|
304
|
-
- **case 生命周期 + 质量门禁(A5)**——case 遵循 `draft → calibrating → frozen` 状态机;`casecheck` 运行机械质量校验(能力合约、区分点、快捷方式);`pilot` 执行单次校准运行;`freeze` 将 case 锁定为正式基线(需通过质量门禁);`meta` 设置 case 元数据字段
|
|
305
|
-
- **条目目录视图(B3)**——注入块现在包含所有非归档条目的轻量目录(id + title,每条一行),在条目超出精选封顶时自动展示,为模型提供零成本全局概览
|
|
306
|
-
- **review 模型分离(C1)**——`reviewModel` 配置项让 review 门禁可使用比主 agent 更便宜的模型
|
|
307
|
-
- **blast-radius 标注(C2)**——每条编辑现在携带 `blastRadius` 字段(`general` / `project` / `session`);规划器被要求标注该字段,解析器验证取值
|
|
308
|
-
- **耗时追踪(C3)**——每个评估单元格记录 `durationMs`(墙钟时间);聚合总计和决策报告展示耗时对比
|
|
309
|
-
- **2026-08-18 代码重构(完成)**:
|
|
310
|
-
- **循环依赖拆解(P1-1)**——抽出 `skill-render.ts` 解耦 `skill.ts ↔ skillquality.ts`
|
|
311
|
-
- **LLM 调用去重(P1-2)**——抽出 `llm-text.ts` 共享 `streamText()`(review/planner/wrapup 删除 ~107 行重复)
|
|
312
|
-
- **config 类型推导(P2-1)**——`EvolveConfig` 改为 `Schemastery.TypeT` 推导(消除 20 行手写接口)
|
|
313
|
-
- **command.ts 拆分(P2-2)**——860 行 god file 拆为 `goal-command.ts`、`mount-command.ts`、`benchmark-command.ts`、`wrapup-command.ts`
|
|
314
|
-
- **P3 清理**——`questionServiceOf()` cast 去重(4 处)、死导出清理、矛盾注释修复
|
|
125
|
+
## 文档与出处
|
|
315
126
|
|
|
316
|
-
|
|
127
|
+
- 设计:[`docs/design.md`](docs/design.md) · 踩坑:[`docs/FAQ.md`](docs/FAQ.md) · 差距分析:[`docs/gap-analysis.md`](docs/gap-analysis.md) · D2 实验:[`docs/experiment-bootstrap.md`](docs/experiment-bootstrap.md)
|
|
128
|
+
- 血统:**penguin-harness**(概念;Apache-2.0)——报告见 [`docs/research/penguin-harness-self-evolution.md`](docs/research/penguin-harness-self-evolution.md);**prime-agent `/refine`**(工程形态;MIT)——注释参考源码见 [`docs/research/prime-agent-refinement.ts`](docs/research/prime-agent-refinement.ts)。本包是面向 DSH 插件表面的原创实现。
|
|
317
129
|
|
|
318
130
|
## License
|
|
319
131
|
|
package/lib/apply.js
CHANGED
|
@@ -6,7 +6,13 @@ export function applyRefinementProposal(state, proposal, options) {
|
|
|
6
6
|
const touched = new Set();
|
|
7
7
|
const now = new Date().toISOString();
|
|
8
8
|
for (const edit of proposal.edits) {
|
|
9
|
-
|
|
9
|
+
// Store-prefix hygiene: planner edits are written against the MERGED
|
|
10
|
+
// view, where colliding local ids carry a `local:` / `global:` prefix.
|
|
11
|
+
// A CREATE must never bake that view prefix into a permanent id
|
|
12
|
+
// (observed: global entries literally named "local:handoff_todo_…").
|
|
13
|
+
// Updates/deletes keep the raw id — they address existing entries.
|
|
14
|
+
const requestedId = edit.action === "create" ? edit.id?.replace(/^(?:local|global):/, "") : edit.id;
|
|
15
|
+
const computedId = requestedId ?? (edit.action === "create" ? slug(edit.title ?? edit.kind, edit.kind) : undefined);
|
|
10
16
|
const id = computedId ?? "";
|
|
11
17
|
const validationError = validateEdit(edit, computedId, options.scope);
|
|
12
18
|
if (validationError) {
|
package/lib/auto.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
|
3
3
|
import type { HarnessState, RefinementEdit, RefinementProposal } from "./types.js";
|
|
4
4
|
import type { EvolutionEngine } from "./service.js";
|
|
5
5
|
import { type AutoRefineReason } from "./review.js";
|
|
6
|
+
import type { PromotionPolicy } from "./promotion.js";
|
|
6
7
|
export interface AutoReviewConfig {
|
|
7
8
|
intervalTurns: number;
|
|
8
9
|
maxInputChars: number;
|
|
@@ -38,6 +39,11 @@ export interface AutoReviewConfig {
|
|
|
38
39
|
* proposal then follows the normal fate cooldown.
|
|
39
40
|
*/
|
|
40
41
|
goalBlockedWrapupTurns: number;
|
|
42
|
+
/**
|
|
43
|
+
* Promotion policy (2026-08-22): mechanical promote guards applied by the
|
|
44
|
+
* local-fate dimension before anything reaches the global store.
|
|
45
|
+
*/
|
|
46
|
+
promotionPolicy: PromotionPolicy;
|
|
41
47
|
}
|
|
42
48
|
export interface GateState {
|
|
43
49
|
turns: number;
|
|
@@ -82,6 +88,17 @@ export interface ReviewRecord {
|
|
|
82
88
|
*/
|
|
83
89
|
export declare function advanceGateState(state: GateState, status: string): boolean;
|
|
84
90
|
export declare function registerAutoReview(ctx: Context, engine: EvolutionEngine, config: AutoReviewConfig): void;
|
|
91
|
+
/**
|
|
92
|
+
* Gap C1: parse a "provider/model" or "model" string into its components.
|
|
93
|
+
* Returns undefined when the input is empty (no override). A bare model name
|
|
94
|
+
* falls back to the agent's provider, then "deepseek".
|
|
95
|
+
* Exported for unit testing (the advanceGateState precedent); production
|
|
96
|
+
* resolves it inside runReviewPhase.
|
|
97
|
+
*/
|
|
98
|
+
export declare function parseReviewModel(reviewModel: string | undefined, fallbackProvider: string | undefined): {
|
|
99
|
+
provider: string;
|
|
100
|
+
model: string;
|
|
101
|
+
} | undefined;
|
|
85
102
|
/**
|
|
86
103
|
* The state view the gate and planner judge: the session's local entries
|
|
87
104
|
* merged with the global store, each entry carrying its real scope. Without
|
package/lib/auto.js
CHANGED
|
@@ -137,9 +137,12 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
139
|
* Gap C1: parse a "provider/model" or "model" string into its components.
|
|
140
|
-
* Returns undefined when the input is empty (no override).
|
|
140
|
+
* Returns undefined when the input is empty (no override). A bare model name
|
|
141
|
+
* falls back to the agent's provider, then "deepseek".
|
|
142
|
+
* Exported for unit testing (the advanceGateState precedent); production
|
|
143
|
+
* resolves it inside runReviewPhase.
|
|
141
144
|
*/
|
|
142
|
-
function parseReviewModel(reviewModel, fallbackProvider) {
|
|
145
|
+
export function parseReviewModel(reviewModel, fallbackProvider) {
|
|
143
146
|
if (!reviewModel || reviewModel.trim().length === 0)
|
|
144
147
|
return undefined;
|
|
145
148
|
const slash = reviewModel.indexOf("/");
|
package/lib/benchmark-command.js
CHANGED
|
@@ -258,6 +258,8 @@ export async function executeBenchmarkCommand(ctx, engine, invocation, rest, run
|
|
|
258
258
|
const scoreText = cell?.status === "ok" ? `${cell.score} (${cell.passed ? "passed" : "below threshold"})` : `failed: ${cell?.notes ?? "unknown"}`;
|
|
259
259
|
// Record in calibration history.
|
|
260
260
|
const updatedMeta = loadCaseMeta(baseDir, bid, cid) ?? { status: "calibrating", capability: "", distinguisher: "", shortcuts: "", calibrationHistory: [] };
|
|
261
|
+
// Older or externally-written meta.json may lack the history array.
|
|
262
|
+
updatedMeta.calibrationHistory ??= [];
|
|
261
263
|
updatedMeta.calibrationHistory.push({
|
|
262
264
|
runAt: new Date().toISOString(),
|
|
263
265
|
score: cell?.status === "ok" ? cell.score : 0,
|
package/lib/command.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { Context } from "@deepseek-ai/cordis";
|
|
6
6
|
import type { HarnessEntry, HarnessState, RefinementKind } from "./types.js";
|
|
7
7
|
import type { EvolutionEngine } from "./service.js";
|
|
8
|
+
import type { PromotionPolicy } from "./promotion.js";
|
|
8
9
|
export interface CommandGateOptions {
|
|
9
10
|
requireGlobalApproval: boolean;
|
|
10
11
|
}
|
|
@@ -12,6 +13,8 @@ export interface CommandRuntimeOptions {
|
|
|
12
13
|
rubricKey: Buffer;
|
|
13
14
|
/** When a benchmark decision rejects a candidate, roll the refinement back automatically. */
|
|
14
15
|
autoRollbackOnReject: boolean;
|
|
16
|
+
/** Mechanical promotion guards for wrapup/fate (2026-08-22 policy). */
|
|
17
|
+
promotionPolicy: PromotionPolicy;
|
|
15
18
|
}
|
|
16
19
|
export declare function registerEvolveCommand(ctx: Context, engine: EvolutionEngine, opts: CommandGateOptions, runtime: CommandRuntimeOptions): void;
|
|
17
20
|
/**
|
package/lib/command.js
CHANGED
|
@@ -23,6 +23,7 @@ const USAGE = `Usage:
|
|
|
23
23
|
to the global store (approval required), archive one-offs
|
|
24
24
|
/evolve archive <id> [global] hide an entry from injection (data kept, restorable)
|
|
25
25
|
/evolve unarchive <id> [global] restore an archived entry
|
|
26
|
+
/evolve demote <id> hide a (global) entry from injection, keep data
|
|
26
27
|
/evolve log [tail N] show the recent plugin log (default 50 lines)
|
|
27
28
|
/evolve failures aggregated failure counts (gate + benchmark, by class)
|
|
28
29
|
/evolve export [global] <path> backup a store to a JSON file
|
|
@@ -139,12 +140,16 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
139
140
|
return success(renderResult(result));
|
|
140
141
|
}
|
|
141
142
|
case "archive":
|
|
142
|
-
case "unarchive":
|
|
143
|
+
case "unarchive":
|
|
144
|
+
case "demote": {
|
|
143
145
|
const { scope, rest: after } = scopeArg(rest);
|
|
144
146
|
const id = stripAngleBrackets(after[0] ?? "");
|
|
145
147
|
if (!id) {
|
|
146
148
|
return error(`${sub} requires an entry id.\n${USAGE}`);
|
|
147
149
|
}
|
|
150
|
+
if (sub === "demote") {
|
|
151
|
+
return demoteEntry(engine, id, sessionId);
|
|
152
|
+
}
|
|
148
153
|
const state = engine.load(scope, sessionId);
|
|
149
154
|
const found = findEntryById(state, id);
|
|
150
155
|
if (!found) {
|
|
@@ -289,7 +294,7 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
289
294
|
return success(renderResult(result));
|
|
290
295
|
}
|
|
291
296
|
case "wrapup": {
|
|
292
|
-
return await executeWrapupCommand(ctx, engine, invocation);
|
|
297
|
+
return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy);
|
|
293
298
|
}
|
|
294
299
|
case "goal": {
|
|
295
300
|
return executeGoalCommand(ctx, invocation, rest);
|
|
@@ -311,6 +316,39 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
311
316
|
return error(cause instanceof Error ? cause.message : String(cause));
|
|
312
317
|
}
|
|
313
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Demote (2026-08-22): hide an entry from injection WITHOUT deleting it —
|
|
321
|
+
* the one-command remedy for global-store pollution. Searches the global
|
|
322
|
+
* store first (the primary target: cross-project noise), then the session's
|
|
323
|
+
* local store. The data stays; `/evolve unarchive` restores it.
|
|
324
|
+
*/
|
|
325
|
+
function demoteEntry(engine, id, sessionId) {
|
|
326
|
+
for (const scope of ["global", "local"]) {
|
|
327
|
+
const state = engine.load(scope, sessionId);
|
|
328
|
+
const found = findEntryById(state, id);
|
|
329
|
+
if (!found)
|
|
330
|
+
continue;
|
|
331
|
+
const [kind, entry] = found;
|
|
332
|
+
const result = engine.apply(scope, sessionId, {
|
|
333
|
+
summary: `demote: archive ${kind}:${id} from the ${scope} store`,
|
|
334
|
+
rationale: "Human-invoked demote via the /evolve command.",
|
|
335
|
+
expectedOutcome: "The entry is hidden from injection in every scope it touched; data is kept and restorable.",
|
|
336
|
+
edits: [
|
|
337
|
+
{
|
|
338
|
+
action: "update",
|
|
339
|
+
kind,
|
|
340
|
+
id,
|
|
341
|
+
title: entry.title,
|
|
342
|
+
content: entry.content,
|
|
343
|
+
metadata: { ...entry.metadata, [ARCHIVED_AT_KEY]: new Date().toISOString() },
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
}, { scope });
|
|
347
|
+
const restoreScope = scope === "global" ? " global" : "";
|
|
348
|
+
return success(`demoted ${kind}:${id} from the ${scope} store (archived — restore with /evolve unarchive ${id}${restoreScope})\n${renderResult(result)}`);
|
|
349
|
+
}
|
|
350
|
+
return error(`entry ${id} not found in the global or local store`);
|
|
351
|
+
}
|
|
314
352
|
function renderResult(result) {
|
|
315
353
|
const applied = result.appliedEdits.filter((e) => e.applied);
|
|
316
354
|
const failed = result.appliedEdits.filter((e) => !e.applied);
|
package/lib/fate.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ import type { HarnessState, RefinementResult } from "./types.js";
|
|
|
34
34
|
import type { EvolutionEngine } from "./service.js";
|
|
35
35
|
import type { AutoRefineReason } from "./review.js";
|
|
36
36
|
import type { AutoReviewConfig, GateState, ReviewRecord } from "./auto.js";
|
|
37
|
+
import { type PromotionPolicy } from "./promotion.js";
|
|
37
38
|
import { type WrapupCandidate, type WrapupItem } from "./wrapup.js";
|
|
38
39
|
/** Turns a declined local-fate proposal stays silent before being offered again. */
|
|
39
40
|
export declare const FATE_CONSULT_COOLDOWN_TURNS = 10;
|
|
@@ -68,7 +69,7 @@ export interface FatePlan {
|
|
|
68
69
|
* may have changed while the LLM call was in flight). Pure and unit-tested;
|
|
69
70
|
* mirrors the partition step of the wrap-up command.
|
|
70
71
|
*/
|
|
71
|
-
export declare function planLocalFates(items: readonly WrapupItem[], candidates: readonly WrapupCandidate[], globalState: HarnessState): FatePlan;
|
|
72
|
+
export declare function planLocalFates(items: readonly WrapupItem[], candidates: readonly WrapupCandidate[], globalState: HarnessState, policy?: PromotionPolicy): FatePlan;
|
|
72
73
|
/**
|
|
73
74
|
* The cooldown key of a candidate set: the sorted `kind:id` list. The set is
|
|
74
75
|
* the unit of consultation — a declined proposal is not offered again within
|