dsh-continual-evolve 0.1.1 → 0.3.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.
- package/README.md +172 -17
- package/README.zh.md +87 -10
- package/lib/apply.js +3 -1
- package/lib/approval.d.ts +25 -0
- package/lib/approval.js +9 -1
- package/lib/auto.d.ts +99 -5
- package/lib/auto.js +165 -6
- package/lib/benchmark-command.d.ts +9 -0
- package/lib/benchmark-command.js +331 -0
- package/lib/benchmark.d.ts +84 -0
- package/lib/benchmark.js +107 -1
- package/lib/command.js +33 -221
- package/lib/evaluate.d.ts +43 -7
- package/lib/evaluate.js +172 -43
- package/lib/evolve-event.d.ts +38 -0
- package/lib/evolve-event.js +49 -0
- package/lib/failures.d.ts +39 -0
- package/lib/failures.js +170 -0
- package/lib/fate.d.ts +128 -0
- package/lib/fate.js +342 -0
- package/lib/goal-command.d.ts +7 -0
- package/lib/goal-command.js +37 -0
- package/lib/index.d.ts +51 -21
- package/lib/index.js +32 -2
- package/lib/inject.d.ts +8 -0
- package/lib/inject.js +51 -4
- package/lib/llm-text.d.ts +30 -0
- package/lib/llm-text.js +49 -0
- package/lib/mount-command.d.ts +10 -0
- package/lib/mount-command.js +48 -0
- package/lib/mount.js +5 -0
- package/lib/plan.js +5 -0
- package/lib/planner.d.ts +8 -1
- package/lib/planner.js +40 -39
- package/lib/render.d.ts +1 -3
- package/lib/render.js +2 -5
- package/lib/review.d.ts +5 -2
- package/lib/review.js +27 -38
- package/lib/rollback.d.ts +1 -3
- package/lib/rollback.js +0 -8
- package/lib/score.d.ts +37 -4
- package/lib/score.js +120 -10
- package/lib/service.d.ts +2 -2
- package/lib/service.js +5 -2
- package/lib/skill-render.d.ts +15 -0
- package/lib/skill-render.js +30 -0
- package/lib/skill.d.ts +12 -7
- package/lib/skill.js +36 -31
- package/lib/skillquality.d.ts +80 -0
- package/lib/skillquality.js +311 -0
- package/lib/store.d.ts +1 -3
- package/lib/store.js +0 -7
- package/lib/tool.js +28 -4
- package/lib/types.d.ts +39 -0
- package/lib/types.js +19 -0
- package/lib/usage.d.ts +32 -0
- package/lib/usage.js +84 -0
- package/lib/validate.d.ts +12 -2
- package/lib/validate.js +51 -2
- package/lib/wrapup-command.d.ts +8 -0
- package/lib/wrapup-command.js +211 -0
- package/lib/wrapup.d.ts +215 -0
- package/lib/wrapup.js +427 -0
- package/package.json +8 -8
package/lib/llm-text.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BlockAssembler, createUserMessage, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
/**
|
|
3
|
+
* Stream a single-turn text completion through `ctx.llm`. Forces
|
|
4
|
+
* `reasoningEffort: off` so the model spends its budget on the answer,
|
|
5
|
+
* not visible thinking (reasoning models otherwise produce zero text
|
|
6
|
+
* blocks — the exact failure recorded in FAQ #7).
|
|
7
|
+
*
|
|
8
|
+
* @returns The concatenated text blocks from the response.
|
|
9
|
+
* @throws On provider error, abort, max-token truncation, or empty output.
|
|
10
|
+
*/
|
|
11
|
+
export async function streamText(ctx, opts) {
|
|
12
|
+
const assembler = new BlockAssembler();
|
|
13
|
+
for await (const chunk of ctx.llm.stream({
|
|
14
|
+
provider: opts.provider,
|
|
15
|
+
model: opts.model,
|
|
16
|
+
system: opts.system,
|
|
17
|
+
messages: [
|
|
18
|
+
createUserMessage({
|
|
19
|
+
content: [{ type: "text", text: opts.prompt }],
|
|
20
|
+
source: { kind: "plugin", plugin: "dsh-continual-evolve" },
|
|
21
|
+
}),
|
|
22
|
+
],
|
|
23
|
+
reasoningEffort: ReasoningEffortId("off"),
|
|
24
|
+
maxTokens: opts.maxTokens ?? 8000,
|
|
25
|
+
...(opts.signal ? { signal: opts.signal } : {}),
|
|
26
|
+
})) {
|
|
27
|
+
assembler.push(chunk);
|
|
28
|
+
}
|
|
29
|
+
const finish = assembler.finish;
|
|
30
|
+
if (finish.kind === "error") {
|
|
31
|
+
throw new Error(`evolve: LLM call failed: ${finish.failure?.message ?? "unknown"}`);
|
|
32
|
+
}
|
|
33
|
+
if (finish.kind === "aborted") {
|
|
34
|
+
throw new Error("evolve: LLM call aborted");
|
|
35
|
+
}
|
|
36
|
+
if (finish.kind === "max-tokens") {
|
|
37
|
+
throw new Error("evolve: LLM output budget exhausted (max-tokens)");
|
|
38
|
+
}
|
|
39
|
+
const text = assembler
|
|
40
|
+
.blocks()
|
|
41
|
+
.filter((block) => block.type === "text")
|
|
42
|
+
.map((block) => block.text)
|
|
43
|
+
.join("\n");
|
|
44
|
+
if (text.length === 0) {
|
|
45
|
+
throw new Error("evolve: LLM produced no text output");
|
|
46
|
+
}
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=llm-text.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/evolve mount` and `/evolve unmount` subcommand handlers.
|
|
3
|
+
* Extracted from command.ts (P2-2).
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
6
|
+
import type { CommandInvocation, CommandResult } from "@deepseek-ai/dsh-commands";
|
|
7
|
+
import type { EvolutionEngine } from "./service.js";
|
|
8
|
+
export declare function executeMountCommand(ctx: Context, engine: EvolutionEngine, invocation: CommandInvocation, rest: string[]): Promise<CommandResult>;
|
|
9
|
+
export declare function executeUnmountCommand(ctx: Context, engine: EvolutionEngine, rest: string[]): Promise<CommandResult>;
|
|
10
|
+
//# sourceMappingURL=mount-command.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { loadLedger, mountSkill, unmountSkill } from "./mount.js";
|
|
2
|
+
import { stripAngleBrackets } from "./command.js";
|
|
3
|
+
function success(text) {
|
|
4
|
+
return { kind: "success", text };
|
|
5
|
+
}
|
|
6
|
+
function error(text) {
|
|
7
|
+
return { kind: "error", text };
|
|
8
|
+
}
|
|
9
|
+
export async function executeMountCommand(ctx, engine, invocation, rest) {
|
|
10
|
+
const sub = rest[0] ?? "";
|
|
11
|
+
if (sub === "list") {
|
|
12
|
+
const ledger = loadLedger(engine.baseDir);
|
|
13
|
+
if (ledger.mounted.length === 0) {
|
|
14
|
+
return success("(no hot-mounted plugins — /evolve mount <skillId>)");
|
|
15
|
+
}
|
|
16
|
+
return success(ledger.mounted.map((m) => `- ${m.id} (${m.entryId}, v${m.version}, ${m.mountedAt})`).join("\n"));
|
|
17
|
+
}
|
|
18
|
+
const skillId = stripAngleBrackets(sub);
|
|
19
|
+
if (!skillId) {
|
|
20
|
+
return error(`mount requires a skill entry id.\nUsage: /evolve mount <skillId> | /evolve mount list`);
|
|
21
|
+
}
|
|
22
|
+
const sessionId = invocation.agent.id;
|
|
23
|
+
const local = engine.load("local", sessionId);
|
|
24
|
+
const globalState = engine.load("global", undefined);
|
|
25
|
+
const entry = local.entries.skill[skillId] ??
|
|
26
|
+
globalState.entries.skill[skillId] ??
|
|
27
|
+
Object.values(local.entries.skill).find((e) => e.id === skillId) ??
|
|
28
|
+
Object.values(globalState.entries.skill).find((e) => e.id === skillId);
|
|
29
|
+
if (!entry) {
|
|
30
|
+
return error(`skill entry ${skillId} not found in local or global store`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const record = await mountSkill(ctx, engine.baseDir, entry);
|
|
34
|
+
return success(`mounted ${record.id} as ${record.entryId} (v${record.version}) — tool: skill_${record.id.replace(/_/g, "-")}`);
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return error(cause instanceof Error ? cause.message : String(cause));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function executeUnmountCommand(ctx, engine, rest) {
|
|
41
|
+
const id = stripAngleBrackets(rest[0] ?? "");
|
|
42
|
+
if (!id) {
|
|
43
|
+
return error(`unmount requires a mount id (see /evolve mount list).`);
|
|
44
|
+
}
|
|
45
|
+
const record = await unmountSkill(ctx, engine.baseDir, id);
|
|
46
|
+
return record ? success(`unmounted ${record.id} (${record.entryId})`) : error(`no mount found for ${id}`);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=mount-command.js.map
|
package/lib/mount.js
CHANGED
|
@@ -124,6 +124,11 @@ export function renderParameters(entry) {
|
|
|
124
124
|
* the ledger records the entry for the next boot.
|
|
125
125
|
*/
|
|
126
126
|
export async function mountSkill(ctx, baseDir, entry) {
|
|
127
|
+
// Guidance skills are SKILL.md documents with no python reference — there
|
|
128
|
+
// is no function to mount as a tool; only executable skills can hot-mount.
|
|
129
|
+
if (entry.skill_kind === "guidance" || Object.keys(entry.reference ?? {}).length === 0) {
|
|
130
|
+
throw new Error(`skill ${entry.id} has no python reference (guidance skills cannot be mounted — load them with the skill tool instead)`);
|
|
131
|
+
}
|
|
127
132
|
const dir = renderMountPackage(baseDir, entry);
|
|
128
133
|
const entryId = `evolve-mount-${skillNameOf(entry.id)}`;
|
|
129
134
|
const loader = ctx.get("loader");
|
package/lib/plan.js
CHANGED
|
@@ -99,6 +99,11 @@ export function parseProposal(text) {
|
|
|
99
99
|
assignIfString(built, "content", edit["content"]);
|
|
100
100
|
assignIfString(built, "path", edit["path"]);
|
|
101
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
|
+
}
|
|
102
107
|
const reference = asRecord(edit["reference"]);
|
|
103
108
|
if (reference)
|
|
104
109
|
built.reference = reference;
|
package/lib/planner.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { Context } from "@deepseek-ai/cordis";
|
|
10
10
|
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
11
11
|
import type { HarnessState, RefinementProposal, RefinementResult } from "./types.js";
|
|
12
|
-
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- prompt = narrow behavioral policy addendums; memory = durable facts/preferences/failures;\n skill = repeatable procedures (must carry a python reference {type:\"python\", import, callable}\n and an arguments object); subagent = reusable delegation roles.\n- Local edits are session-scoped; global edits persist across sessions.\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 \"metadata\": {},\n \"reason\": \"why this edit is useful\"\n }\n ]\n}";
|
|
12
|
+
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- prompt = narrow behavioral policy addendums; memory = durable facts/preferences/failures;\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}";
|
|
13
13
|
export interface PlanOptions {
|
|
14
14
|
agent: Agent;
|
|
15
15
|
state: HarnessState;
|
|
@@ -22,6 +22,13 @@ export interface PlanOptions {
|
|
|
22
22
|
* every planning call is grounded in what the user actually said.
|
|
23
23
|
*/
|
|
24
24
|
trajectory?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Skills root to read the skill-creator template facts from
|
|
27
|
+
* (`<root>/skill-creator/references/template.md`). When omitted or the
|
|
28
|
+
* skills are not installed, the builtin distilled quality guide is
|
|
29
|
+
* injected instead — the skill standard is always present.
|
|
30
|
+
*/
|
|
31
|
+
skillsRoot?: string;
|
|
25
32
|
global?: boolean;
|
|
26
33
|
signal?: AbortSignal;
|
|
27
34
|
maxOutputTokens?: number;
|
package/lib/planner.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { BlockAssembler, createUserMessage, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
2
1
|
import { parseProposal } from "./plan.js";
|
|
3
2
|
import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
4
3
|
import { recentUserText } from "./inject.js";
|
|
4
|
+
import { skillQualityGuide } from "./skillquality.js";
|
|
5
|
+
import { streamText } from "./llm-text.js";
|
|
5
6
|
export const PLANNER_SYSTEM_PROMPT = `You are the /evolve continual harness subsystem.
|
|
6
7
|
|
|
7
8
|
Your job is to improve the editable continual harness state. Instead of
|
|
@@ -14,7 +15,32 @@ Rules:
|
|
|
14
15
|
- prompt = narrow behavioral policy addendums; memory = durable facts/preferences/failures;
|
|
15
16
|
skill = repeatable procedures (must carry a python reference {type:"python", import, callable}
|
|
16
17
|
and an arguments object); subagent = reusable delegation roles.
|
|
18
|
+
- Skill entries are authored to the DSH skill quality standard
|
|
19
|
+
(skill-creator, distilled from the official deepseek-harness 11 skills;
|
|
20
|
+
the full facts are provided in the <skill_quality_standard> block below):
|
|
21
|
+
only for a REAL trigger scenario grounded in the trajectory
|
|
22
|
+
(who, in what real task, what signal) — never invent one to pad the store;
|
|
23
|
+
never duplicate the official 11 skills or existing entries; content is a
|
|
24
|
+
SKILL.md document (frontmatter routing with "use when / do not use when"
|
|
25
|
+
description, boundary declaration, prerequisites and exclusions, layered
|
|
26
|
+
information, verifiable completion criteria). Self-check every proposed
|
|
27
|
+
skill against the 7 structural features and state the result in its
|
|
28
|
+
reason field.
|
|
29
|
+
- Repeated multi-step workflows (session start/end routines, recurring
|
|
30
|
+
wrap-up or handoff procedures) may be proposed as guidance skills:
|
|
31
|
+
kind=skill, skill_kind="guidance", content = a SKILL.md document (no
|
|
32
|
+
python reference — executable skills keep requiring reference +
|
|
33
|
+
arguments). Only propose with repeated evidence in the trajectory, never
|
|
34
|
+
for one-off flows. Guidance skills materialize as discoverable SKILL.md
|
|
35
|
+
files under <skillsRoot>/<kebab-name>/SKILL.md and are always offered to
|
|
36
|
+
the user for a decision before they land.
|
|
17
37
|
- Local edits are session-scoped; global edits persist across sessions.
|
|
38
|
+
- Gap C2: EVERY edit MUST include a "blastRadius" field indicating how broadly
|
|
39
|
+
the edit applies: "general" (cross-project tactical rule, applies everywhere),
|
|
40
|
+
"project" (valid for the current project/repo), or "session" (one-off,
|
|
41
|
+
specific to this session's context). The review gate validates that
|
|
42
|
+
local-scope edits use "session" or "project", and global-scope edits use
|
|
43
|
+
"general" or "project". When in doubt, prefer narrower blast radius.
|
|
18
44
|
- Ground every edit in evidence: the session trajectory (recent direct user
|
|
19
45
|
messages) is provided when available; prefer edits backed by it over
|
|
20
46
|
speculation, and never invent preferences the user did not express.
|
|
@@ -37,8 +63,10 @@ Rules:
|
|
|
37
63
|
"path": "optional grouping path",
|
|
38
64
|
"reference": {"type":"python","import":"pkg.mod","callable":"fn"} ,
|
|
39
65
|
"arguments": {"name": {"type":"string","required":true,"description":"..."}},
|
|
66
|
+
"skill_kind": "executable|guidance (optional; skill kind only — guidance = SKILL.md document without reference)",
|
|
40
67
|
"metadata": {},
|
|
41
|
-
"reason": "why this edit is useful"
|
|
68
|
+
"reason": "why this edit is useful",
|
|
69
|
+
"blastRadius": "general|project|session"
|
|
42
70
|
}
|
|
43
71
|
]
|
|
44
72
|
}`;
|
|
@@ -54,57 +82,30 @@ export async function planWithLlm(ctx, options) {
|
|
|
54
82
|
// most recent direct user messages ("" when none qualify — the block is
|
|
55
83
|
// then omitted entirely, keeping an empty trajectory zero-cost).
|
|
56
84
|
const trajectory = options.trajectory ?? recentUserText(agent);
|
|
85
|
+
// The skill quality standard is always present: the skill-creator
|
|
86
|
+
// template facts when installed, the builtin distilled guide otherwise
|
|
87
|
+
// (~1KB — planning is low-frequency, and the standard keeps skill
|
|
88
|
+
// proposals from drifting off the quality bar).
|
|
89
|
+
const qualityGuide = skillQualityGuide(options.skillsRoot);
|
|
57
90
|
const userPrompt = [
|
|
58
91
|
`<current_harness_state>\n${formatHarnessStateForPrompt(state)}\n</current_harness_state>`,
|
|
59
92
|
`<refinement_history>\n${historyForPrompt(history)}\n</refinement_history>`,
|
|
60
93
|
`<scope_policy>\n${scopeInstruction}\n</scope_policy>`,
|
|
61
94
|
trajectory ? `<session_trajectory>\n${trajectory}\n</session_trajectory>` : "",
|
|
95
|
+
`<skill_quality_standard>\n${qualityGuide.text}\n</skill_quality_standard>`,
|
|
62
96
|
options.instructions ? `<user_instructions>\n${options.instructions}\n</user_instructions>` : "",
|
|
63
97
|
"Return only JSON edits. If no useful edit is justified, return an empty edits array with a rationale.",
|
|
64
98
|
]
|
|
65
99
|
.filter(Boolean)
|
|
66
100
|
.join("\n\n");
|
|
67
|
-
const
|
|
68
|
-
for await (const chunk of ctx.llm.stream({
|
|
101
|
+
const text = await streamText(ctx, {
|
|
69
102
|
provider: agent.options.provider,
|
|
70
103
|
model: agent.options.model,
|
|
71
104
|
system: PLANNER_SYSTEM_PROMPT,
|
|
72
|
-
|
|
73
|
-
createUserMessage({
|
|
74
|
-
content: [{ type: "text", text: userPrompt }],
|
|
75
|
-
source: { kind: "plugin", plugin: "dsh-continual-evolve" },
|
|
76
|
-
}),
|
|
77
|
-
],
|
|
78
|
-
// Force non-reasoning output: the proposal must be pure JSON text.
|
|
79
|
-
reasoningEffort: ReasoningEffortId("off"),
|
|
105
|
+
prompt: userPrompt,
|
|
80
106
|
maxTokens: options.maxOutputTokens ?? 8000,
|
|
81
|
-
|
|
82
|
-
})
|
|
83
|
-
assembler.push(chunk);
|
|
84
|
-
}
|
|
85
|
-
throwOnFinishError(assembler.finish);
|
|
86
|
-
const blocks = assembler.blocks();
|
|
87
|
-
const text = blocks
|
|
88
|
-
.filter((block) => block.type === "text")
|
|
89
|
-
.map((block) => block.text)
|
|
90
|
-
.join("\n");
|
|
91
|
-
if (text.length === 0) {
|
|
92
|
-
throw new Error("evolve: planner produced no text output");
|
|
93
|
-
}
|
|
107
|
+
signal: options.signal,
|
|
108
|
+
});
|
|
94
109
|
return parseProposal(text);
|
|
95
110
|
}
|
|
96
|
-
/** Surface terminal stream states as errors so the caller never sees a silent partial plan. */
|
|
97
|
-
function throwOnFinishError(finish) {
|
|
98
|
-
switch (finish.kind) {
|
|
99
|
-
case "stop":
|
|
100
|
-
case "tool-calls":
|
|
101
|
-
return;
|
|
102
|
-
case "max-tokens":
|
|
103
|
-
throw new Error("evolve: planner output budget exhausted (max-tokens)");
|
|
104
|
-
case "aborted":
|
|
105
|
-
throw new Error("evolve: planner call aborted");
|
|
106
|
-
case "error":
|
|
107
|
-
throw new Error(`evolve: planner call failed: ${finish.failure?.message ?? "unknown error"}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
111
|
//# sourceMappingURL=planner.js.map
|
package/lib/render.d.ts
CHANGED
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
* refinement history. Every cap exists to keep token cost predictable no
|
|
4
4
|
* matter how large the store grows.
|
|
5
5
|
*/
|
|
6
|
-
import type { HarnessEntry,
|
|
6
|
+
import type { HarnessEntry, HarnessState, RefinementResult } from "./types.js";
|
|
7
7
|
export declare function compactText(text: string, maxLength: number): string;
|
|
8
8
|
export declare function entryLine(entry: HarnessEntry, maxContentLength: number): string;
|
|
9
9
|
/** Render the full merged state as a bounded overview for the system prompt. */
|
|
10
10
|
export declare function formatHarnessStateForPrompt(state: HarnessState): string;
|
|
11
11
|
/** Render recent refinement results for the planner. */
|
|
12
12
|
export declare function historyForPrompt(history: readonly RefinementResult[]): string;
|
|
13
|
-
/** Serialize a refinement event for persistence (lightweight). */
|
|
14
|
-
export declare function eventToLine(event: HarnessRefinementEvent): string;
|
|
15
13
|
//# sourceMappingURL=render.d.ts.map
|
package/lib/render.js
CHANGED
|
@@ -18,7 +18,8 @@ export function entryLine(entry, maxContentLength) {
|
|
|
18
18
|
: "";
|
|
19
19
|
const citationText = citationSuffix(entry);
|
|
20
20
|
const archivedText = isArchived(entry) ? " [archived]" : "";
|
|
21
|
-
|
|
21
|
+
const formText = entry.kind === "skill" && entry.skill_kind === "guidance" ? " [guidance]" : "";
|
|
22
|
+
return `- [${entry.scope}:${entry.id}] ${entry.title} (${entry.path}, v${entry.version})${archivedText}${formText}${referenceText}${argumentsText}${citationText}: ${compactText(entry.content, maxContentLength)}`;
|
|
22
23
|
}
|
|
23
24
|
/** Trajectory citation suffix (` src=sessionId:1,2`), empty when uncited. */
|
|
24
25
|
function citationSuffix(entry) {
|
|
@@ -76,8 +77,4 @@ export function historyForPrompt(history) {
|
|
|
76
77
|
})
|
|
77
78
|
.join("\n\n");
|
|
78
79
|
}
|
|
79
|
-
/** Serialize a refinement event for persistence (lightweight). */
|
|
80
|
-
export function eventToLine(event) {
|
|
81
|
-
return JSON.stringify(event);
|
|
82
|
-
}
|
|
83
80
|
//# sourceMappingURL=render.js.map
|
package/lib/review.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface AutoRefineReview {
|
|
|
13
13
|
rationale: string;
|
|
14
14
|
instructions?: string;
|
|
15
15
|
}
|
|
16
|
-
export type AutoRefineReason = "turn_interval" | "compact";
|
|
16
|
+
export type AutoRefineReason = "turn_interval" | "compact" | "goal_blocked";
|
|
17
17
|
export interface AutoRefineReviewContext {
|
|
18
18
|
reason: AutoRefineReason;
|
|
19
19
|
turnsSinceLastReview: number;
|
|
@@ -27,8 +27,11 @@ export interface ReviewOptions {
|
|
|
27
27
|
trajectory?: string;
|
|
28
28
|
signal?: AbortSignal;
|
|
29
29
|
budgetTokens?: number;
|
|
30
|
+
/** Gap C1: optional provider/model override for the review gate (cheaper model). */
|
|
31
|
+
overrideProvider?: string;
|
|
32
|
+
overrideModel?: string;
|
|
30
33
|
}
|
|
31
|
-
export declare const AUTO_REVIEW_SYSTEM_PROMPT = "You are the automatic /evolve review gate.\n\nDecide whether this checkpoint should run /evolve. Auto /evolve writes local\nharness state by default, so approve when the trajectory contains evidence\nuseful to this session's future turns: a repeated failure, a reusable tactic,\na repeated delegation role, a durable fact or preference, a user correction\nthat should persist, or a narrow behavioral policy.\n\nThe current harness state below includes GLOBAL entries (scope=global) plus\nthis session's local entries (scope=local). When a topic is already covered\nby a global entry, do NOT approve a local duplicate of it \u2014 decline and say\nin the rationale that the topic is already covered globally.\n\nReject one-off noise, unsupported hypotheses, transient tool outputs, and\nrequests that carry no reusable content.\n\nStale local entries (superseded, long-unused, obsolete facts) are a valid\nrefine target: approve with instructions naming the entry ids, and tell the\nplanner to archive them (archive hides from injection, data stays restorable)\nrather than delete.\n\nReturn JSON only:\n{\n \"shouldRefine\": true|false,\n \"rationale\": \"short reason\",\n \"instructions\": \"optional concise instructions for /evolve if shouldRefine is true\"\n}";
|
|
34
|
+
export declare const AUTO_REVIEW_SYSTEM_PROMPT = "You are the automatic /evolve review gate.\n\nDecide whether this checkpoint should run /evolve. Auto /evolve writes local\nharness state by default, so approve when the trajectory contains evidence\nuseful to this session's future turns: a repeated failure, a reusable tactic,\na repeated delegation role, a durable fact or preference, a user correction\nthat should persist, or a narrow behavioral policy.\n\nThe current harness state below includes GLOBAL entries (scope=global) plus\nthis session's local entries (scope=local). When a topic is already covered\nby a global entry, do NOT approve a local duplicate of it \u2014 decline and say\nin the rationale that the topic is already covered globally.\n\nReject one-off noise, unsupported hypotheses, transient tool outputs, and\nrequests that carry no reusable content.\n\nStale local entries (superseded, long-unused, obsolete facts) are a valid\nrefine target: approve with instructions naming the entry ids, and tell the\nplanner to archive them (archive hides from injection, data stays restorable)\nrather than delete.\n\nSkill-related trajectories (the evidence concerns creating or improving a\nskill entry) are judged against the DSH skill quality standard (skill-audit\ndimensions: frontmatter routing, the 7 structural features, paragraph\nskeleton, no duplication of the official 11 skills or covered skills).\nApprove only when the trajectory shows a REAL trigger scenario and the\nresulting skill would meet the standard; otherwise decline and say in the\nrationale what must improve \u2014 drafting follows skill-creator, and the\nplanner receives the standard as its <skill_quality_standard> block.\n\nRepeated multi-step workflows (session start/end routines, recurring\nwrap-up or handoff procedures) are a valid refine target: approve with\ninstructions telling the planner to propose a guidance skill (kind=skill,\nskill_kind=guidance \u2014 a SKILL.md document, no python reference). Only\npropose when the same workflow recurs in the trajectory \u2014 never for\none-off flows. Auto-created skills are always offered to the user for a\ndecision before they land; the gate never writes a skill silently.\n\nReturn JSON only:\n{\n \"shouldRefine\": true|false,\n \"rationale\": \"short reason\",\n \"instructions\": \"optional concise instructions for /evolve if shouldRefine is true\"\n}";
|
|
32
35
|
/** Parse the gate's JSON reply. */
|
|
33
36
|
export declare function parseAutoRefineReview(text: string): AutoRefineReview;
|
|
34
37
|
/** Serialize surface events to bounded role-prefixed text. */
|
package/lib/review.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { BlockAssembler, createUserMessage, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
|
|
2
1
|
import { extractJsonObject } from "./plan.js";
|
|
3
2
|
import { formatHarnessStateForPrompt, historyForPrompt } from "./render.js";
|
|
3
|
+
import { streamText } from "./llm-text.js";
|
|
4
4
|
export const AUTO_REVIEW_SYSTEM_PROMPT = `You are the automatic /evolve review gate.
|
|
5
5
|
|
|
6
6
|
Decide whether this checkpoint should run /evolve. Auto /evolve writes local
|
|
@@ -22,6 +22,23 @@ refine target: approve with instructions naming the entry ids, and tell the
|
|
|
22
22
|
planner to archive them (archive hides from injection, data stays restorable)
|
|
23
23
|
rather than delete.
|
|
24
24
|
|
|
25
|
+
Skill-related trajectories (the evidence concerns creating or improving a
|
|
26
|
+
skill entry) are judged against the DSH skill quality standard (skill-audit
|
|
27
|
+
dimensions: frontmatter routing, the 7 structural features, paragraph
|
|
28
|
+
skeleton, no duplication of the official 11 skills or covered skills).
|
|
29
|
+
Approve only when the trajectory shows a REAL trigger scenario and the
|
|
30
|
+
resulting skill would meet the standard; otherwise decline and say in the
|
|
31
|
+
rationale what must improve — drafting follows skill-creator, and the
|
|
32
|
+
planner receives the standard as its <skill_quality_standard> block.
|
|
33
|
+
|
|
34
|
+
Repeated multi-step workflows (session start/end routines, recurring
|
|
35
|
+
wrap-up or handoff procedures) are a valid refine target: approve with
|
|
36
|
+
instructions telling the planner to propose a guidance skill (kind=skill,
|
|
37
|
+
skill_kind=guidance — a SKILL.md document, no python reference). Only
|
|
38
|
+
propose when the same workflow recurs in the trajectory — never for
|
|
39
|
+
one-off flows. Auto-created skills are always offered to the user for a
|
|
40
|
+
decision before they land; the gate never writes a skill silently.
|
|
41
|
+
|
|
25
42
|
Return JSON only:
|
|
26
43
|
{
|
|
27
44
|
"shouldRefine": true|false,
|
|
@@ -71,7 +88,9 @@ export function serializeSurface(events, maxChars) {
|
|
|
71
88
|
}
|
|
72
89
|
export async function reviewAutoRefine(ctx, options) {
|
|
73
90
|
const { agent, state, history } = options;
|
|
74
|
-
|
|
91
|
+
const provider = options.overrideProvider ?? agent.options.provider;
|
|
92
|
+
const model = options.overrideModel ?? agent.options.model;
|
|
93
|
+
if (!provider || !model) {
|
|
75
94
|
throw new Error("evolve: no provider/model route for the review gate");
|
|
76
95
|
}
|
|
77
96
|
if (!options.trajectory || options.trajectory.length === 0) {
|
|
@@ -84,44 +103,14 @@ export async function reviewAutoRefine(ctx, options) {
|
|
|
84
103
|
`<conversation>\n${options.trajectory}\n</conversation>`,
|
|
85
104
|
"Return shouldRefine=true when the trajectory contains evidence useful to this session's future turns. Prefer local edits; do not ask for global refinement here.",
|
|
86
105
|
].join("\n\n");
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
model: agent.options.model,
|
|
106
|
+
const text = await streamText(ctx, {
|
|
107
|
+
provider,
|
|
108
|
+
model,
|
|
91
109
|
system: AUTO_REVIEW_SYSTEM_PROMPT,
|
|
92
|
-
|
|
93
|
-
createUserMessage({
|
|
94
|
-
content: [{ type: "text", text: userPrompt }],
|
|
95
|
-
source: { kind: "plugin", plugin: "dsh-continual-evolve" },
|
|
96
|
-
}),
|
|
97
|
-
],
|
|
98
|
-
// Force non-reasoning output so the model spends its budget on the JSON
|
|
99
|
-
// answer, not on visible thinking (reasoning models otherwise produce
|
|
100
|
-
// zero text blocks — the exact failure recorded in reviews.jsonl).
|
|
101
|
-
reasoningEffort: ReasoningEffortId("off"),
|
|
110
|
+
prompt: userPrompt,
|
|
102
111
|
maxTokens: options.budgetTokens ?? 8000,
|
|
103
|
-
|
|
104
|
-
})
|
|
105
|
-
assembler.push(chunk);
|
|
106
|
-
}
|
|
107
|
-
const finish = assembler.finish;
|
|
108
|
-
if (finish.kind === "error") {
|
|
109
|
-
throw new Error(`evolve: review gate call failed: ${finish.failure?.message ?? "unknown"}`);
|
|
110
|
-
}
|
|
111
|
-
if (finish.kind === "aborted") {
|
|
112
|
-
throw new Error("evolve: review gate call aborted");
|
|
113
|
-
}
|
|
114
|
-
if (finish.kind === "max-tokens") {
|
|
115
|
-
throw new Error("evolve: review gate output budget exhausted (max-tokens)");
|
|
116
|
-
}
|
|
117
|
-
const text = assembler
|
|
118
|
-
.blocks()
|
|
119
|
-
.filter((block) => block.type === "text")
|
|
120
|
-
.map((block) => block.text)
|
|
121
|
-
.join("\n");
|
|
122
|
-
if (text.length === 0) {
|
|
123
|
-
throw new Error("evolve: review gate produced no text");
|
|
124
|
-
}
|
|
112
|
+
signal: options.signal,
|
|
113
|
+
});
|
|
125
114
|
return parseAutoRefineReview(text);
|
|
126
115
|
}
|
|
127
116
|
//# sourceMappingURL=review.js.map
|
package/lib/rollback.d.ts
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* result, in reverse order. Rollback is pure data transformation — no LLM
|
|
4
4
|
* is asked to "guess" the previous state.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { RefinementProposal, RefinementResult } from "./types.js";
|
|
7
7
|
/** Build the inverse proposal for an applied refinement. */
|
|
8
8
|
export declare function rollbackProposal(target: RefinementResult): RefinementProposal;
|
|
9
|
-
/** Recreate an entry from a prior snapshot (used when an inverse edit is an update). */
|
|
10
|
-
export declare function restoreEntry(prior: HarnessEntry): HarnessEntry;
|
|
11
9
|
//# sourceMappingURL=rollback.d.ts.map
|
package/lib/rollback.js
CHANGED
|
@@ -58,12 +58,4 @@ function inverseEdit(edit, refinementId) {
|
|
|
58
58
|
}
|
|
59
59
|
return undefined;
|
|
60
60
|
}
|
|
61
|
-
/** Recreate an entry from a prior snapshot (used when an inverse edit is an update). */
|
|
62
|
-
export function restoreEntry(prior) {
|
|
63
|
-
return {
|
|
64
|
-
...prior,
|
|
65
|
-
updated_at: new Date().toISOString(),
|
|
66
|
-
version: prior.version + 1,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
61
|
//# sourceMappingURL=rollback.js.map
|
package/lib/score.d.ts
CHANGED
|
@@ -9,12 +9,30 @@ export interface AggregateOptions {
|
|
|
9
9
|
passThreshold: number;
|
|
10
10
|
/** Per-case regression tolerance: candidate may drop below reference by at most this much. */
|
|
11
11
|
regressionTolerance: number;
|
|
12
|
+
/**
|
|
13
|
+
* Failure-cell protocol (gap A2): a round with more failed cells than
|
|
14
|
+
* this is rejected outright — failed cells are NEVER averaged in as
|
|
15
|
+
* zeros. Default 0 (any failure rejects the round).
|
|
16
|
+
*/
|
|
17
|
+
maxFailedCells: number;
|
|
12
18
|
}
|
|
13
19
|
export declare const DEFAULT_AGGREGATE: AggregateOptions;
|
|
14
|
-
|
|
15
|
-
export declare function aggregate(cells: readonly CellScore[]): Record<string, number | null> & {
|
|
20
|
+
export interface AggregateResult extends Record<string, number | null> {
|
|
16
21
|
overall: number | null;
|
|
17
|
-
|
|
22
|
+
/** Count of failed cells (excluded from every mean). */
|
|
23
|
+
failed: number;
|
|
24
|
+
/** Total cells considered (ok + failed). */
|
|
25
|
+
total: number;
|
|
26
|
+
/** Gap C3: total wall-clock duration of all cells in milliseconds. */
|
|
27
|
+
totalDurationMs: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Aggregate raw cells into code-owned per-case means + overall mean.
|
|
31
|
+
* Failure-cell protocol (gap A2): failed cells are EXCLUDED from means and
|
|
32
|
+
* counted separately — a crashed unit can never silently drag the mean down
|
|
33
|
+
* like a zero. A case whose cells all failed reports null (no mean).
|
|
34
|
+
*/
|
|
35
|
+
export declare function aggregate(cells: readonly CellScore[]): AggregateResult;
|
|
18
36
|
export declare function entryFromCells(label: string, cells: readonly CellScore[], refinementId?: string): EvaluationEntry;
|
|
19
37
|
export interface Decision {
|
|
20
38
|
accepted: boolean;
|
|
@@ -22,10 +40,25 @@ export interface Decision {
|
|
|
22
40
|
}
|
|
23
41
|
/** Human-readable decision report with per-case before → after deltas. */
|
|
24
42
|
export declare function decisionReport(reference: EvaluationEntry, candidate: EvaluationEntry, decision: Decision): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Gap A3 (version_changed semantics): detect cells whose case material
|
|
45
|
+
* changed between the reference and candidate evaluation runs. A candidate
|
|
46
|
+
* cell whose `caseHash` differs from the reference cell of the SAME case
|
|
47
|
+
* means the statement/rubric was edited between the two runs — its score is
|
|
48
|
+
* not comparable to the baseline and must not count toward the decision.
|
|
49
|
+
*
|
|
50
|
+
* The check is conservative: cells without a hash on either side (pre-A3
|
|
51
|
+
* data) and cells already failed are left untouched. Mismatched cells are
|
|
52
|
+
* returned re-marked as `failed` with a reason in notes, so aggregation
|
|
53
|
+
* excludes them and the acceptance rule can reject the round.
|
|
54
|
+
*/
|
|
55
|
+
export declare function flagMaterialDrift(reference: EvaluationEntry, candidateCells: readonly CellScore[]): CellScore[];
|
|
25
56
|
/**
|
|
26
57
|
* Non-regressive acceptance rule (Self-Harness style):
|
|
27
58
|
* the candidate is accepted iff its overall mean is STRICTLY higher than the
|
|
28
|
-
* reference
|
|
59
|
+
* reference, no case regresses by more than `regressionTolerance` points,
|
|
60
|
+
* and neither side has more failed cells than `maxFailedCells` (failure-cell
|
|
61
|
+
* protocol, gap A2 — a partial/invalid round is never accepted).
|
|
29
62
|
*/
|
|
30
63
|
export declare function decide(reference: EvaluationEntry, candidate: EvaluationEntry, opts: AggregateOptions): Decision;
|
|
31
64
|
//# sourceMappingURL=score.d.ts.map
|