Files
gbrain/test/brainbench-metrics.test.ts
Garry Tan 75fae742d5 v0.44.0.0 feat(eval): BrainBench — cross-harness memory conformance suite (Cathedral 2) (#2158)
Public, reproducible, cross-harness benchmark for the four ways agent memory fails (know-to-ask, push precision/recall, write-back fidelity, cross-session continuity). Hermetic PGLite, sealed gold, deterministic corpus, CI gate vs master's committed baseline, published JSON-schema foreign-runner contract. See docs/eval/BRAINBENCH.md.
2026-08-10 09:44:46 -07:00

114 lines
4.5 KiB
TypeScript

/**
* BrainBench metric formulas — pure scoring over hand-built turn rows.
* Edge cases: zero should_retrieve turns, empty injections, acceptable-vs-gold
* asymmetry, micro-averaging weights.
*/
import { describe, expect, test } from 'bun:test';
import { scoreKnowToAsk } from '../src/eval/brainbench/metrics/know-to-ask.ts';
import { scorePush } from '../src/eval/brainbench/metrics/push.ts';
import type { TurnRow } from '../src/eval/brainbench/types.ts';
function row(partial: Partial<TurnRow> & { gold: TurnRow['gold'] }): TurnRow {
return {
fixture_id: 'fx',
turn_id: 1,
harness: 'openclaw',
suite: 'know-to-ask',
injected_slugs: [],
injected_tokens: 0,
cross_source_slugs: [],
latency_ms: 0,
...partial,
};
}
describe('scoreKnowToAsk', () => {
test('miss on should_retrieve counts; acceptable_slugs also satisfy', () => {
const rows = [
row({ turn_id: 1, gold: { should_retrieve: true, gold_slugs: ['a'] }, injected_slugs: [] }),
row({ turn_id: 2, gold: { should_retrieve: true, gold_slugs: ['a'], acceptable_slugs: ['b'] }, injected_slugs: ['b'] }),
row({ turn_id: 3, gold: { should_retrieve: true, gold_slugs: ['a'] }, injected_slugs: ['a'] }),
];
const s = scoreKnowToAsk(rows);
expect(s.metrics.know_to_ask_failure_rate).toBeCloseTo(1 / 3);
expect(s.gold_failed).toBe(1);
expect(s.failed_items[0]).toContain('fx#1');
});
test('false fire: injection on a stay-silent turn', () => {
const rows = [
row({ turn_id: 1, gold: { should_retrieve: false }, injected_slugs: ['x'] }),
row({ turn_id: 2, gold: { should_retrieve: false }, injected_slugs: [] }),
];
const s = scoreKnowToAsk(rows);
expect(s.metrics.false_fire_rate).toBeCloseTo(0.5);
expect(s.metrics.know_to_ask_failure_rate).toBe(0); // zero should_retrieve turns → 0, not NaN
expect(s.gold_total).toBe(2);
});
test('rows without gold are ignored; empty input is all-zeros', () => {
expect(scoreKnowToAsk([row({ gold: null })]).gold_total).toBe(0);
const s = scoreKnowToAsk([]);
expect(s.metrics.know_to_ask_failure_rate).toBe(0);
expect(s.metrics.false_fire_rate).toBe(0);
});
test('"always inject" games the failure rate but bleeds false fires (anti-gaming pair)', () => {
const rows = [
row({ turn_id: 1, gold: { should_retrieve: true, gold_slugs: ['a'] }, injected_slugs: ['a', 'junk'] }),
row({ turn_id: 2, gold: { should_retrieve: false }, injected_slugs: ['junk'] }),
];
const s = scoreKnowToAsk(rows);
expect(s.metrics.know_to_ask_failure_rate).toBe(0);
expect(s.metrics.false_fire_rate).toBe(1);
expect(s.gold_failed).toBe(1);
});
});
describe('scorePush', () => {
test('micro-averaged: a 3-slug turn weighs 3x a 1-slug turn', () => {
const rows = [
row({ suite: 'push', turn_id: 1, gold: { should_retrieve: true, gold_slugs: ['a', 'b', 'c'] }, injected_slugs: ['a', 'b', 'junk'] }),
row({ suite: 'push', turn_id: 2, gold: { should_retrieve: true, gold_slugs: ['d'] }, injected_slugs: ['d'] }),
];
const s = scorePush(rows);
// precision: relevant 3 (a,b,d) of 4 injected; recall: hit 3 (a,b,d) of 4 gold
expect(s.metrics.push_precision).toBeCloseTo(3 / 4);
expect(s.metrics.push_recall).toBeCloseTo(3 / 4);
expect(s.gold_total).toBe(4);
expect(s.gold_failed).toBe(1);
expect(s.failed_items[0]).toContain('c');
});
test('acceptable counts for precision, NOT required for recall', () => {
const rows = [
row({
suite: 'push',
gold: { should_retrieve: true, gold_slugs: ['a'], acceptable_slugs: ['extra'] },
injected_slugs: ['a', 'extra'],
}),
];
const s = scorePush(rows);
expect(s.metrics.push_precision).toBe(1); // 'extra' is not junk
expect(s.metrics.push_recall).toBe(1); // 'extra' was never owed
expect(s.gold_total).toBe(1);
});
test('no injections anywhere → precision 1 (vacuously clean), recall 0 with misses counted', () => {
const rows = [
row({ suite: 'push', gold: { should_retrieve: true, gold_slugs: ['a', 'b'] }, injected_slugs: [] }),
];
const s = scorePush(rows);
expect(s.metrics.push_precision).toBe(1);
expect(s.metrics.push_recall).toBe(0);
expect(s.gold_failed).toBe(2);
});
test('empty input → both 1 (nothing owed, nothing junk), zero counts', () => {
const s = scorePush([]);
expect(s.metrics.push_precision).toBe(1);
expect(s.metrics.push_recall).toBe(1);
expect(s.gold_total).toBe(0);
});
});