dsh-continual-evolve 0.7.4 → 0.7.5
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 +18 -12
- package/README.zh.md +8 -7
- package/lib/auto.d.ts +7 -0
- package/lib/auto.js +19 -7
- package/lib/command.d.ts +3 -4
- package/lib/command.js +24 -12
- package/lib/index.d.ts +12 -4
- package/lib/index.js +46 -31
- package/lib/turn-snapshot.d.ts +5 -1
- package/lib/turn-snapshot.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,14 +19,14 @@ 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**:
|
|
22
|
+
- **Dedicated background memory agent**: when `autoReview: true`, eligible successful turns feed a bounded ZCode-style loop that 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. The generic review/planner/fate path is not run by this listener
|
|
23
23
|
- **Deterministic rollback**: inverse edits generated from applied results — no LLM re-guessing
|
|
24
24
|
- **Benchmark loop**: candidate refinements are evaluated against frozen cases by a separate scorer before acceptance (rubric encrypted at rest)
|
|
25
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
|
|
26
26
|
|
|
27
27
|
## How it works
|
|
28
28
|
|
|
29
|
-
1. **Sediment** — the model creates entries via `evolve_add`, or the automatic
|
|
29
|
+
1. **Sediment** — the model creates entries via `evolve_add`, or (only when `autoReview: true`) the automatic Memory Agent consumes incremental snapshots after successful turns (plus compaction checkpoints). Generic review/planner remains manual unless separately invoked.
|
|
30
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
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
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.
|
|
@@ -79,13 +79,14 @@ Injection shape: prompt notes and delegation specs inject with content (≤6/kin
|
|
|
79
79
|
| Key | Default | Meaning |
|
|
80
80
|
|---|---|---|
|
|
81
81
|
| `baseDir` | resolved DSH home | root for the `evolve/` stores |
|
|
82
|
-
| `autoReview` | `false` |
|
|
82
|
+
| `autoReview` | `false` | explicit opt-in for the dedicated Memory Agent listener; when true, generic review/planner/fate are not run |
|
|
83
|
+
| `memoryMinUserWords` | `3` | ZCode-style minimum lexical words in one direct user text part; uses CJK-aware segmentation |
|
|
83
84
|
| `reviewIntervalTurns` | `6` | legacy local-fate cadence fallback; successful-turn review no longer waits for this interval |
|
|
84
85
|
| `maxReviewInputChars` | `40000` | trajectory slice handed to the gate |
|
|
85
86
|
| `reviewBudgetTokens` | `4096` | output budget for the gate call |
|
|
86
87
|
| `notifyOnAutoReview` | `true` | visible follow-up notice after an applied gate run |
|
|
87
88
|
| `requireGlobalApproval` | `true` | global and project edits ask for explicit approval |
|
|
88
|
-
| `localFate` | `
|
|
89
|
+
| `localFate` | `false` | optional local-entry promote/archive fate assessment; requires `autoReview: true` and an explicit `localFate: true` |
|
|
89
90
|
| `fateIntervalTurns` | follows `reviewIntervalTurns` | minimum turns between fate assessments |
|
|
90
91
|
| `goalBlockedWrapupTurns` | `3` | consecutive blocked-goal gate runs trigger one fate assessment (`0` disables) |
|
|
91
92
|
| `promotionBlockPatterns` | POSIX paths, session ids, `~/.dsh` | content matching these is project-scoped and never promoted to global |
|
|
@@ -111,18 +112,23 @@ Example profile patch:
|
|
|
111
112
|
reviewIntervalTurns: 6
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
Automatic evolution is an explicit source-level opt-in. With the default
|
|
116
|
+
`autoReview: false`, the plugin does not register turn/compaction listeners.
|
|
117
|
+
With `autoReview: true`, it registers only the dedicated Memory Agent listener;
|
|
118
|
+
the generic review/planner, prompt/skill writes, and local-fate phases are not
|
|
119
|
+
run automatically. The memory trigger follows ZCode's lightweight eligibility:
|
|
120
|
+
direct user text must contain at least `memoryMinUserWords` lexical words
|
|
121
|
+
(CJK-aware segmentation), while empty/internal/direct-memory-write snapshots
|
|
122
|
+
are skipped; compaction does not add a separate memory-only trigger. Manual
|
|
123
|
+
`evolve_*` tools and `/evolve` commands remain available. `/evolve pause`,
|
|
124
|
+
`/evolve resume`, and `/evolve status` control or report the Memory Agent
|
|
125
|
+
listener.
|
|
120
126
|
|
|
121
127
|
## Development
|
|
122
128
|
|
|
123
129
|
```bash
|
|
124
130
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
125
|
-
pnpm test # vitest (
|
|
131
|
+
pnpm test # vitest (743 tests)
|
|
126
132
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
127
133
|
pnpm lint # oxlint src test
|
|
128
134
|
```
|
|
@@ -131,7 +137,7 @@ Project layout:
|
|
|
131
137
|
|
|
132
138
|
```
|
|
133
139
|
├── src/ # engine, tools, commands, memory agent, gate, fate, benchmark, injection + token usage…
|
|
134
|
-
├── test/ # vitest suites (
|
|
140
|
+
├── test/ # vitest suites (45 files)
|
|
135
141
|
├── lib/ # build output (tsc)
|
|
136
142
|
├── docs/
|
|
137
143
|
│ ├── design.md # full design doc (hardening matrix)
|
package/README.zh.md
CHANGED
|
@@ -19,14 +19,14 @@ 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
|
|
22
|
+
- **专用后台记忆 Agent**:`autoReview: true` 时,成功回合通过机械 eligibility 后才有界 ZCode 式 loop 检索冻结的 memory manifest,并经闭集工具提出纯 memory 编辑;不能调用 Agent、MCP、网络,也不能写源码。该 listener 不运行通用 review/planner/fate
|
|
23
23
|
- **确定性回滚**:逆操作编辑由已应用结果生成——不靠 LLM 重新猜测
|
|
24
24
|
- **benchmark 闭环**:候选沉淀先经冻结用例 + 独立评分者评估再接受(rubric 加密落盘)
|
|
25
25
|
- **store 卫生**:`/evolve consolidate` 把写入时冲突提示与零使用陈旧条目变成一次批准、完全可逆的批量归档——加 `merge` 可将近重复内容并入幸存原条目
|
|
26
26
|
|
|
27
27
|
## 工作原理
|
|
28
28
|
|
|
29
|
-
1. **沉淀**——模型经 `evolve_add`
|
|
29
|
+
1. **沉淀**——模型经 `evolve_add` 创建条目,或仅在 `autoReview: true` 时由专用 Memory Agent 消费成功回合后的增量 snapshot(以及压缩检查点)。通用 review/planner 保持手动,除非另行显式调用。
|
|
30
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
31
|
3. **守卫**——代码强制校验:编辑 schema、blast-radius 与作用域一致性、晋升政策(项目专属标记 / 过薄内容 / 近似重复检测 / 凭据筛查保持全局库干净——密钥类内容在所有写入出口被拒,含 mount 物化)。全局 create 与既有条目高度相似(≥0.8)时写入即拒;中等重叠带 `conflictHint` 供后续合并。
|
|
32
32
|
4. **审批**——全局与项目写入需明确人工批准;弹窗展示有界结构化编辑 diff 与冲突提示,弹窗丢失/响应畸形会重试,不会被误记为拒绝。
|
|
@@ -79,13 +79,14 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
|
79
79
|
| 键 | 默认 | 含义 |
|
|
80
80
|
|---|---|---|
|
|
81
81
|
| `baseDir` | 解析后的 DSH home | `evolve/` 存储根目录 |
|
|
82
|
-
| `autoReview` | `false` |
|
|
82
|
+
| `autoReview` | `false` | 专用 Memory Agent listener 的显式 opt-in;为 true 时也不会自动运行通用 review/planner/fate |
|
|
83
|
+
| `memoryMinUserWords` | `3` | ZCode 风格:单个直接用户文本至少包含的词数;使用 CJK 分词 |
|
|
83
84
|
| `reviewIntervalTurns` | `6` | local-fate 的兼容节奏;成功回合 review 不再等待这个间隔 |
|
|
84
85
|
| `maxReviewInputChars` | `40000` | 交给门禁的轨迹切片 |
|
|
85
86
|
| `reviewBudgetTokens` | `4096` | 门禁调用输出预算 |
|
|
86
87
|
| `notifyOnAutoReview` | `true` | 门禁应用后发可见跟进通知 |
|
|
87
88
|
| `requireGlobalApproval` | `true` | 全局与项目写入需明确批准 |
|
|
88
|
-
| `localFate` | `
|
|
89
|
+
| `localFate` | `false` | 可选的本地条目晋升/归档 fate 评估;需要同时设置 `autoReview: true` 与显式 `localFate: true` |
|
|
89
90
|
| `fateIntervalTurns` | 跟随 `reviewIntervalTurns` | 归宿评估的最小回合间隔 |
|
|
90
91
|
| `goalBlockedWrapupTurns` | `3` | 连续阻塞目标的门禁轮数触发一次归宿评估(`0` 关闭) |
|
|
91
92
|
| `promotionBlockPatterns` | POSIX 路径、session id、`~/.dsh` | 内容命中即判定项目专属,永不晋升全局 |
|
|
@@ -111,13 +112,13 @@ profile patch 示例:
|
|
|
111
112
|
reviewIntervalTurns: 6
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
自动进化是项目源码层面的显式 opt-in。默认 `autoReview: false` 时,插件不注册回合/压缩监听器。设置 `autoReview: true` 时只注册专用 Memory Agent listener,不自动运行通用 review/planner、prompt/skill 写入或 local fate。Memory 触发采用 ZCode 的轻量 eligibility:单个直接用户文本至少包含 `memoryMinUserWords` 个词(使用 CJK 分词);空增量、内部 Agent、直接 memory 写入会跳过,压缩不会额外触发 memory-only 管线。手动 `evolve_*` 工具和 `/evolve` 命令仍可用;`/evolve pause`、`/evolve resume`、`/evolve status` 控制或报告 Memory Agent listener。
|
|
115
116
|
|
|
116
117
|
## 开发
|
|
117
118
|
|
|
118
119
|
```bash
|
|
119
120
|
pnpm install && pnpm build # 依赖 + tsc -> lib/
|
|
120
|
-
pnpm test # vitest(
|
|
121
|
+
pnpm test # vitest(743 例)
|
|
121
122
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
122
123
|
pnpm lint # oxlint src test
|
|
123
124
|
```
|
|
@@ -126,7 +127,7 @@ pnpm lint # oxlint src test
|
|
|
126
127
|
|
|
127
128
|
```
|
|
128
129
|
├── src/ # 引擎、工具、命令、memory Agent、门禁、fate、benchmark、注入 + token 用量…
|
|
129
|
-
├── test/ # vitest 测试套件(
|
|
130
|
+
├── test/ # vitest 测试套件(45 个文件)
|
|
130
131
|
├── lib/ # 构建产物(tsc)
|
|
131
132
|
├── docs/
|
|
132
133
|
│ ├── design.md # 完整设计文档(硬化矩阵)
|
package/lib/auto.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface AutoReviewConfig {
|
|
|
12
12
|
intervalTurns: number;
|
|
13
13
|
/** Initial runtime state when no runtime.json exists. */
|
|
14
14
|
enabledByDefault?: boolean;
|
|
15
|
+
/** ZCode-style minimum lexical words in one direct user text part. */
|
|
16
|
+
memoryMinUserWords?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Run only the dedicated memory extraction phase. The general review,
|
|
19
|
+
* planner, prompt/skill writes, and local-fate phases are never entered.
|
|
20
|
+
*/
|
|
21
|
+
memoryOnly?: boolean;
|
|
15
22
|
maxInputChars: number;
|
|
16
23
|
budgetTokens: number;
|
|
17
24
|
/** Queue a visible follow-up notice after an approved, applied gate run. */
|
package/lib/auto.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The automatic
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* The automatic evolution driver: watches agent turns and session compaction.
|
|
3
|
+
* In the production wiring it runs only the dedicated Memory Agent; the
|
|
4
|
+
* general review/planner and local-fate phases remain available to direct
|
|
5
|
+
* callers but are not entered by memory-only mode. All auxiliary work is
|
|
6
|
+
* fire-and-forget with error containment: an automatic failure never disturbs
|
|
7
|
+
* the agent loop.
|
|
7
8
|
*
|
|
8
9
|
* Every gate decision (approved / declined / failed / skipped) is appended to
|
|
9
10
|
* `<dshHome>/evolve/reviews.jsonl` so auto-review activity is durably
|
|
@@ -80,7 +81,12 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
80
81
|
return "no-op";
|
|
81
82
|
}
|
|
82
83
|
try {
|
|
83
|
-
|
|
84
|
+
if (config.memoryOnly) {
|
|
85
|
+
await runMemoryExtractionPhase(ctx, engine, agent, config, state, snapshot, signal);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await runGate(ctx, engine, agent, config, state, snapshot, record, signal);
|
|
89
|
+
}
|
|
84
90
|
return "success";
|
|
85
91
|
}
|
|
86
92
|
catch (cause) {
|
|
@@ -118,6 +124,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
118
124
|
reason,
|
|
119
125
|
...(cursor !== undefined ? { cursor } : {}),
|
|
120
126
|
maxChars: config.maxInputChars,
|
|
127
|
+
...(config.memoryMinUserWords !== undefined ? { minUserWords: config.memoryMinUserWords } : {}),
|
|
121
128
|
});
|
|
122
129
|
if (!disposedSessions.has(agent.id))
|
|
123
130
|
schedulerFor(agent, state).schedule(snapshot);
|
|
@@ -169,7 +176,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
169
176
|
reason: "boot",
|
|
170
177
|
turnsSinceLastReview: 0,
|
|
171
178
|
outcome: "armed",
|
|
172
|
-
rationale: `
|
|
179
|
+
rationale: `automatic evolution listener registered (mode=${config.memoryOnly ? "memory-only" : "full"}; default=${defaultEnabled ? "on" : "off"}; per-success-turn snapshots)`,
|
|
173
180
|
})}\n`, "utf8");
|
|
174
181
|
}
|
|
175
182
|
catch (cause) {
|
|
@@ -192,6 +199,11 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
192
199
|
perSession.delete(agent.id);
|
|
193
200
|
});
|
|
194
201
|
ctx.on("session/event", (session, event) => {
|
|
202
|
+
// ZCode schedules extraction from successful turns, not from compaction.
|
|
203
|
+
// Keep the legacy compaction path for direct/full callers, but do not
|
|
204
|
+
// add an extra automatic Memory Agent trigger in memory-only mode.
|
|
205
|
+
if (config.memoryOnly)
|
|
206
|
+
return;
|
|
195
207
|
if (event.type !== "compaction/start")
|
|
196
208
|
return;
|
|
197
209
|
const agents = ctx.agents;
|
package/lib/command.d.ts
CHANGED
|
@@ -17,11 +17,10 @@ export interface CommandRuntimeOptions {
|
|
|
17
17
|
autoCase: boolean;
|
|
18
18
|
/** Mechanical promotion guards for wrapup/fate (2026-08-22 policy). */
|
|
19
19
|
promotionPolicy: PromotionPolicy;
|
|
20
|
-
/**
|
|
21
|
-
* Initial auto-review default from plugin config. The listener is always
|
|
22
|
-
* registered; this is not a registration gate.
|
|
23
|
-
*/
|
|
20
|
+
/** Explicit source-level opt-in for the automatic listener. `false` means no wiring. */
|
|
24
21
|
autoReview?: boolean;
|
|
22
|
+
/** The automatic listener runs only the dedicated Memory Agent. */
|
|
23
|
+
memoryOnly?: boolean;
|
|
25
24
|
}
|
|
26
25
|
export declare function registerEvolveCommand(ctx: Context, engine: EvolutionEngine, opts: CommandGateOptions, runtime: CommandRuntimeOptions): void;
|
|
27
26
|
/**
|
package/lib/command.js
CHANGED
|
@@ -44,8 +44,8 @@ const USAGE = `Usage:
|
|
|
44
44
|
/evolve goal show the evolution goal (round-driven auto-review)
|
|
45
45
|
/evolve goal <objective> create/update the evolution goal
|
|
46
46
|
/evolve goal done complete the evolution goal
|
|
47
|
-
/evolve pause | resume pause/resume the
|
|
48
|
-
/evolve status
|
|
47
|
+
/evolve pause | resume pause/resume the automatic Memory Agent (manual tools and commands keep working)
|
|
48
|
+
/evolve status automatic Memory Agent state plus store entry counts
|
|
49
49
|
/evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
|
|
50
50
|
export function registerEvolveCommand(ctx, engine, opts, runtime) {
|
|
51
51
|
ctx.commands.register({
|
|
@@ -376,22 +376,26 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
376
376
|
}
|
|
377
377
|
case "pause":
|
|
378
378
|
case "resume": {
|
|
379
|
+
if (runtime.autoReview === false) {
|
|
380
|
+
return success("automatic evolution wiring is disabled by project policy; no automatic Memory Agent listener is registered. Manual evolve_* tools and /evolve commands keep working.");
|
|
381
|
+
}
|
|
382
|
+
const memoryOnly = runtime.memoryOnly !== false;
|
|
379
383
|
// The switch is independent of the static registration flag. A
|
|
380
|
-
// resume explicitly enables
|
|
381
|
-
//
|
|
384
|
+
// resume explicitly enables the Memory Agent; a pause leaves the
|
|
385
|
+
// enable bit intact so status explains the paused state.
|
|
382
386
|
const pausing = sub === "pause";
|
|
383
387
|
const defaultEnabled = runtime.autoReview ?? false;
|
|
384
388
|
const current = loadGateRuntime(engine.baseDir, defaultEnabled);
|
|
385
389
|
if (pausing && current.paused) {
|
|
386
|
-
return success("
|
|
390
|
+
return success("automatic Memory Agent is already paused (no change).");
|
|
387
391
|
}
|
|
388
392
|
if (!pausing && current.enabled && !current.paused) {
|
|
389
|
-
return success("
|
|
393
|
+
return success(memoryOnly ? "automatic Memory Agent is already running (no change)." : "automatic evolution is already running (no change).");
|
|
390
394
|
}
|
|
391
395
|
saveGateRuntime(engine.baseDir, pausing, pausing ? current.enabled : true);
|
|
392
396
|
return success(pausing
|
|
393
|
-
? "
|
|
394
|
-
: "
|
|
397
|
+
? "automatic Memory Agent paused: no automatic memory extraction calls until /evolve resume. Manual evolve_* tools and /evolve commands keep working."
|
|
398
|
+
: "automatic Memory Agent resumed: successful turns will produce incremental memory snapshots immediately.");
|
|
395
399
|
}
|
|
396
400
|
case "status": {
|
|
397
401
|
return success(renderGateStatus(engine, sessionId, projectKeyOf(invocation.agent), runtime));
|
|
@@ -458,14 +462,22 @@ function demoteEntry(engine, id, sessionId, projectKey) {
|
|
|
458
462
|
return error(`entry ${id} not found in the global, project, or local store`);
|
|
459
463
|
}
|
|
460
464
|
/**
|
|
461
|
-
* Status answers whether the
|
|
462
|
-
*
|
|
465
|
+
* Status answers whether the automatic listener is wired and what
|
|
466
|
+
* stores/retention it currently sees.
|
|
463
467
|
*/
|
|
464
468
|
function renderGateStatus(engine, sessionId, projectKey, runtime) {
|
|
465
469
|
const configuredDefault = runtime.autoReview === undefined ? "unknown" : runtime.autoReview ? "on" : "off";
|
|
466
470
|
const current = loadGateRuntime(engine.baseDir, runtime.autoReview ?? false);
|
|
467
|
-
const
|
|
468
|
-
const
|
|
471
|
+
const memoryOnly = runtime.memoryOnly !== false;
|
|
472
|
+
const effective = runtime.autoReview === false
|
|
473
|
+
? "disabled by project policy"
|
|
474
|
+
: current.paused
|
|
475
|
+
? "PAUSED (resume with /evolve resume)"
|
|
476
|
+
: current.enabled
|
|
477
|
+
? memoryOnly ? "Memory Agent running" : "running"
|
|
478
|
+
: memoryOnly ? "Memory Agent off (enable with /evolve resume)" : "off (enable with /evolve resume)";
|
|
479
|
+
const listener = runtime.autoReview === false ? "not wired" : memoryOnly ? "Memory Agent-only listener registered" : "listener registered";
|
|
480
|
+
const gateLine = `gate: ${listener} · config default ${configuredDefault} · runtime ${effective}`;
|
|
469
481
|
const countEntries = (state) => Object.values(state.entries).reduce((n, byKind) => n + Object.keys(byKind).length, 0);
|
|
470
482
|
const lines = [gateLine];
|
|
471
483
|
lines.push(`stores: global ${countEntries(engine.load("global", undefined))} entries · local(${sessionId}) ${countEntries(engine.load("local", sessionId))} entries`);
|
package/lib/index.d.ts
CHANGED
|
@@ -10,8 +10,10 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
10
10
|
baseDir: z<string, string, "plain">;
|
|
11
11
|
/** System-prompt section order for the evolution guidance. */
|
|
12
12
|
sectionOrder: z<number, number, "defined">;
|
|
13
|
-
/**
|
|
13
|
+
/** Explicit opt-in for the dedicated Memory Agent listener (off by default). */
|
|
14
14
|
autoReview: z<boolean, boolean, "defined">;
|
|
15
|
+
/** ZCode-style minimum lexical words in one direct user text part. */
|
|
16
|
+
memoryMinUserWords: z<number, number, "defined">;
|
|
15
17
|
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
16
18
|
reviewIntervalTurns: z<number, number, "defined">;
|
|
17
19
|
/** Trajectory slice handed to the gate, in characters. */
|
|
@@ -58,7 +60,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
58
60
|
/**
|
|
59
61
|
* Gate local-fate dimension (#11 P2): the gate audits the session's local
|
|
60
62
|
* entries on its own cadence and proposes promote/archive — consulted
|
|
61
|
-
* first, never written silently. Only meaningful with autoReview on
|
|
63
|
+
* first, never written silently. Only meaningful with autoReview on;
|
|
64
|
+
* disabled by default even when the automatic pipeline is explicitly enabled.
|
|
62
65
|
*/
|
|
63
66
|
localFate: z<boolean, boolean, "defined">;
|
|
64
67
|
/**
|
|
@@ -107,8 +110,10 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
107
110
|
baseDir: z<string, string, "plain">;
|
|
108
111
|
/** System-prompt section order for the evolution guidance. */
|
|
109
112
|
sectionOrder: z<number, number, "defined">;
|
|
110
|
-
/**
|
|
113
|
+
/** Explicit opt-in for the dedicated Memory Agent listener (off by default). */
|
|
111
114
|
autoReview: z<boolean, boolean, "defined">;
|
|
115
|
+
/** ZCode-style minimum lexical words in one direct user text part. */
|
|
116
|
+
memoryMinUserWords: z<number, number, "defined">;
|
|
112
117
|
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
113
118
|
reviewIntervalTurns: z<number, number, "defined">;
|
|
114
119
|
/** Trajectory slice handed to the gate, in characters. */
|
|
@@ -155,7 +160,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
155
160
|
/**
|
|
156
161
|
* Gate local-fate dimension (#11 P2): the gate audits the session's local
|
|
157
162
|
* entries on its own cadence and proposes promote/archive — consulted
|
|
158
|
-
* first, never written silently. Only meaningful with autoReview on
|
|
163
|
+
* first, never written silently. Only meaningful with autoReview on;
|
|
164
|
+
* disabled by default even when the automatic pipeline is explicitly enabled.
|
|
159
165
|
*/
|
|
160
166
|
localFate: z<boolean, boolean, "defined">;
|
|
161
167
|
/**
|
|
@@ -209,5 +215,7 @@ export interface EvolutionService {
|
|
|
209
215
|
readonly engine: EvolutionEngine;
|
|
210
216
|
readonly baseDir: string;
|
|
211
217
|
}
|
|
218
|
+
/** True only when the project explicitly opts into automatic evolution wiring. */
|
|
219
|
+
export declare function automaticEvolutionWired(config: Pick<EvolveConfig, "autoReview">): boolean;
|
|
212
220
|
export declare function apply(ctx: Context, config: EvolveConfig): void;
|
|
213
221
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.js
CHANGED
|
@@ -3,10 +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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* (only under an explicit `autoReview: true` opt-in) the dedicated Memory
|
|
7
|
+
* Agent listener. With the default `autoReview: false`, the automatic
|
|
8
|
+
* turn/compaction listeners are not registered; manual tools and commands
|
|
9
|
+
* remain available.
|
|
10
10
|
*/
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -30,8 +30,10 @@ export const Config = z.object({
|
|
|
30
30
|
baseDir: z.string(),
|
|
31
31
|
/** System-prompt section order for the evolution guidance. */
|
|
32
32
|
sectionOrder: z.natural().default(118),
|
|
33
|
-
/**
|
|
33
|
+
/** Explicit opt-in for the dedicated Memory Agent listener (off by default). */
|
|
34
34
|
autoReview: z.boolean().default(false),
|
|
35
|
+
/** ZCode-style minimum lexical words in one direct user text part. */
|
|
36
|
+
memoryMinUserWords: z.natural().default(3),
|
|
35
37
|
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
36
38
|
reviewIntervalTurns: z.natural().default(6),
|
|
37
39
|
/** Trajectory slice handed to the gate, in characters. */
|
|
@@ -78,9 +80,10 @@ export const Config = z.object({
|
|
|
78
80
|
/**
|
|
79
81
|
* Gate local-fate dimension (#11 P2): the gate audits the session's local
|
|
80
82
|
* entries on its own cadence and proposes promote/archive — consulted
|
|
81
|
-
* first, never written silently. Only meaningful with autoReview on
|
|
83
|
+
* first, never written silently. Only meaningful with autoReview on;
|
|
84
|
+
* disabled by default even when the automatic pipeline is explicitly enabled.
|
|
82
85
|
*/
|
|
83
|
-
localFate: z.boolean().default(
|
|
86
|
+
localFate: z.boolean().default(false),
|
|
84
87
|
/**
|
|
85
88
|
* Minimum turns between local-fate assessments on the turn-interval path
|
|
86
89
|
* (compaction is unconditional). Absent → follows reviewIntervalTurns.
|
|
@@ -118,6 +121,10 @@ export const Config = z.object({
|
|
|
118
121
|
tokenUsage: z.natural(),
|
|
119
122
|
}),
|
|
120
123
|
});
|
|
124
|
+
/** True only when the project explicitly opts into automatic evolution wiring. */
|
|
125
|
+
export function automaticEvolutionWired(config) {
|
|
126
|
+
return config.autoReview === true;
|
|
127
|
+
}
|
|
121
128
|
export function apply(ctx, config) {
|
|
122
129
|
const baseDir = resolveDshHome(config.baseDir);
|
|
123
130
|
const skillsRoot = config.skillsDir ? expandHomePath(config.skillsDir) : join(baseDir, "skills");
|
|
@@ -163,7 +170,8 @@ export function apply(ctx, config) {
|
|
|
163
170
|
autoRollbackOnReject: config.autoRollbackOnReject ?? true,
|
|
164
171
|
autoCase: config.autoCase ?? true,
|
|
165
172
|
promotionPolicy,
|
|
166
|
-
|
|
173
|
+
autoReview: config.autoReview ?? false,
|
|
174
|
+
memoryOnly: config.autoReview === true,
|
|
167
175
|
});
|
|
168
176
|
// Plugin-owned file logging: every cordis log message lands in
|
|
169
177
|
// <baseDir>/evolve/plugin.log regardless of how dsh web was launched —
|
|
@@ -178,29 +186,36 @@ export function apply(ctx, config) {
|
|
|
178
186
|
void restoreMounted(ctx, baseDir).catch((cause) => {
|
|
179
187
|
ctx.logger("continual-evolve").warn(`mount restore failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
180
188
|
});
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
189
|
+
// `autoReview` is an explicit opt-in for the dedicated Memory Agent only.
|
|
190
|
+
// The general review/planner, prompt/skill writes, and local-fate phases
|
|
191
|
+
// are not reachable from this listener. Manual tools and commands remain
|
|
192
|
+
// available below.
|
|
193
|
+
if (automaticEvolutionWired(config)) {
|
|
194
|
+
registerAutoReview(ctx, engine, {
|
|
195
|
+
intervalTurns: config.reviewIntervalTurns ?? 6,
|
|
196
|
+
enabledByDefault: true,
|
|
197
|
+
memoryOnly: true,
|
|
198
|
+
memoryMinUserWords: config.memoryMinUserWords ?? 3,
|
|
199
|
+
maxInputChars: config.maxReviewInputChars ?? 40000,
|
|
200
|
+
budgetTokens: config.reviewBudgetTokens ?? 4096,
|
|
201
|
+
notifyOnAutoReview: config.notifyOnAutoReview ?? true,
|
|
202
|
+
localFate: config.localFate ?? false,
|
|
203
|
+
fateIntervalTurns: config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6,
|
|
204
|
+
goalBlockedWrapupTurns: config.goalBlockedWrapupTurns ?? 3,
|
|
205
|
+
promotionPolicy,
|
|
206
|
+
autoCase: config.autoCase ?? true,
|
|
207
|
+
rubricKey,
|
|
208
|
+
...(config.historyRetain?.reviews !== undefined ? { reviewsRetain: config.historyRetain.reviews } : {}),
|
|
209
|
+
...(config.reviewModel ? { reviewModel: config.reviewModel } : {}),
|
|
210
|
+
requireGlobalApproval: config.requireGlobalApproval ?? true,
|
|
211
|
+
...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
|
|
212
|
+
...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
|
|
213
|
+
});
|
|
214
|
+
ctx.logger("continual-evolve").info("continual-evolve automatic listener registered (Memory Agent only; generic review/planner/fate disconnected)");
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
ctx.logger("continual-evolve").info("continual-evolve automatic evolution wiring disabled (autoReview is not explicitly true; manual tools and commands remain available)");
|
|
218
|
+
}
|
|
204
219
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
|
205
220
|
}
|
|
206
221
|
//# sourceMappingURL=index.js.map
|
package/lib/turn-snapshot.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface TurnSnapshot {
|
|
|
24
24
|
readonly userText: string;
|
|
25
25
|
readonly sourceSeqs: readonly number[];
|
|
26
26
|
readonly maxChars: number;
|
|
27
|
+
readonly minUserWords: number;
|
|
27
28
|
/** Cursor supplied by the shared scheduler before this snapshot selected events. */
|
|
28
29
|
readonly capturedAfterCursor?: string;
|
|
29
30
|
readonly eligible: boolean;
|
|
@@ -35,6 +36,8 @@ export interface SnapshotEligibility {
|
|
|
35
36
|
readonly reason?: SnapshotSkipReason;
|
|
36
37
|
readonly userText: string;
|
|
37
38
|
}
|
|
39
|
+
/** ZCode's minimum user-prose threshold, counted as lexical words. */
|
|
40
|
+
export declare const MEMORY_MIN_USER_WORDS = 3;
|
|
38
41
|
/**
|
|
39
42
|
* Capture the current durable surface and select only rows after `cursor`.
|
|
40
43
|
* A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
|
|
@@ -45,6 +48,7 @@ export declare function captureTurnSnapshot(ctx: Context, agent: Agent, opts: {
|
|
|
45
48
|
reason: TurnSnapshotReason;
|
|
46
49
|
cursor?: string;
|
|
47
50
|
maxChars: number;
|
|
51
|
+
minUserWords?: number;
|
|
48
52
|
}): Promise<TurnSnapshot>;
|
|
49
53
|
/**
|
|
50
54
|
* Rebase one captured snapshot onto a phase-specific cursor while preserving
|
|
@@ -58,7 +62,7 @@ export declare function sliceTurnSnapshot(snapshot: TurnSnapshot, cursor: string
|
|
|
58
62
|
* does not decide whether a fact is valuable; that remains the review model's
|
|
59
63
|
* job. It only removes mechanical noise before the scheduler spends tokens.
|
|
60
64
|
*/
|
|
61
|
-
export declare function evaluateSnapshotEligibility(events: readonly unknown[], internalAgent?: boolean): SnapshotEligibility;
|
|
65
|
+
export declare function evaluateSnapshotEligibility(events: readonly unknown[], internalAgent?: boolean, minUserWords?: number): SnapshotEligibility;
|
|
62
66
|
/** True when an assistant row records an explicit evolve memory mutation. */
|
|
63
67
|
export declare function containsDirectMemoryWrite(events: readonly unknown[]): boolean;
|
|
64
68
|
/** Serialize only user/assistant text, retaining the bounded tail. */
|
package/lib/turn-snapshot.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { sessionEventsOf } from "./inject.js";
|
|
2
2
|
import { projectKeyOf } from "./project.js";
|
|
3
|
+
/** ZCode's minimum user-prose threshold, counted as lexical words. */
|
|
4
|
+
export const MEMORY_MIN_USER_WORDS = 3;
|
|
5
|
+
const WORD_SEGMENTER = new Intl.Segmenter("und", { granularity: "word" });
|
|
6
|
+
function countUserWords(text) {
|
|
7
|
+
return [...WORD_SEGMENTER.segment(text)].filter((segment) => segment.isWordLike).length;
|
|
8
|
+
}
|
|
9
|
+
function containsEligibleUserProse(events, minUserWords) {
|
|
10
|
+
return events.some((event) => {
|
|
11
|
+
if (!isDirectUserEvent(event))
|
|
12
|
+
return false;
|
|
13
|
+
const row = event;
|
|
14
|
+
return countUserWords(contentText(row.data?.content)) >= minUserWords;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
3
17
|
/**
|
|
4
18
|
* Capture the current durable surface and select only rows after `cursor`.
|
|
5
19
|
* A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
|
|
@@ -9,7 +23,8 @@ export async function captureTurnSnapshot(ctx, agent, opts) {
|
|
|
9
23
|
const allEvents = await readSurface(ctx, agent);
|
|
10
24
|
const cursor = boundaryCursor(allEvents);
|
|
11
25
|
const events = eventsAfterCursor(allEvents, opts.cursor);
|
|
12
|
-
const
|
|
26
|
+
const minUserWords = opts.minUserWords ?? MEMORY_MIN_USER_WORDS;
|
|
27
|
+
const eligibility = evaluateSnapshotEligibility(events, isInternalAgent(agent), minUserWords);
|
|
13
28
|
const projectKey = projectKeyOf(agent);
|
|
14
29
|
return {
|
|
15
30
|
sessionId: agent.id,
|
|
@@ -22,6 +37,7 @@ export async function captureTurnSnapshot(ctx, agent, opts) {
|
|
|
22
37
|
userText: eligibility.userText,
|
|
23
38
|
sourceSeqs: directUserSeqs(events),
|
|
24
39
|
maxChars: opts.maxChars,
|
|
40
|
+
minUserWords,
|
|
25
41
|
...(opts.cursor === undefined ? {} : { capturedAfterCursor: opts.cursor }),
|
|
26
42
|
eligible: eligibility.eligible,
|
|
27
43
|
...(eligibility.reason ? { skipReason: eligibility.reason } : {}),
|
|
@@ -35,7 +51,7 @@ export async function captureTurnSnapshot(ctx, agent, opts) {
|
|
|
35
51
|
*/
|
|
36
52
|
export function sliceTurnSnapshot(snapshot, cursor) {
|
|
37
53
|
const events = sliceSnapshotEvents(snapshot, cursor);
|
|
38
|
-
const eligibility = evaluateSnapshotEligibility(events, false);
|
|
54
|
+
const eligibility = evaluateSnapshotEligibility(events, false, snapshot.minUserWords);
|
|
39
55
|
return {
|
|
40
56
|
...snapshot,
|
|
41
57
|
events,
|
|
@@ -51,7 +67,7 @@ export function sliceTurnSnapshot(snapshot, cursor) {
|
|
|
51
67
|
* does not decide whether a fact is valuable; that remains the review model's
|
|
52
68
|
* job. It only removes mechanical noise before the scheduler spends tokens.
|
|
53
69
|
*/
|
|
54
|
-
export function evaluateSnapshotEligibility(events, internalAgent = false) {
|
|
70
|
+
export function evaluateSnapshotEligibility(events, internalAgent = false, minUserWords = MEMORY_MIN_USER_WORDS) {
|
|
55
71
|
if (internalAgent)
|
|
56
72
|
return { eligible: false, reason: "internal-agent", userText: "" };
|
|
57
73
|
if (events.length === 0)
|
|
@@ -59,7 +75,7 @@ export function evaluateSnapshotEligibility(events, internalAgent = false) {
|
|
|
59
75
|
if (containsDirectMemoryWrite(events))
|
|
60
76
|
return { eligible: false, reason: "direct-memory-write", userText: "" };
|
|
61
77
|
const userText = directUserText(events);
|
|
62
|
-
if (
|
|
78
|
+
if (!containsEligibleUserProse(events, minUserWords))
|
|
63
79
|
return { eligible: false, reason: "no-user-prose", userText };
|
|
64
80
|
return { eligible: true, userText };
|
|
65
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Continual self-evolution plugin for DeepSeek Harness: versioned, auditable, rollback-safe harness state (prompt notes, memories, skills, subagent specs) refined from session trajectories.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|