dsh-continual-evolve 0.7.2 → 0.7.4
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 +16 -14
- package/README.zh.md +16 -14
- package/lib/approval.d.ts +11 -4
- package/lib/approval.js +23 -7
- package/lib/auto.d.ts +24 -0
- package/lib/auto.js +143 -14
- package/lib/index.js +6 -4
- package/lib/llm-text.d.ts +56 -5
- package/lib/llm-text.js +100 -27
- package/lib/memory-agent.d.ts +113 -0
- package/lib/memory-agent.js +536 -0
- package/lib/planner.js +2 -1
- package/lib/review-scheduler.d.ts +9 -8
- package/lib/review-scheduler.js +118 -36
- package/lib/review.d.ts +1 -1
- package/lib/review.js +8 -6
- package/lib/service.d.ts +9 -0
- package/lib/service.js +46 -20
- package/lib/token-usage.d.ts +1 -1
- package/lib/token-usage.js +8 -8
- package/lib/turn-snapshot.d.ts +10 -0
- package/lib/turn-snapshot.js +34 -0
- package/lib/wrapup.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
|
|
12
12
|
Continual self-evolution for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): a versioned, auditable, rollback-safe harness state layer — prompt notes, memories, skills, subagent specs — refined from session trajectories.
|
|
13
13
|
|
|
@@ -19,17 +19,19 @@ Agents accumulate reusable experience (repeated failures, durable facts, reusabl
|
|
|
19
19
|
|
|
20
20
|
- **Three scopes** with merge semantics (global < project < local): **local** per-session staging, **project** per-workspace cross-session store, **global** cross-project — plus mechanical promotion guards so only portable, substantial, non-duplicate knowledge reaches global
|
|
21
21
|
- **Typed one-fact memories**: every memory entry carries a recall type (`user | feedback | project | reference`); pitfalls (`feedback`) must include Why + How to apply
|
|
22
|
+
- **Dedicated background memory agent**: after eligible successful turns, a bounded ZCode-style loop searches the frozen memory manifest and proposes memory-only edits through a closed tool set; it cannot call agents, MCP, the network, or write source files
|
|
22
23
|
- **Deterministic rollback**: inverse edits generated from applied results — no LLM re-guessing
|
|
23
24
|
- **Benchmark loop**: candidate refinements are evaluated against frozen cases by a separate scorer before acceptance (rubric encrypted at rest)
|
|
24
25
|
- **Store hygiene**: `/evolve consolidate` turns write-time conflict hints and zero-use staleness into one approved, fully reversible batch of archives — with `merge`, near-duplicate content folds into the surviving original
|
|
25
26
|
|
|
26
27
|
## How it works
|
|
27
28
|
|
|
28
|
-
1. **Sediment** — the model creates entries via `evolve_add`, or the automatic
|
|
29
|
-
2. **
|
|
30
|
-
3. **
|
|
31
|
-
4. **
|
|
32
|
-
5. **
|
|
29
|
+
1. **Sediment** — the model creates entries via `evolve_add`, or the automatic pipeline consumes incremental snapshots captured after successful turns (plus compaction checkpoints): a dedicated memory agent runs first, then the general review/planner proposes non-memory refinements.
|
|
30
|
+
2. **Capability-aware auxiliary calls** — the memory loop, review, planner, wrapup, and fate resolve exact provider/model metadata through [`src/llm-text.ts`](src/llm-text.ts), use the lowest advertised enabled reasoning effort (falling back to a closing effort only when no enabled level exists), and forward the host session id for provider routing; models without reasoning metadata use their provider default.
|
|
31
|
+
3. **Guard** — code-enforced validation: edit schema, blast-radius/scope coherence, and the promotion policy (project-scoped markers, thin content, near-duplicate detection, credential screening keep the global store clean — secrets are rejected at every write sink, including mount materialization). Global creates that near-duplicate an existing entry are rejected at write time (≥0.8 similarity); moderate overlaps carry a `conflictHint` for later consolidation.
|
|
32
|
+
4. **Approve** — global and project writes require explicit human approval; the dialog shows the bounded structured edit diff and conflict warnings, while malformed/lost responses remain retryable rather than counting as rejection.
|
|
33
|
+
5. **Apply & inject** — memory batches preflight every persistent approval, recheck abort before writes, and compensate earlier scope writes if a later batch fails; every successful scope still passes through snapshot + audit. Prompt notes and delegation specs inject into the system prompt (capped, relevance-ranked, contradicted entries demoted, zero tokens when empty); memories/skills appear as a relevance-ordered capped directory index (`- [memory:type:id] title` hooks, full text one `evolve_list` away).
|
|
34
|
+
6. **Validate & roll back** — benchmarks score candidates against frozen cases; rejected candidates roll back deterministically and are captured as draft regression cases (`auto_regression` benchmark).
|
|
33
35
|
|
|
34
36
|
## Install
|
|
35
37
|
|
|
@@ -41,7 +43,7 @@ dsh plugin add dsh-continual-evolve
|
|
|
41
43
|
dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
Restart `dsh web` after installing or updating.
|
|
46
|
+
Restart the DSH profile you use (`dsh web` or the desktop host) after installing or updating.
|
|
45
47
|
|
|
46
48
|
## Usage
|
|
47
49
|
|
|
@@ -62,13 +64,13 @@ Commands (in-session):
|
|
|
62
64
|
| `/evolve goal [objective · done · block]` | round-driven auto-review goal |
|
|
63
65
|
| `/evolve benchmark …` | case lifecycle, runs, acceptance |
|
|
64
66
|
| `/evolve pause · resume · status` | pause/resume the auto-review gate (manual tools keep working), gate state |
|
|
65
|
-
| `/evolve usage` | per-entry injection counts + exact provider-reported tokens for direct review/planner/wrapup/fate calls (benchmark host subagents excluded) |
|
|
67
|
+
| `/evolve usage` | per-entry injection counts + exact provider-reported tokens for direct memory/review/planner/wrapup/fate calls (benchmark host subagents excluded) |
|
|
66
68
|
|
|
67
69
|
Model tools: `evolve_list / add / update / delete / rollback` (`evolve_delete` takes `id` or a batch `ids` array — one refinement, one approval).
|
|
68
70
|
|
|
69
71
|
For third-party consumers: every applied evolution (gate or manual) appends a structured `evolve_complete` event to `reviews.jsonl` (`src/evolve-event.ts` defines the shape) alongside the human-readable audit records.
|
|
70
72
|
|
|
71
|
-
`/evolve usage` also reads `evolve/token-usage.jsonl`: exact provider-reported input/cache/output/total tokens for the plugin's direct review, planner, manual-wrapup, and automatic-fate calls. The report covers a retained tail rather than lifetime usage, distinguishes missing provider samples, and explicitly excludes host benchmark subagents, their agent-loop calls, and per-entry injection attribution.
|
|
73
|
+
`/evolve usage` also reads `evolve/token-usage.jsonl`: exact provider-reported input/cache/output/total tokens for the plugin's direct memory-agent, review, planner, manual-wrapup, and automatic-fate calls. The report covers a retained tail rather than lifetime usage, distinguishes missing provider samples, and explicitly excludes host benchmark subagents, their agent-loop calls, and per-entry injection attribution.
|
|
72
74
|
|
|
73
75
|
Injection shape: prompt notes and delegation specs inject with content (≤6/kind × 180 chars, relevance-ranked). Memories and skills appear as a relevance-ordered directory index (`[memory:type:id] title` hooks, capped at 15 lines with a fold counter) — full text via `evolve_list`. Empty store = zero injected tokens.
|
|
74
76
|
|
|
@@ -77,7 +79,7 @@ Injection shape: prompt notes and delegation specs inject with content (≤6/kin
|
|
|
77
79
|
| Key | Default | Meaning |
|
|
78
80
|
|---|---|---|
|
|
79
81
|
| `baseDir` | resolved DSH home | root for the `evolve/` stores |
|
|
80
|
-
| `autoReview` | `false` | initial
|
|
82
|
+
| `autoReview` | `false` | initial memory+review pipeline default when no runtime switch exists; the listener is always registered |
|
|
81
83
|
| `reviewIntervalTurns` | `6` | legacy local-fate cadence fallback; successful-turn review no longer waits for this interval |
|
|
82
84
|
| `maxReviewInputChars` | `40000` | trajectory slice handed to the gate |
|
|
83
85
|
| `reviewBudgetTokens` | `4096` | output budget for the gate call |
|
|
@@ -95,7 +97,7 @@ Injection shape: prompt notes and delegation specs inject with content (≤6/kin
|
|
|
95
97
|
| `logToFile` / `logLevel` / `logMaxBytes` | `true` / `1` / 5 MiB | plugin-owned JSONL file log with rotation |
|
|
96
98
|
| `autoRollbackOnReject` | `true` | deterministic rollback after a benchmark rejection |
|
|
97
99
|
| `autoCase` | `true` | failed evolution attempts are captured as draft regression cases (`auto_regression` benchmark) |
|
|
98
|
-
| `reviewModel` | agent's own | optional cheaper model for the gate (`"provider/model"`) |
|
|
100
|
+
| `reviewModel` | agent's own | optional cheaper model for the dedicated memory agent and review gate (`"provider/model"`) |
|
|
99
101
|
| `plannerPrefixCache` | `auto` | Route A session-prefix input when cache evidence exists (`session` always, `off` legacy flat text) |
|
|
100
102
|
| `plannerPrefixMaxChars` | `12000` | session-prefix budget for Route A planning inputs (chars) |
|
|
101
103
|
| `historyRetain` | `{snapshots: 20, refinements: 500, reviews: 500, tokenUsage: 500}` | storage hygiene: snapshots per store, tail lines per store history, shared `reviews.jsonl` tail, and direct-call `token-usage.jsonl` tail |
|
|
@@ -120,7 +122,7 @@ paused.
|
|
|
120
122
|
|
|
121
123
|
```bash
|
|
122
124
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
123
|
-
pnpm test # vitest (
|
|
125
|
+
pnpm test # vitest (737 tests)
|
|
124
126
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
125
127
|
pnpm lint # oxlint src test
|
|
126
128
|
```
|
|
@@ -128,8 +130,8 @@ pnpm lint # oxlint src test
|
|
|
128
130
|
Project layout:
|
|
129
131
|
|
|
130
132
|
```
|
|
131
|
-
├── src/ # engine, tools, commands, gate, fate, benchmark, injection + token usage…
|
|
132
|
-
├── test/ # vitest suites (
|
|
133
|
+
├── src/ # engine, tools, commands, memory agent, gate, fate, benchmark, injection + token usage…
|
|
134
|
+
├── test/ # vitest suites (44 files)
|
|
133
135
|
├── lib/ # build output (tsc)
|
|
134
136
|
├── docs/
|
|
135
137
|
│ ├── design.md # full design doc (hardening matrix)
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
|
|
12
12
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的持续自进化插件:一套**版本化、可审计、可回滚**的 harness 状态层——提示词补充、记忆、技能、子代理规格——从会话轨迹中沉淀而来。
|
|
13
13
|
|
|
@@ -19,17 +19,19 @@ Agent 在每个会话里积累可复用经验(重复失败、持久事实、
|
|
|
19
19
|
|
|
20
20
|
- **三作用域**与合并语义(global < project < local):**local** 本会话暂存、**project** 本项目跨会话库、**global** 跨项目——配合机械化晋升守卫,只有可携带、有分量、非重复的知识才能进全局
|
|
21
21
|
- **类型化单条记忆**:每条 memory 带召回类型(`user | feedback | project | reference`);踩坑(`feedback`)必须含 Why + How to apply
|
|
22
|
+
- **专用后台记忆 Agent**:成功回合通过机械 eligibility 后,有界 ZCode 式 loop 检索冻结的 memory manifest,并经闭集工具提出纯 memory 编辑;不能调用 Agent、MCP、网络,也不能写源码
|
|
22
23
|
- **确定性回滚**:逆操作编辑由已应用结果生成——不靠 LLM 重新猜测
|
|
23
24
|
- **benchmark 闭环**:候选沉淀先经冻结用例 + 独立评分者评估再接受(rubric 加密落盘)
|
|
24
25
|
- **store 卫生**:`/evolve consolidate` 把写入时冲突提示与零使用陈旧条目变成一次批准、完全可逆的批量归档——加 `merge` 可将近重复内容并入幸存原条目
|
|
25
26
|
|
|
26
27
|
## 工作原理
|
|
27
28
|
|
|
28
|
-
1. **沉淀**——模型经 `evolve_add`
|
|
29
|
-
2.
|
|
30
|
-
3.
|
|
31
|
-
4.
|
|
32
|
-
5.
|
|
29
|
+
1. **沉淀**——模型经 `evolve_add` 创建条目,或自动管线消费成功回合后的增量 snapshot(以及压缩检查点):先运行专用 memory Agent,再由通用 review/planner 提议非 memory 精炼。
|
|
30
|
+
2. **能力感知的辅助调用**——memory loop、review、planner、wrapup、fate 通过 [`src/llm-text.ts`](src/llm-text.ts) 解析精确 provider/model 能力并使用模型公布的最低开启 reasoning effort,同时转发 host session id 供 provider 路由;只有没有开启档时才回退关闭档,没有 reasoning 元数据时使用 provider 默认行为。
|
|
31
|
+
3. **守卫**——代码强制校验:编辑 schema、blast-radius 与作用域一致性、晋升政策(项目专属标记 / 过薄内容 / 近似重复检测 / 凭据筛查保持全局库干净——密钥类内容在所有写入出口被拒,含 mount 物化)。全局 create 与既有条目高度相似(≥0.8)时写入即拒;中等重叠带 `conflictHint` 供后续合并。
|
|
32
|
+
4. **审批**——全局与项目写入需明确人工批准;弹窗展示有界结构化编辑 diff 与冲突提示,弹窗丢失/响应畸形会重试,不会被误记为拒绝。
|
|
33
|
+
5. **应用与注入**——memory 批次先完成所有持久化审批,写前重查 abort;后续 scope 失败时补偿回滚先前写入,成功 scope 仍保留快照与审计。prompt 补充与委派规格注入系统提示词(封顶、按相关性排序、被证伪条目降权、空 store 零 token);memory/skill 以按相关性排序的目录索引出现(`[memory:type:id] 标题`钩子)。
|
|
34
|
+
6. **验证与回滚**——benchmark 用冻结用例为候选打分;被拒候选确定性回滚,并自动沉淀为 draft 回归用例(`auto_regression` 基准)。
|
|
33
35
|
|
|
34
36
|
## 安装
|
|
35
37
|
|
|
@@ -41,7 +43,7 @@ dsh plugin add dsh-continual-evolve
|
|
|
41
43
|
dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
安装或更新后,重启你实际使用的 DSH profile(`dsh web` 或桌面宿主)。
|
|
45
47
|
|
|
46
48
|
## 使用
|
|
47
49
|
|
|
@@ -62,13 +64,13 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
62
64
|
| `/evolve goal [objective · done · block]` | 回合驱动的自进化目标 |
|
|
63
65
|
| `/evolve benchmark …` | 用例生命周期、运行、接受决策 |
|
|
64
66
|
| `/evolve pause · resume · status` | 暂停/恢复自动门禁(手动工具不受影响)、门禁状态 |
|
|
65
|
-
| `/evolve usage` | 每条目注入次数 + review/planner/wrapup/fate 直属调用的 provider 精确 token(不含 benchmark 宿主子代理) |
|
|
67
|
+
| `/evolve usage` | 每条目注入次数 + memory/review/planner/wrapup/fate 直属调用的 provider 精确 token(不含 benchmark 宿主子代理) |
|
|
66
68
|
|
|
67
69
|
模型工具:`evolve_list / add / update / delete / rollback`(`evolve_delete` 支持 `id` 或批量 `ids` 数组——一次 refinement、一次审批)。
|
|
68
70
|
|
|
69
71
|
第三方消费:每次进化落地(门禁或手动)都会向 `reviews.jsonl` 追加结构化 `evolve_complete` 事件(shape 见 `src/evolve-event.ts`),与人类可读的审计记录并存。
|
|
70
72
|
|
|
71
|
-
`/evolve usage` 还会读取 `evolve/token-usage.jsonl`:插件直属 review、planner、手动 wrapup 与自动 fate 调用的 provider 精确 input/cache/output/total token。报告只覆盖保留尾部而非终身累计,单独显示 provider 未返回 usage 的调用,并明确排除宿主 benchmark 子代理、其 agent-loop 调用与逐条 memory 注入归因。
|
|
73
|
+
`/evolve usage` 还会读取 `evolve/token-usage.jsonl`:插件直属 memory Agent、review、planner、手动 wrapup 与自动 fate 调用的 provider 精确 input/cache/output/total token。报告只覆盖保留尾部而非终身累计,单独显示 provider 未返回 usage 的调用,并明确排除宿主 benchmark 子代理、其 agent-loop 调用与逐条 memory 注入归因。
|
|
72
74
|
|
|
73
75
|
注入形态:prompt 补充与委派规格带内容注入(每 kind ≤6 条 × 180 字符,按相关性排序)。memory/skill 以按相关性排序的目录索引出现(`[memory:type:id] 标题`钩子,15 行封顶 + 折叠计数行)——全文经 `evolve_list` 获取。空 store = 零注入 token。
|
|
74
76
|
|
|
@@ -77,7 +79,7 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
77
79
|
| 键 | 默认 | 含义 |
|
|
78
80
|
|---|---|---|
|
|
79
81
|
| `baseDir` | 解析后的 DSH home | `evolve/` 存储根目录 |
|
|
80
|
-
| `autoReview` | `false` | 没有 runtime
|
|
82
|
+
| `autoReview` | `false` | 没有 runtime 开关时自动 memory+review 管线的初始默认;监听器始终注册 |
|
|
81
83
|
| `reviewIntervalTurns` | `6` | local-fate 的兼容节奏;成功回合 review 不再等待这个间隔 |
|
|
82
84
|
| `maxReviewInputChars` | `40000` | 交给门禁的轨迹切片 |
|
|
83
85
|
| `reviewBudgetTokens` | `4096` | 门禁调用输出预算 |
|
|
@@ -95,7 +97,7 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
95
97
|
| `logToFile` / `logLevel` / `logMaxBytes` | `true` / `1` / 5 MiB | 插件自带 JSONL 文件日志带轮转 |
|
|
96
98
|
| `autoRollbackOnReject` | `true` | benchmark 拒绝后自动确定性回滚 |
|
|
97
99
|
| `autoCase` | `true` | 失败的进化尝试自动沉淀为 draft 回归用例(`auto_regression` 基准) |
|
|
98
|
-
| `reviewModel` | agent 自身 | 门禁可选更便宜的模型(`"provider/model"`) |
|
|
100
|
+
| `reviewModel` | agent 自身 | 专用 memory Agent 与 review 门禁可选更便宜的模型(`"provider/model"`) |
|
|
99
101
|
| `plannerPrefixCache` | `auto` | 有缓存证据时用会话前缀输入(`session` 总是前缀,`off` 保持旧扁平文本) |
|
|
100
102
|
| `plannerPrefixMaxChars` | `12000` | Route A 会话前缀预算(字符) |
|
|
101
103
|
| `historyRetain` | `{snapshots: 20, refinements: 500, reviews: 500, tokenUsage: 500}` | 存储卫生:每 store 快照数、每 store 历史尾行、共享 `reviews.jsonl` 尾行、直属调用 `token-usage.jsonl` 尾行 |
|
|
@@ -115,7 +117,7 @@ profile patch 示例:
|
|
|
115
117
|
|
|
116
118
|
```bash
|
|
117
119
|
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
118
|
-
pnpm test # vitest(
|
|
120
|
+
pnpm test # vitest(737 例)
|
|
119
121
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
120
122
|
pnpm lint # oxlint src test
|
|
121
123
|
```
|
|
@@ -123,8 +125,8 @@ pnpm lint # oxlint src test
|
|
|
123
125
|
目录结构:
|
|
124
126
|
|
|
125
127
|
```
|
|
126
|
-
├── src/ #
|
|
127
|
-
├── test/ # vitest 测试套件(
|
|
128
|
+
├── src/ # 引擎、工具、命令、memory Agent、门禁、fate、benchmark、注入 + token 用量…
|
|
129
|
+
├── test/ # vitest 测试套件(44 个文件)
|
|
128
130
|
├── lib/ # 构建产物(tsc)
|
|
129
131
|
├── docs/
|
|
130
132
|
│ ├── design.md # 完整设计文档(硬化矩阵)
|
package/lib/approval.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Human approval gate for
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Human approval gate for persistent-scope evolution edits. Project and
|
|
3
|
+
* global edits require an explicit human "批准" before they are applied;
|
|
4
|
+
* rollbacks (which restore prior recorded state) do not. The engine itself
|
|
5
|
+
* stays a pure library — this is a policy at the boundary.
|
|
6
6
|
*/
|
|
7
7
|
import type { Context } from "@deepseek-ai/cordis";
|
|
8
8
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
9
|
+
export type ScopeApprovalDecision = "approved" | "declined";
|
|
9
10
|
export interface QuestionService {
|
|
10
11
|
ask(request: {
|
|
11
12
|
questions: {
|
|
@@ -31,6 +32,12 @@ export interface QuestionService {
|
|
|
31
32
|
* whether that is an error or a fallback.
|
|
32
33
|
*/
|
|
33
34
|
export declare function questionServiceOf(ctx: Context): QuestionService | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Ask once for a persistent-scope edit. Only a unique, explicit approval
|
|
37
|
+
* choice is accepted. A missing/ambiguous/unknown answer throws so callers
|
|
38
|
+
* never interpret a lost or malformed dialog as a durable rejection.
|
|
39
|
+
*/
|
|
40
|
+
export declare function requestScopeApproval(ctx: Context, agent: Agent | undefined, signal: AbortSignal | undefined, scope: "project" | "global", what: string): Promise<ScopeApprovalDecision>;
|
|
34
41
|
/**
|
|
35
42
|
* Ask the user to approve a global edit. Throws when the service is missing,
|
|
36
43
|
* the user declines, or the question cannot be answered.
|
package/lib/approval.js
CHANGED
|
@@ -7,28 +7,44 @@ export function questionServiceOf(ctx) {
|
|
|
7
7
|
return ctx.userQuestions;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Ask
|
|
11
|
-
*
|
|
10
|
+
* Ask once for a persistent-scope edit. Only a unique, explicit approval
|
|
11
|
+
* choice is accepted. A missing/ambiguous/unknown answer throws so callers
|
|
12
|
+
* never interpret a lost or malformed dialog as a durable rejection.
|
|
12
13
|
*/
|
|
13
|
-
export async function
|
|
14
|
+
export async function requestScopeApproval(ctx, agent, signal, scope, what) {
|
|
14
15
|
const userQuestions = questionServiceOf(ctx);
|
|
15
16
|
if (!userQuestions) {
|
|
16
17
|
throw new Error("global evolution edits require the userQuestions service (load @deepseek-ai/dsh-user-questions)");
|
|
17
18
|
}
|
|
19
|
+
const storeLabel = scope === "project" ? "本项目跨会话 store" : "跨会话全局 store";
|
|
18
20
|
const answer = await userQuestions.ask({
|
|
19
21
|
questions: [
|
|
20
22
|
{
|
|
21
23
|
id: "approve-global-evolve",
|
|
22
|
-
question:
|
|
24
|
+
question: `批准写入${storeLabel}?\n\n${what}`,
|
|
23
25
|
options: [{ label: "批准" }, { label: "拒绝" }],
|
|
24
26
|
},
|
|
25
27
|
],
|
|
26
28
|
...(agent ? { agent } : {}),
|
|
27
29
|
...(signal ? { signal } : {}),
|
|
28
30
|
});
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const matching = answer.answers?.filter((entry) => entry.id === "approve-global-evolve") ?? [];
|
|
32
|
+
if (matching.length !== 1 || !Array.isArray(matching[0]?.selected)) {
|
|
33
|
+
throw new Error("evolution approval returned no unique explicit decision");
|
|
34
|
+
}
|
|
35
|
+
const selected = [...new Set(matching[0]?.selected ?? [])];
|
|
36
|
+
const known = selected.filter((label) => label === "批准" || label === "拒绝");
|
|
37
|
+
if (known.length !== 1 || selected.length !== 1) {
|
|
38
|
+
throw new Error(`evolution approval returned an invalid decision: ${JSON.stringify(selected)}`);
|
|
39
|
+
}
|
|
40
|
+
return known[0] === "批准" ? "approved" : "declined";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Ask the user to approve a global edit. Throws when the service is missing,
|
|
44
|
+
* the user declines, or the question cannot be answered.
|
|
45
|
+
*/
|
|
46
|
+
export async function requireGlobalApproval(ctx, agent, signal, what) {
|
|
47
|
+
if ((await requestScopeApproval(ctx, agent, signal, "global", what)) !== "approved") {
|
|
32
48
|
throw new Error("global evolution edit rejected by the user");
|
|
33
49
|
}
|
|
34
50
|
}
|
package/lib/auto.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { ScopeApprovalDecision } from "./approval.js";
|
|
1
2
|
import type { Context } from "@deepseek-ai/cordis";
|
|
2
3
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
3
4
|
import type { HarnessState, RefinementEdit, RefinementProposal } from "./types.js";
|
|
4
5
|
import type { EvolutionEngine } from "./service.js";
|
|
5
6
|
import { type AutoRefineReason } from "./review.js";
|
|
7
|
+
import { type TurnSnapshot } from "./turn-snapshot.js";
|
|
6
8
|
import type { PromotionPolicy } from "./promotion.js";
|
|
7
9
|
import type { PlannerPrefixCacheMode } from "./prefix-cache.js";
|
|
8
10
|
export interface AutoReviewConfig {
|
|
@@ -32,6 +34,8 @@ export interface AutoReviewConfig {
|
|
|
32
34
|
* When absent, the review gate uses the agent's own provider/model.
|
|
33
35
|
*/
|
|
34
36
|
reviewModel?: string;
|
|
37
|
+
/** Dedicated memory-agent writes to project/global require the normal human approval boundary. */
|
|
38
|
+
requireGlobalApproval?: boolean;
|
|
35
39
|
/**
|
|
36
40
|
* Prefix-cache routing for the gate input (see reviewAutoRefine).
|
|
37
41
|
* Absent mode → auto-detect; absent budget → the default prefix budget.
|
|
@@ -74,6 +78,10 @@ export interface GateState {
|
|
|
74
78
|
completedTurn: number;
|
|
75
79
|
/** Latest turn whose snapshot acquisition was reserved. */
|
|
76
80
|
lastSnapshotTurn: number;
|
|
81
|
+
/** Latest successful/no-op memory boundary, independent of later review/fate completion. */
|
|
82
|
+
memoryCheckpoint?: string;
|
|
83
|
+
/** Per-boundary/scope approval decisions retained until the memory phase settles. */
|
|
84
|
+
memoryDecisions: Record<string, ScopeApprovalDecision>;
|
|
77
85
|
lastReviewAt: number;
|
|
78
86
|
running: boolean;
|
|
79
87
|
/**
|
|
@@ -145,6 +153,22 @@ export declare function loadGateHarnessView(engine: EvolutionEngine, sessionId:
|
|
|
145
153
|
* it from runGate.
|
|
146
154
|
*/
|
|
147
155
|
export declare function runGoalBlockedFate(ctx: Context, engine: EvolutionEngine, agent: Agent, config: AutoReviewConfig, state: GateState, _reason: AutoRefineReason, record: (entry: Omit<ReviewRecord, "timestamp">) => void): Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* Dedicated ZCode-style memory phase. It shares the scheduler snapshot and
|
|
158
|
+
* snapshot with the general review, but owns its request loop, manifest and
|
|
159
|
+
* memory-only tool policy. Its phase-specific checkpoint advances after a
|
|
160
|
+
* successful/no-op memory outcome; later review/fate failures do not replay
|
|
161
|
+
* old memory decisions, while memory failures leave the boundary retryable.
|
|
162
|
+
*/
|
|
163
|
+
export declare function runMemoryExtractionPhase(ctx: Context, engine: EvolutionEngine, agent: Agent, config: AutoReviewConfig, state: GateState, snapshot: TurnSnapshot, signal?: AbortSignal): Promise<void>;
|
|
164
|
+
/** True when the general planner returned only edits owned by the memory phase. */
|
|
165
|
+
export declare function isDedicatedMemoryOnlyProposal(proposal: RefinementProposal): boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Remove memory edits from the general auto-review planner. The dedicated
|
|
168
|
+
* memory phase owns this responsibility and has already run against the same
|
|
169
|
+
* snapshot; accepting a second memory proposal would reintroduce duplicates.
|
|
170
|
+
*/
|
|
171
|
+
export declare function stripDedicatedMemoryEdits(proposal: RefinementProposal): RefinementProposal;
|
|
148
172
|
/**
|
|
149
173
|
* Split a proposal into skill edits and everything else. Skill edits are the
|
|
150
174
|
* governed part: they need explicit user consent before the gate applies
|
package/lib/auto.js
CHANGED
|
@@ -30,14 +30,18 @@ import { questionServiceOf } from "./approval.js";
|
|
|
30
30
|
import { buildEvolveCompleteEvent, emitEvolveComplete } from "./evolve-event.js";
|
|
31
31
|
import { DEFAULT_REVIEWS_RETAIN, pruneJsonlFile } from "./store.js";
|
|
32
32
|
import { isGateEnabled } from "./runtime.js";
|
|
33
|
-
import { createReviewScheduler } from "./review-scheduler.js";
|
|
34
|
-
import { captureTurnSnapshot } from "./turn-snapshot.js";
|
|
33
|
+
import { compareReviewCursors, createReviewScheduler } from "./review-scheduler.js";
|
|
34
|
+
import { captureTurnSnapshot, sliceTurnSnapshot } from "./turn-snapshot.js";
|
|
35
35
|
import { captureAutoCase } from "./autocase.js";
|
|
36
|
+
import { applyMemoryExtractionProposal, buildMemoryManifest, runMemoryAgent, } from "./memory-agent.js";
|
|
37
|
+
import { asLlmSessionId } from "./llm-text.js";
|
|
36
38
|
/** Turns a rejected skill candidate stays silent before being offered again. */
|
|
37
39
|
export const SKILL_CONSULT_COOLDOWN_TURNS = 10;
|
|
38
40
|
export function registerAutoReview(ctx, engine, config) {
|
|
39
41
|
const perSession = new Map();
|
|
40
42
|
const schedulers = new Map();
|
|
43
|
+
const captureTails = new Map();
|
|
44
|
+
const disposedSessions = new Set();
|
|
41
45
|
const logger = ctx.logger("continual-evolve");
|
|
42
46
|
const reviewsPath = join(engine.baseDir, "evolve", "reviews.jsonl");
|
|
43
47
|
const defaultEnabled = config.enabledByDefault ?? true;
|
|
@@ -96,7 +100,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
96
100
|
return scheduler;
|
|
97
101
|
};
|
|
98
102
|
const captureAndSchedule = (agent, state, reason) => {
|
|
99
|
-
if (!isGateEnabled(engine.baseDir, defaultEnabled))
|
|
103
|
+
if (disposedSessions.has(agent.id) || !isGateEnabled(engine.baseDir, defaultEnabled))
|
|
100
104
|
return;
|
|
101
105
|
const turn = state.completedTurn;
|
|
102
106
|
const previousSnapshotTurn = state.lastSnapshotTurn;
|
|
@@ -104,15 +108,20 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
104
108
|
return;
|
|
105
109
|
if (reason === "turn_snapshot")
|
|
106
110
|
state.lastSnapshotTurn = turn;
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
const previousCapture = captureTails.get(agent.id) ?? Promise.resolve();
|
|
112
|
+
const captureTask = previousCapture.then(async () => {
|
|
113
|
+
if (disposedSessions.has(agent.id) || !isGateEnabled(engine.baseDir, defaultEnabled))
|
|
114
|
+
return;
|
|
115
|
+
const cursor = schedulerFor(agent, state).getCursor();
|
|
116
|
+
const snapshot = await captureTurnSnapshot(ctx, agent, {
|
|
117
|
+
turn,
|
|
118
|
+
reason,
|
|
119
|
+
...(cursor !== undefined ? { cursor } : {}),
|
|
120
|
+
maxChars: config.maxInputChars,
|
|
121
|
+
});
|
|
122
|
+
if (!disposedSessions.has(agent.id))
|
|
123
|
+
schedulerFor(agent, state).schedule(snapshot);
|
|
124
|
+
}).catch((cause) => {
|
|
116
125
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
117
126
|
logger.warn(`auto-review snapshot failed for ${agent.id}: ${message}`);
|
|
118
127
|
record({
|
|
@@ -126,6 +135,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
126
135
|
state.lastSnapshotTurn = previousSnapshotTurn;
|
|
127
136
|
}
|
|
128
137
|
});
|
|
138
|
+
captureTails.set(agent.id, captureTask);
|
|
129
139
|
};
|
|
130
140
|
// DSH emits this awaited boundary when a turn is about to close. The idle
|
|
131
141
|
// transition is the safe point at which to read the durable surface.
|
|
@@ -175,8 +185,10 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
175
185
|
const agent = payload.agent;
|
|
176
186
|
if (!agent)
|
|
177
187
|
return;
|
|
188
|
+
disposedSessions.add(agent.id);
|
|
178
189
|
schedulers.get(agent.id)?.shutdown();
|
|
179
190
|
schedulers.delete(agent.id);
|
|
191
|
+
captureTails.delete(agent.id);
|
|
180
192
|
perSession.delete(agent.id);
|
|
181
193
|
});
|
|
182
194
|
ctx.on("session/event", (session, event) => {
|
|
@@ -213,6 +225,7 @@ function stateFor(map, sessionId) {
|
|
|
213
225
|
turns: 0,
|
|
214
226
|
completedTurn: 0,
|
|
215
227
|
lastSnapshotTurn: 0,
|
|
228
|
+
memoryDecisions: {},
|
|
216
229
|
lastReviewAt: 0,
|
|
217
230
|
running: false,
|
|
218
231
|
skillRejects: new Map(),
|
|
@@ -224,6 +237,21 @@ function stateFor(map, sessionId) {
|
|
|
224
237
|
}
|
|
225
238
|
return state;
|
|
226
239
|
}
|
|
240
|
+
function advanceMemoryCheckpoint(state, next) {
|
|
241
|
+
if (state.memoryCheckpoint === undefined) {
|
|
242
|
+
state.memoryCheckpoint = next;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const order = compareReviewCursors(next, state.memoryCheckpoint);
|
|
246
|
+
if (order === undefined || order > 0)
|
|
247
|
+
state.memoryCheckpoint = next;
|
|
248
|
+
}
|
|
249
|
+
function rememberMemoryDecision(state, key, decision) {
|
|
250
|
+
state.memoryDecisions[key] = decision;
|
|
251
|
+
const keys = Object.keys(state.memoryDecisions);
|
|
252
|
+
while (keys.length > 128)
|
|
253
|
+
delete state.memoryDecisions[keys.shift() ?? ""];
|
|
254
|
+
}
|
|
227
255
|
/**
|
|
228
256
|
* The state view the gate and planner judge: the session's local entries
|
|
229
257
|
* merged with the project and global stores, each entry carrying its real
|
|
@@ -255,6 +283,7 @@ async function runGate(ctx, engine, agent, config, state, snapshot, record, sign
|
|
|
255
283
|
}
|
|
256
284
|
state.running = true;
|
|
257
285
|
try {
|
|
286
|
+
await runMemoryExtractionPhase(ctx, engine, agent, config, state, snapshot, signal);
|
|
258
287
|
await runReviewPhase(ctx, engine, agent, config, state, snapshot, record, signal);
|
|
259
288
|
// D3: a goal stuck in "blocked" for consecutive gate runs gets one
|
|
260
289
|
// local-fate assessment (the pipeline below), so whatever led the
|
|
@@ -295,6 +324,85 @@ export async function runGoalBlockedFate(ctx, engine, agent, config, state, _rea
|
|
|
295
324
|
logger.info(`auto-review goal-blocked trigger [${agent.id}]: ${config.goalBlockedWrapupTurns} consecutive blocked gate runs → local-fate assessment`);
|
|
296
325
|
await runLocalFatePhase(ctx, engine, agent, config, state, "goal_blocked", record);
|
|
297
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Dedicated ZCode-style memory phase. It shares the scheduler snapshot and
|
|
329
|
+
* snapshot with the general review, but owns its request loop, manifest and
|
|
330
|
+
* memory-only tool policy. Its phase-specific checkpoint advances after a
|
|
331
|
+
* successful/no-op memory outcome; later review/fate failures do not replay
|
|
332
|
+
* old memory decisions, while memory failures leave the boundary retryable.
|
|
333
|
+
*/
|
|
334
|
+
export async function runMemoryExtractionPhase(ctx, engine, agent, config, state, snapshot, signal) {
|
|
335
|
+
const memorySnapshot = state.memoryCheckpoint ? sliceTurnSnapshot(snapshot, state.memoryCheckpoint) : snapshot;
|
|
336
|
+
if (!memorySnapshot.eligible || !memorySnapshot.trajectory) {
|
|
337
|
+
advanceMemoryCheckpoint(state, snapshot.cursor);
|
|
338
|
+
ctx.logger("continual-evolve").info(`memory agent no-op (${snapshot.reason}) [${agent.id}]: no new eligible evidence after checkpoint`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const sessionId = agent.id;
|
|
342
|
+
const projectKey = snapshot.projectKey ?? projectKeyOf(agent);
|
|
343
|
+
const baselines = {
|
|
344
|
+
local: engine.load("local", sessionId),
|
|
345
|
+
global: engine.load("global", undefined),
|
|
346
|
+
...(projectKey ? { project: engine.load("project", projectKey) } : {}),
|
|
347
|
+
};
|
|
348
|
+
const manifest = buildMemoryManifest(loadGateHarnessView(engine, sessionId, projectKey ? { projectKey } : undefined));
|
|
349
|
+
const overrideRoute = parseReviewModel(config.reviewModel, agent.options.provider);
|
|
350
|
+
const provider = overrideRoute?.provider ?? agent.options.provider;
|
|
351
|
+
const model = overrideRoute?.model ?? agent.options.model;
|
|
352
|
+
if (!provider || !model)
|
|
353
|
+
throw new Error("evolve: no provider/model route for the memory extraction agent");
|
|
354
|
+
const tokenUsage = {
|
|
355
|
+
baseDir: engine.baseDir,
|
|
356
|
+
sessionId,
|
|
357
|
+
retain: engine.retention.tokenUsage,
|
|
358
|
+
onError: (cause) => ctx
|
|
359
|
+
.logger("continual-evolve")
|
|
360
|
+
.warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
361
|
+
};
|
|
362
|
+
const run = await runMemoryAgent(ctx, {
|
|
363
|
+
provider,
|
|
364
|
+
model,
|
|
365
|
+
sessionId: asLlmSessionId(sessionId),
|
|
366
|
+
manifest,
|
|
367
|
+
trajectory: memorySnapshot.trajectory,
|
|
368
|
+
trajectoryEvents: memorySnapshot.events,
|
|
369
|
+
maxOutputTokens: config.budgetTokens,
|
|
370
|
+
...(signal ? { signal } : {}),
|
|
371
|
+
tokenUsage,
|
|
372
|
+
...((config.prefixCacheMode !== undefined || config.prefixMaxChars !== undefined
|
|
373
|
+
? {
|
|
374
|
+
prefixCache: {
|
|
375
|
+
...(config.prefixCacheMode !== undefined ? { mode: config.prefixCacheMode } : {}),
|
|
376
|
+
...(config.prefixMaxChars !== undefined ? { maxChars: config.prefixMaxChars } : {}),
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
: {})),
|
|
380
|
+
});
|
|
381
|
+
if (run.proposal.edits.length === 0) {
|
|
382
|
+
advanceMemoryCheckpoint(state, memorySnapshot.cursor);
|
|
383
|
+
ctx.logger("continual-evolve").info(`memory agent no-op (${snapshot.reason}) [${sessionId}] after ${run.turns} turn(s): ${run.proposal.rationale}`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const source = { sessionId, ...(memorySnapshot.sourceSeqs.length > 0 ? { seqs: [...memorySnapshot.sourceSeqs] } : {}) };
|
|
387
|
+
const application = await applyMemoryExtractionProposal(ctx, engine, run.proposal, {
|
|
388
|
+
agent,
|
|
389
|
+
baselines,
|
|
390
|
+
...(projectKey ? { projectKey } : {}),
|
|
391
|
+
requireApproval: config.requireGlobalApproval ?? true,
|
|
392
|
+
source,
|
|
393
|
+
decisionCursor: memorySnapshot.cursor,
|
|
394
|
+
scopeDecisions: state.memoryDecisions,
|
|
395
|
+
onScopeDecision: (key, decision) => rememberMemoryDecision(state, key, decision),
|
|
396
|
+
...(signal ? { signal } : {}),
|
|
397
|
+
});
|
|
398
|
+
for (const result of application.results) {
|
|
399
|
+
emitEvolveComplete(engine.baseDir, buildEvolveCompleteEvent(result, `memory_agent:${snapshot.reason}`, sessionId), config.reviewsRetain ?? DEFAULT_REVIEWS_RETAIN);
|
|
400
|
+
}
|
|
401
|
+
state.memoryCheckpoint = memorySnapshot.cursor;
|
|
402
|
+
const applied = application.results.reduce((count, result) => count + result.appliedEdits.filter((edit) => edit.applied).length, 0);
|
|
403
|
+
const declined = application.declinedScopes.length > 0 ? `; declined scopes=${application.declinedScopes.join(",")}` : "";
|
|
404
|
+
ctx.logger("continual-evolve").info(`memory agent applied ${applied} edit(s) across ${application.results.length} scope batch(es) [${sessionId}] after ${run.turns} turn(s)${declined}: ${run.proposal.rationale}`);
|
|
405
|
+
}
|
|
298
406
|
async function runReviewPhase(ctx, engine, agent, config, state, snapshot, record, signal) {
|
|
299
407
|
const sessionId = agent.id;
|
|
300
408
|
const reason = snapshot.reason;
|
|
@@ -345,7 +453,7 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
345
453
|
record({ sessionId, reason, turnsSinceLastReview, outcome: "declined", rationale: review.rationale });
|
|
346
454
|
return;
|
|
347
455
|
}
|
|
348
|
-
const
|
|
456
|
+
const plannedProposal = await planWithLlm(ctx, {
|
|
349
457
|
agent,
|
|
350
458
|
state: harnessState,
|
|
351
459
|
history,
|
|
@@ -367,6 +475,8 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
367
475
|
}
|
|
368
476
|
: {})),
|
|
369
477
|
});
|
|
478
|
+
const proposal = stripDedicatedMemoryEdits(plannedProposal);
|
|
479
|
+
const memoryOnlyPlan = isDedicatedMemoryOnlyProposal(plannedProposal);
|
|
370
480
|
// Skills are governed resources: an auto-created skill is OFFERED to the
|
|
371
481
|
// user for a decision (固化/不固化) before it lands — the gate never
|
|
372
482
|
// writes a skill silently. Without consent the skill edits are withheld
|
|
@@ -385,7 +495,7 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
385
495
|
logger.info(`auto-review declined (${reason}) [${sessionId}]: no consented edits${withheld} — ${review.rationale}`);
|
|
386
496
|
// P1 auto-case capture: an attempted evolution that never landed is a
|
|
387
497
|
// regression asset. Contained — capture failure must not disturb the gate.
|
|
388
|
-
if (config.autoCase) {
|
|
498
|
+
if (config.autoCase && !memoryOnlyPlan) {
|
|
389
499
|
try {
|
|
390
500
|
const captured = captureAutoCase({
|
|
391
501
|
baseDir: engine.baseDir,
|
|
@@ -422,6 +532,25 @@ async function runReviewPhase(ctx, engine, agent, config, state, snapshot, recor
|
|
|
422
532
|
notifyAutoReview(ctx, agent, result, turnsSinceLastReview);
|
|
423
533
|
}
|
|
424
534
|
}
|
|
535
|
+
/** True when the general planner returned only edits owned by the memory phase. */
|
|
536
|
+
export function isDedicatedMemoryOnlyProposal(proposal) {
|
|
537
|
+
return proposal.edits.length > 0 && proposal.edits.every((edit) => edit.kind === "memory");
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Remove memory edits from the general auto-review planner. The dedicated
|
|
541
|
+
* memory phase owns this responsibility and has already run against the same
|
|
542
|
+
* snapshot; accepting a second memory proposal would reintroduce duplicates.
|
|
543
|
+
*/
|
|
544
|
+
export function stripDedicatedMemoryEdits(proposal) {
|
|
545
|
+
const edits = proposal.edits.filter((edit) => edit.kind !== "memory");
|
|
546
|
+
if (edits.length === proposal.edits.length)
|
|
547
|
+
return proposal;
|
|
548
|
+
return {
|
|
549
|
+
...proposal,
|
|
550
|
+
edits,
|
|
551
|
+
summary: `${proposal.summary} (memory edits owned by dedicated extractor)`,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
425
554
|
/**
|
|
426
555
|
* Split a proposal into skill edits and everything else. Skill edits are the
|
|
427
556
|
* governed part: they need explicit user consent before the gate applies
|
package/lib/index.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Mounts the evolution engine, registers the model-facing evolve_* tools,
|
|
5
5
|
* the human-facing /evolve command, the system-prompt guidance section, and
|
|
6
|
-
* the automatic review
|
|
7
|
-
* effective state is controlled by the runtime switch. Successful
|
|
8
|
-
*
|
|
6
|
+
* the automatic memory+review pipeline. The listener is always registered,
|
|
7
|
+
* while its effective state is controlled by the runtime switch. Successful
|
|
8
|
+
* turns feed a dedicated memory agent and general review through one
|
|
9
|
+
* per-session serial scheduler.
|
|
9
10
|
*/
|
|
10
11
|
import { join } from "node:path";
|
|
11
12
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -194,11 +195,12 @@ export function apply(ctx, config) {
|
|
|
194
195
|
rubricKey,
|
|
195
196
|
...(config.historyRetain?.reviews !== undefined ? { reviewsRetain: config.historyRetain.reviews } : {}),
|
|
196
197
|
...(config.reviewModel ? { reviewModel: config.reviewModel } : {}),
|
|
198
|
+
requireGlobalApproval: config.requireGlobalApproval ?? true,
|
|
197
199
|
...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
|
|
198
200
|
...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
|
|
199
201
|
});
|
|
200
202
|
const runtimeDefault = config.autoReview ?? false;
|
|
201
|
-
ctx.logger("continual-evolve").info(`continual-evolve auto-review registered (default ${runtimeDefault ? "on" : "off"}; successful-turn
|
|
203
|
+
ctx.logger("continual-evolve").info(`continual-evolve auto-review registered (default ${runtimeDefault ? "on" : "off"}; successful-turn memory agent + general review; local-fate ${config.localFate ?? true ? "on" : "off"} every ${config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6} turns)`);
|
|
202
204
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
|
203
205
|
}
|
|
204
206
|
//# sourceMappingURL=index.js.map
|