@hmharness/evolution 0.14.15 → 0.14.18

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @hmharness/evolution - External Task Injection (P0-06)
3
+ *
4
+ * The audit's anti-self-validation requirement: "如果 Agent 自己生成任务、
5
+ * 自己执行、自己评测,就很容易得到自证式 improvement"
6
+ *
7
+ * This module provides:
8
+ * 1. External task intake: real user/external project tasks enter the system
9
+ * 2. Holdout marking: external tasks go to a SEPARATE holdout set
10
+ * 3. Contamination prevention: external holdout tasks NEVER enter training
11
+ * 4. Independence verification: holdout results are measured separately
12
+ */
13
+ export interface ExternalTask {
14
+ /** unique id */
15
+ id: string;
16
+ /** the task prompt (from a real user, not self-generated) */
17
+ prompt: string;
18
+ /** source identifier (e.g. "github-issue", "user-request", "external-benchmark") */
19
+ source: string;
20
+ /** when this task was submitted */
21
+ submittedAt: string;
22
+ /** expected outcome (if known) for verification */
23
+ expectedOutcome?: string;
24
+ /** the agent's actual result (filled after execution) */
25
+ actualResult?: string;
26
+ /** whether this task passed */
27
+ passed?: boolean;
28
+ /** metadata */
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+ export interface HoldoutReport {
32
+ totalExternal: number;
33
+ totalEvaluated: number;
34
+ passRate: number;
35
+ bySource: Record<string, {
36
+ total: number;
37
+ passed: number;
38
+ }>;
39
+ /** contamination check: any external tasks found in training data? */
40
+ contaminationDetected: boolean;
41
+ contaminationCount: number;
42
+ }
43
+ /**
44
+ * Submit an external task into the system.
45
+ * External tasks are marked separately and NEVER enter the training set.
46
+ */
47
+ export declare function submitExternalTask(home: string, task: {
48
+ prompt: string;
49
+ source: string;
50
+ expectedOutcome?: string;
51
+ metadata?: Record<string, unknown>;
52
+ }): Promise<ExternalTask>;
53
+ /**
54
+ * Read all external tasks.
55
+ */
56
+ export declare function readExternalTasks(home: string): Promise<ExternalTask[]>;
57
+ /**
58
+ * CRITICAL: Check that no external holdout tasks have leaked into
59
+ * the training data (DPO pairs). This is the audit's anti-cheating mechanism.
60
+ *
61
+ * Pure - testable.
62
+ */
63
+ export declare function checkContamination(externalTasks: ExternalTask[], trainingPairPrompts: string[]): {
64
+ contaminated: boolean;
65
+ count: number;
66
+ details: string[];
67
+ };
68
+ /**
69
+ * Build a holdout report from external task results.
70
+ * Pure - testable.
71
+ */
72
+ export declare function buildHoldoutReport(tasks: ExternalTask[], contaminationResult: {
73
+ contaminated: boolean;
74
+ count: number;
75
+ }): HoldoutReport;
76
+ /**
77
+ * Generate a summary of external task status.
78
+ * Pure - testable.
79
+ */
80
+ export declare function externalTaskSummary(tasks: ExternalTask[]): string;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @hmharness/evolution - External Task Injection (P0-06)
3
+ *
4
+ * The audit's anti-self-validation requirement: "如果 Agent 自己生成任务、
5
+ * 自己执行、自己评测,就很容易得到自证式 improvement"
6
+ *
7
+ * This module provides:
8
+ * 1. External task intake: real user/external project tasks enter the system
9
+ * 2. Holdout marking: external tasks go to a SEPARATE holdout set
10
+ * 3. Contamination prevention: external holdout tasks NEVER enter training
11
+ * 4. Independence verification: holdout results are measured separately
12
+ */
13
+ import { readFile, appendFile, mkdir } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ const externalFile = (home) => join(home, 'evolution', 'external-tasks.jsonl');
16
+ /**
17
+ * Submit an external task into the system.
18
+ * External tasks are marked separately and NEVER enter the training set.
19
+ */
20
+ export async function submitExternalTask(home, task) {
21
+ const dir = join(home, 'evolution');
22
+ await mkdir(dir, { recursive: true });
23
+ const ext = {
24
+ id: `ext-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
25
+ prompt: task.prompt,
26
+ source: task.source,
27
+ submittedAt: new Date().toISOString(),
28
+ expectedOutcome: task.expectedOutcome,
29
+ metadata: task.metadata,
30
+ };
31
+ await appendFile(externalFile(home), JSON.stringify(ext) + '\n', 'utf8');
32
+ return ext;
33
+ }
34
+ /**
35
+ * Read all external tasks.
36
+ */
37
+ export async function readExternalTasks(home) {
38
+ try {
39
+ const text = await readFile(externalFile(home), 'utf8');
40
+ return text.split('\n').filter(Boolean).map(l => JSON.parse(l));
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * CRITICAL: Check that no external holdout tasks have leaked into
48
+ * the training data (DPO pairs). This is the audit's anti-cheating mechanism.
49
+ *
50
+ * Pure - testable.
51
+ */
52
+ export function checkContamination(externalTasks, trainingPairPrompts) {
53
+ const externalPrompts = new Set(externalTasks.map(t => t.prompt.trim().toLowerCase()));
54
+ const details = [];
55
+ let count = 0;
56
+ for (const prompt of trainingPairPrompts) {
57
+ const normalized = prompt.trim().toLowerCase();
58
+ // exact match or high overlap (first 100 chars)
59
+ const prefix = normalized.slice(0, 100);
60
+ if (externalPrompts.has(normalized) || [...externalPrompts].some(ep => ep.startsWith(prefix))) {
61
+ count++;
62
+ details.push(`training prompt matches external task: "${prompt.slice(0, 60)}..."`);
63
+ }
64
+ }
65
+ return { contaminated: count > 0, count, details };
66
+ }
67
+ /**
68
+ * Build a holdout report from external task results.
69
+ * Pure - testable.
70
+ */
71
+ export function buildHoldoutReport(tasks, contaminationResult) {
72
+ const evaluated = tasks.filter(t => t.passed !== undefined);
73
+ const passed = evaluated.filter(t => t.passed).length;
74
+ const bySource = {};
75
+ for (const t of evaluated) {
76
+ const src = t.source || 'unknown';
77
+ if (!bySource[src])
78
+ bySource[src] = { total: 0, passed: 0 };
79
+ bySource[src].total++;
80
+ if (t.passed)
81
+ bySource[src].passed++;
82
+ }
83
+ return {
84
+ totalExternal: tasks.length,
85
+ totalEvaluated: evaluated.length,
86
+ passRate: evaluated.length > 0 ? passed / evaluated.length : 0,
87
+ bySource,
88
+ contaminationDetected: contaminationResult.contaminated,
89
+ contaminationCount: contaminationResult.count,
90
+ };
91
+ }
92
+ /**
93
+ * Generate a summary of external task status.
94
+ * Pure - testable.
95
+ */
96
+ export function externalTaskSummary(tasks) {
97
+ const pending = tasks.filter(t => t.passed === undefined).length;
98
+ const evaluated = tasks.filter(t => t.passed !== undefined).length;
99
+ const passed = tasks.filter(t => t.passed === true).length;
100
+ const sources = [...new Set(tasks.map(t => t.source))];
101
+ return `external tasks: ${tasks.length} total, ${pending} pending, ${evaluated} evaluated (${passed} passed), sources: ${sources.join(', ') || 'none'}`;
102
+ }
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './readiness.ts';
7
7
  export * from './labels.ts';
8
8
  export * from './reward-model.ts';
9
9
  export * from './judge.ts';
10
+ export * from './sequential.ts';
10
11
  export * from './insights.ts';
11
12
  export * from './skills.ts';
12
13
  export * from './bench.ts';
@@ -16,3 +17,4 @@ export * from './impact.ts';
16
17
  export * from './workflows.ts';
17
18
  export * from './knowledge.ts';
18
19
  export * from './radar.ts';
20
+ export * from './external.ts';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export * from "./readiness.js";
7
7
  export * from "./labels.js";
8
8
  export * from "./reward-model.js";
9
9
  export * from "./judge.js";
10
+ export * from "./sequential.js";
10
11
  export * from "./insights.js";
11
12
  export * from "./skills.js";
12
13
  export * from "./bench.js";
@@ -16,3 +17,4 @@ export * from "./impact.js";
16
17
  export * from "./workflows.js";
17
18
  export * from "./knowledge.js";
18
19
  export * from "./radar.js";
20
+ export * from "./external.js";
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @hmharness/evolution - Sequential Experimentation (P0-05, 2026-09-24 audit)
3
+ *
4
+ * Replaces the fixed "10 percentage points difference" threshold with
5
+ * Sequential Probability Ratio Test (SPRT) for binomial outcomes.
6
+ *
7
+ * The audit said: "应升级为 Sequential Probability Ratio Test / Bayesian
8
+ * decision / anytime-valid inference 中的一个正式方案, 并同时加入多重比较、
9
+ * 实验停止规则、护栏指标和效应量置信区间"
10
+ *
11
+ * SPRT advantage over fixed-threshold:
12
+ * - Can stop EARLY when evidence is conclusive (saves API calls)
13
+ * - Has provable error rates (alpha/beta are hard bounds)
14
+ * - Anytime-valid: the error guarantee holds at ANY stopping point
15
+ */
16
+ export interface SPRTParams {
17
+ /** H0: p_control (null hypothesis baseline success rate) */
18
+ p0: number;
19
+ /** H1: p_treatment (alternative hypothesis success rate) */
20
+ p1: number;
21
+ /** Type I error rate (false positive): reject H0 when H0 is true */
22
+ alpha: number;
23
+ /** Type II error rate (false negative): fail to reject H0 when H1 is true */
24
+ beta: number;
25
+ }
26
+ export interface SPRTResult {
27
+ /** decision at current sample sizes */
28
+ decision: 'continue' | 'accept-h0' | 'accept-h1' | 'insufficient';
29
+ /** log-likelihood ratio */
30
+ logLR: number;
31
+ /** lower boundary for accepting H0 */
32
+ lowerBound: number;
33
+ /** upper boundary for accepting H1 */
34
+ upperBound: number;
35
+ /** total observations so far */
36
+ nControl: number;
37
+ nTreatment: number;
38
+ /** estimated effect (treatment - control success rate) */
39
+ effectEstimate: number;
40
+ /** 95% CI for the effect */
41
+ effectCI: [number, number];
42
+ /** minimum observations per arm before any decision */
43
+ minN: number;
44
+ }
45
+ /**
46
+ * Wald's Sequential Probability Ratio Test for comparing two binomial rates.
47
+ *
48
+ * At each observation, compute the log-likelihood ratio:
49
+ * Λ = Σ log(L(p1)/L(p0))
50
+ *
51
+ * Decision boundaries:
52
+ * Λ ≤ log(β/(1-α)) → accept H0 (no difference / treatment not better)
53
+ * Λ ≥ log((1-β)/α) → accept H1 (treatment IS better)
54
+ * otherwise → continue collecting data
55
+ */
56
+ export declare function sprt(controlSuccesses: number, controlTotal: number, treatmentSuccesses: number, treatmentTotal: number, params: SPRTParams, minN?: number): SPRTResult;
57
+ /**
58
+ * Convenience: standard SPRT for evolution experiments.
59
+ * H0: treatment is NOT better than control (same rate)
60
+ * H1: treatment is at least 5pp better
61
+ */
62
+ export declare function evolutionSPRT(controlSuccesses: number, controlTotal: number, treatmentSuccesses: number, treatmentTotal: number): SPRTResult;
63
+ /**
64
+ * Multiple comparison correction (Bonferroni) for running multiple
65
+ * experiments simultaneously - the audit's "多重比较" requirement.
66
+ */
67
+ export declare function bonferroniAlpha(numExperiments: number, familyAlpha?: number): number;
68
+ /**
69
+ * Guardrail check: a treatment that improves the target metric but
70
+ * degrades a guardrail metric beyond tolerance should be rejected.
71
+ * The audit's "护栏指标" requirement.
72
+ */
73
+ export declare function checkGuardrails(target: SPRTResult, guardrails: Array<{
74
+ name: string;
75
+ controlValue: number;
76
+ treatmentValue: number;
77
+ maxRegression: number;
78
+ }>): {
79
+ pass: boolean;
80
+ violations: string[];
81
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @hmharness/evolution - Sequential Experimentation (P0-05, 2026-09-24 audit)
3
+ *
4
+ * Replaces the fixed "10 percentage points difference" threshold with
5
+ * Sequential Probability Ratio Test (SPRT) for binomial outcomes.
6
+ *
7
+ * The audit said: "应升级为 Sequential Probability Ratio Test / Bayesian
8
+ * decision / anytime-valid inference 中的一个正式方案, 并同时加入多重比较、
9
+ * 实验停止规则、护栏指标和效应量置信区间"
10
+ *
11
+ * SPRT advantage over fixed-threshold:
12
+ * - Can stop EARLY when evidence is conclusive (saves API calls)
13
+ * - Has provable error rates (alpha/beta are hard bounds)
14
+ * - Anytime-valid: the error guarantee holds at ANY stopping point
15
+ */
16
+ /**
17
+ * Wald's Sequential Probability Ratio Test for comparing two binomial rates.
18
+ *
19
+ * At each observation, compute the log-likelihood ratio:
20
+ * Λ = Σ log(L(p1)/L(p0))
21
+ *
22
+ * Decision boundaries:
23
+ * Λ ≤ log(β/(1-α)) → accept H0 (no difference / treatment not better)
24
+ * Λ ≥ log((1-β)/α) → accept H1 (treatment IS better)
25
+ * otherwise → continue collecting data
26
+ */
27
+ export function sprt(controlSuccesses, controlTotal, treatmentSuccesses, treatmentTotal, params, minN = 30) {
28
+ const { p0, p1, alpha, beta } = params;
29
+ const nC = controlTotal;
30
+ const nT = treatmentTotal;
31
+ const xC = controlSuccesses;
32
+ const xT = treatmentSuccesses;
33
+ // boundaries
34
+ const lowerBound = Math.log(beta / (1 - alpha));
35
+ const upperBound = Math.log((1 - beta) / alpha);
36
+ // log-likelihood ratio for binomial
37
+ // Under H0: both arms have success rate p0
38
+ // Under H1: treatment has p1, control has p0
39
+ let logLR = 0;
40
+ // treatment arm observations
41
+ if (nT > 0) {
42
+ const logL1 = xT * Math.log(p1) + (nT - xT) * Math.log(1 - p1);
43
+ const logL0 = xT * Math.log(p0) + (nT - xT) * Math.log(1 - p0);
44
+ logLR += logL1 - logL0;
45
+ }
46
+ // minimum sample check
47
+ if (nC < minN || nT < minN) {
48
+ return {
49
+ decision: 'insufficient',
50
+ logLR, lowerBound, upperBound,
51
+ nControl: nC, nTreatment: nT,
52
+ effectEstimate: nT > 0 && nC > 0 ? (xT / nT) - (xC / nC) : 0,
53
+ effectCI: [-1, 1],
54
+ minN,
55
+ };
56
+ }
57
+ // decision
58
+ let decision;
59
+ if (logLR <= lowerBound) {
60
+ decision = 'accept-h0';
61
+ }
62
+ else if (logLR >= upperBound) {
63
+ decision = 'accept-h1';
64
+ }
65
+ else {
66
+ decision = 'continue';
67
+ }
68
+ // effect estimate and CI (Wald normal approximation)
69
+ const pControl = nC > 0 ? xC / nC : 0;
70
+ const pTreatment = nT > 0 ? xT / nT : 0;
71
+ const effect = pTreatment - pControl;
72
+ const seC = nC > 0 ? Math.sqrt(pControl * (1 - pControl) / nC) : 0;
73
+ const seT = nT > 0 ? Math.sqrt(pTreatment * (1 - pTreatment) / nT) : 0;
74
+ const se = Math.sqrt(seC * seC + seT * seT);
75
+ const z = 1.96; // 95% CI
76
+ const ci = [
77
+ Math.max(-1, effect - z * se),
78
+ Math.min(1, effect + z * se),
79
+ ];
80
+ return {
81
+ decision, logLR, lowerBound, upperBound,
82
+ nControl: nC, nTreatment: nT,
83
+ effectEstimate: effect,
84
+ effectCI: ci,
85
+ minN,
86
+ };
87
+ }
88
+ /**
89
+ * Convenience: standard SPRT for evolution experiments.
90
+ * H0: treatment is NOT better than control (same rate)
91
+ * H1: treatment is at least 5pp better
92
+ */
93
+ export function evolutionSPRT(controlSuccesses, controlTotal, treatmentSuccesses, treatmentTotal) {
94
+ // estimate baseline from control
95
+ const p0 = controlTotal > 0 ? controlSuccesses / controlTotal : 0.5;
96
+ // H1: 5pp improvement (minimum meaningful effect)
97
+ const p1 = Math.min(0.99, p0 + 0.05);
98
+ return sprt(controlSuccesses, controlTotal, treatmentSuccesses, treatmentTotal, {
99
+ p0, p1, alpha: 0.05, beta: 0.10,
100
+ }, 30);
101
+ }
102
+ /**
103
+ * Multiple comparison correction (Bonferroni) for running multiple
104
+ * experiments simultaneously - the audit's "多重比较" requirement.
105
+ */
106
+ export function bonferroniAlpha(numExperiments, familyAlpha = 0.05) {
107
+ return familyAlpha / Math.max(1, numExperiments);
108
+ }
109
+ /**
110
+ * Guardrail check: a treatment that improves the target metric but
111
+ * degrades a guardrail metric beyond tolerance should be rejected.
112
+ * The audit's "护栏指标" requirement.
113
+ */
114
+ export function checkGuardrails(target, guardrails) {
115
+ const violations = [];
116
+ for (const g of guardrails) {
117
+ const regression = g.treatmentValue - g.controlValue;
118
+ if (regression > g.maxRegression) {
119
+ violations.push(`${g.name}: regression ${regression.toFixed(3)} > tolerance ${g.maxRegression}`);
120
+ }
121
+ }
122
+ return { pass: violations.length === 0, violations };
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmharness/evolution",
3
- "version": "0.14.15",
3
+ "version": "0.14.18",
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",
@@ -15,7 +15,7 @@
15
15
  "build": "tsc -p tsconfig.build.json"
16
16
  },
17
17
  "dependencies": {
18
- "@hmharness/kernel": "0.9.2"
18
+ "@hmharness/kernel": "0.9.4"
19
19
  },
20
20
  "files": [
21
21
  "dist"