@metaharness/flywheel 0.1.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 ADDED
@@ -0,0 +1,129 @@
1
+ # @metaharness/flywheel
2
+
3
+ **A verifiable self-improvement loop for agent harnesses.**
4
+ _Freeze the model. Evolve the harness. Promote only what proves lift._
5
+
6
+ [![npm](https://img.shields.io/npm/v/@metaharness/flywheel.svg)](https://www.npmjs.com/package/@metaharness/flywheel)
7
+ [![types](https://img.shields.io/npm/types/@metaharness/flywheel.svg)](https://www.npmjs.com/package/@metaharness/flywheel)
8
+ [![license](https://img.shields.io/npm/l/@metaharness/flywheel.svg)](./LICENSE)
9
+
10
+ The reusable engine for **run → measure → mutate → verify → promote**. It turns "self-improving agent
11
+ harness" from a claim into an **installable primitive**: plug in your own proposer, evaluator, gate,
12
+ holdouts, and cost/security rules and get the same **auditable, replayable** improvement loop — a signed
13
+ promotion lineage and a compounding lift curve you (or an outside auditor) can verify with no trust in the
14
+ machine that produced it.
15
+
16
+ Part of the [MetaHarness](https://www.npmjs.com/package/metaharness) stack: **`metaharness`** mints
17
+ harnesses, **`@metaharness/darwin`** evolves them, and **`@metaharness/flywheel`** formalizes the promotion
18
+ loop so every host and vertical harness reuses one engine instead of copying code.
19
+
20
+ ---
21
+
22
+ ## Why
23
+
24
+ Most "self-improving agent" pitches are unfalsifiable. The flywheel makes improvement **provable**:
25
+
26
+ - **The gate is the product.** A promotion is only trusted if it clears a **frozen, conjunctive** gate
27
+ (`meetsPromotionRule`). The gate never moves during a run, and its fingerprint is recorded so anyone can
28
+ prove it was unchanged.
29
+ - **Freeze the model, evolve the harness.** The expensive model stays fixed; what evolves is the cheap
30
+ executor's **operating policy**. (ADR-226 receipts: a read-only advisor loop produced *zero* marginal
31
+ lift at *5.4×* cost — the executor policy is the part that mattered. Invest in policy evolution + gates,
32
+ not expensive advisory loops.)
33
+ - **Compounding, not searching.** Each generation re-bases on the previous **promoted winner**, so verified
34
+ wins accumulate into an immutable lineage — a *lift curve*, not a scatter of one-off tweaks.
35
+ - **Anti-Goodhart by construction.** A candidate must clear both a **holdout** and a **frozen anchor** it is
36
+ never optimized against.
37
+ - **Receipt-backed + replayable.** Every promotion is Ed25519-signed; an external reviewer replays the
38
+ bundle, reconstructs the lineage to gen-0, and verifies the gate — trusting the *signature*, not you.
39
+
40
+ ## Features
41
+
42
+ - 🎯 **One tiny API** — `runFlywheelGenerations()` drives coding *and* non-coding harnesses unchanged.
43
+ - 🧊 **Frozen, pluggable gate** — ship the default `meetsPromotionRule` or inject your own compliance/cost gate.
44
+ - 🧬 **Compounding lineage (DAG)** — "git for operating policies": every promotion is a parent-linked commit.
45
+ - 📈 **Lift curve** — the observable proof the wheel *climbs*, generation over generation.
46
+ - 🧾 **Ed25519 receipts + `verifyReplayBundle()`** — independent, no-trust replay.
47
+ - 🔒 **Gate fingerprint** — prove the promotion rule was unchanged between runs.
48
+ - 🧩 **Zero host/benchmark coupling** — knows only candidates, scores, gates, receipts, lineage.
49
+ - 🪶 **Thin + dependency-free** at runtime (Node `crypto` only). ESM, fully typed.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ npm install @metaharness/flywheel
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ```ts
60
+ import { runFlywheelGenerations, meetsPromotionRule, makeSigner, verifyReplayBundle } from "@metaharness/flywheel";
61
+
62
+ const result = await runFlywheelGenerations({
63
+ rootPolicy: { reasoning: "", format: "", verification: "" }, // the levers you evolve
64
+ proposer: async (base, target) => improve(base.policy[target]), // your model call
65
+ evaluator: async (policy, suite) => score(policy, suite), // your host/benchmark → Score
66
+ promotionRule: meetsPromotionRule, // the frozen gate (or inject your own)
67
+ holdout: { id: "holdout", items: myHoldoutTasks },
68
+ anchor: { id: "anchor", items: myAnchorTasks }, // never optimized against
69
+ maxGenerations: 10,
70
+ signer: makeSigner(),
71
+ dataSource: "LIVE",
72
+ });
73
+
74
+ console.log(result.liftCurve); // [{ generation, primary, delta, anchor }, …] — the climb
75
+ console.log(result.promotions); // the promoted chain (current → gen-0 root)
76
+ console.log(result.milestoneReached); // ≥2 anchor-surviving compounding improvements
77
+
78
+ // Anyone can replay it — no trust in the producer:
79
+ const verdict = verifyReplayBundle(result.replayBundle);
80
+ console.log(verdict.pass, verdict.chainSummary); // true "gen4(format) → gen2(reasoning) → gen1(…) → gen0(root)"
81
+ ```
82
+
83
+ Your `Evaluator` projects whatever your domain measures onto four abstract axes — the **only** place a
84
+ host or benchmark enters:
85
+
86
+ ```ts
87
+ interface Score {
88
+ primary: number; // wins / accuracy / resolved (higher better)
89
+ noopRate: number; // no-op / empty / abstained (lower better)
90
+ costPerWin: number; // resource cost per success (lower better)
91
+ regressed: boolean; // hard safety/security stop
92
+ }
93
+ ```
94
+
95
+ ## API
96
+
97
+ | Export | What |
98
+ | --- | --- |
99
+ | `runFlywheelGenerations(config)` | the promotion loop → `{ liftCurve, promotions, replayBundle, … }` |
100
+ | `meetsPromotionRule` | the default frozen conjunctive gate (`PromotionRule`) |
101
+ | `gateFingerprint(rule)` | sha256 of a gate's source — prove it was unchanged |
102
+ | `makeSigner()` / `verifyReceipt` / `canon` | Ed25519 receipts |
103
+ | `InMemoryLineageStore` / `computeLiftCurve` | lineage graph + lift curve |
104
+ | `verifyReplayBundle(bundle, { pinnedGateFingerprint })` | the external acceptance test |
105
+ | types | `Policy` · `PolicyGenome` · `CandidateMutation` · `Score` · `PromotionReceipt` · `LiftCurve` · `LineageStore` · `ReplayBundle` · `AnchorSuite` · `HoldoutSuite` · `Proposer` · `Evaluator` · `PromotionRule` · `Signer` |
106
+
107
+ ## Package boundary
108
+
109
+ | Package | Job |
110
+ | --- | --- |
111
+ | `metaharness` | CLI, Studio, repo analysis, user entry point |
112
+ | `@metaharness/darwin` | mutation strategy + evolutionary search |
113
+ | **`@metaharness/flywheel`** | **promotion loop, receipts, lineage, replay, lift curve** |
114
+ | `@metaharness/router` | model / host routing |
115
+ | `@metaharness/hosts-*` | Claude Code, Codex, Hermes, OpenClaw, RVM adapters |
116
+
117
+ **Design rule:** the flywheel must not know about Claude Code, SWE-bench, GLM, Sonnet, Fable, or any
118
+ benchmark. If you need a benchmark-specific branch, it belongs in your `Evaluator`, not here.
119
+
120
+ ## Topics
121
+
122
+ `agent-harness` · `self-improving-agents` · `llm-evaluation` · `holdout` · `promotion-gate` ·
123
+ `evolutionary-optimization` · `policy-optimization` · `verifiable-ai` · `audit-trail` · `lineage` ·
124
+ `ed25519` · `receipts` · `provenance` · `goodhart` · `anti-goodhart` · `lift-curve` · `agentic-ci` ·
125
+ `prompt-optimization` · `metaharness` · `darwin` · `flywheel`
126
+
127
+ ## License
128
+
129
+ MIT © MetaHarness
package/dist/gate.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { PromotionEvidence, PromotionDecision, PromotionRule } from './types.js';
2
+ /**
3
+ * The default frozen gate. Conjunctive — a candidate is promoted iff EVERY clause holds:
4
+ * 1. primary does not regress (candidate.primary ≥ baseline.primary)
5
+ * 2. no-op rate strictly improves (candidate.noopRate < baseline.noopRate) — the load-bearing signal;
6
+ * a policy earns a promotion by making the executor COMMIT more, not just score higher
7
+ * 3. cost/win does not worsen (candidate.costPerWin ≤ baseline.costPerWin)
8
+ * 4. no hard safety/security regression
9
+ * 5. if an anchor is supplied, it must not regress (candidate ≥ baseline) — the anti-Goodhart guard
10
+ */
11
+ export declare function meetsPromotionRule(e: PromotionEvidence): PromotionDecision;
12
+ /**
13
+ * A fingerprint of a promotion rule's source — an external reviewer recomputes this and compares it to a
14
+ * pinned value to prove the gate was UNCHANGED between runs. `Function.prototype.toString` is stable for
15
+ * a given source; for a build-artifact-level guarantee, hash the rule's source file instead and pass it.
16
+ */
17
+ export declare function gateFingerprint(rule: PromotionRule): string;
18
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEtF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,CAQ1E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAE3D"}
package/dist/gate.js ADDED
@@ -0,0 +1,39 @@
1
+ // @metaharness/flywheel — the DEFAULT promotion gate + its fingerprint.
2
+ //
3
+ // "The gate is the product." A promotion is only as trustworthy as the rule that admitted it, and that
4
+ // rule must be FROZEN for a deployment and VERIFIABLY unchanged. This is the default conjunctive rule
5
+ // (every clause load-bearing; ALL must hold) — but it is just a `PromotionRule`, so a caller may inject
6
+ // its own (stricter compliance gate, cost policy, etc.) and fingerprint that instead.
7
+ import { createHash } from 'node:crypto';
8
+ /**
9
+ * The default frozen gate. Conjunctive — a candidate is promoted iff EVERY clause holds:
10
+ * 1. primary does not regress (candidate.primary ≥ baseline.primary)
11
+ * 2. no-op rate strictly improves (candidate.noopRate < baseline.noopRate) — the load-bearing signal;
12
+ * a policy earns a promotion by making the executor COMMIT more, not just score higher
13
+ * 3. cost/win does not worsen (candidate.costPerWin ≤ baseline.costPerWin)
14
+ * 4. no hard safety/security regression
15
+ * 5. if an anchor is supplied, it must not regress (candidate ≥ baseline) — the anti-Goodhart guard
16
+ */
17
+ export function meetsPromotionRule(e) {
18
+ const reasons = [];
19
+ if (e.candidate.primary < e.baseline.primary)
20
+ reasons.push('primary_regressed');
21
+ if (!(e.candidate.noopRate < e.baseline.noopRate))
22
+ reasons.push('noop_rate_not_improved');
23
+ if (e.candidate.costPerWin > e.baseline.costPerWin)
24
+ reasons.push('cost_per_win_worsened');
25
+ if (e.candidate.regressed)
26
+ reasons.push('safety_regressed');
27
+ if (e.anchor && e.anchor.candidate < e.anchor.baseline)
28
+ reasons.push('anchor_regressed');
29
+ return { promote: reasons.length === 0, reasons };
30
+ }
31
+ /**
32
+ * A fingerprint of a promotion rule's source — an external reviewer recomputes this and compares it to a
33
+ * pinned value to prove the gate was UNCHANGED between runs. `Function.prototype.toString` is stable for
34
+ * a given source; for a build-artifact-level guarantee, hash the rule's source file instead and pass it.
35
+ */
36
+ export function gateFingerprint(rule) {
37
+ return createHash('sha256').update(rule.toString()).digest('hex');
38
+ }
39
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,EAAE;AACF,uGAAuG;AACvG,sGAAsG;AACtG,wGAAwG;AACxG,sFAAsF;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAoB;IACrD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAChF,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAC1F,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC,QAAQ,CAAC,UAAU;QAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC1F,IAAI,CAAC,CAAC,SAAS,CAAC,SAAS;QAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC5D,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACzF,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { runFlywheelGenerations } from './run.js';
2
+ export type { FlywheelConfig, FlywheelResult } from './run.js';
3
+ export { meetsPromotionRule, gateFingerprint } from './gate.js';
4
+ export { makeSigner, verifyReceipt, canon } from './receipts.js';
5
+ export { InMemoryLineageStore, computeLiftCurve, liftPoint } from './lineage.js';
6
+ export { verifyReplayBundle } from './replay.js';
7
+ export type { ReplayVerdict } from './replay.js';
8
+ export type { Policy, PolicyGenome, CandidateMutation, Score, PromotionEvidence, PromotionDecision, PromotionRule, Suite, HoldoutSuite, AnchorSuite, Proposer, Evaluator, PromotionReceipt, Signer, LineageCommit, LineageStore, LiftPoint, LiftCurve, ReplayBundle, } from './types.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,YAAY,EACV,MAAM,EACN,YAAY,EACZ,iBAAiB,EACjB,KAAK,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,KAAK,EACL,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // @metaharness/flywheel — a verifiable self-improvement loop for agent harnesses.
2
+ // Freeze the model. Evolve the harness. Promote only what proves lift.
3
+ //
4
+ // The reusable engine for run → measure → mutate → verify → promote. It knows only candidates, scores,
5
+ // gates, receipts, and promotion lineage — never a host, model, or benchmark. Plug in your own proposer,
6
+ // evaluator, gate, holdouts, and cost/security rules; get the same auditable, replayable improvement loop.
7
+ export { runFlywheelGenerations } from './run.js';
8
+ export { meetsPromotionRule, gateFingerprint } from './gate.js';
9
+ export { makeSigner, verifyReceipt, canon } from './receipts.js';
10
+ export { InMemoryLineageStore, computeLiftCurve, liftPoint } from './lineage.js';
11
+ export { verifyReplayBundle } from './replay.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,uEAAuE;AACvE,EAAE;AACF,uGAAuG;AACvG,yGAAyG;AACzG,2GAA2G;AAC3G,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAGlD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { LineageCommit, LineageStore, LiftCurve, LiftPoint } from './types.js';
2
+ export declare class InMemoryLineageStore implements LineageStore {
3
+ private readonly commits;
4
+ append(commit: LineageCommit): Promise<void>;
5
+ get(id: string): Promise<LineageCommit | null>;
6
+ walkToRoot(id: string): Promise<LineageCommit[]>;
7
+ list(): Promise<LineageCommit[]>;
8
+ }
9
+ /** The compounding lift curve: root primary, then each promoted generation's primary + delta + anchor.
10
+ * `chain` is current→root (as returned by walkToRoot); we reverse to read root→current. */
11
+ export declare function computeLiftCurve(chain: LineageCommit[], rootPrimary: number): LiftCurve;
12
+ /** Convenience: a single point (used when composing a curve incrementally). */
13
+ export declare function liftPoint(generation: number, primary: number, delta: number, anchor: number | null): LiftPoint;
14
+ //# sourceMappingURL=lineage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../src/lineage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpF,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAEtD,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAG5C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAI9C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAWhD,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;CAGvC;AAED;4FAC4F;AAC5F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,CAavF;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAE9G"}
@@ -0,0 +1,46 @@
1
+ export class InMemoryLineageStore {
2
+ commits = new Map();
3
+ async append(commit) {
4
+ this.commits.set(commit.id, { ...commit });
5
+ }
6
+ async get(id) {
7
+ const c = this.commits.get(id);
8
+ return c ? { ...c } : null;
9
+ }
10
+ async walkToRoot(id) {
11
+ const chain = [];
12
+ const seen = new Set();
13
+ let cur = this.commits.get(id) ?? null;
14
+ while (cur && !seen.has(cur.id)) {
15
+ seen.add(cur.id);
16
+ chain.push({ ...cur });
17
+ cur = cur.parents[0] ? this.commits.get(cur.parents[0]) ?? null : null;
18
+ }
19
+ return chain;
20
+ }
21
+ async list() {
22
+ return [...this.commits.values()].map((c) => ({ ...c }));
23
+ }
24
+ }
25
+ /** The compounding lift curve: root primary, then each promoted generation's primary + delta + anchor.
26
+ * `chain` is current→root (as returned by walkToRoot); we reverse to read root→current. */
27
+ export function computeLiftCurve(chain, rootPrimary) {
28
+ const rootFirst = [...chain].reverse();
29
+ const curve = [];
30
+ let running = rootPrimary;
31
+ for (const c of rootFirst) {
32
+ if (c.verdict === 'ROOT') {
33
+ curve.push({ generation: c.generation, primary: rootPrimary, delta: 0, anchor: c.anchorScore });
34
+ }
35
+ else {
36
+ running += c.primaryDelta;
37
+ curve.push({ generation: c.generation, primary: running, delta: c.primaryDelta, anchor: c.anchorScore });
38
+ }
39
+ }
40
+ return curve;
41
+ }
42
+ /** Convenience: a single point (used when composing a curve incrementally). */
43
+ export function liftPoint(generation, primary, delta, anchor) {
44
+ return { generation, primary, delta, anchor };
45
+ }
46
+ //# sourceMappingURL=lineage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lineage.js","sourceRoot":"","sources":["../src/lineage.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,oBAAoB;IACd,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE5D,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7B,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,EAAU;QACzB,MAAM,KAAK,GAAoB,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;QACvC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;YACvB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACzE,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,IAAI;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;CACF;AAED;4FAC4F;AAC5F,MAAM,UAAU,gBAAgB,CAAC,KAAsB,EAAE,WAAmB;IAC1E,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;IACvC,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,IAAI,OAAO,GAAG,WAAW,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAClG,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,CAAC,YAAY,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3G,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,SAAS,CAAC,UAAkB,EAAE,OAAe,EAAE,KAAa,EAAE,MAAqB;IACjG,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { PromotionReceipt, Signer } from './types.js';
2
+ /** Deterministic, sorted-key JSON canonicalization — the exact bytes that get signed. */
3
+ export declare function canon(v: unknown): string;
4
+ /** A per-process Ed25519 signer. For production, wrap a secret-backed or HSM key behind the same
5
+ * {@link Signer} interface — the flywheel core never sees the private key. */
6
+ export declare function makeSigner(): Signer;
7
+ /** Independently verify a receipt — recompute canon, check the signature against the EMBEDDED key. */
8
+ export declare function verifyReceipt(r: PromotionReceipt): boolean;
9
+ //# sourceMappingURL=receipts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipts.d.ts","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAE3D,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAKxC;AAED;+EAC+E;AAC/E,wBAAgB,UAAU,IAAI,MAAM,CAYnC;AAED,sGAAsG;AACtG,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAO1D"}
@@ -0,0 +1,39 @@
1
+ // @metaharness/flywheel — Ed25519 receipts. Every promotion is signed; a receiver VERIFIES the
2
+ // signature with the embedded public key. Trust comes from the verifiable signature, not from the
3
+ // producer — so a lineage can be replayed and audited with no access to the machine that made it.
4
+ import { generateKeyPairSync, sign as edSign, verify as edVerify, createPublicKey } from 'node:crypto';
5
+ /** Deterministic, sorted-key JSON canonicalization — the exact bytes that get signed. */
6
+ export function canon(v) {
7
+ if (v === null || typeof v !== 'object')
8
+ return JSON.stringify(v);
9
+ if (Array.isArray(v))
10
+ return `[${v.map(canon).join(',')}]`;
11
+ const o = v;
12
+ return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${canon(o[k])}`).join(',')}}`;
13
+ }
14
+ /** A per-process Ed25519 signer. For production, wrap a secret-backed or HSM key behind the same
15
+ * {@link Signer} interface — the flywheel core never sees the private key. */
16
+ export function makeSigner() {
17
+ const kp = generateKeyPairSync('ed25519');
18
+ const pub = kp.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
19
+ return {
20
+ publicKey: () => pub,
21
+ sign: (payload) => ({
22
+ payload,
23
+ signature: edSign(null, Buffer.from(canon(payload)), kp.privateKey).toString('base64'),
24
+ publicKey: pub,
25
+ alg: 'ed25519',
26
+ }),
27
+ };
28
+ }
29
+ /** Independently verify a receipt — recompute canon, check the signature against the EMBEDDED key. */
30
+ export function verifyReceipt(r) {
31
+ try {
32
+ const pub = createPublicKey({ key: Buffer.from(r.publicKey, 'base64'), format: 'der', type: 'spki' });
33
+ return edVerify(null, Buffer.from(canon(r.payload)), pub, Buffer.from(r.signature, 'base64'));
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ //# sourceMappingURL=receipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipts.js","sourceRoot":"","sources":["../src/receipts.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,kGAAkG;AAClG,kGAAkG;AAClG,OAAO,EAAE,mBAAmB,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,IAAI,QAAQ,EAAE,eAAe,EAAkB,MAAM,aAAa,CAAC;AAGvH,yFAAyF;AACzF,MAAM,UAAU,KAAK,CAAC,CAAU;IAC9B,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3D,MAAM,CAAC,GAAG,CAA4B,CAAC;IACvC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClG,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,UAAU;IACxB,MAAM,EAAE,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAI,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChG,OAAO;QACL,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG;QACpB,IAAI,EAAE,CAAC,OAAgC,EAAoB,EAAE,CAAC,CAAC;YAC7D,OAAO;YACP,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACtF,SAAS,EAAE,GAAG;YACd,GAAG,EAAE,SAAS;SACf,CAAC;KACH,CAAC;AACJ,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,aAAa,CAAC,CAAmB;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAc,eAAe,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACjH,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ReplayBundle } from './types.js';
2
+ export interface ReplayVerdict {
3
+ pass: boolean;
4
+ checks: {
5
+ receipts: boolean;
6
+ reachesRoot: boolean;
7
+ contiguousParents: boolean;
8
+ allPromoted: boolean;
9
+ gateUnchanged: boolean;
10
+ };
11
+ failures: string[];
12
+ chainSummary: string;
13
+ }
14
+ export declare function verifyReplayBundle(bundle: ReplayBundle, opts?: {
15
+ pinnedGateFingerprint?: string;
16
+ }): ReplayVerdict;
17
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE;QACN,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,OAAO,CAAC;QAC3B,WAAW,EAAE,OAAO,CAAC;QACrB,aAAa,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,GAAE;IAAE,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,aAAa,CA8BrH"}
package/dist/replay.js ADDED
@@ -0,0 +1,39 @@
1
+ // @metaharness/flywheel — independent replay. Given ONLY a ReplayBundle (and, optionally, the pinned
2
+ // gate fingerprint), an external reviewer establishes the run with no trust in the producer:
3
+ // (1) every promotion receipt verifies (Ed25519, recompute canon vs the embedded key);
4
+ // (2) the promoted lineage reconstructs current → gen-0 immutable root, contiguously;
5
+ // (3) every commit on the promoted chain is actually PROMOTED (no rejected node smuggled in);
6
+ // (4) the gate fingerprint matches the pinned value ⇒ the promotion rule was UNCHANGED.
7
+ import { verifyReceipt } from './receipts.js';
8
+ export function verifyReplayBundle(bundle, opts = {}) {
9
+ const failures = [];
10
+ const chain = bundle.chain;
11
+ const receipts = chain.length > 0 && chain.every((c) => verifyReceipt(c.receipt));
12
+ if (!receipts)
13
+ failures.push('receipts');
14
+ const root = chain[chain.length - 1];
15
+ const reachesRoot = !!root && root.parents.length === 0 && root.id === bundle.root_id;
16
+ if (!reachesRoot)
17
+ failures.push('reachesRoot');
18
+ let contiguousParents = chain.length > 0;
19
+ for (let i = 0; i < chain.length - 1; i++) {
20
+ if (!chain[i].parents.includes(chain[i + 1].id))
21
+ contiguousParents = false;
22
+ }
23
+ if (!contiguousParents)
24
+ failures.push('contiguousParents');
25
+ const promos = chain.filter((c) => c.verdict !== 'ROOT');
26
+ const allPromoted = promos.length > 0 && promos.every((c) => c.verdict === 'PROMOTED');
27
+ if (!allPromoted)
28
+ failures.push('allPromoted');
29
+ const gateUnchanged = opts.pinnedGateFingerprint ? bundle.gate_fingerprint === opts.pinnedGateFingerprint : true;
30
+ if (!gateUnchanged)
31
+ failures.push('gateUnchanged');
32
+ return {
33
+ pass: failures.length === 0,
34
+ checks: { receipts, reachesRoot, contiguousParents, allPromoted, gateUnchanged },
35
+ failures,
36
+ chainSummary: chain.map((c) => `gen${c.generation}${c.mutation ? `(${c.mutation.target})` : '(root)'}`).join(' → '),
37
+ };
38
+ }
39
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,6FAA6F;AAC7F,yFAAyF;AACzF,wFAAwF;AACxF,gGAAgG;AAChG,0FAA0F;AAC1F,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAgB9C,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,OAA2C,EAAE;IACpG,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAE3B,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,QAAQ;QAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,WAAW,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,OAAO,CAAC;IACtF,IAAI,CAAC,WAAW;QAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAE/C,IAAI,iBAAiB,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,EAAE,CAAC;YAAE,iBAAiB,GAAG,KAAK,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAE3D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC;IACvF,IAAI,CAAC,WAAW;QAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAE/C,MAAM,aAAa,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,KAAK,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;IACjH,IAAI,CAAC,aAAa;QAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAEnD,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC3B,MAAM,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE;QAChF,QAAQ;QACR,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;KACpH,CAAC;AACJ,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { Policy, Proposer, Evaluator, PromotionRule, Signer, HoldoutSuite, AnchorSuite, LineageStore, LineageCommit, LiftCurve, ReplayBundle } from './types.js';
2
+ export interface FlywheelConfig {
3
+ /** The gen-0 policy — the immutable root every promotion chains back to. */
4
+ rootPolicy: Policy;
5
+ proposer: Proposer;
6
+ evaluator: Evaluator;
7
+ /** The FROZEN gate. Default: {@link meetsPromotionRule}. Inject your own compliance/cost gate here. */
8
+ promotionRule?: PromotionRule;
9
+ holdout: HoldoutSuite;
10
+ /** Optional frozen anchor suite — never optimized against; a winner must not regress it to count. */
11
+ anchor?: AnchorSuite;
12
+ /** Which policy levers to try each generation. Default: every key of `rootPolicy`. */
13
+ mutationTargets?: string[];
14
+ maxGenerations: number;
15
+ signer: Signer;
16
+ /** Stop early once `spent() ≥ total` (e.g. a $ budget). */
17
+ budget?: {
18
+ total: number;
19
+ spent: () => number;
20
+ };
21
+ /** Caller-supplied ISO/label per generation (determinism; no clock in the engine). */
22
+ now?: (generation: number) => string;
23
+ /** Stamped on the replay bundle — 'SYNTHETIC' | 'LIVE' | …. NEVER a benchmark name. */
24
+ dataSource?: string;
25
+ lineageStore?: LineageStore;
26
+ rootId?: string;
27
+ }
28
+ export interface FlywheelResult {
29
+ liftCurve: LiftCurve;
30
+ /** The promoted chain (current → root). */
31
+ promotions: LineageCommit[];
32
+ lineage: LineageStore;
33
+ replayBundle: ReplayBundle;
34
+ generationsRun: number;
35
+ /** ≥2 anchor-surviving verified improvements joined the immutable lineage with no human. */
36
+ milestoneReached: boolean;
37
+ finalPolicy: Policy;
38
+ }
39
+ export declare function runFlywheelGenerations(cfg: FlywheelConfig): Promise<FlywheelResult>;
40
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,MAAM,EAAgB,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAChE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAChF,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB,uGAAuG;IACvG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,EAAE,YAAY,CAAC;IACtB,qGAAqG;IACrG,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,MAAM,CAAA;KAAE,CAAC;IAChD,sFAAsF;IACtF,GAAG,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACrC,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,2CAA2C;IAC3C,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,OAAO,EAAE,YAAY,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,4FAA4F;IAC5F,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA2FzF"}
package/dist/run.js ADDED
@@ -0,0 +1,94 @@
1
+ // @metaharness/flywheel — runFlywheelGenerations(): the promotion LOOP. run → measure → mutate → verify
2
+ // → promote, generation after generation, each re-basing on the previous promoted winner so verified
3
+ // wins COMPOUND into an auditable lineage. Host- and benchmark-agnostic: everything specific enters via
4
+ // the injected `proposer` / `evaluator` / `promotionRule`. The gate selects the winner on the HOLDOUT;
5
+ // the FROZEN anchor is a separate survival check (never optimized against — the anti-Goodhart guard).
6
+ import { InMemoryLineageStore, computeLiftCurve } from './lineage.js';
7
+ import { meetsPromotionRule, gateFingerprint } from './gate.js';
8
+ export async function runFlywheelGenerations(cfg) {
9
+ const rule = cfg.promotionRule ?? meetsPromotionRule;
10
+ const targets = cfg.mutationTargets ?? Object.keys(cfg.rootPolicy);
11
+ const store = cfg.lineageStore ?? new InMemoryLineageStore();
12
+ const now = cfg.now ?? ((g) => `gen-${g}`);
13
+ const rootId = cfg.rootId ?? 'root';
14
+ const anchorOf = async (p) => cfg.anchor ? (await cfg.evaluator(p, cfg.anchor)).primary : null;
15
+ // ── gen-0: the immutable root. Evaluate it once (the baseline) + its anchor (the frozen bar). ──
16
+ const rootScore = await cfg.evaluator(cfg.rootPolicy, cfg.holdout);
17
+ const rootAnchor = await anchorOf(cfg.rootPolicy);
18
+ await store.append({
19
+ id: rootId, generation: 0, parents: [], mutation: null, primaryDelta: 0, anchorScore: rootAnchor,
20
+ verdict: 'ROOT', failureReasons: [], receipt: cfg.signer.sign({ kind: 'root', root: rootId }), createdAt: now(0),
21
+ });
22
+ let parentId = rootId;
23
+ let policy = { ...cfg.rootPolicy };
24
+ let score = rootScore;
25
+ const allCommits = [];
26
+ let generationsRun = 0;
27
+ for (let gen = 1; gen <= cfg.maxGenerations; gen++) {
28
+ if (cfg.budget && cfg.budget.spent() >= cfg.budget.total)
29
+ break;
30
+ generationsRun = gen;
31
+ // propose + evaluate one candidate per mutation target, gate each on the HOLDOUT.
32
+ const base = { id: parentId, generation: gen, parents: [parentId], policy };
33
+ const cands = [];
34
+ for (const target of targets) {
35
+ const proposed = await cfg.proposer(base, target);
36
+ const candPolicy = { ...policy, [target]: proposed };
37
+ const candScore = await cfg.evaluator(candPolicy, cfg.holdout);
38
+ const decision = rule({ baseline: score, candidate: candScore });
39
+ cands.push({ target, policy: candPolicy, score: candScore, reasons: decision.reasons, promote: decision.promote });
40
+ }
41
+ // winner = highest primary among the promotable; then verify it survives the FROZEN anchor.
42
+ const promotable = cands.filter((c) => c.promote).sort((a, b) => b.score.primary - a.score.primary);
43
+ const winner = promotable[0] ?? null;
44
+ const winnerAnchor = winner ? await anchorOf(winner.policy) : null;
45
+ const anchorSurvives = winner ? (rootAnchor === null || (winnerAnchor ?? -Infinity) >= rootAnchor) : false;
46
+ // A winner that regresses the anchor is NOT promoted (Goodhart guard) — it becomes a rejection.
47
+ const promotedWinner = winner && anchorSurvives ? winner : null;
48
+ for (const c of cands) {
49
+ const isWinner = c === promotedWinner;
50
+ const id = `${parentId}__${c.target}_gen${gen}`;
51
+ const primaryDelta = c.score.primary - score.primary;
52
+ const commit = {
53
+ id, generation: gen, parents: [parentId],
54
+ mutation: { target: c.target, summary: `adapt ${c.target}` },
55
+ primaryDelta,
56
+ anchorScore: isWinner ? winnerAnchor : c === winner ? winnerAnchor : null,
57
+ verdict: isWinner ? 'PROMOTED' : 'REJECTED',
58
+ failureReasons: isWinner ? [] : c === winner && !anchorSurvives ? ['anchor_regressed'] : c.reasons,
59
+ receipt: cfg.signer.sign({ kind: 'candidate', id, target: c.target, verdict: isWinner ? 'PROMOTED' : 'REJECTED', primaryDelta }),
60
+ createdAt: now(gen),
61
+ };
62
+ await store.append(commit);
63
+ allCommits.push(commit);
64
+ }
65
+ if (promotedWinner) {
66
+ parentId = `${parentId}__${promotedWinner.target}_gen${gen}`;
67
+ policy = promotedWinner.policy;
68
+ score = promotedWinner.score;
69
+ }
70
+ }
71
+ const chain = await store.walkToRoot(parentId);
72
+ const liftCurve = computeLiftCurve(chain, rootScore.primary);
73
+ const promotions = chain.filter((c) => c.verdict === 'PROMOTED');
74
+ const verified = promotions.filter((c) => c.primaryDelta > 0).length;
75
+ const anchorSurviving = promotions.filter((c) => c.primaryDelta > 0 && (rootAnchor === null || (c.anchorScore ?? -Infinity) >= rootAnchor)).length;
76
+ const replayBundle = {
77
+ data_source: cfg.dataSource ?? 'UNSPECIFIED',
78
+ root_id: rootId,
79
+ chain,
80
+ all_commits: allCommits,
81
+ lift_curve: liftCurve,
82
+ gate_fingerprint: gateFingerprint(rule),
83
+ verified_improvements: verified,
84
+ anchor_surviving_improvements: anchorSurviving,
85
+ milestone_reached: anchorSurviving >= 2,
86
+ created_at: now(cfg.maxGenerations),
87
+ };
88
+ return {
89
+ liftCurve, promotions, lineage: store, replayBundle,
90
+ generationsRun,
91
+ milestoneReached: anchorSurviving >= 2, finalPolicy: policy,
92
+ };
93
+ }
94
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ /** A policy is an opaque bag of named string levers — the thing being evolved. The flywheel never
2
+ * interprets a lever's meaning; the caller's Evaluator does. */
3
+ export type Policy = Record<string, string>;
4
+ /** A versioned policy node in the evolution graph. `parents` is a DAG (usually one parent = the winner
5
+ * it re-based on); the gen-0 root has `parents: []` and never changes. */
6
+ export interface PolicyGenome {
7
+ id: string;
8
+ generation: number;
9
+ parents: string[];
10
+ policy: Policy;
11
+ }
12
+ /** One mutation attempt: which lever was changed, and a short human summary of how. */
13
+ export interface CandidateMutation {
14
+ target: string;
15
+ summary: string;
16
+ }
17
+ /** The abstract quality of a policy on a suite. All host/benchmark meaning is projected onto these four
18
+ * axes by the Evaluator. Higher `primary` is better; lower `noopRate`/`costPerWin` is better;
19
+ * `regressed` is a hard safety/security stop. (Named generically on purpose — "primary" not "gold".) */
20
+ export interface Score {
21
+ /** The main quality signal (wins / accuracy / resolved) — higher is better. */
22
+ primary: number;
23
+ /** Fraction of non-committal / empty / no-op outputs — lower is better (the "never end empty" signal). */
24
+ noopRate: number;
25
+ /** Resource cost per successful outcome — lower is better. */
26
+ costPerWin: number;
27
+ /** A hard safety/security regression flag — any `true` blocks promotion outright. */
28
+ regressed: boolean;
29
+ }
30
+ /** What a promotion gate decides over. `anchor` (optional) is the FROZEN, never-optimized-against suite
31
+ * score — the anti-Goodhart check. */
32
+ export interface PromotionEvidence {
33
+ baseline: Score;
34
+ candidate: Score;
35
+ anchor?: {
36
+ baseline: number;
37
+ candidate: number;
38
+ };
39
+ }
40
+ export interface PromotionDecision {
41
+ promote: boolean;
42
+ reasons: string[];
43
+ }
44
+ /** THE GATE. Pure, deterministic, and — for a given deployment — FROZEN ("the gate is the product").
45
+ * Injectable so enterprises can supply their own; {@link meetsPromotionRule} is the default. */
46
+ export type PromotionRule = (evidence: PromotionEvidence) => PromotionDecision;
47
+ /** An opaque evaluation suite — the flywheel treats `items` as a black box. HoldoutSuite is optimized
48
+ * against; AnchorSuite is NOT (it is the frozen no-regression guard). Same shape; different role. */
49
+ export interface Suite {
50
+ id: string;
51
+ items: unknown[];
52
+ }
53
+ export type HoldoutSuite = Suite;
54
+ export type AnchorSuite = Suite;
55
+ /** Proposes an improved value for ONE policy lever. The ONLY seam where a model/host enters propose. */
56
+ export type Proposer = (base: PolicyGenome, target: string) => Promise<string>;
57
+ /** Scores a policy on a suite. The ONLY seam where a host/benchmark enters evaluate. Everything
58
+ * Claude-Code-, SWE-bench-, or trading-specific lives HERE, in the caller — never in the flywheel. */
59
+ export type Evaluator = (policy: Policy, suite: Suite) => Promise<Score>;
60
+ export interface PromotionReceipt {
61
+ payload: Record<string, unknown>;
62
+ signature: string;
63
+ publicKey: string;
64
+ alg: 'ed25519';
65
+ }
66
+ /** Signs a receipt + publishes its public key. Injectable (per-process, secret-backed, HSM, …). */
67
+ export interface Signer {
68
+ sign(payload: Record<string, unknown>): PromotionReceipt;
69
+ publicKey(): string;
70
+ }
71
+ export interface LineageCommit {
72
+ id: string;
73
+ generation: number;
74
+ parents: string[];
75
+ mutation: CandidateMutation | null;
76
+ /** baseline→candidate deltas on each axis (for the knowledge base / regression ancestry). */
77
+ primaryDelta: number;
78
+ anchorScore: number | null;
79
+ verdict: 'ROOT' | 'PROMOTED' | 'REJECTED';
80
+ failureReasons: string[];
81
+ receipt: PromotionReceipt;
82
+ createdAt: string;
83
+ }
84
+ export interface LineageStore {
85
+ append(commit: LineageCommit): Promise<void>;
86
+ get(id: string): Promise<LineageCommit | null>;
87
+ /** Walk parents from `id` to the immutable gen-0 root (current → root). */
88
+ walkToRoot(id: string): Promise<LineageCommit[]>;
89
+ list(): Promise<LineageCommit[]>;
90
+ }
91
+ /** One point per promoted generation — the compounding curve. */
92
+ export interface LiftPoint {
93
+ generation: number;
94
+ primary: number;
95
+ delta: number;
96
+ anchor: number | null;
97
+ }
98
+ export type LiftCurve = LiftPoint[];
99
+ /** Everything an EXTERNAL reviewer needs to replay the run with no trust in the producer. */
100
+ export interface ReplayBundle {
101
+ data_source: string;
102
+ root_id: string;
103
+ /** current → gen-0 root (the promoted chain). */
104
+ chain: LineageCommit[];
105
+ /** every candidate commit across all generations (promoted + rejected) — the full diagnostic ledger. */
106
+ all_commits: LineageCommit[];
107
+ lift_curve: LiftCurve;
108
+ /** sha256 of the PromotionRule source, when the caller supplies it — proves the gate was UNCHANGED. */
109
+ gate_fingerprint: string | null;
110
+ verified_improvements: number;
111
+ anchor_surviving_improvements: number;
112
+ milestone_reached: boolean;
113
+ created_at: string;
114
+ }
115
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ // @metaharness/flywheel — the abstract API surface.
2
+ //
3
+ // DESIGN RULE (load-bearing): this package must NOT know about any host, model, or benchmark — no
4
+ // Claude Code, no SWE-bench, no GLM/Sonnet/Fable, no code-repair. It knows only CANDIDATES, SCORES,
5
+ // GATES, RECEIPTS, and PROMOTION LINEAGE. Everything host- or benchmark-specific enters through the
6
+ // injected `Proposer` / `Evaluator` (and, if you like, a custom `PromotionRule`). If you find yourself
7
+ // wanting a benchmark-specific branch in here, it belongs in the caller, not the flywheel.
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,uGAAuG;AACvG,2FAA2F"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@metaharness/flywheel",
3
+ "version": "0.1.0",
4
+ "description": "A verifiable self-improvement loop for agent harnesses. Freeze the model. Evolve the harness. Promote only what proves lift.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "README.md"],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "vitest run",
21
+ "lint": "tsc --noEmit"
22
+ },
23
+ "keywords": ["agent", "harness", "self-improvement", "flywheel", "promotion", "lineage", "receipts", "metaharness"],
24
+ "license": "MIT",
25
+ "devDependencies": {
26
+ "typescript": "^5.4.0",
27
+ "vitest": "^2.0.0"
28
+ }
29
+ }