@miller-tech/uap 1.108.1 → 1.109.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.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Self-Harness — the REAL validator (Stage 3), wiring the orchestrator's injected
3
+ * `Validator` seam to the existing paired benchmark (`benchmarks/paired/`).
4
+ *
5
+ * For a candidate `env` Mod the physical A/B is a *server-side* change: the two
6
+ * arms differ only in the inference server's launch env, so they cannot run in a
7
+ * single paired pass. This validator therefore:
8
+ *
9
+ * 1. runs the suite as the `baseline` arm (current harness);
10
+ * 2. physically applies the Mod to the env file + restarts the server;
11
+ * 3. runs the suite again as the `candidate` arm;
12
+ * 4. REVERTS the env file + restarts (each Mod is validated in isolation — §7);
13
+ * 5. pairs the two arms cell-for-cell (`taskId#seed`) and runs `analyze` to get
14
+ * the same paired statistics `uap bench paired` ships.
15
+ *
16
+ * The held-out suite (disjoint tasks) runs the same way to catch overfitting. The
17
+ * suite-arm runner and the server restart are INJECTED so the loop is testable
18
+ * without a live llama server.
19
+ *
20
+ * `scaffold` / `middleware` Mods are NOT auto-validated here — their physical A/B
21
+ * is a prompt/proxy change that the autonomous env loop deliberately does not
22
+ * own; they route through the human-gated pending queue (§9). Passing one returns
23
+ * a null (no-lift) comparison so `decide` rejects it, never silently applying it.
24
+ *
25
+ * See docs/design/SELF_HARNESS.md §4, §7, §11.
26
+ */
27
+ import { runPaired } from '../benchmarks/paired/runner.js';
28
+ import { analyze } from '../benchmarks/paired/report.js';
29
+ import { makeFullCondition, } from '../benchmarks/paired/types.js';
30
+ import { applyEnvModToFile } from './profile.js';
31
+ import { invertMod, describeMod } from './mods.js';
32
+ const BASELINE_LABEL = 'baseline';
33
+ const CANDIDATE_LABEL = 'candidate';
34
+ /**
35
+ * Build a real, live-suite `Validator` for the orchestrator. Only `env` Mods are
36
+ * validated against the server; other kinds return a null comparison (reject).
37
+ */
38
+ export function buildValidator(deps) {
39
+ const components = deps.components ?? makeFullCondition().components;
40
+ const log = deps.log ?? (() => { });
41
+ const runArm = deps.runArm ?? defaultArmRunner(deps, components);
42
+ return async (mod) => {
43
+ if (mod.kind !== 'env') {
44
+ log(` ${describeMod(mod)} is ${mod.kind}, not env — routed to human gate, no auto-validation`);
45
+ return { validation: nullComparison(), heldout: null };
46
+ }
47
+ const envMod = mod;
48
+ const runSuite = async (dir) => {
49
+ // Arm 1 — baseline (current server state).
50
+ const baseRecs = await runArm(dir, BASELINE_LABEL, components);
51
+ // Apply the Mod + restart so the candidate arm sees the new env.
52
+ applyEnvModToFile(deps.envPath, envMod);
53
+ await deps.restart();
54
+ let candRecs;
55
+ try {
56
+ candRecs = await runArm(dir, CANDIDATE_LABEL, components);
57
+ }
58
+ finally {
59
+ // Always revert to leave the server clean regardless of the arm outcome.
60
+ applyEnvModToFile(deps.envPath, invertMod(envMod));
61
+ await deps.restart();
62
+ }
63
+ return compare([...baseRecs, ...candRecs], deps.analyzeOpts);
64
+ };
65
+ log(` validating on ${deps.suiteDir}${deps.heldoutDir ? ` + held-out ${deps.heldoutDir}` : ''}`);
66
+ const validation = await runSuite(deps.suiteDir);
67
+ const heldout = deps.heldoutDir ? await runSuite(deps.heldoutDir) : null;
68
+ return { validation, heldout };
69
+ };
70
+ }
71
+ /** Default arm runner: a single-condition `runPaired` over the suite. */
72
+ function defaultArmRunner(deps, _components) {
73
+ const adapter = deps.adapter;
74
+ if (!adapter) {
75
+ // Defer the error until first use so tests that inject runArm never hit it.
76
+ return async () => {
77
+ throw new Error('buildValidator: default runArm requires an `adapter` in deps');
78
+ };
79
+ }
80
+ return async (suiteDir, label, components) => {
81
+ const { loadSuite } = await import('../benchmarks/paired/suite.js');
82
+ const { mkdtempSync } = await import('fs');
83
+ const { tmpdir } = await import('os');
84
+ const { join } = await import('path');
85
+ const tasks = loadSuite(suiteDir);
86
+ const condition = { label, components };
87
+ const workRoot = mkdtempSync(join(tmpdir(), 'sh-validate-'));
88
+ const out = await runPaired({
89
+ tasks,
90
+ conditions: [condition],
91
+ adapter,
92
+ model: deps.model ?? 'unknown',
93
+ epochs: deps.epochs ?? 5,
94
+ concurrency: deps.concurrency ?? 4,
95
+ workRoot,
96
+ }, suiteDir, new Date().toISOString());
97
+ return out.records;
98
+ };
99
+ }
100
+ /** Wrap combined baseline+candidate records and analyze into a Comparison. */
101
+ export function compare(records, analyzeOpts) {
102
+ const report = analyze({
103
+ records,
104
+ model: records[0]?.model ?? 'unknown',
105
+ adapter: records[0]?.adapter ?? 'unknown',
106
+ epochs: countSeeds(records),
107
+ startedAt: '',
108
+ finishedAt: '',
109
+ }, { ...analyzeOpts, baselineLabel: BASELINE_LABEL });
110
+ const cmp = report.comparisons.find((c) => c.label === CANDIDATE_LABEL);
111
+ if (!cmp) {
112
+ throw new Error(`compare: no '${CANDIDATE_LABEL}' comparison produced (labels: ${report.comparisons
113
+ .map((c) => c.label)
114
+ .join(', ')})`);
115
+ }
116
+ return cmp;
117
+ }
118
+ function countSeeds(records) {
119
+ return new Set(records.map((r) => r.seed)).size || 1;
120
+ }
121
+ /** A no-lift comparison (Δ=0, not significant) so `decide` rejects. */
122
+ export function nullComparison() {
123
+ return {
124
+ label: CANDIDATE_LABEL,
125
+ baseline: BASELINE_LABEL,
126
+ correctness: {
127
+ baselineRate: 0,
128
+ treatmentRate: 0,
129
+ delta: { meanDelta: 0, ci: { lower: 0, upper: 0 }, pValue: 1, n: 0, significant: false },
130
+ mcnemar: { bothCorrect: 0, onlyTreatment: 0, onlyBaseline: 0, bothWrong: 0, netGain: 0, pValue: 1, n: 0 },
131
+ verdict: 'tie',
132
+ },
133
+ metrics: {},
134
+ metricVerdicts: {},
135
+ };
136
+ }
137
+ export { BASELINE_LABEL, CANDIDATE_LABEL };
138
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/self-harness/validate.ts"],"names":[],"mappings":"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"}
@@ -60,9 +60,10 @@ mine→propose→validate→accept and persists results. Humans do both today.
60
60
  key=(model_family, failure_sig) (env + scaffold snapshot)
