dsh-continual-evolve 0.11.0 → 0.12.0

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.
Files changed (154) hide show
  1. package/README.md +35 -115
  2. package/README.zh.md +26 -93
  3. package/cordis.patch.yml +2 -2
  4. package/lib/index.d.ts +44 -225
  5. package/lib/index.js +30 -239
  6. package/lib/memory-guide.d.ts +13 -14
  7. package/lib/memory-guide.js +32 -21
  8. package/lib/memory-section.d.ts +77 -0
  9. package/lib/memory-section.js +189 -0
  10. package/package.json +2 -17
  11. package/lib/apply.d.ts +0 -22
  12. package/lib/apply.js +0 -157
  13. package/lib/approval.d.ts +0 -73
  14. package/lib/approval.js +0 -127
  15. package/lib/audit-log.d.ts +0 -19
  16. package/lib/audit-log.js +0 -52
  17. package/lib/auto.d.ts +0 -236
  18. package/lib/auto.js +0 -773
  19. package/lib/autocase.d.ts +0 -38
  20. package/lib/autocase.js +0 -76
  21. package/lib/benchmark-command.d.ts +0 -9
  22. package/lib/benchmark-command.js +0 -357
  23. package/lib/benchmark.d.ts +0 -151
  24. package/lib/benchmark.js +0 -250
  25. package/lib/command-util.d.ts +0 -70
  26. package/lib/command-util.js +0 -123
  27. package/lib/command.d.ts +0 -13
  28. package/lib/command.js +0 -134
  29. package/lib/consolidate.d.ts +0 -90
  30. package/lib/consolidate.js +0 -180
  31. package/lib/copy.d.ts +0 -37
  32. package/lib/copy.js +0 -58
  33. package/lib/declines.d.ts +0 -103
  34. package/lib/declines.js +0 -179
  35. package/lib/evaluate.d.ts +0 -74
  36. package/lib/evaluate.js +0 -271
  37. package/lib/evolve-event.d.ts +0 -40
  38. package/lib/evolve-event.js +0 -60
  39. package/lib/extraction-phase.d.ts +0 -132
  40. package/lib/extraction-phase.js +0 -236
  41. package/lib/failures.d.ts +0 -42
  42. package/lib/failures.js +0 -171
  43. package/lib/fate.d.ts +0 -130
  44. package/lib/fate.js +0 -350
  45. package/lib/goal-command.d.ts +0 -7
  46. package/lib/goal-command.js +0 -37
  47. package/lib/goal.d.ts +0 -72
  48. package/lib/goal.js +0 -72
  49. package/lib/inject.d.ts +0 -90
  50. package/lib/inject.js +0 -164
  51. package/lib/injection-caps.d.ts +0 -4
  52. package/lib/injection-caps.js +0 -4
  53. package/lib/injection-directory.d.ts +0 -29
  54. package/lib/injection-directory.js +0 -78
  55. package/lib/injection-rank.d.ts +0 -85
  56. package/lib/injection-rank.js +0 -161
  57. package/lib/listener.d.ts +0 -45
  58. package/lib/listener.js +0 -199
  59. package/lib/llm-text.d.ts +0 -104
  60. package/lib/llm-text.js +0 -140
  61. package/lib/logfile.d.ts +0 -71
  62. package/lib/logfile.js +0 -159
  63. package/lib/maintenance-commands.d.ts +0 -23
  64. package/lib/maintenance-commands.js +0 -136
  65. package/lib/memory-agent.d.ts +0 -93
  66. package/lib/memory-agent.js +0 -300
  67. package/lib/memory-apply.d.ts +0 -44
  68. package/lib/memory-apply.js +0 -202
  69. package/lib/memory-benchmark.d.ts +0 -76
  70. package/lib/memory-benchmark.js +0 -170
  71. package/lib/memory-index.d.ts +0 -114
  72. package/lib/memory-index.js +0 -197
  73. package/lib/memory-manifest.d.ts +0 -16
  74. package/lib/memory-manifest.js +0 -70
  75. package/lib/message-source.d.ts +0 -23
  76. package/lib/message-source.js +0 -12
  77. package/lib/mount-command.d.ts +0 -10
  78. package/lib/mount-command.js +0 -48
  79. package/lib/mount.d.ts +0 -42
  80. package/lib/mount.js +0 -212
  81. package/lib/notify.d.ts +0 -48
  82. package/lib/notify.js +0 -57
  83. package/lib/plan-command.d.ts +0 -12
  84. package/lib/plan-command.js +0 -38
  85. package/lib/plan.d.ts +0 -16
  86. package/lib/plan.js +0 -126
  87. package/lib/planner.d.ts +0 -61
  88. package/lib/planner.js +0 -144
  89. package/lib/pool.d.ts +0 -7
  90. package/lib/pool.js +0 -25
  91. package/lib/prefix-cache.d.ts +0 -79
  92. package/lib/prefix-cache.js +0 -168
  93. package/lib/project.d.ts +0 -28
  94. package/lib/project.js +0 -66
  95. package/lib/projection.d.ts +0 -22
  96. package/lib/projection.js +0 -112
  97. package/lib/promotion.d.ts +0 -94
  98. package/lib/promotion.js +0 -171
  99. package/lib/recall.d.ts +0 -85
  100. package/lib/recall.js +0 -142
  101. package/lib/record-language.d.ts +0 -85
  102. package/lib/record-language.js +0 -152
  103. package/lib/render.d.ts +0 -13
  104. package/lib/render.js +0 -80
  105. package/lib/review-scheduler.d.ts +0 -38
  106. package/lib/review-scheduler.js +0 -183
  107. package/lib/review.d.ts +0 -60
  108. package/lib/review.js +0 -145
  109. package/lib/rollback.d.ts +0 -9
  110. package/lib/rollback.js +0 -68
  111. package/lib/rubric.d.ts +0 -29
  112. package/lib/rubric.js +0 -119
  113. package/lib/runtime-commands.d.ts +0 -14
  114. package/lib/runtime-commands.js +0 -115
  115. package/lib/runtime.d.ts +0 -24
  116. package/lib/runtime.js +0 -87
  117. package/lib/score.d.ts +0 -64
  118. package/lib/score.js +0 -191
  119. package/lib/search.d.ts +0 -83
  120. package/lib/search.js +0 -136
  121. package/lib/service.d.ts +0 -63
  122. package/lib/service.js +0 -253
  123. package/lib/skill-render.d.ts +0 -23
  124. package/lib/skill-render.js +0 -69
  125. package/lib/skill.d.ts +0 -15
  126. package/lib/skill.js +0 -80
  127. package/lib/skillquality.d.ts +0 -85
  128. package/lib/skillquality.js +0 -320
  129. package/lib/source.d.ts +0 -30
  130. package/lib/source.js +0 -43
  131. package/lib/state.d.ts +0 -26
  132. package/lib/state.js +0 -151
  133. package/lib/store.d.ts +0 -61
  134. package/lib/store.js +0 -164
  135. package/lib/token-usage.d.ts +0 -68
  136. package/lib/token-usage.js +0 -211
  137. package/lib/tool.d.ts +0 -34
  138. package/lib/tool.js +0 -320
  139. package/lib/turn-snapshot.d.ts +0 -78
  140. package/lib/turn-snapshot.js +0 -252
  141. package/lib/types.d.ts +0 -218
  142. package/lib/types.js +0 -133
  143. package/lib/usage.d.ts +0 -36
  144. package/lib/usage.js +0 -98
  145. package/lib/validate.d.ts +0 -28
  146. package/lib/validate.js +0 -181
  147. package/lib/wrapup-candidates.d.ts +0 -127
  148. package/lib/wrapup-candidates.js +0 -161
  149. package/lib/wrapup-command.d.ts +0 -10
  150. package/lib/wrapup-command.js +0 -243
  151. package/lib/wrapup-proposals.d.ts +0 -101
  152. package/lib/wrapup-proposals.js +0 -281
  153. package/lib/wrapup.d.ts +0 -56
  154. package/lib/wrapup.js +0 -105
