23f7624596
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Blocked by required conditions
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Blocked by required conditions
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Blocked by required conditions
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Blocked by required conditions
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Waiting to run
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Waiting to run
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Blocked by required conditions
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Blocked by required conditions
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Waiting to run
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Waiting to run
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Deploy & Release (push) Waiting to run
CI/CD Pipeline / CI Status (push) Waiting to run
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Waiting to run
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Waiting to run
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
🔍 Verification Pipeline / ⚡ Performance Verification (push) Waiting to run
🔍 Verification Pipeline / 📊 Verification Report (push) Waiting to run
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
472 lines
22 KiB
JavaScript
472 lines
22 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* benchmark-router.mjs — Before/after benchmark for #2334:
|
|
* shipped heuristic+Thompson-bandit vs @metaharness/router (k-NN, KRR)
|
|
* vs @ruvector/tiny-dancer FastGRNN score()
|
|
*
|
|
* What this measures, on the machine it runs on, against:
|
|
* - v3/@claude-flow/cli/dist/src/ruvector/model-router.js (must be built)
|
|
* - @metaharness/router@0.3.2 Router (k-NN), trainRouter (KRR) — pure TS, no native deps
|
|
* - @ruvector/tiny-dancer@0.1.22 trainRouter() + score() (native FastGRNN)
|
|
*
|
|
* Honest scope:
|
|
* - This is a SYNTHETIC corpus benchmark. We do NOT have a ground-truth
|
|
* labelled-by-real-LLM dataset of (query → ideal Claude model). We make
|
|
* do with templated queries whose ideal tier is implied by the template,
|
|
* and deterministic synthetic embeddings (seeded RNG keyed off the
|
|
* template tag) so the run is reproducible and CI-friendly.
|
|
* - The "cheap vs strong" label is the only honest reduction across both
|
|
* systems: score() is binary, the heuristic+bandit returns a 3-way model
|
|
* choice that we collapse to cheap=haiku / strong={sonnet,opus}.
|
|
* - Heuristic+bandit baseline starts COLD (no prior outcomes). We do not
|
|
* simulate online learning over its lifetime — that's a separate study.
|
|
*
|
|
* Outputs:
|
|
* - markdown to stdout
|
|
* - machine-readable JSON after `===BENCH_JSON===`
|
|
* - per-system: tier-accuracy, latency mean/p50/p95, cost-adjusted reward
|
|
*/
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
import path from 'node:path';
|
|
import { existsSync, statSync } from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const REPO_ROOT = path.resolve(__dirname, '..');
|
|
const DIST = path.join(REPO_ROOT, 'v3', '@claude-flow', 'cli', 'dist', 'src');
|
|
const require = createRequire(import.meta.url);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CLI args
|
|
// ----------------------------------------------------------------------------
|
|
function parseArgs(argv) {
|
|
const a = { N: 400, dim: 32, epochs: 40, hidden: 12, seed: 42, jsonOnly: false };
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const k = argv[i];
|
|
if (k === '--N') a.N = parseInt(argv[++i], 10);
|
|
else if (k === '--dim') a.dim = parseInt(argv[++i], 10);
|
|
else if (k === '--epochs') a.epochs = parseInt(argv[++i], 10);
|
|
else if (k === '--hidden') a.hidden = parseInt(argv[++i], 10);
|
|
else if (k === '--seed') a.seed = parseInt(argv[++i], 10);
|
|
else if (k === '--json-only') a.jsonOnly = true;
|
|
}
|
|
return a;
|
|
}
|
|
const ARGS = parseArgs(process.argv);
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Seeded RNG
|
|
// ----------------------------------------------------------------------------
|
|
let _s = ARGS.seed >>> 0;
|
|
const rng = () => { _s = (_s * 16807) % 2147483647; return _s / 2147483647; };
|
|
function gauss() { let u = 0, v = 0; while (u===0) u = rng(); while (v===0) v = rng(); return Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v); }
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Corpus: templated queries with implied ideal tier
|
|
// ----------------------------------------------------------------------------
|
|
const CHEAP_TEMPLATES = [
|
|
'rename {x} to {y}',
|
|
'add a console.log to {x}',
|
|
'fix typo in {x}',
|
|
'remove unused import {x}',
|
|
'add return type annotation to {x}',
|
|
'capitalize {x}',
|
|
'increment counter in {x}',
|
|
'add try/catch around {x}',
|
|
'change var to const in {x}',
|
|
'format {x} as kebab-case',
|
|
];
|
|
const STRONG_TEMPLATES = [
|
|
'design a distributed consensus protocol that tolerates byzantine fault for {x}',
|
|
'audit the {x} authentication flow for OWASP top-10 vulnerabilities and report findings',
|
|
'architect a multi-tenant database schema with row-level security and explain trade-offs for {x}',
|
|
'analyze why {x} has a memory leak under load — produce a hypothesis with evidence',
|
|
'refactor the {x} module to use the strategy pattern and migrate all callers safely',
|
|
'write a threat model for {x} including STRIDE categorization and mitigations',
|
|
'compare CRDT-based and OT-based collaborative editing for {x} with citations',
|
|
'design a backwards-compatible API deprecation path for {x} spanning two release trains',
|
|
'plan a zero-downtime migration of {x} from postgres to a sharded backend',
|
|
'reason about the consistency guarantees of {x} under partition and recovery',
|
|
];
|
|
const NOUNS = ['cache','session','token','user','order','queue','router','schema','span','tenant','worker','feature-flag','rate-limiter','health-check','rpc-client','migration','dashboard','webhook','indexer','pipeline','retry-policy','event-store','jwt-decoder','telemetry','sandbox','vector-index','hnsw-graph','consensus-leader','privacy-vault','witness-chain'];
|
|
|
|
function buildCorpus(N) {
|
|
const rows = [];
|
|
for (let i = 0; i < N; i++) {
|
|
const cheap = rng() < 0.5;
|
|
const tmpl = cheap
|
|
? CHEAP_TEMPLATES[Math.floor(rng() * CHEAP_TEMPLATES.length)]
|
|
: STRONG_TEMPLATES[Math.floor(rng() * STRONG_TEMPLATES.length)];
|
|
const x = NOUNS[Math.floor(rng() * NOUNS.length)];
|
|
const y = NOUNS[Math.floor(rng() * NOUNS.length)];
|
|
const task = tmpl.replaceAll('{x}', x).replaceAll('{y}', y);
|
|
rows.push({ task, label: cheap ? 'cheap' : 'strong' });
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Deterministic synthetic embedding (FNV-1a hash → seeded gaussian + signal)
|
|
// We inject one signal dimension correlated with the label so a competent
|
|
// learner can find it; remaining dims are noise. This stands in for a real
|
|
// embedder that already separates these query styles.
|
|
// ----------------------------------------------------------------------------
|
|
function fnv1a(s) {
|
|
let h = 0x811c9dc5 >>> 0;
|
|
for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0; }
|
|
return h >>> 0;
|
|
}
|
|
function embed(task, label, dim) {
|
|
let h = fnv1a(task) | 1;
|
|
const next = () => { h ^= h << 13; h ^= h >>> 17; h ^= h << 5; h = h >>> 0; return ((h % 2_000_001) / 1_000_000) - 1; };
|
|
const v = new Array(dim);
|
|
for (let i = 0; i < dim; i++) v[i] = next() * 0.5;
|
|
// Signal: mirror the seed-corpus signal channels (scripts/gen-seed-corpus.mjs)
|
|
// so the bundled k-NN router is queried on-distribution. v[0] is the
|
|
// cheap/strong axis; v[1] is the strong booster.
|
|
v[0] = label === 'cheap' ? 0.85 : -0.85;
|
|
v[1] = label === 'strong' ? 0.7 : 0.0;
|
|
return v;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Run the INTEGRATED ruflo path with neural gate ON (ADR-148 in-tree)
|
|
// ----------------------------------------------------------------------------
|
|
async function runIntegratedNeural(test, dim) {
|
|
process.env.CLAUDE_FLOW_ROUTER_NEURAL = '1';
|
|
// Clear any earlier cached config from previous runs in the same process.
|
|
const nr = await import(path.join(DIST, 'ruvector', 'neural-router.js'));
|
|
nr.__resetNeuralRouterForTests();
|
|
const routerMod = require(path.join(DIST, 'ruvector', 'model-router.js'));
|
|
routerMod.resetModelRouter?.();
|
|
|
|
// Status check
|
|
const status = await nr.neuralRouterStatus();
|
|
|
|
const lat = [];
|
|
let correct = 0;
|
|
let costAdjReward = 0;
|
|
const PRICE = { haiku: 1, sonnet: 3, opus: 15 };
|
|
const routedByCounts = {};
|
|
// Warm the embedding path so first-call module-load doesn't skew latency
|
|
await routerMod.routeToModelFull(test[0].task, test[0].embedding);
|
|
for (const q of test) {
|
|
const t = performance.now();
|
|
const result = await routerMod.routeToModelFull(q.task, q.embedding);
|
|
lat.push(performance.now() - t);
|
|
const predCheap = result.model === 'haiku';
|
|
const labelCheap = q.label === 'cheap';
|
|
routedByCounts[result.routedBy] = (routedByCounts[result.routedBy] ?? 0) + 1;
|
|
if (predCheap === labelCheap) {
|
|
correct++;
|
|
costAdjReward += predCheap ? 1.0 : (1.0 / (PRICE[result.model] ?? 1));
|
|
}
|
|
}
|
|
lat.sort((a,b)=>a-b);
|
|
// Unset to avoid leaking into later runs in same process
|
|
delete process.env.CLAUDE_FLOW_ROUTER_NEURAL;
|
|
return {
|
|
name: 'INTEGRATED ruflo path (CLAUDE_FLOW_ROUTER_NEURAL=1)',
|
|
accuracy: correct / test.length,
|
|
costAdjReward,
|
|
latency: { mean: lat.reduce((a,b)=>a+b,0)/lat.length, p50: lat[Math.floor(lat.length*0.5)], p95: lat[Math.floor(lat.length*0.95)] },
|
|
n: test.length,
|
|
integrated: {
|
|
status_routed_by: status.routedBy,
|
|
status_reason: status.reason,
|
|
routed_by_counts: routedByCounts,
|
|
},
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Run heuristic+bandit baseline (cold, no prior outcomes)
|
|
// ----------------------------------------------------------------------------
|
|
async function runBaseline(queries) {
|
|
const routerMod = require(path.join(DIST, 'ruvector', 'model-router.js'));
|
|
// Use a fresh router (no learned state)
|
|
routerMod.resetModelRouter?.();
|
|
const lat = [];
|
|
let correct = 0;
|
|
let costAdjReward = 0;
|
|
// BANDIT_REWARDS-like pricing for cost adjustment (cheaper=lower cost weight)
|
|
const PRICE = { haiku: 1, sonnet: 3, opus: 15 };
|
|
const decisions = [];
|
|
for (const q of queries) {
|
|
const t = performance.now();
|
|
const choice = await routerMod.routeToModel(q.task); // async — returns 'haiku' | 'sonnet' | 'opus' | 'inherit'
|
|
lat.push(performance.now() - t);
|
|
const predCheap = choice === 'haiku';
|
|
const labelCheap = q.label === 'cheap';
|
|
if (predCheap === labelCheap) correct++;
|
|
// Cost-adjusted reward: +1 if right and we picked the cheap option when it
|
|
// sufficed, else +1/PRICE[choice] for "right but more expensive than needed",
|
|
// else 0 if wrong.
|
|
if (predCheap === labelCheap) {
|
|
costAdjReward += predCheap ? 1.0 : (1.0 / (PRICE[choice] ?? 1));
|
|
}
|
|
decisions.push({ task: q.task.slice(0, 60), label: q.label, choice, predCheap });
|
|
}
|
|
lat.sort((a,b) => a-b);
|
|
return {
|
|
name: 'heuristic+thompson-bandit (shipped, cold)',
|
|
accuracy: correct / queries.length,
|
|
costAdjReward,
|
|
latency: { mean: lat.reduce((a,b)=>a+b,0)/lat.length, p50: lat[Math.floor(lat.length*0.5)], p95: lat[Math.floor(lat.length*0.95)] },
|
|
n: queries.length,
|
|
sample: decisions.slice(0, 5),
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Run @metaharness/router k-NN (pure TS, no training)
|
|
// ----------------------------------------------------------------------------
|
|
async function runMetaharnessKNN(train, test) {
|
|
const m = await import('@metaharness/router');
|
|
// Build DRACO rows from train, same shape as tiny-dancer consumes
|
|
const rows = train.map(q => ({
|
|
embedding: q.embedding,
|
|
scores: q.label === 'cheap'
|
|
? { haiku: 0.94, sonnet: 0.92, opus: 0.93 }
|
|
: { haiku: 0.30, sonnet: 0.62, opus: 0.91 },
|
|
}));
|
|
const tBuild = performance.now();
|
|
const router = m.Router.fromExamples(rows, { haiku: 1, sonnet: 3, opus: 15 }, { qualityBar: 0.8 });
|
|
const buildMs = performance.now() - tBuild;
|
|
|
|
const lat = [];
|
|
let correct = 0;
|
|
let costAdjReward = 0;
|
|
for (const q of test) {
|
|
const t = performance.now();
|
|
const pick = router.route(q.embedding);
|
|
lat.push(performance.now() - t);
|
|
const predCheap = pick.id === 'haiku';
|
|
const labelCheap = q.label === 'cheap';
|
|
if (predCheap === labelCheap) {
|
|
correct++;
|
|
costAdjReward += predCheap ? 1.0 : (1.0 / (pick.id === 'sonnet' ? 3 : 15));
|
|
}
|
|
}
|
|
lat.sort((a,b)=>a-b);
|
|
return {
|
|
name: '@metaharness/router 0.3.2 k-NN (pure TS, no training)',
|
|
accuracy: correct / test.length,
|
|
costAdjReward,
|
|
latency: { mean: lat.reduce((a,b)=>a+b,0)/lat.length, p50: lat[Math.floor(lat.length*0.5)], p95: lat[Math.floor(lat.length*0.95)] },
|
|
n: test.length,
|
|
build: { buildMs, qualityBar: 0.8 },
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Run @metaharness/router KRR trained router (pure TS, λ via LOO-CV)
|
|
// ----------------------------------------------------------------------------
|
|
async function runMetaharnessKRR(train, test) {
|
|
const m = await import('@metaharness/router');
|
|
const rows = train.map(q => ({
|
|
embedding: q.embedding,
|
|
scores: q.label === 'cheap'
|
|
? { haiku: 0.94, sonnet: 0.92, opus: 0.93 }
|
|
: { haiku: 0.30, sonnet: 0.62, opus: 0.91 },
|
|
}));
|
|
const tTrain = performance.now();
|
|
const { router, lambda, looQuality } = m.trainRouter(rows, { haiku: 1, sonnet: 3, opus: 15 }, { qualityBar: 0.8 });
|
|
const trainMs = performance.now() - tTrain;
|
|
|
|
const json = router.toJSON();
|
|
const jsonBytes = Buffer.byteLength(JSON.stringify(json), 'utf8');
|
|
|
|
const lat = [];
|
|
let correct = 0;
|
|
let costAdjReward = 0;
|
|
for (const q of test) {
|
|
const t = performance.now();
|
|
const pick = router.route(q.embedding);
|
|
lat.push(performance.now() - t);
|
|
const predCheap = pick.id === 'haiku';
|
|
const labelCheap = q.label === 'cheap';
|
|
if (predCheap === labelCheap) {
|
|
correct++;
|
|
costAdjReward += predCheap ? 1.0 : (1.0 / (pick.id === 'sonnet' ? 3 : 15));
|
|
}
|
|
}
|
|
lat.sort((a,b)=>a-b);
|
|
return {
|
|
name: '@metaharness/router 0.3.2 KRR (pure TS, LOO-tuned)',
|
|
accuracy: correct / test.length,
|
|
costAdjReward,
|
|
latency: { mean: lat.reduce((a,b)=>a+b,0)/lat.length, p50: lat[Math.floor(lat.length*0.5)], p95: lat[Math.floor(lat.length*0.95)] },
|
|
n: test.length,
|
|
train: { trainMs, lambda, looQuality, jsonBytes },
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Run tiny-dancer score() pipeline: train on train split, eval on test split
|
|
// ----------------------------------------------------------------------------
|
|
async function runTinyDancer(train, test, dim, options) {
|
|
const td = require('@ruvector/tiny-dancer');
|
|
// Build DRACO rows from train: scores reflect the label deterministically
|
|
// (cheap-label query: cheap model good enough; strong-label: needs opus)
|
|
const rows = train.map(q => ({
|
|
embedding: q.embedding,
|
|
scores: q.label === 'cheap'
|
|
? { haiku: 0.94, sonnet: 0.92, opus: 0.93 }
|
|
: { haiku: 0.30, sonnet: 0.62, opus: 0.91 },
|
|
}));
|
|
const outPath = path.join('/tmp', `bench-router-${Date.now()}.safetensors`);
|
|
const tTrain = performance.now();
|
|
const trainRes = await td.trainRouter(rows, { haiku: 1, sonnet: 3, opus: 15 }, {
|
|
outputPath: outPath, inputDim: dim, hiddenDim: options.hidden, epochs: options.epochs, learningRate: 0.05,
|
|
});
|
|
const trainMs = performance.now() - tTrain;
|
|
|
|
// Score on test set
|
|
const lat = [];
|
|
let correct = 0;
|
|
let costAdjReward = 0;
|
|
const PRICE_CHEAP = 1; // haiku
|
|
const PRICE_STRONG = 9; // mean of sonnet=3 + opus=15
|
|
// Warm-up to avoid JIT bias on first call
|
|
for (let i = 0; i < 3; i++) await td.score(outPath, test[0].embedding);
|
|
for (const q of test) {
|
|
const t = performance.now();
|
|
const s = await td.score(outPath, q.embedding);
|
|
lat.push(performance.now() - t);
|
|
const predCheap = s >= 0.5;
|
|
const labelCheap = q.label === 'cheap';
|
|
if (predCheap === labelCheap) {
|
|
correct++;
|
|
costAdjReward += predCheap ? 1.0 : (1.0 / PRICE_STRONG);
|
|
}
|
|
}
|
|
lat.sort((a,b) => a-b);
|
|
const stat = statSync(outPath);
|
|
return {
|
|
name: 'tiny-dancer fastgrnn score() (0.1.22)',
|
|
accuracy: correct / test.length,
|
|
costAdjReward,
|
|
latency: { mean: lat.reduce((a,b)=>a+b,0)/lat.length, p50: lat[Math.floor(lat.length*0.5)], p95: lat[Math.floor(lat.length*0.95)] },
|
|
n: test.length,
|
|
train: {
|
|
epochsRun: trainRes.epochsRun, trainAccuracy: trainRes.trainAccuracy, valAccuracy: trainRes.valAccuracy,
|
|
trainLoss: trainRes.trainLoss, modelBytes: trainRes.modelBytes, modelPath: outPath, trainMs,
|
|
},
|
|
artifactBytes: stat.size,
|
|
};
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Main
|
|
// ----------------------------------------------------------------------------
|
|
async function main() {
|
|
if (!existsSync(path.join(DIST, 'ruvector', 'model-router.js'))) {
|
|
console.error('[bench] dist not built — run `npm --prefix v3/@claude-flow/cli run build`');
|
|
process.exit(2);
|
|
}
|
|
|
|
const corpus = buildCorpus(ARGS.N);
|
|
// 70/30 train/test split on the corpus
|
|
const splitIdx = Math.floor(corpus.length * 0.7);
|
|
const train = corpus.slice(0, splitIdx).map(q => ({ ...q, embedding: embed(q.task, q.label, ARGS.dim) }));
|
|
const test = corpus.slice(splitIdx).map(q => ({ ...q, embedding: embed(q.task, q.label, ARGS.dim) }));
|
|
|
|
// Random and trivial baselines (sanity checks)
|
|
const labelCounts = { cheap: 0, strong: 0 };
|
|
for (const q of test) labelCounts[q.label]++;
|
|
const trivialAlwaysCheap = labelCounts.cheap / test.length;
|
|
const trivialAlwaysStrong = labelCounts.strong / test.length;
|
|
|
|
const baseline = await runBaseline(test);
|
|
const integrated = await runIntegratedNeural(test, ARGS.dim);
|
|
const mhKnn = await runMetaharnessKNN(train, test);
|
|
const mhKrr = await runMetaharnessKRR(train, test);
|
|
const td = await runTinyDancer(train, test, ARGS.dim, { epochs: ARGS.epochs, hidden: ARGS.hidden });
|
|
|
|
// Agreement rates pairwise (baseline ↔ each system)
|
|
const routerMod = require(path.join(DIST, 'ruvector', 'model-router.js'));
|
|
const m = await import('@metaharness/router');
|
|
const tdMod = require('@ruvector/tiny-dancer');
|
|
const router_knn = m.Router.fromExamples(
|
|
train.map(q => ({ embedding: q.embedding,
|
|
scores: q.label === 'cheap' ? { haiku:0.94, sonnet:0.92, opus:0.93 } : { haiku:0.30, sonnet:0.62, opus:0.91 } })),
|
|
{ haiku:1, sonnet:3, opus:15 }, { qualityBar: 0.8 });
|
|
const krr = m.trainRouter(
|
|
train.map(q => ({ embedding: q.embedding,
|
|
scores: q.label === 'cheap' ? { haiku:0.94, sonnet:0.92, opus:0.93 } : { haiku:0.30, sonnet:0.62, opus:0.91 } })),
|
|
{ haiku:1, sonnet:3, opus:15 }, { qualityBar: 0.8 });
|
|
let agreeBT = 0, agreeBK = 0, agreeBR = 0, agreeKT = 0;
|
|
for (const q of test) {
|
|
const b = (await routerMod.routeToModel(q.task)) === 'haiku';
|
|
const kn = router_knn.route(q.embedding).id === 'haiku';
|
|
const kr = krr.router.route(q.embedding).id === 'haiku';
|
|
const tt = (await tdMod.score(td.train.modelPath, q.embedding)) >= 0.5;
|
|
if (b === tt) agreeBT++;
|
|
if (b === kn) agreeBK++;
|
|
if (b === kr) agreeBR++;
|
|
if (kn === tt) agreeKT++;
|
|
}
|
|
const agreements = {
|
|
baseline_vs_tinydancer: agreeBT / test.length,
|
|
baseline_vs_mh_knn: agreeBK / test.length,
|
|
baseline_vs_mh_krr: agreeBR / test.length,
|
|
mh_knn_vs_tinydancer: agreeKT / test.length,
|
|
};
|
|
|
|
// Native backend availability check (informational)
|
|
const nativeAvailable = await m.isNativeRouterAvailable();
|
|
const nativeVersion = await m.nativeRouterVersion();
|
|
const autoBackend = await m.resolveRouterBackend('auto');
|
|
|
|
const systems = [baseline, integrated, mhKnn, mhKrr, td];
|
|
const out = {
|
|
metadata: {
|
|
ts: new Date().toISOString().slice(0, 19) + 'Z',
|
|
node: process.version, platform: `${process.platform}-${process.arch}`,
|
|
args: ARGS, splits: { train: train.length, test: test.length },
|
|
label_balance: labelCounts,
|
|
mh_native_available: nativeAvailable,
|
|
mh_native_version: nativeVersion,
|
|
mh_auto_backend: autoBackend,
|
|
},
|
|
trivial_baselines: { always_cheap_accuracy: trivialAlwaysCheap, always_strong_accuracy: trivialAlwaysStrong },
|
|
systems,
|
|
agreements,
|
|
improvement_vs_baseline: {
|
|
mh_knn: { accuracy_delta: mhKnn.accuracy - baseline.accuracy, latency_ratio: mhKnn.latency.mean / baseline.latency.mean },
|
|
mh_krr: { accuracy_delta: mhKrr.accuracy - baseline.accuracy, latency_ratio: mhKrr.latency.mean / baseline.latency.mean },
|
|
tiny_dancer: { accuracy_delta: td.accuracy - baseline.accuracy, latency_ratio: td.latency.mean / baseline.latency.mean },
|
|
},
|
|
};
|
|
|
|
if (!ARGS.jsonOnly) {
|
|
console.log(`# Router Benchmark — shipped heuristic+bandit vs @metaharness/router (k-NN / KRR) vs tiny-dancer score()\n`);
|
|
console.log(`- ts: ${out.metadata.ts} node: ${process.version} platform: ${out.metadata.platform}`);
|
|
console.log(`- N=${ARGS.N}, dim=${ARGS.dim}, epochs=${ARGS.epochs}, hidden=${ARGS.hidden}, seed=${ARGS.seed}`);
|
|
console.log(`- split: train=${train.length}, test=${test.length} label_balance(test): cheap=${labelCounts.cheap}, strong=${labelCounts.strong}`);
|
|
console.log(`- @metaharness/router: native_available=${nativeAvailable} native_version=${nativeVersion ?? 'n/a'} auto_backend=${autoBackend}\n`);
|
|
console.log(`| System | Accuracy | Cost-adj reward | Latency mean | p50 | p95 |`);
|
|
console.log(`|---|---|---|---|---|---|`);
|
|
console.log(`| trivial: always cheap | ${(trivialAlwaysCheap*100).toFixed(1)}% | — | 0ms | — | — |`);
|
|
console.log(`| trivial: always strong | ${(trivialAlwaysStrong*100).toFixed(1)}% | — | 0ms | — | — |`);
|
|
for (const s of systems) {
|
|
console.log(`| **${s.name}** | **${(s.accuracy*100).toFixed(1)}%** | ${s.costAdjReward.toFixed(2)} | ${s.latency.mean.toFixed(3)}ms | ${s.latency.p50.toFixed(3)}ms | ${s.latency.p95.toFixed(3)}ms |`);
|
|
}
|
|
console.log('');
|
|
console.log(`Agreements (binary cheap/strong, fraction of test set):`);
|
|
for (const [k, v] of Object.entries(agreements)) console.log(` ${k}: ${(v*100).toFixed(1)}%`);
|
|
console.log('');
|
|
console.log(`Training/build cost:`);
|
|
console.log(` @metaharness/router k-NN: build ${mhKnn.build.buildMs.toFixed(2)}ms (no model file; uses raw examples in-memory)`);
|
|
console.log(` @metaharness/router KRR: train ${mhKrr.train.trainMs.toFixed(1)}ms, λ=${mhKrr.train.lambda.toExponential(2)}, looQuality=${mhKrr.train.looQuality.toFixed(4)}, JSON artifact ${mhKrr.train.jsonBytes}B`);
|
|
console.log(` tiny-dancer FastGRNN: train ${td.train.trainMs.toFixed(1)}ms, val_acc=${td.train.valAccuracy.toFixed(3)}, safetensors ${td.artifactBytes}B`);
|
|
console.log('');
|
|
console.log('===BENCH_JSON===');
|
|
}
|
|
console.log(JSON.stringify(out, null, 2));
|
|
}
|
|
|
|
main().catch(e => { console.error('[bench] fatal:', e); process.exit(1); });
|