dsh-continual-evolve 0.7.4 → 0.7.6
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 +19 -11
- package/README.zh.md +7 -6
- package/lib/auto.d.ts +7 -0
- package/lib/auto.js +19 -7
- package/lib/command.d.ts +1 -1
- package/lib/command.js +16 -12
- package/lib/index.d.ts +10 -4
- package/lib/index.js +21 -14
- 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**: 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 the automatic Memory Agent consumes incremental snapshots after successful turns. 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` | initial
|
|
82
|
+
| `autoReview` | `false` | initial Memory Agent default when no `evolve/runtime.json` exists yet; the listener is always registered, so this is not a registration gate |
|
|
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; unreachable while the listener runs memory-only, so it only affects direct/full callers |
|
|
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,25 @@ Example profile patch:
|
|
|
111
112
|
reviewIntervalTurns: 6
|
|
112
113
|
```
|
|
113
114
|
|
|
114
|
-
The listener is registered even when `autoReview` is `false
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
The Memory Agent listener is registered even when `autoReview` is `false` —
|
|
116
|
+
`autoReview` only supplies the initial default, so an install works without
|
|
117
|
+
editing the profile. Use `/evolve resume` to enable successful-turn snapshots
|
|
118
|
+
immediately, `/evolve pause` to suppress new snapshots and model work, and
|
|
119
|
+
`/evolve status` to inspect the configured default plus the current runtime
|
|
120
|
+
state. The runtime switch is stored in `evolve/runtime.json`; manual `evolve_*`
|
|
121
|
+
tools and `/evolve` commands are not paused. Only the Memory Agent runs
|
|
122
|
+
automatically: the generic review/planner, prompt/skill writes, and local-fate
|
|
123
|
+
phases are not reachable from this listener. The memory trigger follows ZCode's
|
|
124
|
+
lightweight eligibility: direct user text must contain at least
|
|
125
|
+
`memoryMinUserWords` lexical words (CJK-aware segmentation), while
|
|
126
|
+
empty/internal/direct-memory-write snapshots are skipped; compaction does not
|
|
127
|
+
add a separate memory-only trigger.
|
|
120
128
|
|
|
121
129
|
## Development
|
|
122
130
|
|
|
123
131
|
```bash
|
|
124
132
|
pnpm install && pnpm build # deps + tsc -> lib/
|
|
125
|
-
pnpm test # vitest (
|
|
133
|
+
pnpm test # vitest (742 tests)
|
|
126
134
|
pnpm test:coverage # v8 coverage, thresholds enforced in CI
|
|
127
135
|
pnpm lint # oxlint src test
|
|
128
136
|
```
|
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**:成功回合通过机械 eligibility 后,有界 ZCode 式 loop 检索冻结的 memory manifest,并经闭集工具提出纯 memory 编辑;不能调用 Agent、MCP
|
|
22
|
+
- **专用后台记忆 Agent**:成功回合通过机械 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` 创建条目,或由专用 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` | 没有 runtime
|
|
82
|
+
| `autoReview` | `false` | 没有 `evolve/runtime.json` 时 Memory Agent 的初始默认;监听器始终注册,因此它不是注册门 |
|
|
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 评估;listener 以 memory-only 运行时不可达,只对直接/完整调用者生效 |
|
|
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
|
-
即使 `autoReview` 为 `false
|
|
115
|
+
即使 `autoReview` 为 `false`,Memory Agent 监听器也会注册——`autoReview` 只提供初始默认值,装完即用,无需改 profile。使用 `/evolve resume` 立即开启成功回合 snapshot,使用 `/evolve pause` 抑制新 snapshot 与模型调用,使用 `/evolve status` 查看配置默认值与运行时状态。开关保存在 `evolve/runtime.json`;手动 `evolve_*` 工具和 `/evolve` 命令不受暂停影响。自动运行只有 Memory Agent:通用 review/planner、prompt/skill 写入与 local fate 都不由该 listener 触达。Memory 触发采用 ZCode 的轻量 eligibility:单个直接用户文本至少包含 `memoryMinUserWords` 个词(使用 CJK 分词);空增量、内部 Agent、直接 memory 写入会跳过,压缩不会额外触发 memory-only 管线。
|
|
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(742 例)
|
|
121
122
|
pnpm test:coverage # v8 覆盖率,CI 强制阈值
|
|
122
123
|
pnpm lint # oxlint src test
|
|
123
124
|
```
|
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
|
@@ -18,7 +18,7 @@ export interface CommandRuntimeOptions {
|
|
|
18
18
|
/** Mechanical promotion guards for wrapup/fate (2026-08-22 policy). */
|
|
19
19
|
promotionPolicy: PromotionPolicy;
|
|
20
20
|
/**
|
|
21
|
-
* Initial
|
|
21
|
+
* Initial Memory Agent default from plugin config. The listener is always
|
|
22
22
|
* registered; this is not a registration gate.
|
|
23
23
|
*/
|
|
24
24
|
autoReview?: boolean;
|
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({
|
|
@@ -377,21 +377,21 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
377
377
|
case "pause":
|
|
378
378
|
case "resume": {
|
|
379
379
|
// The switch is independent of the static registration flag. A
|
|
380
|
-
// resume explicitly enables
|
|
381
|
-
//
|
|
380
|
+
// resume explicitly enables the Memory Agent; a pause leaves the
|
|
381
|
+
// enable bit intact so status explains the paused state.
|
|
382
382
|
const pausing = sub === "pause";
|
|
383
383
|
const defaultEnabled = runtime.autoReview ?? false;
|
|
384
384
|
const current = loadGateRuntime(engine.baseDir, defaultEnabled);
|
|
385
385
|
if (pausing && current.paused) {
|
|
386
|
-
return success("
|
|
386
|
+
return success("automatic Memory Agent is already paused (no change).");
|
|
387
387
|
}
|
|
388
388
|
if (!pausing && current.enabled && !current.paused) {
|
|
389
|
-
return success("
|
|
389
|
+
return success("automatic Memory Agent is already running (no change).");
|
|
390
390
|
}
|
|
391
391
|
saveGateRuntime(engine.baseDir, pausing, pausing ? current.enabled : true);
|
|
392
392
|
return success(pausing
|
|
393
|
-
? "
|
|
394
|
-
: "
|
|
393
|
+
? "automatic Memory Agent paused: no automatic memory extraction calls until /evolve resume. Manual evolve_* tools and /evolve commands keep working."
|
|
394
|
+
: "automatic Memory Agent resumed: successful turns will produce incremental memory snapshots immediately.");
|
|
395
395
|
}
|
|
396
396
|
case "status": {
|
|
397
397
|
return success(renderGateStatus(engine, sessionId, projectKeyOf(invocation.agent), runtime));
|
|
@@ -458,14 +458,18 @@ function demoteEntry(engine, id, sessionId, projectKey) {
|
|
|
458
458
|
return error(`entry ${id} not found in the global, project, or local store`);
|
|
459
459
|
}
|
|
460
460
|
/**
|
|
461
|
-
* Status answers whether the
|
|
462
|
-
*
|
|
461
|
+
* Status answers whether the automatic listener is wired and what
|
|
462
|
+
* stores/retention it currently sees.
|
|
463
463
|
*/
|
|
464
464
|
function renderGateStatus(engine, sessionId, projectKey, runtime) {
|
|
465
465
|
const configuredDefault = runtime.autoReview === undefined ? "unknown" : runtime.autoReview ? "on" : "off";
|
|
466
466
|
const current = loadGateRuntime(engine.baseDir, runtime.autoReview ?? false);
|
|
467
|
-
const effective = current.paused
|
|
468
|
-
|
|
467
|
+
const effective = current.paused
|
|
468
|
+
? "PAUSED (resume with /evolve resume)"
|
|
469
|
+
: current.enabled
|
|
470
|
+
? "Memory Agent running"
|
|
471
|
+
: "Memory Agent off (enable with /evolve resume)";
|
|
472
|
+
const gateLine = `gate: Memory Agent-only listener registered · config default ${configuredDefault} · runtime ${effective}`;
|
|
469
473
|
const countEntries = (state) => Object.values(state.entries).reduce((n, byKind) => n + Object.keys(byKind).length, 0);
|
|
470
474
|
const lines = [gateLine];
|
|
471
475
|
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
|
-
/** Initial
|
|
13
|
+
/** Initial Memory Agent default when no `evolve/runtime.json` exists; not a registration gate. */
|
|
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
|
-
/** Initial
|
|
113
|
+
/** Initial Memory Agent default when no `evolve/runtime.json` exists; not a registration gate. */
|
|
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
|
/**
|
package/lib/index.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* the dedicated Memory Agent listener. The listener is always registered so
|
|
7
|
+
* an install works without profile edits; `autoReview` only supplies the
|
|
8
|
+
* initial default when `evolve/runtime.json` does not exist yet, and
|
|
9
|
+
* `/evolve pause|resume` own it afterwards. Generic review/planner/fate are
|
|
10
|
+
* not reachable from this listener.
|
|
10
11
|
*/
|
|
11
12
|
import { join } from "node:path";
|
|
12
13
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -30,8 +31,10 @@ export const Config = z.object({
|
|
|
30
31
|
baseDir: z.string(),
|
|
31
32
|
/** System-prompt section order for the evolution guidance. */
|
|
32
33
|
sectionOrder: z.natural().default(118),
|
|
33
|
-
/** Initial
|
|
34
|
+
/** Initial Memory Agent default when no `evolve/runtime.json` exists; not a registration gate. */
|
|
34
35
|
autoReview: z.boolean().default(false),
|
|
36
|
+
/** ZCode-style minimum lexical words in one direct user text part. */
|
|
37
|
+
memoryMinUserWords: z.natural().default(3),
|
|
35
38
|
/** Legacy/local-fate cadence fallback; successful-turn review is snapshot-driven. */
|
|
36
39
|
reviewIntervalTurns: z.natural().default(6),
|
|
37
40
|
/** Trajectory slice handed to the gate, in characters. */
|
|
@@ -78,9 +81,10 @@ export const Config = z.object({
|
|
|
78
81
|
/**
|
|
79
82
|
* Gate local-fate dimension (#11 P2): the gate audits the session's local
|
|
80
83
|
* entries on its own cadence and proposes promote/archive — consulted
|
|
81
|
-
* first, never written silently. Only meaningful with autoReview on
|
|
84
|
+
* first, never written silently. Only meaningful with autoReview on;
|
|
85
|
+
* disabled by default even when the automatic pipeline is explicitly enabled.
|
|
82
86
|
*/
|
|
83
|
-
localFate: z.boolean().default(
|
|
87
|
+
localFate: z.boolean().default(false),
|
|
84
88
|
/**
|
|
85
89
|
* Minimum turns between local-fate assessments on the turn-interval path
|
|
86
90
|
* (compaction is unconditional). Absent → follows reviewIntervalTurns.
|
|
@@ -163,7 +167,7 @@ export function apply(ctx, config) {
|
|
|
163
167
|
autoRollbackOnReject: config.autoRollbackOnReject ?? true,
|
|
164
168
|
autoCase: config.autoCase ?? true,
|
|
165
169
|
promotionPolicy,
|
|
166
|
-
|
|
170
|
+
autoReview: config.autoReview ?? false,
|
|
167
171
|
});
|
|
168
172
|
// Plugin-owned file logging: every cordis log message lands in
|
|
169
173
|
// <baseDir>/evolve/plugin.log regardless of how dsh web was launched —
|
|
@@ -178,16 +182,20 @@ export function apply(ctx, config) {
|
|
|
178
182
|
void restoreMounted(ctx, baseDir).catch((cause) => {
|
|
179
183
|
ctx.logger("continual-evolve").warn(`mount restore failed: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
180
184
|
});
|
|
181
|
-
// The listener is always registered
|
|
182
|
-
//
|
|
183
|
-
//
|
|
185
|
+
// The Memory Agent listener is always registered: an install must work
|
|
186
|
+
// without profile edits, so `autoReview` is only the initial default when
|
|
187
|
+
// no `evolve/runtime.json` exists yet — `/evolve pause|resume` own the
|
|
188
|
+
// switch afterwards. The general review/planner, prompt/skill writes, and
|
|
189
|
+
// local-fate phases are not reachable from this listener.
|
|
184
190
|
registerAutoReview(ctx, engine, {
|
|
185
191
|
intervalTurns: config.reviewIntervalTurns ?? 6,
|
|
186
192
|
enabledByDefault: config.autoReview ?? false,
|
|
193
|
+
memoryOnly: true,
|
|
194
|
+
memoryMinUserWords: config.memoryMinUserWords ?? 3,
|
|
187
195
|
maxInputChars: config.maxReviewInputChars ?? 40000,
|
|
188
196
|
budgetTokens: config.reviewBudgetTokens ?? 4096,
|
|
189
197
|
notifyOnAutoReview: config.notifyOnAutoReview ?? true,
|
|
190
|
-
localFate: config.localFate ??
|
|
198
|
+
localFate: config.localFate ?? false,
|
|
191
199
|
fateIntervalTurns: config.fateIntervalTurns ?? config.reviewIntervalTurns ?? 6,
|
|
192
200
|
goalBlockedWrapupTurns: config.goalBlockedWrapupTurns ?? 3,
|
|
193
201
|
promotionPolicy,
|
|
@@ -199,8 +207,7 @@ export function apply(ctx, config) {
|
|
|
199
207
|
...(config.plannerPrefixCache ? { prefixCacheMode: config.plannerPrefixCache } : {}),
|
|
200
208
|
...(config.plannerPrefixMaxChars !== undefined ? { prefixMaxChars: config.plannerPrefixMaxChars } : {}),
|
|
201
209
|
});
|
|
202
|
-
|
|
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)`);
|
|
210
|
+
ctx.logger("continual-evolve").info("continual-evolve automatic listener registered (Memory Agent only; generic review/planner/fate disconnected)");
|
|
204
211
|
ctx.logger("continual-evolve").info(`continual-evolve mounted (baseDir=${baseDir})`);
|
|
205
212
|
}
|
|
206
213
|
//# 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.6",
|
|
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": {
|