dsh-continual-evolve 0.7.3 → 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 +21 -15
- package/README.zh.md +11 -10
- package/lib/auto.d.ts +7 -0
- package/lib/auto.js +21 -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/llm-text.d.ts +9 -1
- package/lib/llm-text.js +6 -0
- package/lib/memory-agent.d.ts +2 -0
- package/lib/memory-agent.js +1 -0
- package/lib/planner.js +2 -1
- package/lib/review.js +2 -1
- package/lib/turn-snapshot.d.ts +5 -1
- package/lib/turn-snapshot.js +20 -4
- 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,15 +19,15 @@ 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
|
|
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)
|
|
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
|
+
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.
|
|
33
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).
|
|
@@ -43,7 +43,7 @@ dsh plugin add dsh-continual-evolve
|
|
|
43
43
|
dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Restart `dsh web` after installing or updating.
|
|
46
|
+
Restart the DSH profile you use (`dsh web` or the desktop host) after installing or updating.
|
|
47
47
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
@@ -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
|
@@ -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,15 +19,15 @@ 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`
|
|
30
|
-
2. **能力感知的辅助调用**——memory loop、review、planner、wrapup、fate 通过 [`src/llm-text.ts`](src/llm-text.ts) 解析精确 provider/model 能力并使用模型公布的最低开启 reasoning effort
|
|
29
|
+
1. **沉淀**——模型经 `evolve_add` 创建条目,或仅在 `autoReview: true` 时由专用 Memory Agent 消费成功回合后的增量 snapshot(以及压缩检查点)。通用 review/planner 保持手动,除非另行显式调用。
|
|
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 与冲突提示,弹窗丢失/响应畸形会重试,不会被误记为拒绝。
|
|
33
33
|
5. **应用与注入**——memory 批次先完成所有持久化审批,写前重查 abort;后续 scope 失败时补偿回滚先前写入,成功 scope 仍保留快照与审计。prompt 补充与委派规格注入系统提示词(封顶、按相关性排序、被证伪条目降权、空 store 零 token);memory/skill 以按相关性排序的目录索引出现(`[memory:type:id] 标题`钩子)。
|
|
@@ -43,7 +43,7 @@ dsh plugin add dsh-continual-evolve
|
|
|
43
43
|
dsh plugin add ZK-Andy/dsh-continual-evolve
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
安装或更新后,重启你实际使用的 DSH profile(`dsh web` 或桌面宿主)。
|
|
47
47
|
|
|
48
48
|
## 使用
|
|
49
49
|
|
|
@@ -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
|
|
@@ -34,6 +35,7 @@ import { compareReviewCursors, createReviewScheduler } from "./review-scheduler.
|
|
|
34
35
|
import { captureTurnSnapshot, sliceTurnSnapshot } from "./turn-snapshot.js";
|
|
35
36
|
import { captureAutoCase } from "./autocase.js";
|
|
36
37
|
import { applyMemoryExtractionProposal, buildMemoryManifest, runMemoryAgent, } from "./memory-agent.js";
|
|
38
|
+
import { asLlmSessionId } from "./llm-text.js";
|
|
37
39
|
/** Turns a rejected skill candidate stays silent before being offered again. */
|
|
38
40
|
export const SKILL_CONSULT_COOLDOWN_TURNS = 10;
|
|
39
41
|
export function registerAutoReview(ctx, engine, config) {
|
|
@@ -79,7 +81,12 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
79
81
|
return "no-op";
|
|
80
82
|
}
|
|
81
83
|
try {
|
|
82
|
-
|
|
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
|
+
}
|
|
83
90
|
return "success";
|
|
84
91
|
}
|
|
85
92
|
catch (cause) {
|
|
@@ -117,6 +124,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
117
124
|
reason,
|
|
118
125
|
...(cursor !== undefined ? { cursor } : {}),
|
|
119
126
|
maxChars: config.maxInputChars,
|
|
127
|
+
...(config.memoryMinUserWords !== undefined ? { minUserWords: config.memoryMinUserWords } : {}),
|
|
120
128
|
});
|
|
121
129
|
if (!disposedSessions.has(agent.id))
|
|
122
130
|
schedulerFor(agent, state).schedule(snapshot);
|
|
@@ -168,7 +176,7 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
168
176
|
reason: "boot",
|
|
169
177
|
turnsSinceLastReview: 0,
|
|
170
178
|
outcome: "armed",
|
|
171
|
-
rationale: `
|
|
179
|
+
rationale: `automatic evolution listener registered (mode=${config.memoryOnly ? "memory-only" : "full"}; default=${defaultEnabled ? "on" : "off"}; per-success-turn snapshots)`,
|
|
172
180
|
})}\n`, "utf8");
|
|
173
181
|
}
|
|
174
182
|
catch (cause) {
|
|
@@ -191,6 +199,11 @@ export function registerAutoReview(ctx, engine, config) {
|
|
|
191
199
|
perSession.delete(agent.id);
|
|
192
200
|
});
|
|
193
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;
|
|
194
207
|
if (event.type !== "compaction/start")
|
|
195
208
|
return;
|
|
196
209
|
const agents = ctx.agents;
|
|
@@ -361,6 +374,7 @@ export async function runMemoryExtractionPhase(ctx, engine, agent, config, state
|
|
|
361
374
|
const run = await runMemoryAgent(ctx, {
|
|
362
375
|
provider,
|
|
363
376
|
model,
|
|
377
|
+
sessionId: asLlmSessionId(sessionId),
|
|
364
378
|
manifest,
|
|
365
379
|
trajectory: memorySnapshot.trajectory,
|
|
366
380
|
trajectoryEvents: memorySnapshot.events,
|
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/llm-text.d.ts
CHANGED
|
@@ -9,9 +9,13 @@
|
|
|
9
9
|
* and JSON parsing.
|
|
10
10
|
*/
|
|
11
11
|
import type { Context } from "@deepseek-ai/cordis";
|
|
12
|
-
import { type ContentBlock, type LlmResolvedModelInfo, type Message, type ReasoningEffortId, type RequestMessage, type TokenUsage, type ToolSchema } from "@deepseek-ai/dsh-llm";
|
|
12
|
+
import { type ContentBlock, type GenerateOptions, type LlmResolvedModelInfo, type Message, type ReasoningEffortId, type RequestMessage, type TokenUsage, type ToolSchema } from "@deepseek-ai/dsh-llm";
|
|
13
13
|
/** Terminal outcome of one shared direct LLM call. */
|
|
14
14
|
export type StreamTextOutcome = "success" | "max-tokens" | "error" | "aborted" | "empty";
|
|
15
|
+
/** Host-owned session identity type used by provider routing metadata. */
|
|
16
|
+
export type LlmSessionId = NonNullable<GenerateOptions["sessionId"]>;
|
|
17
|
+
/** Carry the host Agent id across the plugin's direct-call boundary. */
|
|
18
|
+
export declare function asLlmSessionId(value: string): LlmSessionId;
|
|
15
19
|
/** Provider usage and terminal result observed after the stream settles. */
|
|
16
20
|
export interface StreamTextObservation {
|
|
17
21
|
usage?: TokenUsage;
|
|
@@ -28,6 +32,8 @@ export interface StreamTextOptions {
|
|
|
28
32
|
prompt: string;
|
|
29
33
|
maxTokens?: number;
|
|
30
34
|
signal?: AbortSignal | undefined;
|
|
35
|
+
/** Host session identity forwarded to adapters for request routing. */
|
|
36
|
+
sessionId?: LlmSessionId;
|
|
31
37
|
/**
|
|
32
38
|
* Route A session prefix: session-derived messages sent before the
|
|
33
39
|
* trailing caller message so providers with prompt caching can serve
|
|
@@ -53,6 +59,8 @@ export interface StreamModelTurnOptions {
|
|
|
53
59
|
tools?: readonly ToolSchema[];
|
|
54
60
|
maxTokens?: number;
|
|
55
61
|
signal?: AbortSignal | undefined;
|
|
62
|
+
/** Host session identity forwarded to adapters for request routing. */
|
|
63
|
+
sessionId?: LlmSessionId;
|
|
56
64
|
/** Require at least one text block; tool-only turns must leave this false. */
|
|
57
65
|
requireText?: boolean;
|
|
58
66
|
/** Require visible text or a tool call; reasoning-only agent turns are empty. */
|
package/lib/llm-text.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { BlockAssembler, createUserMessage, } from "@deepseek-ai/dsh-llm";
|
|
2
2
|
import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
|
|
3
|
+
/** Carry the host Agent id across the plugin's direct-call boundary. */
|
|
4
|
+
export function asLlmSessionId(value) {
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
3
7
|
const CLOSED_REASONING_EFFORTS = new Set(["disabled", "off", "none"]);
|
|
4
8
|
/**
|
|
5
9
|
* Select the lowest reasoning effort an exact model advertises.
|
|
@@ -68,6 +72,7 @@ export async function streamModelTurn(ctx, opts) {
|
|
|
68
72
|
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
69
73
|
maxTokens: opts.maxTokens ?? 8000,
|
|
70
74
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
75
|
+
...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
71
76
|
})) {
|
|
72
77
|
assembler.push(chunk);
|
|
73
78
|
}
|
|
@@ -120,6 +125,7 @@ export async function streamText(ctx, opts) {
|
|
|
120
125
|
requireText: true,
|
|
121
126
|
...(opts.maxTokens === undefined ? {} : { maxTokens: opts.maxTokens }),
|
|
122
127
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
128
|
+
...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
123
129
|
...(opts.onUsage ? { onUsage: opts.onUsage } : {}),
|
|
124
130
|
});
|
|
125
131
|
const text = blocks
|
package/lib/memory-agent.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { Context } from "@deepseek-ai/cordis";
|
|
|
11
11
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
12
12
|
import { type ToolSchema } from "@deepseek-ai/dsh-llm";
|
|
13
13
|
import { type ScopeApprovalDecision } from "./approval.js";
|
|
14
|
+
import { type LlmSessionId } from "./llm-text.js";
|
|
14
15
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
15
16
|
import { type EvolutionEngine } from "./service.js";
|
|
16
17
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
@@ -64,6 +65,7 @@ export declare function searchMemoryManifest(entries: readonly MemoryManifestEnt
|
|
|
64
65
|
export interface MemoryAgentOptions {
|
|
65
66
|
provider: string;
|
|
66
67
|
model: string;
|
|
68
|
+
sessionId?: LlmSessionId;
|
|
67
69
|
manifest: readonly MemoryManifestEntry[];
|
|
68
70
|
trajectory: string;
|
|
69
71
|
trajectoryEvents?: readonly unknown[];
|
package/lib/memory-agent.js
CHANGED
|
@@ -172,6 +172,7 @@ export async function runMemoryAgent(ctx, options) {
|
|
|
172
172
|
const blocks = await streamModelTurn(ctx, {
|
|
173
173
|
provider: options.provider,
|
|
174
174
|
model: options.model,
|
|
175
|
+
...(options.sessionId ? { sessionId: options.sessionId } : {}),
|
|
175
176
|
system: MEMORY_AGENT_SYSTEM_PROMPT,
|
|
176
177
|
messages,
|
|
177
178
|
tools: MEMORY_AGENT_TOOL_SCHEMAS,
|
package/lib/planner.js
CHANGED
|
@@ -3,7 +3,7 @@ import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
|
3
3
|
import { recentUserText, sessionEventsOf } from "./inject.js";
|
|
4
4
|
import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
|
|
5
5
|
import { skillQualityGuide } from "./skillquality.js";
|
|
6
|
-
import { streamText } from "./llm-text.js";
|
|
6
|
+
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
7
7
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
8
8
|
export const PLANNER_SYSTEM_PROMPT = `You are the /evolve continual harness subsystem.
|
|
9
9
|
|
|
@@ -121,6 +121,7 @@ export async function planWithLlm(ctx, options) {
|
|
|
121
121
|
const text = await streamText(ctx, {
|
|
122
122
|
provider: agent.options.provider,
|
|
123
123
|
model: agent.options.model,
|
|
124
|
+
sessionId: asLlmSessionId(agent.id),
|
|
124
125
|
system: PLANNER_SYSTEM_PROMPT,
|
|
125
126
|
prompt: userPrompt,
|
|
126
127
|
maxTokens: options.maxOutputTokens ?? 8000,
|
package/lib/review.js
CHANGED
|
@@ -2,7 +2,7 @@ import { extractJsonObject } from "./plan.js";
|
|
|
2
2
|
import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
3
3
|
import { sessionEventsOf } from "./inject.js";
|
|
4
4
|
import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
|
|
5
|
-
import { streamText } from "./llm-text.js";
|
|
5
|
+
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
6
6
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
7
7
|
export const AUTO_REVIEW_SYSTEM_PROMPT = `You are the automatic /evolve review gate.
|
|
8
8
|
|
|
@@ -126,6 +126,7 @@ export async function reviewAutoRefine(ctx, options) {
|
|
|
126
126
|
const text = await streamText(ctx, {
|
|
127
127
|
provider,
|
|
128
128
|
model,
|
|
129
|
+
sessionId: asLlmSessionId(agent.id),
|
|
129
130
|
system: AUTO_REVIEW_SYSTEM_PROMPT,
|
|
130
131
|
prompt: userPrompt,
|
|
131
132
|
maxTokens: options.budgetTokens ?? 8000,
|
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/lib/wrapup.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ARCHIVED_AT_KEY, PROMOTED_AT_KEY, PROMOTED_TO_KEY, SOURCE_SEQS_KEY, SOU
|
|
|
2
2
|
import { MEMORY_TYPE_KEY, isMemoryType } from "./types.js";
|
|
3
3
|
import { extractJsonObject } from "./plan.js";
|
|
4
4
|
import { compactText } from "./render.js";
|
|
5
|
-
import { streamText } from "./llm-text.js";
|
|
5
|
+
import { asLlmSessionId, streamText } from "./llm-text.js";
|
|
6
6
|
import { getUsageCount, loadUsage } from "./usage.js";
|
|
7
7
|
import { createTokenUsageObserver } from "./token-usage.js";
|
|
8
8
|
import { recencyScore } from "./inject.js";
|
|
@@ -522,6 +522,7 @@ export async function assessLocalEntries(ctx, agent, candidates, options = {}) {
|
|
|
522
522
|
const text = await streamText(ctx, {
|
|
523
523
|
provider: agent.options.provider,
|
|
524
524
|
model: agent.options.model,
|
|
525
|
+
sessionId: asLlmSessionId(agent.id),
|
|
525
526
|
system: WRAPUP_ASSESS_SYSTEM_PROMPT,
|
|
526
527
|
prompt: userPrompt,
|
|
527
528
|
maxTokens: options.maxOutputTokens ?? 4096,
|
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": {
|