@lmzhen/dsh-evolution-curator 0.5.0 → 0.6.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 +3 -0
- package/lib/index.js +126 -27
- package/lib/types/index.d.ts +64 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -26,6 +26,9 @@ Deterministic skill lifecycle and recovery
|
|
|
26
26
|
|
|
27
27
|
- `autoStart` (default true) arms an HOURLY tick that only asks whether the due-ness interval (`intervalHours`, default 168 h) has elapsed — the tick is not the interval — plus a deferred catch-up check `bootGraceSeconds` (default 10) after host boot. Both decide due-ness from the **persisted** `lastRunAt`, so a restart with an overdue schedule runs the first pass within the boot grace instead of waiting a full interval. `bootGraceSeconds: 0` disables the deferral (not recommended: the check may run against a half-mounted host). All scheduling gates — interval, idle, first-run deferral, and the reentrancy guard — remain inside `run()`.
|
|
28
28
|
- `autoStart: false` disables both automatic checks; `/evolution curator run` (manual, gate-skipping) still works.
|
|
29
|
+
- Deprecated name (G0/S0.3): `intervalHours` is the legacy spelling of the policy
|
|
30
|
+
row's `curatorIntervalHours`, whose value the snapshot shadows. Reading it still
|
|
31
|
+
works; writing it is refused, and it is removed in 0.7.0.
|
|
29
32
|
|
|
30
33
|
**Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
|
|
31
34
|
## Notes and history
|
package/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
|
-
import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, parseCuratorNominations, parseFrontmatter, policyStageLimits, probeList, probeMtime, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, transactIo, updateSuppressedNames, usageObserved } from "@lmzhen/dsh-evolution-core";
|
|
6
|
+
import { CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MIN_IDLE_HOURS, DEFAULT_SKILL_LIMITS, DEFAULT_STALE_AFTER_DAYS, EvolutionGateSet, INSTANCE_KEYS, MAX_TIMER_DELAY_MS, PARAM_NAMESPACES, SKILL_NAME_RE, buildCuratorRunReport, claimInstance, clampedNumber, computeDedupGroups, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, emptyRecord, evolutionHome, evolutionIoAdapter, foldCuratorFields, installParamSection, isPresent, isUnknown, loadSuppressedNames, loadUsage, markerEntryName, mutateUsage, newSkillLibrary, parseCuratorNominations, parseFrontmatter, policyStageLimits, probeList, probeMtime, readNumberParam, relatedSkillNames, releaseInstance, renderCuratorReportMarkdown, transactIo, updateSuppressedNames, usageObserved } from "@lmzhen/dsh-evolution-core";
|
|
7
7
|
//#region lib/types/index.js
|
|
8
8
|
/**
|
|
9
9
|
* Deterministic skill lifecycle curator with interval gate and archive.
|
|
@@ -17,6 +17,36 @@ const DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS = 7;
|
|
|
17
17
|
* 120s matches the review subagent default; the 32-bit ceiling is Node's
|
|
18
18
|
* timer-delay limit (`AbortSignal.timeout` throws above it). */
|
|
19
19
|
const DEFAULT_CURATOR_REVIEW_TIMEOUT_MS = 12e4;
|
|
20
|
+
/** Namespace the curator group's user-writable knobs live in (core's PARAM_NAMESPACES). */
|
|
21
|
+
const CURATOR_SETTINGS_NAMESPACE = "evolution-curator";
|
|
22
|
+
/** Schema the platform validates the user layer against; defaults mirror the core
|
|
23
|
+
* constants and the bounds mirror the row schema, so an empty document resolves to
|
|
24
|
+
* today's behaviour and an out-of-range value is refused instead of clamped. */
|
|
25
|
+
const CURATOR_SETTINGS_SCHEMA = z.object({
|
|
26
|
+
curatorIntervalHours: z.number().min(1).default(DEFAULT_CURATOR_INTERVAL_HOURS),
|
|
27
|
+
staleAfterDays: z.number().min(1).default(DEFAULT_STALE_AFTER_DAYS),
|
|
28
|
+
archiveAfterDays: z.number().min(1).default(DEFAULT_ARCHIVE_AFTER_DAYS),
|
|
29
|
+
qualityWarnStaleAfterDays: z.number().min(1).default(DEFAULT_QUALITY_WARN_STALE_AFTER_DAYS),
|
|
30
|
+
minIdleHours: z.number().min(0).default(DEFAULT_MIN_IDLE_HOURS),
|
|
31
|
+
minIdleFailOpen: z.boolean().default(true),
|
|
32
|
+
llmReview: z.boolean().default(false),
|
|
33
|
+
curatorReviewMaxTokens: z.number().min(1).default(DEFAULT_CURATOR_REVIEW_MAX_TOKENS),
|
|
34
|
+
curatorReviewTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS).default(DEFAULT_CURATOR_REVIEW_TIMEOUT_MS),
|
|
35
|
+
healthSoftBodyChars: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.softBodyChars),
|
|
36
|
+
healthStampDensityPerKb: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb),
|
|
37
|
+
healthChurnMinPatches: z.number().min(1).default(DEFAULT_HEALTH_THRESHOLDS.churnMinPatches)
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* The cross-field rule the schema cannot express (A2-17): an archive threshold
|
|
41
|
+
* below the stale threshold makes the engine reactivate stale records instead of
|
|
42
|
+
* archiving them, so a resolved pair that violates it is refused at WRITE time.
|
|
43
|
+
* The runtime keeps its clamp for the carriers this hook never sees (the policy
|
|
44
|
+
* snapshot and the plugin row).
|
|
45
|
+
* @param value - the resolved section the platform hands the owner.
|
|
46
|
+
*/
|
|
47
|
+
function validateCuratorSettings(value) {
|
|
48
|
+
if (value.archiveAfterDays < value.staleAfterDays) throw new Error(`archiveAfterDays (${value.archiveAfterDays}) must be >= staleAfterDays (${value.staleAfterDays})`);
|
|
49
|
+
}
|
|
20
50
|
/**
|
|
21
51
|
* Block LLM-nominated consolidations that would touch a gate-protected name:
|
|
22
52
|
* exclude / referenced / suppressed skills must never merge (neither as the
|
|
@@ -76,6 +106,10 @@ var EvolutionCurator = class extends Service {
|
|
|
76
106
|
healthSoftBodyChars;
|
|
77
107
|
healthStampDensityPerKb;
|
|
78
108
|
healthChurnMinPatches;
|
|
109
|
+
/** G3/S3.3: this row's resolved values — the base layer of the settings section. */
|
|
110
|
+
settingsBase;
|
|
111
|
+
/** The user layer for this row's namespace (absent provider = deployment values). */
|
|
112
|
+
overrides;
|
|
79
113
|
lastRun = 0;
|
|
80
114
|
timer;
|
|
81
115
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -130,7 +164,7 @@ var EvolutionCurator = class extends Service {
|
|
|
130
164
|
if (value !== void 0 && result !== value) clamped.push(name);
|
|
131
165
|
return result;
|
|
132
166
|
};
|
|
133
|
-
this.intervalHours = field("intervalHours", config
|
|
167
|
+
this.intervalHours = field("intervalHours", readNumberParam(config, "curatorIntervalHours"), DEFAULT_CURATOR_INTERVAL_HOURS, 1);
|
|
134
168
|
this.staleAfterDays = field("staleAfterDays", config.staleAfterDays, DEFAULT_STALE_AFTER_DAYS, 1);
|
|
135
169
|
this.archiveAfterDays = field("archiveAfterDays", config.archiveAfterDays, DEFAULT_ARCHIVE_AFTER_DAYS, 1);
|
|
136
170
|
if (this.archiveAfterDays < this.staleAfterDays) {
|
|
@@ -153,6 +187,26 @@ var EvolutionCurator = class extends Service {
|
|
|
153
187
|
this.healthStampDensityPerKb = field("healthStampDensityPerKb", config.healthStampDensityPerKb, DEFAULT_HEALTH_THRESHOLDS.stampDensityPerKb, 1);
|
|
154
188
|
this.healthChurnMinPatches = field("healthChurnMinPatches", config.healthChurnMinPatches, DEFAULT_HEALTH_THRESHOLDS.churnMinPatches, 1);
|
|
155
189
|
if (clamped.length > 0) this.ctx.logger.warn(`evolution-curator: ${clamped.join(", ")} provided an invalid value; falling back to the default`);
|
|
190
|
+
this.settingsBase = {
|
|
191
|
+
curatorIntervalHours: this.intervalHours,
|
|
192
|
+
staleAfterDays: this.staleAfterDays,
|
|
193
|
+
archiveAfterDays: this.archiveAfterDays,
|
|
194
|
+
qualityWarnStaleAfterDays: this.qualityWarnStaleAfterDays,
|
|
195
|
+
minIdleHours: this.minIdleHours,
|
|
196
|
+
minIdleFailOpen: this.minIdleFailOpen,
|
|
197
|
+
llmReview: this.llmReview,
|
|
198
|
+
curatorReviewMaxTokens: this.curatorReviewMaxTokens,
|
|
199
|
+
curatorReviewTimeoutMs: this.curatorReviewTimeoutMs,
|
|
200
|
+
healthSoftBodyChars: this.healthSoftBodyChars,
|
|
201
|
+
healthStampDensityPerKb: this.healthStampDensityPerKb,
|
|
202
|
+
healthChurnMinPatches: this.healthChurnMinPatches
|
|
203
|
+
};
|
|
204
|
+
this.overrides = installParamSection(ctx, PARAM_NAMESPACES["evolution-curator"] ?? "evolution-curator", CURATOR_SETTINGS_SCHEMA, this.settingsBase, {
|
|
205
|
+
warn: (message) => {
|
|
206
|
+
this.ctx.logger.warn("dsh-evolution-curator: " + message);
|
|
207
|
+
},
|
|
208
|
+
validate: validateCuratorSettings
|
|
209
|
+
});
|
|
156
210
|
this.lastRun = Date.now();
|
|
157
211
|
this.ctx.effect(() => {
|
|
158
212
|
return () => {
|
|
@@ -163,17 +217,58 @@ var EvolutionCurator = class extends Service {
|
|
|
163
217
|
}, "evolution-curator.stop");
|
|
164
218
|
if (config.autoStart ?? true) this.start();
|
|
165
219
|
}
|
|
220
|
+
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
221
|
+
policyFields() {
|
|
222
|
+
return this.ctx.get("evolutionPolicy")?.get() ?? {};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* G3/S3.3: the ONE reader of the curator's behaviour knobs. Precedence is
|
|
226
|
+
* user > policy snapshot > this row, and {@link ParamOverrides.get} returns
|
|
227
|
+
* undefined for a key the user never set, so the deployment carriers keep
|
|
228
|
+
* winning exactly as before. Read at USE time: a committed settings change is
|
|
229
|
+
* live without a restart and without a watcher.
|
|
230
|
+
* @returns the resolved section, every numeric field sanitized.
|
|
231
|
+
*/
|
|
232
|
+
settings() {
|
|
233
|
+
const snapshot = this.policyFields();
|
|
234
|
+
const row = this.settingsBase;
|
|
235
|
+
const pick = (key) => {
|
|
236
|
+
const user = this.overrides.get(key);
|
|
237
|
+
if (user !== void 0) return user;
|
|
238
|
+
return snapshot[key] ?? row[key];
|
|
239
|
+
};
|
|
240
|
+
const number = (key, min, max) => {
|
|
241
|
+
const lower = row[key];
|
|
242
|
+
return clampedNumber(pick(key), lower, max === void 0 ? { min } : {
|
|
243
|
+
min,
|
|
244
|
+
max
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
return {
|
|
248
|
+
curatorIntervalHours: number("curatorIntervalHours", 1),
|
|
249
|
+
staleAfterDays: number("staleAfterDays", 1),
|
|
250
|
+
archiveAfterDays: number("archiveAfterDays", 1),
|
|
251
|
+
qualityWarnStaleAfterDays: number("qualityWarnStaleAfterDays", 1),
|
|
252
|
+
minIdleHours: number("minIdleHours", 0),
|
|
253
|
+
minIdleFailOpen: pick("minIdleFailOpen"),
|
|
254
|
+
llmReview: pick("llmReview"),
|
|
255
|
+
curatorReviewMaxTokens: number("curatorReviewMaxTokens", 1),
|
|
256
|
+
curatorReviewTimeoutMs: number("curatorReviewTimeoutMs", 1, MAX_TIMER_DELAY_MS),
|
|
257
|
+
healthSoftBodyChars: number("healthSoftBodyChars", 1),
|
|
258
|
+
healthStampDensityPerKb: number("healthStampDensityPerKb", 1),
|
|
259
|
+
healthChurnMinPatches: number("healthChurnMinPatches", 1)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
166
262
|
lifecycle() {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
archiveAfterDays = staleAfterDays;
|
|
263
|
+
const settings = this.settings();
|
|
264
|
+
let archiveAfterDays = settings.archiveAfterDays;
|
|
265
|
+
if (archiveAfterDays < settings.staleAfterDays) {
|
|
266
|
+
this.ctx.logger.warn(`evolution-curator: archiveAfterDays (${archiveAfterDays}) < staleAfterDays (${settings.staleAfterDays}); using the stale threshold as the archive threshold`);
|
|
267
|
+
archiveAfterDays = settings.staleAfterDays;
|
|
173
268
|
}
|
|
174
269
|
return {
|
|
175
|
-
intervalHours:
|
|
176
|
-
staleAfterDays,
|
|
270
|
+
intervalHours: settings.curatorIntervalHours,
|
|
271
|
+
staleAfterDays: settings.staleAfterDays,
|
|
177
272
|
archiveAfterDays
|
|
178
273
|
};
|
|
179
274
|
}
|
|
@@ -307,6 +402,7 @@ var EvolutionCurator = class extends Service {
|
|
|
307
402
|
"",
|
|
308
403
|
"Return a YAML summary with consolidations and prunings lists. Nominate only actions whose archival/merge is clearly safe."
|
|
309
404
|
].join("\n");
|
|
405
|
+
const settings = this.settings();
|
|
310
406
|
try {
|
|
311
407
|
const assembler = new BlockAssembler();
|
|
312
408
|
for await (const chunk of llm.stream({
|
|
@@ -324,8 +420,8 @@ var EvolutionCurator = class extends Service {
|
|
|
324
420
|
summary: "curator review"
|
|
325
421
|
}
|
|
326
422
|
})],
|
|
327
|
-
maxTokens:
|
|
328
|
-
signal: AbortSignal.timeout(
|
|
423
|
+
maxTokens: settings.curatorReviewMaxTokens,
|
|
424
|
+
signal: AbortSignal.timeout(settings.curatorReviewTimeoutMs)
|
|
329
425
|
})) assembler.push(chunk);
|
|
330
426
|
const parsed = parseCuratorNominations(assembler.blocks().filter((block) => block.type === "text").map((block) => block.text).join("\n"));
|
|
331
427
|
return {
|
|
@@ -398,7 +494,7 @@ var EvolutionCurator = class extends Service {
|
|
|
398
494
|
archiveCandidates: [],
|
|
399
495
|
archived: [],
|
|
400
496
|
failed: [],
|
|
401
|
-
llmReviewEnabled: this.llmReview
|
|
497
|
+
llmReviewEnabled: this.settings().llmReview
|
|
402
498
|
});
|
|
403
499
|
}
|
|
404
500
|
/**
|
|
@@ -506,6 +602,7 @@ var EvolutionCurator = class extends Service {
|
|
|
506
602
|
const runId = randomUUID();
|
|
507
603
|
const stateService = this.curatorStateService();
|
|
508
604
|
const lifecycle = this.lifecycle();
|
|
605
|
+
const settings = this.settings();
|
|
509
606
|
const persisted = await stateService?.loadCuratorState() ?? null;
|
|
510
607
|
if (!ignoreGates && persisted?.paused === true) return {
|
|
511
608
|
stale: [],
|
|
@@ -521,7 +618,7 @@ var EvolutionCurator = class extends Service {
|
|
|
521
618
|
report: this.skippedReport(runId, startedAt),
|
|
522
619
|
skipped: "interval"
|
|
523
620
|
};
|
|
524
|
-
if (!ignoreGates &&
|
|
621
|
+
if (!ignoreGates && settings.minIdleHours > 0 && this.recentSessionActive()) return {
|
|
525
622
|
stale: [],
|
|
526
623
|
archived: [],
|
|
527
624
|
errors: [],
|
|
@@ -584,7 +681,7 @@ var EvolutionCurator = class extends Service {
|
|
|
584
681
|
const result = computeLifecycleTransitions(usage, {
|
|
585
682
|
staleAfterDays: lifecycle.staleAfterDays,
|
|
586
683
|
archiveAfterDays: lifecycle.archiveAfterDays,
|
|
587
|
-
qualityWarnStaleAfterDays:
|
|
684
|
+
qualityWarnStaleAfterDays: settings.qualityWarnStaleAfterDays,
|
|
588
685
|
excludeSkillNames: this.excludeSkillNames,
|
|
589
686
|
manageUnmanaged: this.manageUnmanaged,
|
|
590
687
|
pruneBuiltins: this.pruneBuiltins,
|
|
@@ -593,7 +690,7 @@ var EvolutionCurator = class extends Service {
|
|
|
593
690
|
referencedSkillNames: this.referencedSkillNames
|
|
594
691
|
}, /* @__PURE__ */ new Date(), gates, protectedNames);
|
|
595
692
|
const recommendPool = [...new Set([...result.markStale, ...dedupMembers])].filter((name) => SKILL_NAME_RE.test(name));
|
|
596
|
-
const nominations =
|
|
693
|
+
const nominations = settings.llmReview ? await this.recommend(recommendPool, { dryRun }) : {
|
|
597
694
|
prunings: [],
|
|
598
695
|
consolidations: [],
|
|
599
696
|
warnings: []
|
|
@@ -605,7 +702,7 @@ var EvolutionCurator = class extends Service {
|
|
|
605
702
|
};
|
|
606
703
|
const llmNominations = gatedNominations.prunings;
|
|
607
704
|
const archiveCandidates = [...new Set([...result.archive, ...llmNominations])];
|
|
608
|
-
if (!ignoreGates &&
|
|
705
|
+
if (!ignoreGates && settings.minIdleHours > 0 && this.recentSessionActive()) {
|
|
609
706
|
if (!dryRun) try {
|
|
610
707
|
await mutateUsage(root, this.io, (map) => {
|
|
611
708
|
for (const [name, record] of usage) if (!map.has(name)) map.set(name, { ...record });
|
|
@@ -622,7 +719,7 @@ var EvolutionCurator = class extends Service {
|
|
|
622
719
|
};
|
|
623
720
|
}
|
|
624
721
|
const snapshotPath = dryRun ? void 0 : await this.snapshotFull("pre-curator-run");
|
|
625
|
-
if (!ignoreGates &&
|
|
722
|
+
if (!ignoreGates && settings.minIdleHours > 0 && this.recentSessionActive()) {
|
|
626
723
|
if (!dryRun) try {
|
|
627
724
|
await mutateUsage(root, this.io, (map) => {
|
|
628
725
|
for (const [name, record] of usage) if (!map.has(name)) map.set(name, { ...record });
|
|
@@ -686,7 +783,7 @@ var EvolutionCurator = class extends Service {
|
|
|
686
783
|
})(),
|
|
687
784
|
consolidated,
|
|
688
785
|
...snapshotPath === void 0 ? {} : { snapshotPath },
|
|
689
|
-
llmReviewEnabled:
|
|
786
|
+
llmReviewEnabled: settings.llmReview,
|
|
690
787
|
...gatedNominations.warnings.length > 0 ? { nominationsWarnings: gatedNominations.warnings } : {}
|
|
691
788
|
});
|
|
692
789
|
const reportsRoot = join(evolutionHome(), "reports");
|
|
@@ -697,7 +794,7 @@ var EvolutionCurator = class extends Service {
|
|
|
697
794
|
this.ctx.logger.warn(`evolution-curator: failed to persist report ${runId}`);
|
|
698
795
|
this.ctx.logger.warn(error);
|
|
699
796
|
}
|
|
700
|
-
const llmHint = !
|
|
797
|
+
const llmHint = !settings.llmReview && result.markStale.length > 0 ? " (llmReview: off - deterministic archive only; set llmReview: true for the LLM merge channel)" : "";
|
|
701
798
|
const summary = `${dryRun ? "dry-run" : "auto"}: stale:${result.markStale.length} archived:${archivedSkills.length} consolidated:${consolidated.length}${llmHint}`;
|
|
702
799
|
try {
|
|
703
800
|
await stateService?.transactCuratorState((current) => {
|
|
@@ -718,7 +815,7 @@ var EvolutionCurator = class extends Service {
|
|
|
718
815
|
archived: archivedSkills.map((item) => item.name),
|
|
719
816
|
errors,
|
|
720
817
|
report,
|
|
721
|
-
...
|
|
818
|
+
...settings.llmReview ? { nominations: gatedNominations } : {}
|
|
722
819
|
};
|
|
723
820
|
}
|
|
724
821
|
/**
|
|
@@ -950,15 +1047,16 @@ var EvolutionCurator = class extends Service {
|
|
|
950
1047
|
};
|
|
951
1048
|
}
|
|
952
1049
|
recentSessionActive() {
|
|
1050
|
+
const settings = this.settings();
|
|
953
1051
|
const agents = this.ctx.get("agents");
|
|
954
|
-
if (!agents) return !
|
|
1052
|
+
if (!agents) return !settings.minIdleFailOpen;
|
|
955
1053
|
let latest = 0;
|
|
956
1054
|
for (const agent of agents.list()) {
|
|
957
1055
|
const events = agent.session.snapshotEvents();
|
|
958
1056
|
const last = events.length === 0 ? 0 : events[events.length - 1]?.time ?? 0;
|
|
959
1057
|
latest = Math.max(latest, last);
|
|
960
1058
|
}
|
|
961
|
-
return latest > 0 && Date.now() - latest <
|
|
1059
|
+
return latest > 0 && Date.now() - latest < settings.minIdleHours * 36e5;
|
|
962
1060
|
}
|
|
963
1061
|
/**
|
|
964
1062
|
* Keep only the newest N curator run reports plus at most `errorKeep` error
|
|
@@ -1105,10 +1203,11 @@ var EvolutionCurator = class extends Service {
|
|
|
1105
1203
|
* layer.
|
|
1106
1204
|
*/
|
|
1107
1205
|
async healthView() {
|
|
1206
|
+
const settings = this.settings();
|
|
1108
1207
|
const thresholds = {
|
|
1109
|
-
softBodyChars:
|
|
1110
|
-
stampDensityPerKb:
|
|
1111
|
-
churnMinPatches:
|
|
1208
|
+
softBodyChars: settings.healthSoftBodyChars,
|
|
1209
|
+
stampDensityPerKb: settings.healthStampDensityPerKb,
|
|
1210
|
+
churnMinPatches: settings.healthChurnMinPatches
|
|
1112
1211
|
};
|
|
1113
1212
|
const usage = await loadUsage(this.skills.root, this.io);
|
|
1114
1213
|
const observed = usageObserved(usage);
|
|
@@ -1237,4 +1336,4 @@ function reportsSweepLockTarget() {
|
|
|
1237
1336
|
return join(evolutionHome(), "reports", ".retention");
|
|
1238
1337
|
}
|
|
1239
1338
|
//#endregion
|
|
1240
|
-
export { EvolutionCurator, EvolutionCurator as default, gateConsolidations };
|
|
1339
|
+
export { CURATOR_SETTINGS_NAMESPACE, CURATOR_SETTINGS_SCHEMA, EvolutionCurator, EvolutionCurator as default, gateConsolidations, validateCuratorSettings };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @module @lmzhen/dsh-evolution-curator
|
|
4
4
|
*/
|
|
5
5
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
6
|
+
import z from '@deepseek-ai/schemastery';
|
|
6
7
|
import type Schema from '@deepseek-ai/schemastery';
|
|
7
8
|
import { EvolutionGateSet, type SkillLibrary } from '@lmzhen/dsh-evolution-core';
|
|
8
9
|
import { type CuratorConsolidation, type CuratorNominations, type CuratorRunReport, type ScopeView, type SkillActionResult, type SkillHealthVerdict } from '@lmzhen/dsh-evolution-core';
|
|
@@ -14,6 +15,10 @@ declare module '@deepseek-ai/cordis' {
|
|
|
14
15
|
}
|
|
15
16
|
export interface Config {
|
|
16
17
|
enabled?: boolean;
|
|
18
|
+
/** The due-ness interval. The policy snapshot shadows it in every shipped
|
|
19
|
+
* composition — configure `curatorIntervalHours` there; that name is the
|
|
20
|
+
* canonical id. Deprecated alias (G0/S0.3): still readable, refused by
|
|
21
|
+
* writes; removed 0.7.0. */
|
|
17
22
|
intervalHours?: number;
|
|
18
23
|
staleAfterDays?: number;
|
|
19
24
|
archiveAfterDays?: number;
|
|
@@ -56,6 +61,50 @@ export interface Config {
|
|
|
56
61
|
/** Structure-health write-ghost floor: patches at/above with zero reads (A2). */
|
|
57
62
|
healthChurnMinPatches?: number;
|
|
58
63
|
}
|
|
64
|
+
/** Namespace the curator group's user-writable knobs live in (core's PARAM_NAMESPACES). */
|
|
65
|
+
export declare const CURATOR_SETTINGS_NAMESPACE = "evolution-curator";
|
|
66
|
+
/** Curator behaviour a user may change (G3/S3.3). Field names are the CANONICAL
|
|
67
|
+
* parameter ids from the registry, so the settings document, the params output,
|
|
68
|
+
* the doctor report and the cards all spell one name. */
|
|
69
|
+
export interface CuratorSettings {
|
|
70
|
+
/** Minimum hours between deterministic curation passes. */
|
|
71
|
+
curatorIntervalHours: number;
|
|
72
|
+
/** Inactive days before a skill counts as stale. */
|
|
73
|
+
staleAfterDays: number;
|
|
74
|
+
/** Inactive days before a stale skill is archived (>= staleAfterDays). */
|
|
75
|
+
archiveAfterDays: number;
|
|
76
|
+
/** Age at which a low quality score starts warning. */
|
|
77
|
+
qualityWarnStaleAfterDays: number;
|
|
78
|
+
/** Idle hours required before an automatic pass runs (0 disables the gate). */
|
|
79
|
+
minIdleHours: number;
|
|
80
|
+
/** Let the idle gate open when the activity probe is unavailable. */
|
|
81
|
+
minIdleFailOpen: boolean;
|
|
82
|
+
/** Enable the LLM nomination pass on top of the deterministic lifecycle. */
|
|
83
|
+
llmReview: boolean;
|
|
84
|
+
/** Token budget of the curator LLM review. */
|
|
85
|
+
curatorReviewMaxTokens: number;
|
|
86
|
+
/** Wall-clock bound of the curator LLM review. */
|
|
87
|
+
curatorReviewTimeoutMs: number;
|
|
88
|
+
/** Body character line the health view judges against. */
|
|
89
|
+
healthSoftBodyChars: number;
|
|
90
|
+
/** Stamp density per KB that flags log-like content in a body. */
|
|
91
|
+
healthStampDensityPerKb: number;
|
|
92
|
+
/** Patches without a read that flag a write-ghost skill. */
|
|
93
|
+
healthChurnMinPatches: number;
|
|
94
|
+
}
|
|
95
|
+
/** Schema the platform validates the user layer against; defaults mirror the core
|
|
96
|
+
* constants and the bounds mirror the row schema, so an empty document resolves to
|
|
97
|
+
* today's behaviour and an out-of-range value is refused instead of clamped. */
|
|
98
|
+
export declare const CURATOR_SETTINGS_SCHEMA: z<CuratorSettings>;
|
|
99
|
+
/**
|
|
100
|
+
* The cross-field rule the schema cannot express (A2-17): an archive threshold
|
|
101
|
+
* below the stale threshold makes the engine reactivate stale records instead of
|
|
102
|
+
* archiving them, so a resolved pair that violates it is refused at WRITE time.
|
|
103
|
+
* The runtime keeps its clamp for the carriers this hook never sees (the policy
|
|
104
|
+
* snapshot and the plugin row).
|
|
105
|
+
* @param value - the resolved section the platform hands the owner.
|
|
106
|
+
*/
|
|
107
|
+
export declare function validateCuratorSettings(value: CuratorSettings): void;
|
|
59
108
|
/** Outcome of one curator run pass. */
|
|
60
109
|
export interface CuratorRunOutcome {
|
|
61
110
|
stale: string[];
|
|
@@ -113,6 +162,10 @@ export declare class EvolutionCurator extends Service {
|
|
|
113
162
|
private readonly healthSoftBodyChars;
|
|
114
163
|
private readonly healthStampDensityPerKb;
|
|
115
164
|
private readonly healthChurnMinPatches;
|
|
165
|
+
/** G3/S3.3: this row's resolved values — the base layer of the settings section. */
|
|
166
|
+
private readonly settingsBase;
|
|
167
|
+
/** The user layer for this row's namespace (absent provider = deployment values). */
|
|
168
|
+
private readonly overrides;
|
|
116
169
|
private lastRun;
|
|
117
170
|
private timer;
|
|
118
171
|
/** B-8 (v18): set by the fiber disposer; a triggered autoCheck must not
|
|
@@ -140,6 +193,17 @@ export declare class EvolutionCurator extends Service {
|
|
|
140
193
|
* yielding instance's log line names a concrete holder. */
|
|
141
194
|
private readonly instanceOwner;
|
|
142
195
|
constructor(ctx: Context, config?: Config);
|
|
196
|
+
/** The policy-snapshot carrier for this group (unmounted service = no policy). */
|
|
197
|
+
private policyFields;
|
|
198
|
+
/**
|
|
199
|
+
* G3/S3.3: the ONE reader of the curator's behaviour knobs. Precedence is
|
|
200
|
+
* user > policy snapshot > this row, and {@link ParamOverrides.get} returns
|
|
201
|
+
* undefined for a key the user never set, so the deployment carriers keep
|
|
202
|
+
* winning exactly as before. Read at USE time: a committed settings change is
|
|
203
|
+
* live without a restart and without a watcher.
|
|
204
|
+
* @returns the resolved section, every numeric field sanitized.
|
|
205
|
+
*/
|
|
206
|
+
private settings;
|
|
143
207
|
private lifecycle;
|
|
144
208
|
start(): void;
|
|
145
209
|
stop(): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-curator",
|
|
3
3
|
"description": "Deterministic skill lifecycle and recovery (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,20 +27,20 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.6.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
34
|
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
35
35
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
36
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
37
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
36
|
+
"@lmzhen/dsh-evolution-io": "^0.6.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-state": "^0.6.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@deepseek-ai/dsh-llm": "^0.1.5-rc.2",
|
|
41
41
|
"@deepseek-ai/dsh-session": "^0.1.5-rc.2",
|
|
42
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
43
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
44
|
-
"@lmzhen/dsh-evolution-state": "^0.
|
|
42
|
+
"@lmzhen/dsh-evolution-core": "^0.6.0",
|
|
43
|
+
"@lmzhen/dsh-evolution-io": "^0.6.0",
|
|
44
|
+
"@lmzhen/dsh-evolution-state": "^0.6.0"
|
|
45
45
|
}
|
|
46
46
|
}
|