package/lib/notify.d.ts DELETED
@@ -1,48 +0,0 @@
1
- /**
2
- * Memory-extraction visibility: the extractor works in the background —
3
- * the user would never see a persisted entry or the token spend. This
4
- * module queues a short follow-up turn after an applied extraction moment
5
- * so the user SEES what was persisted, how to inspect it, and how to roll
6
- * it back.
7
- *
8
- * The receipt is a `continual-evolve`-sourced user message (`agent.followup`),
9
- * so it is rendered in the session transcript like any other input and the
10
- * agent answers with a one-line confirmation. It never fakes tool or assistant
11
- * events, so session replay, the ordered surface, and derived history stay
12
- * untouched: the receipt is a plain `user/message` carrying this plugin's own
13
- * source kind.
14
- *
15
- * Every mechanical property stays in code: the receipt text is built from the
16
- * applied refinement batches, never from model text.
17
- */
18
- import type { Context } from "@deepseek-ai/cordis";
19
- import type { Agent } from "@deepseek-ai/dsh-agent";
20
- import type { RefinementResult } from "./types.js";
21
- /** Unified extraction receipt: one shape for applied / declined / no-op. */
22
- export interface MemoryReceipt {
23
- outcome: "applied" | "declined" | "noop";
24
- /** Applied refinement batches (one per scope) for applied outcomes. */
25
- results?: RefinementResult[];
26
- /** Scopes the human explicitly declined. */
27
- declinedScopes?: string[];
28
- /** Extractor loop turns / manifest searches / wall time. */
29
- turns: number;
30
- searches: number;
31
- durationMs: number;
32
- }
33
- /**
34
- * Compose the user-visible memory receipt from applied refinement batches
35
- * (never model text): every landed memory edit with its rollback command.
36
- * No-op and declined-only outcomes render one quiet line each — a foreground
37
- * notice for zero landed edits would be nagging, so callers only queue
38
- * follow-ups for applied outcomes.
39
- */
40
- export declare function buildMemoryReceipt(receipt: MemoryReceipt): string;
41
- /**
42
- * Queue the memory receipt follow-up. Same containment as the gate notice:
43
- * notification failure only logs. Callers gate on applied outcomes — no-op
44
- * and declined-only runs stay audit-only (reviews.jsonl) and never wake
45
- * the agent.
46
- */
47
- export declare function notifyMemoryExtraction(ctx: Context, agent: Agent, receipt: MemoryReceipt): void;
48
- //# sourceMappingURL=notify.d.ts.map
package/lib/notify.js DELETED
@@ -1,57 +0,0 @@
1
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
2
- import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
3
- /**
4
- * Compose the user-visible memory receipt from applied refinement batches
5
- * (never model text): every landed memory edit with its rollback command.
6
- * No-op and declined-only outcomes render one quiet line each — a foreground
7
- * notice for zero landed edits would be nagging, so callers only queue
8
- * follow-ups for applied outcomes.
9
- */
10
- export function buildMemoryReceipt(receipt) {
11
- const seconds = (receipt.durationMs / 1000).toFixed(1);
12
- const stats = `${receipt.turns} 轮推理 / ${receipt.searches} 次检索 / ${seconds}s`;
13
- if (receipt.outcome === "noop") {
14
- return `🧠 记忆提取:本轮无可沉淀的持久事实(no-op,${stats}),未写入任何条目。`;
15
- }
16
- const results = receipt.results ?? [];
17
- const declinedScopes = receipt.declinedScopes ?? [];
18
- if (receipt.outcome === "declined" || results.length === 0) {
19
- const scopes = declinedScopes.join("、") || "持久化作用域";
20
- return `🧠 记忆提取:${scopes}的写入未获批准,已跳过(${stats}),未写入任何条目。`;
21
- }
22
- const applied = results.flatMap((result) => result.appliedEdits.filter((edit) => edit.applied));
23
- const lines = applied.slice(0, 12).map((edit) => `- 记忆「${edit.title ?? edit.id}」(${edit.id})`);
24
- if (applied.length > 12)
25
- lines.push(`- …另有 ${applied.length - 12} 条(见 /evolve history)`);
26
- const declined = declinedScopes.length > 0 ? `\n另有作用域${declinedScopes.join("、")}未经批准(已跳过)。` : "";
27
- const rollbacks = [...new Set(results.map((result) => result.id))].map((id) => `/evolve rollback ${id}`).join(";");
28
- return [
29
- `🧠 记忆提取:本轮沉淀 ${applied.length} 条记忆(${results.length} 个作用域批次,${stats}):`,
30
- lines.length > 0 ? lines.join("\n") : "(无条目成功应用)",
31
- declined,
32
- `回看:/evolve recall <关键词>;回滚:${rollbacks}`,
33
- "请用一句话简短确认即可,不要调用任何工具。",
34
- ]
35
- .filter((part) => part !== "")
36
- .join("\n");
37
- }
38
- /**
39
- * Queue the memory receipt follow-up. Same containment as the gate notice:
40
- * notification failure only logs. Callers gate on applied outcomes — no-op
41
- * and declined-only runs stay audit-only (reviews.jsonl) and never wake
42
- * the agent.
43
- */
44
- export function notifyMemoryExtraction(ctx, agent, receipt) {
45
- try {
46
- agent.followup(createUserMessage({
47
- content: [{ type: "text", text: buildMemoryReceipt(receipt) }],
48
- source: EVOLVE_MESSAGE_SOURCE,
49
- }));
50
- }
51
- catch (cause) {
52
- ctx
53
- .logger("continual-evolve")
54
- .warn(`memory receipt notice failed for ${agent.id}: ${cause instanceof Error ? cause.message : String(cause)}`);
55
- }
56
- }
57
- //# sourceMappingURL=notify.js.map
@@ -1,12 +0,0 @@
1
- /**
2
- * `/evolve plan` — the manual LLM planner path: propose edits against one
3
- * store with the main-session model, ask for approval on persistent scopes,
4
- * and apply with the normal baseline checks.
5
- */
6
- import type { Context } from "@deepseek-ai/cordis";
7
- import type { CommandInvocation } from "@deepseek-ai/dsh-commands";
8
- import type { HarnessScope } from "./types.js";
9
- import type { EvolutionEngine } from "./service.js";
10
- import { type CommandGateOptions, type CommandRuntimeOptions, type CommandTextResult } from "./command-util.js";
11
- export declare function executePlanCommand(ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, scope: HarnessScope, after: string[], opts: CommandGateOptions, runtime: CommandRuntimeOptions): Promise<CommandTextResult>;
12
- //# sourceMappingURL=plan-command.d.ts.map
@@ -1,38 +0,0 @@
1
- import { join } from "node:path";
2
- import { planWithLlm } from "./planner.js";
3
- import { requireGlobalApproval } from "./approval.js";
4
- import { entrySourceOf } from "./source.js";
5
- import { renderResult, storeIdForCommand, success } from "./command-util.js";
6
- export async function executePlanCommand(ctx, engine, invocation, scope, after, opts, runtime) {
7
- const instructions = after.length > 0 ? after.join(" ") : undefined;
8
- const sessionId = invocation.agent.id;
9
- const state = engine.load(scope, storeIdForCommand(scope, invocation));
10
- const history = engine.history(scope, storeIdForCommand(scope, invocation));
11
- const proposal = await planWithLlm(ctx, {
12
- agent: invocation.agent,
13
- state,
14
- history,
15
- ...(instructions ? { instructions } : {}),
16
- global: scope === "global",
17
- signal: invocation.signal,
18
- tokenUsage: {
19
- baseDir: engine.baseDir,
20
- sessionId,
21
- retain: engine.retention.tokenUsage,
22
- onError: (cause) => ctx.logger("continual-evolve").warn(`token-usage ledger failed for ${sessionId}: ${cause instanceof Error ? cause.message : String(cause)}`),
23
- },
24
- // skill-creator template facts (fallback: builtin guide).
25
- skillsRoot: join(engine.baseDir, "skills"),
26
- });
27
- if ((scope === "global" || scope === "project") && opts.requireGlobalApproval && proposal.edits.length > 0) {
28
- await requireGlobalApproval(ctx, invocation.agent, invocation.signal, `/evolve plan ${scope} 将应用 ${proposal.edits.length} 条编辑到${scope === "project" ? "本项目" : "跨会话"} store:${proposal.summary}`, runtime.recordLanguage);
29
- }
30
- const source = entrySourceOf(invocation.agent, sessionId);
31
- const result = engine.apply(scope, storeIdForCommand(scope, invocation), proposal, {
32
- scope,
33
- baselineState: state,
34
- ...(source ? { source } : {}),
35
- });
36
- return success(renderResult(result));
37
- }
38
- //# sourceMappingURL=plan-command.js.map
package/lib/plan.d.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Parsing helpers for the model-produced proposal JSON. A planner reply is
3
- * untrusted text: it may carry prose, a fenced block, or be truncated by an
4
- * exhausted output budget. These helpers recover the JSON object when
5
- * possible and name the cause (truncation vs malformed) when not.
6
- */
7
- import type { RefinementProposal } from "./types.js";
8
- /** True when the text ends mid-string or with unclosed brackets. */
9
- export declare function isIncompleteJson(candidate: string): boolean;
10
- /** Parse a JSON candidate, distinguishing truncation from malformation. */
11
- export declare function parseJsonCandidate(candidate: string): unknown;
12
- /** Recover a JSON object from raw model text. */
13
- export declare function extractJsonObject(text: string): unknown;
14
- /** Parse and shape a proposal, dropping non-object edits. */
15
- export declare function parseProposal(text: string): RefinementProposal;
16
- //# sourceMappingURL=plan.d.ts.map
package/lib/plan.js DELETED
@@ -1,126 +0,0 @@
1
- /** True when the text ends mid-string or with unclosed brackets. */
2
- export function isIncompleteJson(candidate) {
3
- let depth = 0;
4
- let inString = false;
5
- let escaped = false;
6
- for (const char of candidate) {
7
- if (escaped) {
8
- escaped = false;
9
- continue;
10
- }
11
- if (inString) {
12
- if (char === "\\")
13
- escaped = true;
14
- else if (char === '"')
15
- inString = false;
16
- continue;
17
- }
18
- if (char === '"')
19
- inString = true;
20
- else if (char === "{" || char === "[")
21
- depth++;
22
- else if (char === "}" || char === "]")
23
- depth--;
24
- }
25
- return inString || depth > 0;
26
- }
27
- /** Parse a JSON candidate, distinguishing truncation from malformation. */
28
- export function parseJsonCandidate(candidate) {
29
- try {
30
- return JSON.parse(candidate);
31
- }
32
- catch (error) {
33
- if (isIncompleteJson(candidate)) {
34
- throw new Error("the model stopped before completing its JSON object (output budget exhausted?)");
35
- }
36
- throw new Error(`the model did not return valid JSON: ${error instanceof Error ? error.message : String(error)}`);
37
- }
38
- }
39
- /** Recover a JSON object from raw model text. */
40
- export function extractJsonObject(text) {
41
- const trimmed = text.trim();
42
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
43
- return parseJsonCandidate(trimmed);
44
- }
45
- const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
46
- if (fenced) {
47
- return parseJsonCandidate(fenced[1]?.trim() ?? "");
48
- }
49
- const start = trimmed.indexOf("{");
50
- const end = trimmed.lastIndexOf("}");
51
- if (start !== -1 && end > start) {
52
- try {
53
- return JSON.parse(trimmed.slice(start, end + 1));
54
- }
55
- catch {
56
- return parseJsonCandidate(trimmed.slice(start));
57
- }
58
- }
59
- if (isIncompleteJson(trimmed)) {
60
- throw new Error("the model stopped before completing its JSON object (output budget exhausted?)");
61
- }
62
- throw new Error("the planner did not return a JSON object");
63
- }
64
- function asString(value) {
65
- return typeof value === "string" ? value : undefined;
66
- }
67
- function asRecord(value) {
68
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
69
- return undefined;
70
- }
71
- return value;
72
- }
73
- /** Assign a string field only when it is present, honoring exactOptionalPropertyTypes. */
74
- function assignIfString(target, key, value) {
75
- const str = asString(value);
76
- if (str !== undefined) {
77
- target[key] = str;
78
- }
79
- }
80
- /** Parse and shape a proposal, dropping non-object edits. */
81
- export function parseProposal(text) {
82
- const value = extractJsonObject(text);
83
- const record = asRecord(value);
84
- if (!record) {
85
- throw new Error("the planner JSON must be an object");
86
- }
87
- const edits = [];
88
- if (Array.isArray(record["edits"])) {
89
- for (const raw of record["edits"]) {
90
- const edit = asRecord(raw);
91
- if (!edit)
92
- continue;
93
- const built = {
94
- action: asString(edit["action"]),
95
- kind: asString(edit["kind"]),
96
- };
97
- assignIfString(built, "id", edit["id"]);
98
- assignIfString(built, "title", edit["title"]);
99
- assignIfString(built, "content", edit["content"]);
100
- assignIfString(built, "path", edit["path"]);
101
- assignIfString(built, "reason", edit["reason"]);
102
- // Gap C2: validate blastRadius values.
103
- const br = asString(edit["blastRadius"]);
104
- if (br === "general" || br === "project" || br === "session") {
105
- built.blastRadius = br;
106
- }
107
- const reference = asRecord(edit["reference"]);
108
- if (reference)
109
- built.reference = reference;
110
- const argumentsRecord = asRecord(edit["arguments"]);
111
- if (argumentsRecord)
112
- built.arguments = argumentsRecord;
113
- const metadata = asRecord(edit["metadata"]);
114
- if (metadata)
115
- built.metadata = metadata;
116
- edits.push(built);
117
- }
118
- }
119
- return {
120
- summary: asString(record["summary"]) ?? "Refined harness state",
121
- rationale: asString(record["rationale"]) ?? "",
122
- expectedOutcome: asString(record["expectedOutcome"]) ?? "",
123
- edits,
124
- };
125
- }
126
- //# sourceMappingURL=plan.js.map
package/lib/planner.d.ts DELETED
@@ -1,61 +0,0 @@
1
- /**
2
- * The LLM planning pass. Given the current harness state, refinement history,
3
- * and optional instructions, a direct model call produces a JSON proposal
4
- * which is parsed (truncation-aware) and validated by the pure core.
5
- *
6
- * The call routes through `ctx.llm` with the calling agent's own
7
- * provider/model so the plan uses the same model the session runs on.
8
- */
9
- import type { Context } from "@deepseek-ai/cordis";
10
- import type { Agent } from "@deepseek-ai/dsh-agent";
11
- import type { HarnessState, RefinementProposal, RefinementResult } from "./types.js";
12
- import { type PrefixCacheOptions } from "./prefix-cache.js";
13
- import { type TokenUsageTarget } from "./token-usage.js";
14
- import { type RecordLanguage } from "./record-language.js";
15
- export declare const PLANNER_SYSTEM_PROMPT = "You are the /evolve continual harness subsystem.\n\nYour job is to improve the editable continual harness state. Instead of\nsummarizing the conversation you emit precise Create, Update, or Delete edits\nto reusable state: prompt notes, memories, skills, and subagent specs.\n\nRules:\n- The base system prompt is immutable and MUST NOT be rewritten (never edit id \"base_system_prompt\").\n- Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.\n- Repeated evidence is mandatory for prompt/skill/subagent proposals: the same\n pattern must recur at least twice in the trajectory. A single one-off\n interaction never justifies a prompt, skill, or subagent edit \u2014 return an\n empty edits array with a rationale instead. Session-scoped local prompt\n landings converge on /evolve wrapup human review; the turn pipeline must\n not silently persist one-off abstractions.\n- prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType\n- (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;\n- project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).\n- Memory rules: one fact per entry (title = one-line retrieval hook, content = the fact alone);\n- never save what the repo already records (code structure, past fixes, git history, AGENTS.md) or what only\n- matters to this conversation \u2014 save the non-obvious point instead; before creating, update the existing\n- entry when one already covers the fact (creates carry metadata {memoryType} or they are rejected);\n- skill = repeatable procedures (must carry a python reference {type:\"python\", import, callable}\n and an arguments object); subagent = reusable delegation roles.\n- Skill entries are authored to the DSH skill quality standard\n (skill-creator, distilled from the official deepseek-harness 11 skills;\n the full facts are provided in the <skill_quality_standard> block below):\n only for a REAL trigger scenario grounded in the trajectory\n (who, in what real task, what signal) \u2014 never invent one to pad the store;\n never duplicate the official 11 skills or existing entries; content is a\n SKILL.md document (frontmatter routing with \"use when / do not use when\"\n description, boundary declaration, prerequisites and exclusions, layered\n information, verifiable completion criteria). Self-check every proposed\n skill against the 7 structural features and state the result in its\n reason field.\n- Repeated multi-step workflows (session start/end routines, recurring\n wrap-up or handoff procedures) may be proposed as guidance skills:\n kind=skill, skill_kind=\"guidance\", content = a SKILL.md document (no\n python reference \u2014 executable skills keep requiring reference +\n arguments). Only propose with repeated evidence in the trajectory, never\n for one-off flows. Guidance skills materialize as discoverable SKILL.md\n files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to\n the user for a decision before they land.\n- Local edits are session-scoped; global edits persist across sessions.\n- Gap C2: EVERY edit MUST include a \"blastRadius\" field indicating how broadly\n the edit applies: \"general\" (cross-project tactical rule, applies everywhere),\n \"project\" (valid for the current project/repo), or \"session\" (one-off,\n specific to this session's context). The review gate validates that\n local-scope edits use \"session\" or \"project\", and global-scope edits use\n \"general\" or \"project\". When in doubt, prefer narrower blast radius.\n- Ground every edit in evidence: the session trajectory (recent direct user\n messages) is provided when available; prefer edits backed by it over\n speculation, and never invent preferences the user did not express.\n- Stale entries (superseded by newer ones, never referenced in recent\n trajectories, obsolete facts): propose action \"archive\" instead of\n \"delete\" \u2014 archive hides the entry from injection while keeping its data\n restorable; it requires only kind + id.\n- Output JSON only, exactly this shape:\n{\n \"summary\": \"one sentence\",\n \"rationale\": \"why these edits are justified by the evidence\",\n \"expectedOutcome\": \"what should improve and how to validate it\",\n \"edits\": [\n {\n \"action\": \"create|update|delete\",\n \"kind\": \"prompt|memory|skill|subagent\",\n \"id\": \"stable id for update/delete, optional for create\",\n \"title\": \"required for create/update except delete\",\n \"content\": \"required for create/update except delete\",\n \"path\": \"optional grouping path\",\n \"reference\": {\"type\":\"python\",\"import\":\"pkg.mod\",\"callable\":\"fn\"} ,\n \"arguments\": {\"name\": {\"type\":\"string\",\"required\":true,\"description\":\"...\"}},\n \"skill_kind\": \"executable|guidance (optional; skill kind only \u2014 guidance = SKILL.md document without reference)\",\n \"metadata\": {},\n \"reason\": \"why this edit is useful\",\n \"blastRadius\": \"general|project|session\"\n }\n ]\n}";
16
- export interface PlanOptions {
17
- agent: Agent;
18
- state: HarnessState;
19
- history: readonly RefinementResult[];
20
- instructions?: string;
21
- /**
22
- * Explicit session-trajectory text (recent direct user messages). When
23
- * omitted, it is extracted from the agent's own session log via
24
- * `recentUserText` — the same extraction the injection ranking uses — so
25
- * every planning call is grounded in what the user actually said.
26
- */
27
- trajectory?: string;
28
- /**
29
- * Explicit session-trajectory rows for prefix-cache Route A. When supplied,
30
- * the planner sees only the scheduler snapshot rather than the full session.
31
- */
32
- trajectoryEvents?: readonly unknown[];
33
- /**
34
- * Skills root to read the skill-creator template facts from
35
- * (`<root>/skill-creator/references/template.md`). When omitted or the
36
- * skills are not installed, the builtin distilled quality guide is
37
- * injected instead — the skill standard is always present.
38
- */
39
- skillsRoot?: string;
40
- global?: boolean;
41
- signal?: AbortSignal;
42
- maxOutputTokens?: number;
43
- /**
44
- * Prefix-cache routing for the planning input. Route A prepends a
45
- * session-derived message prefix and drops the auto-extracted
46
- * trajectory block (an explicitly passed `trajectory` is always kept);
47
- * Route B keeps the legacy flat-text input. Absent → auto-detect with
48
- * the default budget.
49
- */
50
- prefixCache?: PrefixCacheOptions;
51
- /** Direct-call token ledger destination; the planner phase is fixed by this module. */
52
- tokenUsage?: TokenUsageTarget;
53
- /**
54
- * Authoring language for the proposal. Absent → resolved per call
55
- * (explicit config upstream, else durable client preference, else
56
- * trajectory detection, else `en`).
57
- */
58
- language?: RecordLanguage;
59
- }
60
- export declare function planWithLlm(ctx: Context, options: PlanOptions): Promise<RefinementProposal>;
61
- //# sourceMappingURL=planner.d.ts.map
package/lib/planner.js DELETED
@@ -1,144 +0,0 @@
1
- import { parseProposal } from "./plan.js";
2
- import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
3
- import { recentUserText, sessionEventsOf } from "./inject.js";
4
- import { buildPrefixMessages, detectPlannerRoute, resolvePrefixCache } from "./prefix-cache.js";
5
- import { skillQualityGuide } from "./skillquality.js";
6
- import { asLlmSessionId, streamText } from "./llm-text.js";
7
- import { createTokenUsageObserver } from "./token-usage.js";
8
- import { recordLanguageInstruction, resolveRecordLanguage } from "./record-language.js";
9
- export const PLANNER_SYSTEM_PROMPT = `You are the /evolve continual harness subsystem.
10
-
11
- Your job is to improve the editable continual harness state. Instead of
12
- summarizing the conversation you emit precise Create, Update, or Delete edits
13
- to reusable state: prompt notes, memories, skills, and subagent specs.
14
-
15
- Rules:
16
- - The base system prompt is immutable and MUST NOT be rewritten (never edit id "base_system_prompt").
17
- - Prefer small evidence-backed edits. If no useful edit is justified, return an empty edits array.
18
- - Repeated evidence is mandatory for prompt/skill/subagent proposals: the same
19
- pattern must recur at least twice in the trajectory. A single one-off
20
- interaction never justifies a prompt, skill, or subagent edit — return an
21
- empty edits array with a rationale instead. Session-scoped local prompt
22
- landings converge on /evolve wrapup human review; the turn pipeline must
23
- not silently persist one-off abstractions.
24
- - prompt = narrow behavioral policy addendums; memory = ONE FACT per entry, typed via metadata.memoryType
25
- - (user = who the user is; feedback = a pitfall/correction or confirmed approach, MUST carry Why + How to apply;
26
- - project = work goal/constraint not derivable from the repo; reference = URL/dashboard/ticket pointer).
27
- - Memory rules: one fact per entry (title = one-line retrieval hook, content = the fact alone);
28
- - never save what the repo already records (code structure, past fixes, git history, AGENTS.md) or what only
29
- - matters to this conversation — save the non-obvious point instead; before creating, update the existing
30
- - entry when one already covers the fact (creates carry metadata {memoryType} or they are rejected);
31
- - skill = repeatable procedures (must carry a python reference {type:"python", import, callable}
32
- and an arguments object); subagent = reusable delegation roles.
33
- - Skill entries are authored to the DSH skill quality standard
34
- (skill-creator, distilled from the official deepseek-harness 11 skills;
35
- the full facts are provided in the <skill_quality_standard> block below):
36
- only for a REAL trigger scenario grounded in the trajectory
37
- (who, in what real task, what signal) — never invent one to pad the store;
38
- never duplicate the official 11 skills or existing entries; content is a
39
- SKILL.md document (frontmatter routing with "use when / do not use when"
40
- description, boundary declaration, prerequisites and exclusions, layered
41
- information, verifiable completion criteria). Self-check every proposed
42
- skill against the 7 structural features and state the result in its
43
- reason field.
44
- - Repeated multi-step workflows (session start/end routines, recurring
45
- wrap-up or handoff procedures) may be proposed as guidance skills:
46
- kind=skill, skill_kind="guidance", content = a SKILL.md document (no
47
- python reference — executable skills keep requiring reference +
48
- arguments). Only propose with repeated evidence in the trajectory, never
49
- for one-off flows. Guidance skills materialize as discoverable SKILL.md
50
- files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to
51
- the user for a decision before they land.
52
- - Local edits are session-scoped; global edits persist across sessions.
53
- - Gap C2: EVERY edit MUST include a "blastRadius" field indicating how broadly
54
- the edit applies: "general" (cross-project tactical rule, applies everywhere),
55
- "project" (valid for the current project/repo), or "session" (one-off,
56
- specific to this session's context). The review gate validates that
57
- local-scope edits use "session" or "project", and global-scope edits use
58
- "general" or "project". When in doubt, prefer narrower blast radius.
59
- - Ground every edit in evidence: the session trajectory (recent direct user
60
- messages) is provided when available; prefer edits backed by it over
61
- speculation, and never invent preferences the user did not express.
62
- - Stale entries (superseded by newer ones, never referenced in recent
63
- trajectories, obsolete facts): propose action "archive" instead of
64
- "delete" — archive hides the entry from injection while keeping its data
65
- restorable; it requires only kind + id.
66
- - Output JSON only, exactly this shape:
67
- {
68
- "summary": "one sentence",
69
- "rationale": "why these edits are justified by the evidence",
70
- "expectedOutcome": "what should improve and how to validate it",
71
- "edits": [
72
- {
73
- "action": "create|update|delete",
74
- "kind": "prompt|memory|skill|subagent",
75
- "id": "stable id for update/delete, optional for create",
76
- "title": "required for create/update except delete",
77
- "content": "required for create/update except delete",
78
- "path": "optional grouping path",
79
- "reference": {"type":"python","import":"pkg.mod","callable":"fn"} ,
80
- "arguments": {"name": {"type":"string","required":true,"description":"..."}},
81
- "skill_kind": "executable|guidance (optional; skill kind only — guidance = SKILL.md document without reference)",
82
- "metadata": {},
83
- "reason": "why this edit is useful",
84
- "blastRadius": "general|project|session"
85
- }
86
- ]
87
- }`;
88
- export async function planWithLlm(ctx, options) {
89
- const { agent, state, history } = options;
90
- if (!agent.options.provider || !agent.options.model) {
91
- throw new Error("evolve: the calling agent has no provider/model route to plan with");
92
- }
93
- const scopeInstruction = options.global
94
- ? "Requested scope: global. Only propose stable cross-session lessons, durable preferences, reusable skills/subagents, or explicitly project-qualified facts."
95
- : "Requested scope: local. Propose session-scoped edits only when the trajectory shows repeated evidence (>=2 occurrences of the same pattern); a single one-off interaction returns an empty edits array. Global entries are read-only context — do not propose update/delete for them.";
96
- // Ground the plan in the caller's session: Route A carries the context
97
- // as a session-derived message prefix (cache-eligible on providers with
98
- // prompt caching) and drops the auto-extracted trajectory block; Route B
99
- // keeps the legacy flat-text block. An empty prefix falls back to B.
100
- const events = options.trajectoryEvents ?? sessionEventsOf(agent);
101
- const routing = resolvePrefixCache(options.prefixCache);
102
- const route = detectPlannerRoute(events, routing.mode);
103
- let prefixMessages = [];
104
- if (route === "A") {
105
- prefixMessages = buildPrefixMessages(events, {
106
- ...(agent.options.provider ? { provider: agent.options.provider } : {}),
107
- ...(agent.options.model ? { model: agent.options.model } : {}),
108
- maxChars: routing.maxChars,
109
- });
110
- }
111
- const trajectory = prefixMessages.length > 0 ? (options.trajectory ?? "") : (options.trajectory ?? recentUserText(agent));
112
- const language = options.language ?? resolveRecordLanguage({ ctx, trajectoryText: trajectory });
113
- // The skill quality standard is always present: the skill-creator
114
- // template facts when installed, the builtin distilled guide otherwise
115
- // (~1KB — planning is low-frequency, and the standard keeps skill
116
- // proposals from drifting off the quality bar).
117
- const qualityGuide = skillQualityGuide(options.skillsRoot);
118
- const userPrompt = [
119
- `<current_harness_state>\n${formatHarnessStateForPrompt(state)}\n</current_harness_state>`,
120
- `<refinement_history>\n${historyForPrompt(history)}\n</refinement_history>`,
121
- `<scope_policy>\n${scopeInstruction}\n</scope_policy>`,
122
- trajectory ? `<session_trajectory>\n${trajectory}\n</session_trajectory>` : "",
123
- `<skill_quality_standard>\n${qualityGuide.text}\n</skill_quality_standard>`,
124
- options.instructions ? `<user_instructions>\n${options.instructions}\n</user_instructions>` : "",
125
- "Return only JSON edits. If no useful edit is justified, return an empty edits array with a rationale.",
126
- ]
127
- .filter(Boolean)
128
- .join("\n\n");
129
- const text = await streamText(ctx, {
130
- provider: agent.options.provider,
131
- model: agent.options.model,
132
- sessionId: asLlmSessionId(agent.id),
133
- system: `${PLANNER_SYSTEM_PROMPT}\n\n${recordLanguageInstruction(language)}`,
134
- prompt: userPrompt,
135
- maxTokens: options.maxOutputTokens ?? 8000,
136
- signal: options.signal,
137
- ...(prefixMessages.length > 0 ? { prefixMessages } : {}),
138
- ...(options.tokenUsage
139
- ? { onUsage: createTokenUsageObserver(options.tokenUsage, "planner", agent.options.provider, agent.options.model) }
140
- : {}),
141
- });
142
- return parseProposal(text);
143
- }
144
- //# sourceMappingURL=planner.js.map
package/lib/pool.d.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Bounded-concurrency map: run async workers over items with at most
3
- * `concurrency` in flight. Used by the evaluation matrix so case × run units
4
- * execute in parallel without unbounded subagent fan-out.
5
- */
6
- export declare function mapPool<T, R>(items: readonly T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
7
- //# sourceMappingURL=pool.d.ts.map
package/lib/pool.js DELETED
@@ -1,25 +0,0 @@
1
- /**
2
- * Bounded-concurrency map: run async workers over items with at most
3
- * `concurrency` in flight. Used by the evaluation matrix so case × run units
4
- * execute in parallel without unbounded subagent fan-out.
5
- */
6
- export async function mapPool(items, concurrency, worker) {
7
- if (items.length === 0) {
8
- return [];
9
- }
10
- const results = new Array(items.length);
11
- const limit = Math.max(1, Math.min(concurrency, items.length));
12
- let next = 0;
13
- async function runner() {
14
- while (true) {
15
- const index = next;
16
- next += 1;
17
- if (index >= items.length)
18
- return;
19
- results[index] = await worker(items[index], index);
20
- }
21
- }
22
- await Promise.all(Array.from({ length: limit }, () => runner()));
23
- return results;
24
- }
25
- //# sourceMappingURL=pool.js.map
@@ -1,79 +0,0 @@
1
- /**
2
- * Prefix-cache-aware planning input routing (Route A/B).
3
- *
4
- * The planner and the review gate re-send session context on every call as
5
- * flat text. On providers with prompt caching that context is re-billed at
6
- * full price each time. Route A instead prepends session-derived messages —
7
- * the same evidence in structured form, eligible for cache-read pricing —
8
- * and drops the redundant flat trajectory block. Route B keeps the legacy
9
- * flat-text input byte-for-byte.
10
- *
11
- * Adapted from dsh-continual-harness (Route A/B + cache-detect): this port
12
- * deliberately does NOT reproduce the host loop's byte-identical cache key
13
- * (no deriveMessages/tools/sessionId plumbing on this seam), so a hit needs
14
- * a provider that keys on the message-prefix content. Sources are stamped
15
- * honestly and every reduction is documented below.
16
- */
17
- import { type AssistantMessage, type UserMessage } from "@deepseek-ai/dsh-llm";
18
- /** Planning input shape: A = session-derived message prefix, B = flat-text trajectory. */
19
- export type PlannerRoute = "A" | "B";
20
- /** Routing mode: auto-detect from cache evidence, always prefix, or never. */
21
- export { type PlannerPrefixCacheMode } from "./types.js";
22
- import type { PlannerPrefixCacheMode } from "./types.js";
23
- /** Per-call routing overrides; absent fields fall back to the resolved defaults. */
24
- export interface PrefixCacheOptions {
25
- mode?: PlannerPrefixCacheMode;
26
- maxChars?: number;
27
- }
28
- /** Default session-prefix budget for one Route A input, in characters. */
29
- export declare const DEFAULT_PREFIX_MAX_CHARS = 12000;
30
- /**
31
- * Resolve per-call overrides against the defaults.
32
- *
33
- * @param opts - sparse per-call overrides.
34
- * @returns the effective mode (default "auto") and char budget.
35
- * @throws When maxChars is present but not a positive safe integer (fail loud on misconfiguration).
36
- */
37
- export declare function resolvePrefixCache(opts?: PrefixCacheOptions): {
38
- mode: PlannerPrefixCacheMode;
39
- maxChars: number;
40
- };
41
- /**
42
- * True when any assistant message in this session reported cache-read
43
- * tokens — evidence the provider can serve a warm prefix.
44
- *
45
- * @param events - session log rows, newest last.
46
- * @returns whether cache evidence exists (false on an empty/unreadable log).
47
- */
48
- export declare function hasCacheEvidence(events: readonly unknown[]): boolean;
49
- /**
50
- * Pick the planning input route for one call.
51
- *
52
- * @param events - session log rows, newest last.
53
- * @param mode - routing mode (default "auto").
54
- * @returns "A" when the session prefix should carry the context, else "B".
55
- */
56
- export declare function detectPlannerRoute(events: readonly unknown[], mode?: PlannerPrefixCacheMode): PlannerRoute;
57
- /**
58
- * Rebuild a tail-biased session prefix from the event log.
59
- *
60
- * Inclusion mirrors the trajectory grounding (only direct human `user`
61
- * speech plus the model's own assistant text), so injected plugin context
62
- * and tool results never echo back into the planning input. Whole messages
63
- * are dropped oldest-first over budget; a single over-budget message keeps
64
- * its tail.
65
- *
66
- * @param events - session log rows, newest last.
67
- * @param opts - provider/model stamps the assistant messages (the calling
68
- * agent's own route); maxChars caps the prefix (default {@link DEFAULT_PREFIX_MAX_CHARS}).
69
- * @returns the prefix messages, oldest first ([] when nothing qualifies —
70
- * callers fall back to Route B).
71
- * @throws When assistant text exists but provider/model is missing (an
72
- * assistant message cannot be honestly stamped without its route).
73
- */
74
- export declare function buildPrefixMessages(events: readonly unknown[], opts: {
75
- provider?: string;
76
- model?: string;
77
- maxChars?: number;
78
- }): (UserMessage | AssistantMessage)[];
79
- //# sourceMappingURL=prefix-cache.d.ts.map