@lmzhen/dsh-evolution-feedback 0.3.81 → 0.3.83
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 +2 -2
- package/lib/index.js +77 -5
- package/lib/types/index.d.ts +25 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @lmzhen/dsh-evolution-feedback
|
|
2
2
|
|
|
3
3
|
Feedback-to-quality scoring for self-evolution
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ Feedback-to-quality scoring for self-evolution
|
|
|
8
8
|
|
|
9
9
|
#### What the model sees
|
|
10
10
|
|
|
11
|
-
`@
|
|
11
|
+
`@lmzhen/dsh-evolution-feedback` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
|
|
12
12
|
|
|
13
13
|
#### Token effect
|
|
14
14
|
|
package/lib/index.js
CHANGED
|
@@ -30,6 +30,17 @@ const CACHE_VERSION = 2;
|
|
|
30
30
|
* live in the ACTIVE log (bounded by `EVENT_LOG_ROTATE_AT`), so the next fold
|
|
31
31
|
* is complete. Package-private tunable, not a config surface. */
|
|
32
32
|
const CACHE_SNAP_EVERY = 1024;
|
|
33
|
+
/** C-events-dispatch-1 (v43 audit): the timeline read flags a DROPPED band — an
|
|
34
|
+
* unreadable archive (EISDIR/EACCES) or a body this v1 reader cannot interpret
|
|
35
|
+
* (damaged, or a future version). The boot cache stores the fold BASELINE
|
|
36
|
+
* (`lastSeq`), and `foldWithDelta` only folds `seq > lastSeq`, so folding a
|
|
37
|
+
* truncated read back into it would seal the dropped band against every later
|
|
38
|
+
* boot — the loss becomes unrecoverable without deleting the cache by hand.
|
|
39
|
+
* Both cache writers withhold the refresh when that flag is set (the cache is
|
|
40
|
+
* disposable by contract, so the last COMPLETE fold simply stays) and say so
|
|
41
|
+
* through this ONE message, deduped to a single warn per process (V5-32) because
|
|
42
|
+
* `refold()` re-runs `restore()` before every quality push. */
|
|
43
|
+
const TRUNCATED_TIMELINE_WARN = "evolution-feedback: the evolution event timeline is TRUNCATED - at least one segment (an unreadable archive, or a body this v1 reader cannot interpret, e.g. a future version) was dropped, so the folded counts are incomplete; the boot cache was NOT updated (a truncated lastSeq would seal the missing band against every later boot)";
|
|
33
44
|
var EvolutionFeedback = class EvolutionFeedback {
|
|
34
45
|
state = {
|
|
35
46
|
skills: {},
|
|
@@ -46,6 +57,21 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
46
57
|
* unpersisted note there, and reverting to it resurrects a value the log
|
|
47
58
|
* never held). Seeded from the fold truth, updated on successful appends. */
|
|
48
59
|
durableNote = /* @__PURE__ */ new Map();
|
|
60
|
+
/** PLAN S1.4 (2026-09-16): targets with a feedback append still IN FLIGHT,
|
|
61
|
+
* split by table, as a COUNT per target. record() increments before queueing
|
|
62
|
+
* the append and the append task decrements when it settles (landed or rolled
|
|
63
|
+
* back, finally), dropping the entry only at ZERO — the count covers exactly
|
|
64
|
+
* the optimistic window rc.66 meant to protect, including two appends for the
|
|
65
|
+
* same target in flight at once (review C-P2-1: a per-target boolean marker
|
|
66
|
+
* was cleared by the first settle, so a merge between the two appends folded
|
|
67
|
+
* the second append's increment away). restore() lets memory win ONLY for these targets — a settled
|
|
68
|
+
* target folds from the log truth, so another process's feedback for the
|
|
69
|
+
* same target (audit P1-3) is never overwritten by this process's stale
|
|
70
|
+
* record. */
|
|
71
|
+
pendingAppends = {
|
|
72
|
+
skills: /* @__PURE__ */ new Map(),
|
|
73
|
+
sessions: /* @__PURE__ */ new Map()
|
|
74
|
+
};
|
|
49
75
|
/** P2-32 (v11): process-level bound — every feedbacked session would
|
|
50
76
|
* otherwise keep one entry for the whole process lifetime (a name + note
|
|
51
77
|
* string per session); cap this map and drop the earliest-INSERTED entry on
|
|
@@ -74,6 +100,29 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
74
100
|
attachIo(io) {
|
|
75
101
|
this.io = io;
|
|
76
102
|
}
|
|
103
|
+
/** PLAN S4.6 (2026-09-16): whether a durable backend is attached — the
|
|
104
|
+
* plugin's quality push (`apply`) consults this before writing the
|
|
105
|
+
* feedback pair into the skill-usage sidecar. Same no-io posture as
|
|
106
|
+
* `record`/`refold`: without `ctx.evolutionIo` (when mounted) nothing may
|
|
107
|
+
* turn the optimistic in-memory aggregate into a durable side effect. */
|
|
108
|
+
get durable() {
|
|
109
|
+
return this.io !== void 0 && !!this.eventsPath;
|
|
110
|
+
}
|
|
111
|
+
/** V5-32 posture applied to the read side (C-events-dispatch-1, v43): an
|
|
112
|
+
* event a repeated read keeps reporting must not warn on each read —
|
|
113
|
+
* `refold()` calls `restore()` before every quality push. Shares the bounded
|
|
114
|
+
* `warnedMessages` set (and its FIFO eviction) with the append-failure warn.
|
|
115
|
+
* @param message - the warn text; identical text warns once per process.
|
|
116
|
+
*/
|
|
117
|
+
warnOnce(message) {
|
|
118
|
+
if (this.warnedMessages.has(message)) return;
|
|
119
|
+
if (this.warnedMessages.size >= this.WARNED_CAP) {
|
|
120
|
+
const oldest = this.warnedMessages.values().next().value;
|
|
121
|
+
if (oldest !== void 0) this.warnedMessages.delete(oldest);
|
|
122
|
+
}
|
|
123
|
+
this.warnedMessages.add(message);
|
|
124
|
+
this.warn(message);
|
|
125
|
+
}
|
|
77
126
|
/** Durable-note map key (V4-41): a target shares one record per mode. */
|
|
78
127
|
noteKey(mode, target) {
|
|
79
128
|
return `${mode}\u0000${target}`;
|
|
@@ -99,20 +148,31 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
99
148
|
}
|
|
100
149
|
const initial = await readEvolutionTimeline(io, eventsPath, archiveNames);
|
|
101
150
|
const lateArchives = (await listEventArchives(io, eventsPath)).filter((name) => !archiveNames.includes(name));
|
|
102
|
-
const
|
|
151
|
+
const later = lateArchives.length === 0 ? null : await readEvolutionTimeline(io, eventsPath, [...archiveNames, ...lateArchives]);
|
|
152
|
+
const events = later?.events ?? initial.events;
|
|
153
|
+
const truncated = initial.malformed || later?.malformed === true;
|
|
103
154
|
const cache = parseCache(rawCache, this.warn);
|
|
104
155
|
const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
105
156
|
const floor = events[0]?.seq ?? 0;
|
|
106
157
|
const usableCache = cache && cache.lastSeq >= floor - 1 ? cache : null;
|
|
107
158
|
const truth = usableCache ? foldWithDelta(usableCache, events, this.warn) : foldFeedbackState(events, this.warn);
|
|
159
|
+
const pickPending = (mode) => {
|
|
160
|
+
const picked = {};
|
|
161
|
+
for (const [target, inFlight] of this.pendingAppends[mode]) {
|
|
162
|
+
if (inFlight <= 0) continue;
|
|
163
|
+
const record = this.state[mode][target];
|
|
164
|
+
if (record) picked[target] = record;
|
|
165
|
+
}
|
|
166
|
+
return picked;
|
|
167
|
+
};
|
|
108
168
|
this.state = {
|
|
109
169
|
skills: {
|
|
110
170
|
...truth.skills,
|
|
111
|
-
...
|
|
171
|
+
...pickPending("skills")
|
|
112
172
|
},
|
|
113
173
|
sessions: {
|
|
114
174
|
...truth.sessions,
|
|
115
|
-
...
|
|
175
|
+
...pickPending("sessions")
|
|
116
176
|
}
|
|
117
177
|
};
|
|
118
178
|
const skillEntries = Object.entries(truth.skills);
|
|
@@ -129,7 +189,8 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
129
189
|
if (oldest === void 0) break;
|
|
130
190
|
this.durableNote.delete(oldest);
|
|
131
191
|
}
|
|
132
|
-
if (
|
|
192
|
+
if (truncated) this.warnOnce(TRUNCATED_TIMELINE_WARN);
|
|
193
|
+
else if (maxSeq > 0 && (!cache || cache.lastSeq < maxSeq)) try {
|
|
133
194
|
await io.writeText(path, JSON.stringify({
|
|
134
195
|
version: CACHE_VERSION,
|
|
135
196
|
lastSeq: maxSeq,
|
|
@@ -175,6 +236,8 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
175
236
|
const recordIo = this.io;
|
|
176
237
|
const eventsPath = this.eventsPath;
|
|
177
238
|
if (!recordIo || !eventsPath) return;
|
|
239
|
+
const pending = this.pendingAppends[mode];
|
|
240
|
+
pending.set(target, (pending.get(target) ?? 0) + 1);
|
|
178
241
|
this.mutate(async () => {
|
|
179
242
|
try {
|
|
180
243
|
const seq = await appendEvolutionEvent(recordIo, eventsPath, {
|
|
@@ -215,6 +278,10 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
215
278
|
this.warn(message);
|
|
216
279
|
}
|
|
217
280
|
if (rollback) this.onRollback?.(target, kind);
|
|
281
|
+
} finally {
|
|
282
|
+
const remaining = (pending.get(target) ?? 1) - 1;
|
|
283
|
+
if (remaining > 0) pending.set(target, remaining);
|
|
284
|
+
else pending.delete(target);
|
|
218
285
|
}
|
|
219
286
|
});
|
|
220
287
|
}
|
|
@@ -255,7 +322,11 @@ var EvolutionFeedback = class EvolutionFeedback {
|
|
|
255
322
|
const recordIo = this.io;
|
|
256
323
|
if (!path || !eventsPath || !recordIo) return;
|
|
257
324
|
try {
|
|
258
|
-
const { events } = await readEvolutionTimeline(recordIo, eventsPath);
|
|
325
|
+
const { events, malformed } = await readEvolutionTimeline(recordIo, eventsPath);
|
|
326
|
+
if (malformed) {
|
|
327
|
+
this.warnOnce(TRUNCATED_TIMELINE_WARN);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
259
330
|
const maxSeq = events.reduce((max, event) => Math.max(max, event.seq), 0);
|
|
260
331
|
if (maxSeq === 0) return;
|
|
261
332
|
let rawCache;
|
|
@@ -584,6 +655,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
584
655
|
const skillUsage = skillCtx.skillUsage;
|
|
585
656
|
const pushQuality = (target, kind) => {
|
|
586
657
|
if (kind !== "skill") return;
|
|
658
|
+
if (!feedback.durable) return;
|
|
587
659
|
feedback.refold().then(() => {
|
|
588
660
|
const score = feedback.score(target, "skill");
|
|
589
661
|
const warn = score < qualityWarnThreshold;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -50,6 +50,18 @@ export declare class EvolutionFeedback {
|
|
|
50
50
|
* unpersisted note there, and reverting to it resurrects a value the log
|
|
51
51
|
* never held). Seeded from the fold truth, updated on successful appends. */
|
|
52
52
|
private readonly durableNote;
|
|
53
|
+
/** PLAN S1.4 (2026-09-16): targets with a feedback append still IN FLIGHT,
|
|
54
|
+
* split by table, as a COUNT per target. record() increments before queueing
|
|
55
|
+
* the append and the append task decrements when it settles (landed or rolled
|
|
56
|
+
* back, finally), dropping the entry only at ZERO — the count covers exactly
|
|
57
|
+
* the optimistic window rc.66 meant to protect, including two appends for the
|
|
58
|
+
* same target in flight at once (review C-P2-1: a per-target boolean marker
|
|
59
|
+
* was cleared by the first settle, so a merge between the two appends folded
|
|
60
|
+
* the second append's increment away). restore() lets memory win ONLY for these targets — a settled
|
|
61
|
+
* target folds from the log truth, so another process's feedback for the
|
|
62
|
+
* same target (audit P1-3) is never overwritten by this process's stale
|
|
63
|
+
* record. */
|
|
64
|
+
private readonly pendingAppends;
|
|
53
65
|
/** P2-32 (v11): process-level bound — every feedbacked session would
|
|
54
66
|
* otherwise keep one entry for the whole process lifetime (a name + note
|
|
55
67
|
* string per session); cap this map and drop the earliest-INSERTED entry on
|
|
@@ -71,6 +83,19 @@ export declare class EvolutionFeedback {
|
|
|
71
83
|
constructor(io?: IoLike, home?: string, pathOverride?: string, warn?: (message: string) => void);
|
|
72
84
|
/** Bind the evolution IO backend after construction (S6.4 deferred binding). */
|
|
73
85
|
attachIo(io: IoLike): void;
|
|
86
|
+
/** PLAN S4.6 (2026-09-16): whether a durable backend is attached — the
|
|
87
|
+
* plugin's quality push (`apply`) consults this before writing the
|
|
88
|
+
* feedback pair into the skill-usage sidecar. Same no-io posture as
|
|
89
|
+
* `record`/`refold`: without `ctx.evolutionIo` (when mounted) nothing may
|
|
90
|
+
* turn the optimistic in-memory aggregate into a durable side effect. */
|
|
91
|
+
get durable(): boolean;
|
|
92
|
+
/** V5-32 posture applied to the read side (C-events-dispatch-1, v43): an
|
|
93
|
+
* event a repeated read keeps reporting must not warn on each read —
|
|
94
|
+
* `refold()` calls `restore()` before every quality push. Shares the bounded
|
|
95
|
+
* `warnedMessages` set (and its FIFO eviction) with the append-failure warn.
|
|
96
|
+
* @param message - the warn text; identical text warns once per process.
|
|
97
|
+
*/
|
|
98
|
+
private warnOnce;
|
|
74
99
|
/** Durable-note map key (V4-41): a target shares one record per mode. */
|
|
75
100
|
private noteKey;
|
|
76
101
|
private mutate;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-feedback",
|
|
3
3
|
"description": "Feedback-to-quality scoring for self-evolution (community build)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.83",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.3.83"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-skill-usage": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-io": "^0.3.83",
|
|
35
|
+
"@lmzhen/dsh-skill-usage": "^0.3.83"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
39
|
-
"@lmzhen/dsh-evolution-io-node": "^0.3.
|
|
40
|
-
"@lmzhen/dsh-skill-usage": "^0.3.
|
|
38
|
+
"@lmzhen/dsh-evolution-io": "^0.3.83",
|
|
39
|
+
"@lmzhen/dsh-evolution-io-node": "^0.3.83",
|
|
40
|
+
"@lmzhen/dsh-skill-usage": "^0.3.83"
|
|
41
41
|
}
|
|
42
42
|
}
|