dsh-continual-evolve 0.10.4 → 0.10.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 +14 -12
- package/README.zh.md +14 -12
- package/lib/approval.d.ts +17 -5
- package/lib/approval.js +73 -12
- package/lib/auto.js +1 -0
- package/lib/command.d.ts +3 -0
- package/lib/command.js +20 -148
- package/lib/index.d.ts +36 -0
- package/lib/index.js +48 -2
- package/lib/inject.d.ts +14 -0
- package/lib/inject.js +21 -6
- package/lib/memory-agent.d.ts +3 -1
- package/lib/memory-agent.js +1 -1
- package/lib/memory-guide.d.ts +19 -0
- package/lib/memory-guide.js +73 -0
- package/lib/memory-index.d.ts +114 -0
- package/lib/memory-index.js +197 -0
- package/lib/tool.d.ts +7 -0
- package/lib/tool.js +30 -30
- package/lib/wrapup-command.d.ts +2 -1
- package/lib/wrapup-command.js +7 -4
- package/package.json +2 -1
package/lib/command.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ARCHIVED_AT_KEY
|
|
1
|
+
import { ARCHIVED_AT_KEY } from "./types.js";
|
|
2
2
|
import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
3
|
-
import { compactText } from "./render.js";
|
|
4
3
|
import { planWithLlm } from "./planner.js";
|
|
5
4
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { join } from "node:path";
|
|
@@ -19,9 +18,8 @@ import { projectKeyOf } from "./project.js";
|
|
|
19
18
|
import { loadUsage, getUsageCount } from "./usage.js";
|
|
20
19
|
import { loadGateRuntime, saveGateRuntime } from "./runtime.js";
|
|
21
20
|
import { planConsolidation } from "./consolidate.js";
|
|
22
|
-
import { formatRecallResult, recallMemories, RECALL_MEMORY_TYPES } from "./recall.js";
|
|
23
21
|
import { loadTokenUsage, renderTokenUsageReport } from "./token-usage.js";
|
|
24
|
-
const
|
|
22
|
+
const USAGE_COMMON = `Usage:
|
|
25
23
|
/evolve show this help and the current local store
|
|
26
24
|
/evolve list [project|global] list entries (default local staging; "project" = this project's cross-session store, "global" = cross-project store)
|
|
27
25
|
/evolve history [global] show applied refinements (rollback ids)
|
|
@@ -31,19 +29,16 @@ const USAGE = `Usage:
|
|
|
31
29
|
to the global store (approval required), archive one-offs
|
|
32
30
|
/evolve archive <id> [global] hide an entry from injection (data kept, restorable)
|
|
33
31
|
/evolve unarchive <id> [global] restore an archived entry
|
|
34
|
-
/evolve
|
|
35
|
-
/evolve
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
immediately persist one memory (type = user|feedback|project|reference;
|
|
39
|
-
feedback/project text must carry Why + How; default scope local)
|
|
40
|
-
/evolve forget [scope] <query...> locate one memory by query and archive it (restorable via /evolve unarchive)
|
|
32
|
+
/evolve pause | resume pause/resume the automatic Memory Agent (manual tools and commands keep working)
|
|
33
|
+
/evolve status automatic Memory Agent state plus store entry counts
|
|
34
|
+
/evolve help all every subcommand, including the advanced ones below`;
|
|
35
|
+
const USAGE_ADVANCED = `Advanced:
|
|
41
36
|
/evolve consolidate [apply] [merge]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/evolve log [tail N] show the recent plugin log (default 50 lines)
|
|
37
|
+
report (or apply) a batch archive of conflict-hinted
|
|
38
|
+
and stale zero-use global entries; "merge" folds
|
|
39
|
+
near-duplicate content into the surviving original
|
|
46
40
|
/evolve failures aggregated failure counts (gate + benchmark, by class)
|
|
41
|
+
/evolve log [tail N] show the recent plugin log (default 50 lines)
|
|
47
42
|
/evolve export [global] <path> backup a store to a JSON file
|
|
48
43
|
/evolve import [global] <path> restore a store from an export file
|
|
49
44
|
/evolve mount <skillId> hot-mount a skill entry as a live cordis plugin
|
|
@@ -52,14 +47,15 @@ const USAGE = `Usage:
|
|
|
52
47
|
/evolve goal show the evolution goal (round-driven auto-review)
|
|
53
48
|
/evolve goal <objective> create/update the evolution goal
|
|
54
49
|
/evolve goal done complete the evolution goal
|
|
55
|
-
/evolve
|
|
56
|
-
/evolve status automatic Memory Agent state plus store entry counts
|
|
50
|
+
/evolve benchmark ... case lifecycle, runs, acceptance
|
|
57
51
|
/evolve usage injection counts + exact direct-call token usage (benchmark subagents excluded)`;
|
|
52
|
+
/** Default help (`/evolve`, `/evolve help`): the commands worth knowing. */
|
|
53
|
+
const USAGE = USAGE_COMMON;
|
|
58
54
|
export function registerEvolveCommand(ctx, engine, opts, runtime) {
|
|
59
55
|
ctx.commands.register({
|
|
60
56
|
name: "evolve",
|
|
61
57
|
description: "自进化:检查并演进 harness 状态(记忆/技能/提示词/子代理) | Inspect and evolve the continual harness state (memories, skills, prompt notes, subagent specs)",
|
|
62
|
-
input: { hint: "[list [global] | history [global] | rollback <id> [global] | plan [msg]]" },
|
|
58
|
+
input: { hint: "[list [global] | history [global] | rollback <id> [global] | plan [msg] | help all]" },
|
|
63
59
|
handler: (invocation) => executeEvolveCommand(ctx, engine, invocation, opts, runtime),
|
|
64
60
|
});
|
|
65
61
|
}
|
|
@@ -162,7 +158,9 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
162
158
|
switch (sub) {
|
|
163
159
|
case "":
|
|
164
160
|
case "help":
|
|
165
|
-
|
|
161
|
+
// Layered help (2026-10-01): the default listing stays short;
|
|
162
|
+
// `help all` expands the advanced half.
|
|
163
|
+
return success(`${rest[0] === "all" ? `${USAGE_COMMON}\n\n${USAGE_ADVANCED}` : USAGE_COMMON}\n\n${formatHarnessStateForPrompt(engine.load("local", sessionId))}`);
|
|
166
164
|
case "list": {
|
|
167
165
|
const { scope } = scopeArg(rest);
|
|
168
166
|
return success(formatHarnessStateForPrompt(engine.load(scope, storeIdForCommand(scope, invocation))));
|
|
@@ -182,16 +180,12 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
182
180
|
return success(renderResult(result));
|
|
183
181
|
}
|
|
184
182
|
case "archive":
|
|
185
|
-
case "unarchive":
|
|
186
|
-
case "demote": {
|
|
183
|
+
case "unarchive": {
|
|
187
184
|
const { scope, rest: after } = scopeArg(rest);
|
|
188
185
|
const id = stripAngleBrackets(after[0] ?? "");
|
|
189
186
|
if (!id) {
|
|
190
187
|
return error(`${sub} requires an entry id.\n${USAGE}`);
|
|
191
188
|
}
|
|
192
|
-
if (sub === "demote") {
|
|
193
|
-
return demoteEntry(engine, id, sessionId, projectKeyOf(invocation.agent));
|
|
194
|
-
}
|
|
195
189
|
const state = engine.load(scope, storeIdForCommand(scope, invocation));
|
|
196
190
|
const found = findEntryById(state, id);
|
|
197
191
|
if (!found) {
|
|
@@ -214,90 +208,6 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
214
208
|
}, { scope });
|
|
215
209
|
return success(renderResult(result));
|
|
216
210
|
}
|
|
217
|
-
case "recall": {
|
|
218
|
-
// Human counterpart of the evolve_recall tool: same engine,
|
|
219
|
-
// same filters (memory kind, one scope or all three).
|
|
220
|
-
const { scope, rest: after } = scopeArg(rest);
|
|
221
|
-
const explicitScope = rest.length > 0 && (rest[0] === "global" || rest[0] === "project" || rest[0] === "local");
|
|
222
|
-
const query = after.join(" ");
|
|
223
|
-
const result = recallMemories(engine, { sessionId, projectKey: projectKeyOf(invocation.agent) ?? undefined }, { kinds: ["memory"], ...(explicitScope ? { scopes: [scope] } : {}), ...(query ? { query } : {}), limit: 10 });
|
|
224
|
-
return success(formatRecallResult(result, query || undefined));
|
|
225
|
-
}
|
|
226
|
-
case "remember": {
|
|
227
|
-
// Immediate manual memory path (P1 §6.6): one fact, explicit
|
|
228
|
-
// type, straight into the engine — validation, snapshot,
|
|
229
|
-
// versioning, and approval behave exactly like any other
|
|
230
|
-
// write. feedback/project text must already carry Why + How;
|
|
231
|
-
// the engine rejects it loudly otherwise.
|
|
232
|
-
const type = rest[0] ?? "";
|
|
233
|
-
if (!RECALL_MEMORY_TYPES.includes(type)) {
|
|
234
|
-
return error(`remember requires a memory type: user|feedback|project|reference.\nExample: /evolve remember feedback local 对方偏好深色主题。Why:…… How to apply:……\n${USAGE}`);
|
|
235
|
-
}
|
|
236
|
-
const maybeScope = rest[1] ?? "";
|
|
237
|
-
const scoped = maybeScope === "global" || maybeScope === "project" || maybeScope === "local";
|
|
238
|
-
const scope = scoped ? maybeScope : "local";
|
|
239
|
-
const text = (scoped ? rest.slice(2) : rest.slice(1)).join(" ").trim();
|
|
240
|
-
if (!text) {
|
|
241
|
-
return error(`remember requires the memory text after the type.\n${USAGE}`);
|
|
242
|
-
}
|
|
243
|
-
const storeId = storeIdForCommand(scope, invocation);
|
|
244
|
-
if ((scope === "global" || scope === "project") && opts.requireGlobalApproval) {
|
|
245
|
-
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve remember ${type} → ${scope} store: ${compactText(text, 120)}`);
|
|
246
|
-
}
|
|
247
|
-
const result = engine.apply(scope, storeId, {
|
|
248
|
-
summary: `remember: ${compactText(text, 80)}`,
|
|
249
|
-
rationale: "Human-invoked immediate memory via /evolve remember.",
|
|
250
|
-
expectedOutcome: "The fact is persisted as a typed memory entry.",
|
|
251
|
-
edits: [{
|
|
252
|
-
action: "create",
|
|
253
|
-
kind: "memory",
|
|
254
|
-
title: compactText(text, 80),
|
|
255
|
-
content: text,
|
|
256
|
-
metadata: { [MEMORY_TYPE_KEY]: type },
|
|
257
|
-
}],
|
|
258
|
-
}, { scope });
|
|
259
|
-
return success(renderResult(result));
|
|
260
|
-
}
|
|
261
|
-
case "forget": {
|
|
262
|
-
// Manual forget path (P1 §6.6): locate by query, archive the
|
|
263
|
-
// single match (restorable via unarchive). An ambiguous query
|
|
264
|
-
// lists candidates instead of archiving — a silent multi-
|
|
265
|
-
// archive would look like success while hiding too much.
|
|
266
|
-
const { scope, rest: after } = scopeArg(rest);
|
|
267
|
-
const explicitScope = rest.length > 0 && (rest[0] === "global" || rest[0] === "project" || rest[0] === "local");
|
|
268
|
-
const query = after.join(" ").trim();
|
|
269
|
-
if (!query) {
|
|
270
|
-
return error(`forget requires a query to locate the memory.\n${USAGE}`);
|
|
271
|
-
}
|
|
272
|
-
const recalled = recallMemories(engine, { sessionId, projectKey: projectKeyOf(invocation.agent) ?? undefined }, { query, kinds: ["memory"], ...(explicitScope ? { scopes: [scope] } : {}), limit: 6, includeArchived: false });
|
|
273
|
-
if (recalled.hits.length === 0) {
|
|
274
|
-
return error(`no memory matches "${query}"${recalled.notes.length > 0 ? ` (${recalled.notes.join("; ")})` : ""} — try fewer words, or /evolve list to browse.`);
|
|
275
|
-
}
|
|
276
|
-
if (recalled.hits.length > 1) {
|
|
277
|
-
const list = recalled.hits.map((hit) => `- [${hit.scope}:${hit.id}] ${hit.title}`).join("\n");
|
|
278
|
-
return success(`"${query}" matches ${recalled.hits.length} memories — refine the query so exactly one matches, then forget archives it:\n${list}`);
|
|
279
|
-
}
|
|
280
|
-
const hit = recalled.hits[0];
|
|
281
|
-
const state = engine.load(hit.scope, hit.scope === "local" ? sessionId : hit.scope === "project" ? (projectKeyOf(invocation.agent) ?? undefined) : undefined);
|
|
282
|
-
const entry = state.entries.memory[hit.id];
|
|
283
|
-
if (!entry) {
|
|
284
|
-
return error(`memory ${hit.scope}:${hit.id} vanished since recall — run /evolve forget again.`);
|
|
285
|
-
}
|
|
286
|
-
const result = engine.apply(hit.scope, hit.scope === "local" ? sessionId : hit.scope === "project" ? (projectKeyOf(invocation.agent) ?? undefined) : undefined, {
|
|
287
|
-
summary: `forget: archive memory ${hit.scope}:${hit.id}`,
|
|
288
|
-
rationale: `Human-invoked forget via /evolve forget "${query}".`,
|
|
289
|
-
expectedOutcome: "The entry is hidden from injection (data kept; restorable via /evolve unarchive).",
|
|
290
|
-
edits: [{
|
|
291
|
-
action: "update",
|
|
292
|
-
kind: "memory",
|
|
293
|
-
id: hit.id,
|
|
294
|
-
title: entry.title,
|
|
295
|
-
content: entry.content,
|
|
296
|
-
metadata: { ...entry.metadata, [ARCHIVED_AT_KEY]: new Date().toISOString() },
|
|
297
|
-
}],
|
|
298
|
-
}, { scope: hit.scope });
|
|
299
|
-
return success(`forgot [${hit.scope}:${hit.id}] ${hit.title} (archived — restore with /evolve unarchive ${hit.id}${hit.scope === "local" ? "" : ` ${hit.scope}`})\n${renderResult(result)}`);
|
|
300
|
-
}
|
|
301
211
|
case "consolidate": {
|
|
302
212
|
// R3: deterministic global-store hygiene. Report by default;
|
|
303
213
|
// `apply` re-scans fresh state and lands the whole batch as ONE
|
|
@@ -454,7 +364,7 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
454
364
|
skillsRoot: join(engine.baseDir, "skills"),
|
|
455
365
|
});
|
|
456
366
|
if ((scope === "global" || scope === "project") && opts.requireGlobalApproval && proposal.edits.length > 0) {
|
|
457
|
-
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}
|
|
367
|
+
await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`, runtime.recordLanguage);
|
|
458
368
|
}
|
|
459
369
|
const result = engine.apply(scope, storeIdForCommand(scope, invocation), proposal, {
|
|
460
370
|
scope,
|
|
@@ -464,7 +374,7 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
464
374
|
return success(renderResult(result));
|
|
465
375
|
}
|
|
466
376
|
case "wrapup": {
|
|
467
|
-
return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy);
|
|
377
|
+
return await executeWrapupCommand(ctx, engine, invocation, runtime.promotionPolicy, runtime.recordLanguage);
|
|
468
378
|
}
|
|
469
379
|
case "pause":
|
|
470
380
|
case "resume": {
|
|
@@ -511,44 +421,6 @@ async function executeEvolveCommand(ctx, engine, invocation, opts, runtime) {
|
|
|
511
421
|
return error(cause instanceof Error ? cause.message : String(cause));
|
|
512
422
|
}
|
|
513
423
|
}
|
|
514
|
-
/**
|
|
515
|
-
* Demote (2026-08-22): hide an entry from injection WITHOUT deleting it —
|
|
516
|
-
* the one-command remedy for store pollution. Searches the global store
|
|
517
|
-
* first (the primary target: cross-project noise), then the project store,
|
|
518
|
-
* then the session's local store. The data stays; `/evolve unarchive` restores it.
|
|
519
|
-
*/
|
|
520
|
-
function demoteEntry(engine, id, sessionId, projectKey) {
|
|
521
|
-
const targets = [
|
|
522
|
-
{ scope: "global", storeId: undefined },
|
|
523
|
-
...(projectKey ? [{ scope: "project", storeId: projectKey }] : []),
|
|
524
|
-
{ scope: "local", storeId: sessionId },
|
|
525
|
-
];
|
|
526
|
-
for (const { scope, storeId } of targets) {
|
|
527
|
-
const state = engine.load(scope, storeId);
|
|
528
|
-
const found = findEntryById(state, id);
|
|
529
|
-
if (!found)
|
|
530
|
-
continue;
|
|
531
|
-
const [kind, entry] = found;
|
|
532
|
-
const result = engine.apply(scope, storeId, {
|
|
533
|
-
summary: `demote: archive ${kind}:${id} from the ${scope} store`,
|
|
534
|
-
rationale: "Human-invoked demote via the /evolve command.",
|
|
535
|
-
expectedOutcome: "The entry is hidden from injection in every scope it touched; data is kept and restorable.",
|
|
536
|
-
edits: [
|
|
537
|
-
{
|
|
538
|
-
action: "update",
|
|
539
|
-
kind,
|
|
540
|
-
id,
|
|
541
|
-
title: entry.title,
|
|
542
|
-
content: entry.content,
|
|
543
|
-
metadata: { ...entry.metadata, [ARCHIVED_AT_KEY]: new Date().toISOString() },
|
|
544
|
-
},
|
|
545
|
-
],
|
|
546
|
-
}, { scope });
|
|
547
|
-
const restoreScope = scope === "global" ? " global" : scope === "project" ? " project" : "";
|
|
548
|
-
return success(`demoted ${kind}:${id} from the ${scope} store (archived — restore with /evolve unarchive ${id}${restoreScope})\n${renderResult(result)}`);
|
|
549
|
-
}
|
|
550
|
-
return error(`entry ${id} not found in the global, project, or local store`);
|
|
551
|
-
}
|
|
552
424
|
/**
|
|
553
425
|
* Status answers whether the automatic listener is wired and what
|
|
554
426
|
* stores/retention it currently sees.
|
package/lib/index.d.ts
CHANGED
|
@@ -98,6 +98,24 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
98
98
|
promotionMinChars: z<number, number, "defined">;
|
|
99
99
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
100
100
|
injectionDirectoryLines: z<number, number, "defined">;
|
|
101
|
+
/**
|
|
102
|
+
* evolve v2 memory index: the section that injects memory CONTENT (not just
|
|
103
|
+
* an id/title directory) at session start, frozen per session to keep the
|
|
104
|
+
* system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
|
|
105
|
+
* `maxChars` default to true/true/400/6000; with `guide` off and an empty
|
|
106
|
+
* store the section renders to "" and costs no tokens.
|
|
107
|
+
*/
|
|
108
|
+
memoryIndex: z<Schemastery.ObjectS<NoInfer<{
|
|
109
|
+
enabled: z<boolean, boolean, "plain">;
|
|
110
|
+
guide: z<boolean, boolean, "plain">;
|
|
111
|
+
order: z<number, number, "plain">;
|
|
112
|
+
maxChars: z<number, number, "plain">;
|
|
113
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
114
|
+
enabled: z<boolean, boolean, "plain">;
|
|
115
|
+
guide: z<boolean, boolean, "plain">;
|
|
116
|
+
order: z<number, number, "plain">;
|
|
117
|
+
maxChars: z<number, number, "plain">;
|
|
118
|
+
}>>, "plain">;
|
|
101
119
|
/**
|
|
102
120
|
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
103
121
|
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
|
@@ -210,6 +228,24 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
210
228
|
promotionMinChars: z<number, number, "defined">;
|
|
211
229
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
212
230
|
injectionDirectoryLines: z<number, number, "defined">;
|
|
231
|
+
/**
|
|
232
|
+
* evolve v2 memory index: the section that injects memory CONTENT (not just
|
|
233
|
+
* an id/title directory) at session start, frozen per session to keep the
|
|
234
|
+
* system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
|
|
235
|
+
* `maxChars` default to true/true/400/6000; with `guide` off and an empty
|
|
236
|
+
* store the section renders to "" and costs no tokens.
|
|
237
|
+
*/
|
|
238
|
+
memoryIndex: z<Schemastery.ObjectS<NoInfer<{
|
|
239
|
+
enabled: z<boolean, boolean, "plain">;
|
|
240
|
+
guide: z<boolean, boolean, "plain">;
|
|
241
|
+
order: z<number, number, "plain">;
|
|
242
|
+
maxChars: z<number, number, "plain">;
|
|
243
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
244
|
+
enabled: z<boolean, boolean, "plain">;
|
|
245
|
+
guide: z<boolean, boolean, "plain">;
|
|
246
|
+
order: z<number, number, "plain">;
|
|
247
|
+
maxChars: z<number, number, "plain">;
|
|
248
|
+
}>>, "plain">;
|
|
213
249
|
/**
|
|
214
250
|
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
215
251
|
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
package/lib/index.js
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* initial default when `evolve/runtime.json` does not exist yet, and
|
|
9
9
|
* `/evolve pause|resume` own it afterwards. Generic review/planner/fate are
|
|
10
10
|
* not reachable from this listener.
|
|
11
|
+
*
|
|
12
|
+
* evolve v2 registers a third section (`evolve:memory-index`) that injects
|
|
13
|
+
* memory content plus the when_to_save guide, frozen per session — see
|
|
14
|
+
* memory-index.ts.
|
|
11
15
|
*/
|
|
12
16
|
import { join } from "node:path";
|
|
13
17
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -18,6 +22,7 @@ import { registerEvolveCommand } from "./command.js";
|
|
|
18
22
|
import { registerAutoReview } from "./auto.js";
|
|
19
23
|
import { syncSkillsFromResult } from "./skill.js";
|
|
20
24
|
import { entriesSectionText } from "./inject.js";
|
|
25
|
+
import { createFrozenMemorySection, DEFAULT_MEMORY_INDEX_MAX_CHARS, DEFAULT_MEMORY_SECTION_ORDER, MEMORY_SECTION_NAME, memoryIndexSectionText, } from "./memory-index.js";
|
|
21
26
|
import { resolveRubricKey } from "./rubric.js";
|
|
22
27
|
import { restoreMounted } from "./mount.js";
|
|
23
28
|
import { registerFileLogger } from "./logfile.js";
|
|
@@ -119,6 +124,19 @@ export const Config = z.object({
|
|
|
119
124
|
promotionMinChars: z.natural().default(100),
|
|
120
125
|
/** Entry-directory lines injected per build before folding into a counter. */
|
|
121
126
|
injectionDirectoryLines: z.natural().default(15),
|
|
127
|
+
/**
|
|
128
|
+
* evolve v2 memory index: the section that injects memory CONTENT (not just
|
|
129
|
+
* an id/title directory) at session start, frozen per session to keep the
|
|
130
|
+
* system prompt byte-stable (prompt cache). `enabled`/`guide`/`order`/
|
|
131
|
+
* `maxChars` default to true/true/400/6000; with `guide` off and an empty
|
|
132
|
+
* store the section renders to "" and costs no tokens.
|
|
133
|
+
*/
|
|
134
|
+
memoryIndex: z.object({
|
|
135
|
+
enabled: z.boolean(),
|
|
136
|
+
guide: z.boolean(),
|
|
137
|
+
order: z.natural(),
|
|
138
|
+
maxChars: z.natural(),
|
|
139
|
+
}),
|
|
122
140
|
/**
|
|
123
141
|
* Storage hygiene (#20): how much append-only past each write keeps.
|
|
124
142
|
* `snapshots` = full-state copies per store, `refinements` = tail lines
|
|
@@ -162,12 +180,39 @@ export function apply(ctx, config) {
|
|
|
162
180
|
// entries real delegation specs. The text is a provider evaluated at every
|
|
163
181
|
// assembly with the assembling agent; a store without prompt/subagent
|
|
164
182
|
// entries renders to "" and the prompt renderer drops the section.
|
|
183
|
+
//
|
|
184
|
+
// evolve v2: when the memory index section carries memory content, the
|
|
185
|
+
// directory stops listing memories so the same entries are never paid for
|
|
186
|
+
// twice (one fact, one home).
|
|
187
|
+
const memoryIndexEnabled = config.memoryIndex?.enabled ?? true;
|
|
165
188
|
ctx.systemPrompt.section({
|
|
166
189
|
name: "tool:continual-evolve:entries",
|
|
167
190
|
order: (config.sectionOrder ?? 118) + 1,
|
|
168
|
-
text: (context) => entriesSectionText(engine, context.agent, undefined, {
|
|
191
|
+
text: (context) => entriesSectionText(engine, context.agent, undefined, {
|
|
192
|
+
directoryLines: config.injectionDirectoryLines ?? 15,
|
|
193
|
+
includeMemoryDirectory: !memoryIndexEnabled,
|
|
194
|
+
}),
|
|
169
195
|
});
|
|
170
|
-
|
|
196
|
+
// evolve v2 passive read path: memory content in the prompt at session
|
|
197
|
+
// start, frozen per session (see memory-index.ts) so the whole system
|
|
198
|
+
// prompt stays byte-stable within a session and prompt-cache reads keep
|
|
199
|
+
// hitting. Registered in the empty 1–499 order slot — the two sections
|
|
200
|
+
// above sit at 118/119, upstream's named slots start at PLAN_POLICY=500.
|
|
201
|
+
if (memoryIndexEnabled) {
|
|
202
|
+
const frozenMemories = createFrozenMemorySection();
|
|
203
|
+
const memoryIndexOrder = config.memoryIndex?.order ?? DEFAULT_MEMORY_SECTION_ORDER;
|
|
204
|
+
const memoryIndexMaxChars = config.memoryIndex?.maxChars ?? DEFAULT_MEMORY_INDEX_MAX_CHARS;
|
|
205
|
+
const memoryIndexGuide = config.memoryIndex?.guide ?? true;
|
|
206
|
+
ctx.systemPrompt.section({
|
|
207
|
+
name: MEMORY_SECTION_NAME,
|
|
208
|
+
order: memoryIndexOrder,
|
|
209
|
+
text: (context) => frozenMemories.textFor(context.agent, (agent) => memoryIndexSectionText(engine, agent, { maxChars: memoryIndexMaxChars, guide: memoryIndexGuide })),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
const gate = {
|
|
213
|
+
requireGlobalApproval: config.requireGlobalApproval ?? true,
|
|
214
|
+
...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
|
|
215
|
+
};
|
|
171
216
|
const promotionPolicy = resolvePromotionPolicy({
|
|
172
217
|
blockPatterns: config.promotionBlockPatterns,
|
|
173
218
|
minPromoteChars: config.promotionMinChars,
|
|
@@ -180,6 +225,7 @@ export function apply(ctx, config) {
|
|
|
180
225
|
autoCase: config.autoCase ?? true,
|
|
181
226
|
promotionPolicy,
|
|
182
227
|
autoReview: config.autoReview ?? false,
|
|
228
|
+
...(config.recordLanguage !== undefined ? { recordLanguage: config.recordLanguage } : {}),
|
|
183
229
|
});
|
|
184
230
|
// Plugin-owned file logging: every cordis log message lands in
|
|
185
231
|
// <baseDir>/evolve/plugin.log regardless of how dsh web was launched —
|
package/lib/inject.d.ts
CHANGED
|
@@ -158,6 +158,16 @@ export declare function rankedDirectoryEntries(maxLines: number, query: string |
|
|
|
158
158
|
* beyond the nearest carrying store).
|
|
159
159
|
*/
|
|
160
160
|
export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, agent: AgentLike): HarnessState | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* The single three-store merge every injection path reads from: the global
|
|
163
|
+
* store merged with this project's store (when the session cwd resolves one)
|
|
164
|
+
* and the nearest carrying local store (precedence global < project < local —
|
|
165
|
+
* see {@link mergeHarnessStates}). One home, so the memory section and the
|
|
166
|
+
* entry directory can never disagree about what the model can see.
|
|
167
|
+
*/
|
|
168
|
+
export declare function mergedInjectionState(engine: EvolutionEngine, agent: AgentLike, opts?: {
|
|
169
|
+
projectKey?: string;
|
|
170
|
+
}): HarnessState;
|
|
161
171
|
/**
|
|
162
172
|
* Compose the full injected block for one assembling agent: global entries
|
|
163
173
|
* merged with this project's store (when the session cwd resolves one) and
|
|
@@ -171,6 +181,9 @@ export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, ag
|
|
|
171
181
|
* `opts.directoryLines` caps the entry-directory index (2026-08-22 throttle);
|
|
172
182
|
* `opts.projectKey` pins the project layer explicitly (tests, tools) —
|
|
173
183
|
* otherwise it is derived from the agent's session cwd, best-effort.
|
|
184
|
+
* `opts.includeMemoryDirectory` (default true) drops memory lines from the
|
|
185
|
+
* directory when the memory section already carries them (evolve v2) — one
|
|
186
|
+
* fact, one home, so the same memories are never paid for twice.
|
|
174
187
|
* Usage recording covers ALL kinds — memories and skills appear as directory
|
|
175
188
|
* lines, prompts/subagents as content — and is deduped per session so the
|
|
176
189
|
* counts read "how many sessions saw this", not "how many prompt builds".
|
|
@@ -178,5 +191,6 @@ export declare function nearestLocalStateWithEntries(engine: EvolutionEngine, ag
|
|
|
178
191
|
export declare function entriesSectionText(engine: EvolutionEngine, agent: AgentLike | undefined, query?: string, opts?: {
|
|
179
192
|
directoryLines?: number;
|
|
180
193
|
projectKey?: string;
|
|
194
|
+
includeMemoryDirectory?: boolean;
|
|
181
195
|
}): string;
|
|
182
196
|
//# sourceMappingURL=inject.d.ts.map
|
package/lib/inject.js
CHANGED
|
@@ -324,6 +324,20 @@ export function nearestLocalStateWithEntries(engine, agent) {
|
|
|
324
324
|
}
|
|
325
325
|
return undefined;
|
|
326
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* The single three-store merge every injection path reads from: the global
|
|
329
|
+
* store merged with this project's store (when the session cwd resolves one)
|
|
330
|
+
* and the nearest carrying local store (precedence global < project < local —
|
|
331
|
+
* see {@link mergeHarnessStates}). One home, so the memory section and the
|
|
332
|
+
* entry directory can never disagree about what the model can see.
|
|
333
|
+
*/
|
|
334
|
+
export function mergedInjectionState(engine, agent, opts) {
|
|
335
|
+
const globalState = engine.load("global", undefined);
|
|
336
|
+
const projectKey = opts?.projectKey ?? projectKeyOf(agent);
|
|
337
|
+
const projectState = projectKey ? engine.load("project", projectKey) : undefined;
|
|
338
|
+
const localState = nearestLocalStateWithEntries(engine, agent);
|
|
339
|
+
return mergeHarnessStates(globalState, localState, projectState ? { projectState } : undefined);
|
|
340
|
+
}
|
|
327
341
|
/**
|
|
328
342
|
* Compose the full injected block for one assembling agent: global entries
|
|
329
343
|
* merged with this project's store (when the session cwd resolves one) and
|
|
@@ -337,6 +351,9 @@ export function nearestLocalStateWithEntries(engine, agent) {
|
|
|
337
351
|
* `opts.directoryLines` caps the entry-directory index (2026-08-22 throttle);
|
|
338
352
|
* `opts.projectKey` pins the project layer explicitly (tests, tools) —
|
|
339
353
|
* otherwise it is derived from the agent's session cwd, best-effort.
|
|
354
|
+
* `opts.includeMemoryDirectory` (default true) drops memory lines from the
|
|
355
|
+
* directory when the memory section already carries them (evolve v2) — one
|
|
356
|
+
* fact, one home, so the same memories are never paid for twice.
|
|
340
357
|
* Usage recording covers ALL kinds — memories and skills appear as directory
|
|
341
358
|
* lines, prompts/subagents as content — and is deduped per session so the
|
|
342
359
|
* counts read "how many sessions saw this", not "how many prompt builds".
|
|
@@ -345,11 +362,7 @@ export function entriesSectionText(engine, agent, query, opts) {
|
|
|
345
362
|
if (!agent) {
|
|
346
363
|
return "";
|
|
347
364
|
}
|
|
348
|
-
const
|
|
349
|
-
const projectKey = opts?.projectKey ?? projectKeyOf(agent);
|
|
350
|
-
const projectState = projectKey ? engine.load("project", projectKey) : undefined;
|
|
351
|
-
const localState = nearestLocalStateWithEntries(engine, agent);
|
|
352
|
-
const merged = mergeHarnessStates(globalState, localState, projectState ? { projectState } : undefined);
|
|
365
|
+
const merged = mergedInjectionState(engine, agent, opts);
|
|
353
366
|
const promptEntries = Object.values(merged.entries.prompt);
|
|
354
367
|
const subagentEntries = Object.values(merged.entries.subagent);
|
|
355
368
|
const relevanceQuery = (query ?? recentUserText(agent)).trim();
|
|
@@ -371,7 +384,9 @@ export function entriesSectionText(engine, agent, query, opts) {
|
|
|
371
384
|
const directoryLines = opts?.directoryLines ?? DEFAULT_DIRECTORY_LINES;
|
|
372
385
|
const promptKind = Object.values(merged.entries.prompt);
|
|
373
386
|
const subagentKind = Object.values(merged.entries.subagent);
|
|
374
|
-
|
|
387
|
+
// v2: memories live in their own content-carrying section, so the directory
|
|
388
|
+
// stops listing them (passing [] keeps the positional contract intact).
|
|
389
|
+
const memoryKind = (opts?.includeMemoryDirectory ?? true) ? Object.values(merged.entries.memory) : [];
|
|
375
390
|
const skillKind = Object.values(merged.entries.skill);
|
|
376
391
|
const directoryText = formatEntriesDirectoryRanked(directoryLines, relevanceQuery, promptKind, subagentKind, memoryKind, skillKind);
|
|
377
392
|
// Directory-visible keys count too: a memory's injection IS its directory
|
package/lib/memory-agent.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { type LlmSessionId } from "./llm-text.js";
|
|
|
15
15
|
import { type PrefixCacheOptions } from "./prefix-cache.js";
|
|
16
16
|
import { type EvolutionEngine } from "./service.js";
|
|
17
17
|
import { type TokenUsageTarget } from "./token-usage.js";
|
|
18
|
-
import { type RecordLanguage } from "./record-language.js";
|
|
18
|
+
import { type RecordLanguage, type RecordLanguagePreference } from "./record-language.js";
|
|
19
19
|
import type { EntrySource, HarnessEntry, HarnessScope, HarnessState, RefinementEdit, RefinementProposal, RefinementResult } from "./types.js";
|
|
20
20
|
/** ZCode parity: the extractor may inspect memory and propose at most five times. */
|
|
21
21
|
export declare const MEMORY_AGENT_MAX_TURNS = 5;
|
|
@@ -110,6 +110,8 @@ export interface ApplyMemoryProposalOptions {
|
|
|
110
110
|
scopeDecisions?: Readonly<Record<string, ScopeApprovalDecision>>;
|
|
111
111
|
/** Persist each decision before a later scope can fail. */
|
|
112
112
|
onScopeDecision?: (key: string, decision: ScopeApprovalDecision) => void;
|
|
113
|
+
/** Plugin `recordLanguage` preference for the approval dialog copy. */
|
|
114
|
+
recordLanguage?: RecordLanguagePreference;
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* Render the authoritative persistent-scope diff shown to the human. Model
|
package/lib/memory-agent.js
CHANGED
|
@@ -362,7 +362,7 @@ export async function applyMemoryExtractionProposal(ctx, engine, proposal, optio
|
|
|
362
362
|
declinedScopes.push(scope);
|
|
363
363
|
continue;
|
|
364
364
|
}
|
|
365
|
-
const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline));
|
|
365
|
+
const approved = await requestScopeApproval(ctx, options.agent, options.signal, scope, renderMemoryApprovalDetails(proposal, scope, baseline), options.recordLanguage);
|
|
366
366
|
if (decisionKey !== undefined)
|
|
367
367
|
options.onScopeDecision?.(decisionKey, approved);
|
|
368
368
|
if (approved === "approved")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistent-memory guide injected as part of the `evolve:memory-index`
|
|
3
|
+
* system-prompt section (evolve v2, change two).
|
|
4
|
+
*
|
|
5
|
+
* This text is the write-path half of the v2 plan: it teaches the model when a
|
|
6
|
+
* durable memory is worth creating, which recall type it belongs to, what body
|
|
7
|
+
* structure each type owes, and that the main session may write directly with
|
|
8
|
+
* `evolve_add` instead of waiting for the background pipeline.
|
|
9
|
+
*
|
|
10
|
+
* Provenance: `docs/research/evolve-v2-passive-read-write-plan.md` §改动二
|
|
11
|
+
* (Chinese full text, adapted from ZCode's persistent-memory prompt). Kept in
|
|
12
|
+
* its own module so the wording is a single home — `memory-index.ts` only
|
|
13
|
+
* composes it with the `<memories>` block.
|
|
14
|
+
*/
|
|
15
|
+
/** Heading and framing before the `<memories>` block. */
|
|
16
|
+
export declare const MEMORY_GUIDE_INTRO = "# \u6301\u4E45\u8BB0\u5FC6\n\n\u4F60\u6709\u4E00\u4E2A\u8DE8\u4F1A\u8BDD\u7684\u6301\u4E45\u8BB0\u5FC6\u7CFB\u7EDF\u3002\u4EE5\u4E0B\u662F\u5F53\u524D\u8BB0\u5FC6\uFF08\u5DF2\u76F4\u63A5\u6CE8\u5165\uFF0C\u65E0\u9700\u67E5\u8BE2\u5373\u53EF\u4F7F\u7528\uFF1B\u9700\u8981\u68C0\u7D22\u5B8C\u6574\u5185\u5BB9\u6216\u786E\u8BA4\u662F\u5426\u6709\u76F8\u5173\u8BB0\u5FC6\u65F6\uFF0C\u8C03\u7528 evolve_recall\uFF09\u3002\u672C\u4F1A\u8BDD\u5185\u53EF\u4EE5\u76F4\u63A5\u7528 evolve_add \u843D\u76D8\uFF0C\u4E0D\u5FC5\u7B49\u540E\u53F0\u6D41\u7A0B\u2014\u2014\u9ED8\u8BA4\u5199 local\uFF0C\u53EA\u6709\u7A33\u5B9A\u7684\u8DE8\u9879\u76EE\u6559\u8BAD\u624D\u5199 global\uFF08global \u5199\u5165\u4F1A\u5F39\u4EBA\u5DE5\u6279\u51C6\uFF09\u3002";
|
|
17
|
+
/** The when_to_save / how_to_use rules and maintenance discipline. */
|
|
18
|
+
export declare const MEMORY_GUIDE_RULES = "## \u4F55\u65F6\u5199\u5165\u8BB0\u5FC6\n\n\u7528\u6237\u660E\u786E\u8981\u6C42\u8BB0\u4F4F\u65F6\uFF0C\u7ACB\u5373\u5B58\u4E3A\u6700\u5408\u9002\u7684\u7C7B\u578B\uFF1B\u8981\u6C42\u5FD8\u8BB0\u65F6\uFF0C\u627E\u5230\u5E76\u5220\u9664\u76F8\u5173\u6761\u76EE\u3002\n\u9664\u6B64\u4E4B\u5916\uFF0C\u5728\u5BF9\u8BDD\u4E2D\u81EA\u7136\u9047\u5230\u4EE5\u4E0B\u4FE1\u53F7\u65F6\u4E3B\u52A8\u5199\u5165\u3002\u5B58\u4E4B\u524D\u5148\u8FC7\u4E00\u9053\u7B5B\u5B50\uFF1A\n**\u80FD\u4ECE\u4EE3\u7801\u3001git \u5386\u53F2\u3001\u4ED3\u5E93\u6587\u4EF6\u91CC\u91CD\u65B0\u63A8\u5BFC\u51FA\u6765\u7684\u4E8B\u5B9E\u4E0D\u5B58**\u2014\u2014\u53EA\u5B58\u91CD\u63A8\u5BFC\u4E0D\u51FA\u6765\u7684\n\u80CC\u666F\u3001\u51B3\u7B56\u548C\u504F\u597D\u3002\u76F8\u5BF9\u65E5\u671F\u4E00\u5F8B\u8F6C\u6210\u7EDD\u5BF9\u65E5\u671F\uFF08\"\u5468\u56DB\"\u2192\"2026-10-08\"\uFF09\uFF0C\u5426\u5219\u672A\u6765\n\u7684\u4F1A\u8BDD\u65E0\u6CD5\u89E3\u8BFB\u3002\u4E00\u6761\u4E8B\u5B9E\u4E00\u4E2A\u6761\u76EE\uFF1B\u4E0D\u786E\u5B9A\u503C\u4E0D\u503C\u5F97\u5B58\u65F6\uFF0C\u95EE\u81EA\u5DF1\"\u8FD9\u6761\u7684\u4FE1\u606F\u91CF\n\u662F\u5426\u5728'\u4E3A\u4EC0\u4E48'\u91CC\uFF0C\u800C\u4E0D\u5728'\u662F\u4EC0\u4E48'\u91CC\"\u3002\n\n<type name=\"user\">\n <when_to_save>\u5F97\u77E5\u7528\u6237\u7684\u89D2\u8272\u3001\u6280\u672F\u6808\u3001\u7ECF\u9A8C\u6C34\u5E73\u3001\u504F\u597D\u6216\u77E5\u8BC6\u80CC\u666F\u7684\u4EFB\u4F55\u7EC6\u8282\u65F6\u3002\n \u4F8B\u5982\u7528\u6237\u81EA\u8FF0\"\u6211\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\u5728\u67E5\u65E5\u5FD7\u7CFB\u7EDF\"\u2014\u2014\u5B58\uFF1A\u7528\u6237\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\n \u5F53\u524D\u5173\u6CE8\u53EF\u89C2\u6D4B\u6027/\u65E5\u5FD7\u3002</when_to_save>\n <how_to_use>\u540E\u7EED\u89E3\u91CA\u548C\u5EFA\u8BAE\u6309\u7528\u6237\u7684\u80CC\u666F\u5B9A\u5236\u6DF1\u6D45\u4E0E\u7C7B\u6BD4\uFF1B\u5BF9\u8D44\u6DF1\u5DE5\u7A0B\u5E08\n \u4E0D\u89E3\u91CA\u57FA\u7840\u6982\u5FF5\uFF0C\u5BF9\u67D0\u9886\u57DF\u7684\u65B0\u624B\u7528\u5176\u719F\u6089\u9886\u57DF\u7684\u7C7B\u6BD4\u5207\u5165\u3002</how_to_use>\n</type>\n\n<type name=\"feedback\">\n <when_to_save>\u4E24\u6761\u4FE1\u53F7\uFF0C\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n \uFF081\uFF09\u7EA0\u6B63\u2014\u2014\u7528\u6237\u8BF4\"\u4E0D\u662F\u8FD9\u6837\"\"\u522B\"\"\u505C\u4E0B\"\u65F6\uFF0C\u5B58\u88AB\u7EA0\u6B63\u7684\u505A\u6CD5\u4E0E\u6B63\u786E\u505A\u6CD5\uFF1B\n \uFF082\uFF09\u786E\u8BA4\u2014\u2014\u8FD9\u662F\u66F4\u5B89\u9759\u3001\u66F4\u5BB9\u6613\u6F0F\u7684\u4FE1\u53F7\uFF1A\u7528\u6237\u63A5\u53D7\u4E86\u4F60\u4E0D\u5BFB\u5E38\u7684\u9009\u62E9\u800C\u6CA1\u6709\u5F02\u8BAE\uFF0C\n \u6216\u660E\u786E\u8BF4\"\u5BF9\uFF0C\u5C31\u8FD9\u6837\"\"\u4FDD\u6301\u8FD9\u6837\"\u3002\u786E\u8BA4\u8FC7\u7684\u5224\u65AD\u662F\u5DF2\u9A8C\u8BC1\u7684\u65B9\u6CD5\u8BBA\uFF0C\u53EA\u5B58\u7EA0\u9519\n \u4F1A\u8BA9\u4F60\u7684\u884C\u4E3A\u8D8A\u6765\u8D8A\u4FDD\u5B88\u3001\u504F\u79BB\u7528\u6237\u5DF2\u8BA4\u53EF\u7684\u8DEF\u3002\n \u4F8B\uFF1A\"\u6D4B\u8BD5\u522B mock \u6570\u636E\u5E93\uFF0C\u4E0A\u6B21 mock \u8FC7\u4E86\u7EBF\u4E0A\u8FC1\u79FB\u6302\u4E86\"\u2014\u2014\u5B58\u89C4\u5219\u5E76\u5E26\u4E0A\u4E8B\u6545\u539F\u56E0\u3002\n \u4F8B\uFF1A\"\u7ED3\u5C3E\u4E0D\u7528\u603B\u7ED3\uFF0Cdiff \u6211\u81EA\u5DF1\u4F1A\u770B\"\u2014\u2014\u5B58\uFF1A\u8BE5\u7528\u6237\u4E0D\u8981\u5C3E\u968F\u6027\u603B\u7ED3\u3002</when_to_save>\n <how_to_use>\u8BA9\u7528\u6237\u4E0D\u5FC5\u628A\u540C\u4E00\u6761\u6307\u5BFC\u8BF4\u7B2C\u4E8C\u904D\u3002</how_to_use>\n <body_structure>\u89C4\u5219\u5148\u884C\uFF0C\u968F\u540E\u5FC5\u5E26\u4E24\u884C\uFF1A**Why:**\uFF08\u7528\u6237\u7ED9\u7684\u7406\u7531\uFF0C\u5E38\u662F\u67D0\u6B21\n \u4E8B\u6545\u6216\u5F3A\u504F\u597D\u2014\u2014\u77E5\u9053\u4E3A\u4EC0\u4E48\u624D\u80FD\u5224\u65AD\u8FB9\u7F18\u60C5\u51B5\u8BE5\u4E0D\u8BE5\u7834\u4F8B\uFF09\u548C **How to apply:**\n \uFF08\u4F55\u65F6\u4F55\u5730\u751F\u6548\uFF09\u3002feedback \u7C7B\u578B\u7F3A\u8FD9\u4E24\u884C\u89C6\u4E3A\u4E0D\u5B8C\u6574\u3002</body_structure>\n</type>\n\n<type name=\"project\">\n <when_to_save>\u5F97\u77E5\u8FDB\u884C\u4E2D\u7684\u5DE5\u4F5C\u3001\u76EE\u6807\u3001\u51B3\u7B56\u3001\u622A\u6B62\u671F\u6216\u4E8B\u6545\u7684\u80CC\u666F\u52A8\u673A\u65F6\u2014\u2014\n \u51E1\u662F\"\u8C01\u5728\u505A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u3001\u5230\u4EC0\u4E48\u65F6\u5019\"\u4E14\u65E0\u6CD5\u4ECE\u4EE3\u7801\u6216 git \u63A8\u5BFC\u7684\u4FE1\u606F\u3002\n \u8FD9\u7C7B\u8BB0\u5FC6\u8870\u51CF\u5FEB\uFF0C\u72B6\u6001\u53D8\u5316\u65F6\u66F4\u65B0\u5B83\uFF0C\u522B\u5806\u53E0\u65B0\u6761\u76EE\u3002</when_to_save>\n <how_to_use>\u7528\u5B83\u8865\u5168\u8BF7\u6C42\u80CC\u540E\u7684\u4E0A\u4E0B\u6587\u4E0E\u52A8\u673A\uFF0C\u8BA9\u5EFA\u8BAE\u8D34\u4F4F\u771F\u5B9E\u7EA6\u675F\u3002\n \u4F8B\uFF1A\"\u5468\u56DB\u8D77\u51BB\u7ED3\u5408\u5E76\"\u2014\u2014\u5B58\uFF1A2026-03-05 \u8D77\u51BB\u7ED3\u5408\u5E76\uFF0C\u975E\u5173\u952E PR \u63D0\u524D\u6807\u8BB0\u3002</how_to_use>\n <body_structure>\u4E8B\u5B9E/\u51B3\u7B56\u5148\u884C + **Why:**\uFF08\u52A8\u673A\uFF1A\u7EA6\u675F\u3001deadline\u3001\u5E72\u7CFB\u4EBA\u8981\u6C42\uFF09\n + **How to apply:**\uFF08\u8FD9\u6761\u5982\u4F55\u5F71\u54CD\u540E\u7EED\u5EFA\u8BAE\uFF09\u3002</body_structure>\n</type>\n\n<type name=\"reference\">\n <when_to_save>\u7528\u6237\u63D0\u5230\u672A\u6765\u4F1A\u7528\u5230\u7684\u5916\u90E8\u8D44\u6E90\u6307\u9488\uFF1AURL\u3001\u4EEA\u8868\u76D8\u3001\u5DE5\u5355\u3001\n \u6587\u6863\u4F4D\u7F6E\u3002</when_to_save>\n <how_to_use>\u9700\u8981\u65F6\u76F4\u63A5\u53D6\u7528\uFF0C\u4E0D\u91CD\u65B0\u68C0\u7D22\u3002</how_to_use>\n</type>\n\n## \u5982\u4F55\u7EF4\u62A4\n\n- \u66F4\u65B0\u4F18\u5148\u4E8E\u65B0\u5EFA\uFF1A\u72B6\u6001\u53D8\u4E86\u6539\u539F\u6761\u76EE\uFF0C\u4E0D\u53E6\u8D77\u4E00\u6761\uFF08\u65B0\u65E7\u5E76\u5B58\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\uFF09\u3002\n- \u6BCF\u6761\u7684 description \u662F\u7ED9\u672A\u6765\u4F1A\u8BDD\u7684\u76F8\u5173\u6027 hook\u2014\u2014\u5199\"\u51B3\u5B9A\u4E86\u4E00\u6761\u8BB0\u5FC6\u4F1A\u4E0D\u4F1A\n \u88AB\u60F3\u8D77\"\u7684\u4E00\u53E5\u8BDD\uFF0C\u4E0D\u5199\u6D41\u6C34\u8D26\u3002\n- [[\u540D\u5B57]] \u4E92\u76F8\u5F15\u7528\u76F8\u5173\u8BB0\u5FC6\uFF1B\u5F15\u7528\u8FD8\u4E0D\u5B58\u5728\u7684\u540D\u5B57\u662F\u5408\u6CD5\u7684\uFF0C\u5B83\u6807\u8BB0\"\u503C\u5F97\u5C06\u6765\u8865\u5199\"\u3002";
|
|
19
|
+
//# sourceMappingURL=memory-guide.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The persistent-memory guide injected as part of the `evolve:memory-index`
|
|
3
|
+
* system-prompt section (evolve v2, change two).
|
|
4
|
+
*
|
|
5
|
+
* This text is the write-path half of the v2 plan: it teaches the model when a
|
|
6
|
+
* durable memory is worth creating, which recall type it belongs to, what body
|
|
7
|
+
* structure each type owes, and that the main session may write directly with
|
|
8
|
+
* `evolve_add` instead of waiting for the background pipeline.
|
|
9
|
+
*
|
|
10
|
+
* Provenance: `docs/research/evolve-v2-passive-read-write-plan.md` §改动二
|
|
11
|
+
* (Chinese full text, adapted from ZCode's persistent-memory prompt). Kept in
|
|
12
|
+
* its own module so the wording is a single home — `memory-index.ts` only
|
|
13
|
+
* composes it with the `<memories>` block.
|
|
14
|
+
*/
|
|
15
|
+
/** Heading and framing before the `<memories>` block. */
|
|
16
|
+
export const MEMORY_GUIDE_INTRO = `# 持久记忆
|
|
17
|
+
|
|
18
|
+
你有一个跨会话的持久记忆系统。以下是当前记忆(已直接注入,无需查询即可使用;需要检索完整内容或确认是否有相关记忆时,调用 evolve_recall)。本会话内可以直接用 evolve_add 落盘,不必等后台流程——默认写 local,只有稳定的跨项目教训才写 global(global 写入会弹人工批准)。`;
|
|
19
|
+
/** The when_to_save / how_to_use rules and maintenance discipline. */
|
|
20
|
+
export const MEMORY_GUIDE_RULES = `## 何时写入记忆
|
|
21
|
+
|
|
22
|
+
用户明确要求记住时,立即存为最合适的类型;要求忘记时,找到并删除相关条目。
|
|
23
|
+
除此之外,在对话中自然遇到以下信号时主动写入。存之前先过一道筛子:
|
|
24
|
+
**能从代码、git 历史、仓库文件里重新推导出来的事实不存**——只存重推导不出来的
|
|
25
|
+
背景、决策和偏好。相对日期一律转成绝对日期("周四"→"2026-10-08"),否则未来
|
|
26
|
+
的会话无法解读。一条事实一个条目;不确定值不值得存时,问自己"这条的信息量
|
|
27
|
+
是否在'为什么'里,而不在'是什么'里"。
|
|
28
|
+
|
|
29
|
+
<type name="user">
|
|
30
|
+
<when_to_save>得知用户的角色、技术栈、经验水平、偏好或知识背景的任何细节时。
|
|
31
|
+
例如用户自述"我是数据科学家,在查日志系统"——存:用户是数据科学家,
|
|
32
|
+
当前关注可观测性/日志。</when_to_save>
|
|
33
|
+
<how_to_use>后续解释和建议按用户的背景定制深浅与类比;对资深工程师
|
|
34
|
+
不解释基础概念,对某领域的新手用其熟悉领域的类比切入。</how_to_use>
|
|
35
|
+
</type>
|
|
36
|
+
|
|
37
|
+
<type name="feedback">
|
|
38
|
+
<when_to_save>两条信号,缺一不可:
|
|
39
|
+
(1)纠正——用户说"不是这样""别""停下"时,存被纠正的做法与正确做法;
|
|
40
|
+
(2)确认——这是更安静、更容易漏的信号:用户接受了你不寻常的选择而没有异议,
|
|
41
|
+
或明确说"对,就这样""保持这样"。确认过的判断是已验证的方法论,只存纠错
|
|
42
|
+
会让你的行为越来越保守、偏离用户已认可的路。
|
|
43
|
+
例:"测试别 mock 数据库,上次 mock 过了线上迁移挂了"——存规则并带上事故原因。
|
|
44
|
+
例:"结尾不用总结,diff 我自己会看"——存:该用户不要尾随性总结。</when_to_save>
|
|
45
|
+
<how_to_use>让用户不必把同一条指导说第二遍。</how_to_use>
|
|
46
|
+
<body_structure>规则先行,随后必带两行:**Why:**(用户给的理由,常是某次
|
|
47
|
+
事故或强偏好——知道为什么才能判断边缘情况该不该破例)和 **How to apply:**
|
|
48
|
+
(何时何地生效)。feedback 类型缺这两行视为不完整。</body_structure>
|
|
49
|
+
</type>
|
|
50
|
+
|
|
51
|
+
<type name="project">
|
|
52
|
+
<when_to_save>得知进行中的工作、目标、决策、截止期或事故的背景动机时——
|
|
53
|
+
凡是"谁在做什么、为什么、到什么时候"且无法从代码或 git 推导的信息。
|
|
54
|
+
这类记忆衰减快,状态变化时更新它,别堆叠新条目。</when_to_save>
|
|
55
|
+
<how_to_use>用它补全请求背后的上下文与动机,让建议贴住真实约束。
|
|
56
|
+
例:"周四起冻结合并"——存:2026-03-05 起冻结合并,非关键 PR 提前标记。</how_to_use>
|
|
57
|
+
<body_structure>事实/决策先行 + **Why:**(动机:约束、deadline、干系人要求)
|
|
58
|
+
+ **How to apply:**(这条如何影响后续建议)。</body_structure>
|
|
59
|
+
</type>
|
|
60
|
+
|
|
61
|
+
<type name="reference">
|
|
62
|
+
<when_to_save>用户提到未来会用到的外部资源指针:URL、仪表盘、工单、
|
|
63
|
+
文档位置。</when_to_save>
|
|
64
|
+
<how_to_use>需要时直接取用,不重新检索。</how_to_use>
|
|
65
|
+
</type>
|
|
66
|
+
|
|
67
|
+
## 如何维护
|
|
68
|
+
|
|
69
|
+
- 更新优先于新建:状态变了改原条目,不另起一条(新旧并存比没有记忆更糟)。
|
|
70
|
+
- 每条的 description 是给未来会话的相关性 hook——写"决定了一条记忆会不会
|
|
71
|
+
被想起"的一句话,不写流水账。
|
|
72
|
+
- [[名字]] 互相引用相关记忆;引用还不存在的名字是合法的,它标记"值得将来补写"。`;
|
|
73
|
+
//# sourceMappingURL=memory-guide.js.map
|