61
61
  ```
62
62
 
63
- New code lives under `src/self-harness/`: `orchestrator.ts`, `mine.ts`, `propose.ts`, `mods.ts` (the
64
- DSL), `apply.ts`, `transfer.ts`, plus `uap self-harness` CLI. It **reuses** `benchmarks/paired/` for
65
- validation and the HALO exporter for mining no reimplementation.
63
+ New code lives under `src/self-harness/`: `orchestrator.ts` (one iteration), `validate.ts` (the real
64
+ paired-bench validator), `run.ts` (the committing loop + versioned snapshot), `mine.ts`, `propose.ts`,
65
+ `mods.ts` (the DSL), `transfer.ts`, plus the `uap self-harness` CLI. It **reuses** `benchmarks/paired/`
66
+ for validation and the HALO exporter for mining — no reimplementation.
66
67
 
67
68
  ## 4. The Modification DSL — bounded, typed, reversible
68
69
 
@@ -171,9 +172,15 @@ Controls:
171
172
  - **P0 — Plumbing (small).** Promote the failure-mode logs to structured HALO spans; add the held-out
172
173
  regression suite; pin a stable `signature` hashing. *Exit:* `uap harness analyze` emits a typed
173
174
  `WeaknessReport`.
174
- - **P1 — Closed loop, env+scaffold DSL (Option A).** `uap self-harness run` orchestrating
175
- mine→propose→validate→accept over `env`+`scaffold` Mods, reusing `benchmarks/paired/`. *Exit:* one
176
- autonomous iteration that accepts a real Mod (e.g. re-discovers `LLAMA_N_PREDICT=4096`) with stats.
175
+ - **P1 — Closed loop, env DSL (Option A). [BUILT]** `uap self-harness run` orchestrates
176
+ mine→propose→validate→decide over `env` Mods, reusing `benchmarks/paired/`. The real validator
177
+ (`validate.ts`) runs a baseline arm, physically toggles the env knob + restarts the server, runs a
178
+ candidate arm, reverts, and pairs the two into a `Comparison`; held-out runs the same way. `--apply`
179
+ commits accepted Mods to the env file, restarts once, and writes a **versioned profile snapshot**
180
+ (`run.ts`, `profile.ts`) + append-only history; without it the run is a pure dry-run that touches
181
+ nothing. *Exit met:* one autonomous iteration accepts a real env Mod (e.g. re-discovers
182
+ `LLAMA_N_PREDICT=4096`) with paired stats. `scaffold`/`middleware` Mods are not auto-validated here —
183
+ they route through the human-gated pending queue (§9).
177
184
  - **P2 — Middleware Mods (Option B).** Add the `middleware` Mod class + the `toolcall-path-normalizer`.
178
185
  *Exit:* the loop proposes+validates the normalizer and measurably cuts path-garbling on the medium
179
186
  suite (the ceiling manual fixes couldn't crack).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.108.1",
3
+ "version": "1.109.1",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1164,10 +1164,18 @@
1164
1164
  `<strong style="color:#58a6ff;font-size:18px">${fmt(sv.totalTokensSaved)}</strong> tokens · ` +
1165
1165
  `<strong style="color:#3fb950;font-size:18px">$${(sv.totalCostSavedUsd||0).toFixed(2)}</strong> saved across all UAP influences`);
1166
1166
  if (!sv.influences.length) { setHTML('savings-table', '<div class="empty">No savings data</div>'); return; }
1167
- const rows = sv.influences.map(i =>
1168
- `<tr><td>${esc(i.influence)}</td><td style="text-align:right">${fmt(i.tokensSaved)}</td>` +
1169
- `<td style="text-align:right;color:#3fb950">$${(i.costSavedUsd||0).toFixed(4)}</td>` +
1170
- `<td>${badge(i.quality)}</td><td style="color:var(--fg3);font-size:11px">${esc(i.detail||'')}</td></tr>`).join('');
1167
+ // Unmeasured influences (e.g. routing with no UAP-driven calls yet) render an
1168
+ // explicit dimmed "—" rather than a real-looking $0, so idle != broken.
1169
+ const idleCell = '<span style="color:var(--fg3)" title="not active / no data recorded yet">\u2014</span>';
1170
+ const rows = sv.influences.map(i => {
1171
+ const idle = i.quality === 'unmeasured';
1172
+ const tok = idle ? idleCell : fmt(i.tokensSaved);
1173
+ const cost = idle ? idleCell : `<span style="color:#3fb950">$${(i.costSavedUsd||0).toFixed(4)}</span>`;
1174
+ return `<tr${idle ? ' style="opacity:.55"' : ''}><td>${esc(i.influence)}</td>` +
1175
+ `<td style="text-align:right">${tok}</td>` +
1176
+ `<td style="text-align:right">${cost}</td>` +
1177
+ `<td>${badge(i.quality)}</td><td style="color:var(--fg3);font-size:11px">${esc(i.detail||'')}</td></tr>`;
1178
+ }).join('');
1171
1179
  setHTML('savings-table',
1172
1180
  `<table><thead><tr><th>Influence</th><th style="text-align:right">Tokens saved</th>` +
1173
1181
  `<th style="text-align:right">Cost saved</th><th>Quality</th><th>Detail</th></tr></thead><tbody>${rows}</tbody></table>`);