@hmharness/evolution 0.13.3 → 0.14.1
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/dist/evolve.js +19 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/labels.d.ts +20 -0
- package/dist/labels.js +57 -0
- package/dist/skillpayload.d.ts +1 -0
- package/dist/skillpayload.js +28 -0
- package/package.json +1 -1
package/dist/evolve.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* config, security settings, or code
|
|
14
14
|
* - memory is append-only (ACE: rewriting is how context gets lost)
|
|
15
15
|
*/
|
|
16
|
-
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
16
|
+
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { chat, loadConfig } from '@hmharness/kernel';
|
|
19
19
|
import { listCases, matchCase, seedCases } from "./bench.js";
|
|
@@ -337,6 +337,24 @@ export async function runEvolution(opts) {
|
|
|
337
337
|
lineage: { parentInsights: insightIds, scores: { train: candRate, holdout: holdout.length ? holdoutRate : undefined }, metaModel: provider.model, decidedAt: new Date().toISOString() },
|
|
338
338
|
});
|
|
339
339
|
say(` promoted to canary${holdout.length ? ` (holdout ${(holdoutRate * 100).toFixed(0)}%)` : ' [weak gate: no holdout]'}`);
|
|
340
|
+
// AWM workflows are versioned artifacts: a promoted *-workflow skill is
|
|
341
|
+
// ALSO recorded under evolution/workflows/<name>.json - the readiness
|
|
342
|
+
// "version-provenance" condition scans that dir (it was permanently
|
|
343
|
+
// empty before this, making condition 5 structurally unmeetable).
|
|
344
|
+
if (/workflow$/.test(p.name)) {
|
|
345
|
+
try {
|
|
346
|
+
const wfDir = join(home, 'evolution', 'workflows');
|
|
347
|
+
await mkdir(wfDir, { recursive: true });
|
|
348
|
+
await writeFile(join(wfDir, `${p.name}.json`), JSON.stringify({
|
|
349
|
+
name: p.name,
|
|
350
|
+
version: candRate.toFixed(2),
|
|
351
|
+
skillMd: p.skill_md,
|
|
352
|
+
promotedAt: new Date().toISOString(),
|
|
353
|
+
trainPassRate: candRate,
|
|
354
|
+
}, null, 2) + '\n', 'utf8');
|
|
355
|
+
}
|
|
356
|
+
catch { /* versioning is best-effort bookkeeping */ }
|
|
357
|
+
}
|
|
340
358
|
}
|
|
341
359
|
catch (err) {
|
|
342
360
|
report.outcomes.push({ name: p.name, action: 'error', reason: String(err).slice(0, 200) });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './memory.ts';
|
|
2
2
|
export * from './ranker.ts';
|
|
3
3
|
export * from './candidates.ts';
|
|
4
|
+
export * from './skillpayload.ts';
|
|
4
5
|
export * from './dataset.ts';
|
|
5
6
|
export * from './readiness.ts';
|
|
7
|
+
export * from './labels.ts';
|
|
6
8
|
export * from './insights.ts';
|
|
7
9
|
export * from './skills.ts';
|
|
8
10
|
export * from './bench.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from "./memory.js";
|
|
2
2
|
export * from "./ranker.js";
|
|
3
3
|
export * from "./candidates.js";
|
|
4
|
+
export * from "./skillpayload.js";
|
|
4
5
|
export * from "./dataset.js";
|
|
5
6
|
export * from "./readiness.js";
|
|
7
|
+
export * from "./labels.js";
|
|
6
8
|
export * from "./insights.js";
|
|
7
9
|
export * from "./skills.js";
|
|
8
10
|
export * from "./bench.js";
|
package/dist/labels.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface HumanLabel {
|
|
2
|
+
session: string;
|
|
3
|
+
score: number;
|
|
4
|
+
note?: string;
|
|
5
|
+
time: string;
|
|
6
|
+
}
|
|
7
|
+
/** Append one human score (dedupe: a session keeps its FIRST label). */
|
|
8
|
+
export declare function labelSession(home: string, session: string, score: number, note?: string): Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
reason?: string;
|
|
11
|
+
count: number;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function readLabels(home: string): Promise<HumanLabel[]>;
|
|
14
|
+
/** Recent sessions from the insight feed, each with its label (if any) -
|
|
15
|
+
* the pick list for `hmh label`. */
|
|
16
|
+
export declare function labelableSessions(home: string, limit?: number): Promise<Array<{
|
|
17
|
+
session: string;
|
|
18
|
+
task: string;
|
|
19
|
+
label?: HumanLabel;
|
|
20
|
+
}>>;
|
package/dist/labels.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/evolution - labels (SELFFEED month-2 foundation)
|
|
3
|
+
* Human reward-label channel: the M11 readiness condition
|
|
4
|
+
* 'reward-human-correlation' needs >=100 human-scored samples; this is the
|
|
5
|
+
* write path for them (evolution/reward-human-labels.jsonl, one line per
|
|
6
|
+
* sample, deduplicated by session). `hmh label <session-id> <1-5> [note]`.
|
|
7
|
+
*/
|
|
8
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
const labelsFile = (home) => join(home, 'evolution', 'reward-human-labels.jsonl');
|
|
11
|
+
/** Append one human score (dedupe: a session keeps its FIRST label). */
|
|
12
|
+
export async function labelSession(home, session, score, note) {
|
|
13
|
+
if (!session || !/^\d{4}-\d{2}-\d{2}T/.test(session))
|
|
14
|
+
return { ok: false, reason: 'session id looks wrong (expected YYYY-MM-DDThh-mm-ss-xxxxxx)', count: (await readLabels(home)).length };
|
|
15
|
+
if (!Number.isInteger(score) || score < 1 || score > 5)
|
|
16
|
+
return { ok: false, reason: 'score must be an integer 1..5', count: (await readLabels(home)).length };
|
|
17
|
+
const dir = join(home, 'evolution');
|
|
18
|
+
await mkdir(dir, { recursive: true });
|
|
19
|
+
const existing = await readLabels(home);
|
|
20
|
+
if (existing.some((l) => l.session === session))
|
|
21
|
+
return { ok: false, reason: `session ${session} already labeled (first label wins)`, count: existing.length };
|
|
22
|
+
const label = { session, score, ...(note ? { note: note.slice(0, 120) } : {}), time: new Date().toISOString() };
|
|
23
|
+
await appendFile(labelsFile(home), JSON.stringify(label) + '\n', 'utf8');
|
|
24
|
+
return { ok: true, count: existing.length + 1 };
|
|
25
|
+
}
|
|
26
|
+
export async function readLabels(home) {
|
|
27
|
+
try {
|
|
28
|
+
const text = await readFile(labelsFile(home), 'utf8');
|
|
29
|
+
return text.split('\n').filter(Boolean).map((l) => JSON.parse(l));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Recent sessions from the insight feed, each with its label (if any) -
|
|
36
|
+
* the pick list for `hmh label`. */
|
|
37
|
+
export async function labelableSessions(home, limit = 12) {
|
|
38
|
+
const { readInsights } = await import("./insights.js");
|
|
39
|
+
const labels = await readLabels(home);
|
|
40
|
+
const bySession = new Map(labels.map((l) => [l.session, l]));
|
|
41
|
+
const insights = await readInsights(home, 200);
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const i of insights) {
|
|
45
|
+
if (seen.has(i.session) || bySession.has(i.session))
|
|
46
|
+
continue;
|
|
47
|
+
seen.add(i.session);
|
|
48
|
+
out.push({ session: i.session, task: i.task.slice(0, 90), label: bySession.get(i.session) });
|
|
49
|
+
if (out.length >= limit)
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
// labeled ones first for re-inspection, then unlabeled
|
|
53
|
+
return [
|
|
54
|
+
...labels.slice(-limit).reverse().map((l) => ({ session: l.session, task: '(labeled)', label: l })),
|
|
55
|
+
...out,
|
|
56
|
+
];
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loadSkillPayload(home: string, payload: string): Promise<string>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a treatment-arm skills prompt for a skill-target candidate: the
|
|
3
|
+
* payload names the skill; the arm must inject the skill's CONTENT (draft
|
|
4
|
+
* first, promoted second), never the bare name string. (Day-49 SELFFEED:
|
|
5
|
+
* the first real experiment injected "+34 tokens" of name - it measured the
|
|
6
|
+
* instrument, not the skill.)
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
export async function loadSkillPayload(home, payload) {
|
|
11
|
+
const name = payload.trim();
|
|
12
|
+
if (!name)
|
|
13
|
+
return '';
|
|
14
|
+
const candidates = [
|
|
15
|
+
join(home, 'skills', 'drafts', `${name}.md`),
|
|
16
|
+
join(home, 'skills', `${name}.md`),
|
|
17
|
+
];
|
|
18
|
+
for (const f of candidates) {
|
|
19
|
+
try {
|
|
20
|
+
const md = await readFile(f, 'utf8');
|
|
21
|
+
if (md.trim())
|
|
22
|
+
return md.trim().slice(0, 4000);
|
|
23
|
+
}
|
|
24
|
+
catch { /* next */ }
|
|
25
|
+
}
|
|
26
|
+
// no file found: fall back to the raw string so the arm still runs
|
|
27
|
+
return name;
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hmharness/evolution",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "hmharness evolution subsystem: persistent memory, insight capture, skill library, and the bench that gives evolution its fitness signal. First-class kernel citizen, not a plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|