Skip to content

Upgrade: career-aligned rigour + BYOK AI (Cachex-AI) - #12

Merged
rNLKJA merged 18 commits into
mainfrom
upgrade/career-aligned
Oct 5, 2026
Merged

rNLKJA merged 18 commits into
mainfrom
upgrade/career-aligned

Conversation

@rNLKJA

@rNLKJA rNLKJA commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Career-aligned upgrade of the Cachex revival: statistical rigour around the original 2022 results, and optional bring-your-own-key AI features with transparency and an audit trail. The original coursework and its results are unchanged; the upgrade adds analysis around them.

Live: https://cachex-ai.vercel.app

What's new

Statistics and evaluation

  • web/src/lib/stats/: Wilson and Newcombe intervals, seeded percentile and paired bootstraps, the Wilcoxon signed-rank test, Bradley-Terry fits on the Elo scale, and an exact McNemar test. Unit tests check them against scipy, statsmodels and R.
  • /tournament: a seeded, colour-swapped round robin. You can rerun it in the browser in Web Workers, with CSV export. It also has the 1,200-game reference run, a cross-check against the original Python, the original 144/160 benchmark (90.0%, 95% CI 84.4% to 93.8%) and an alpha-beta efficiency study.
  • /astar: a paired study of Manhattan versus Euclidean (980 boards). It reports a bootstrap CI and Wilcoxon test for node counts, and a BFS optimality check that adds a paired difference and McNemar test.
  • /methods: data provenance, methods, evaluation design, assumptions, limitations and "what I'd change". It renders the agent card and decision records DR-001 to DR-006.

Bring-your-own-key AI (optional; the site works fully without a key)

  • AI settings: Anthropic is the default (Claude Haiku 4.5, or Sonnet 5.5) and OpenAI is optional. The key is kept in sessionStorage unless you choose to remember it, and "Forget key" removes every saved key. Calls go directly from the browser to the provider.
  • The move commentator on /play and /spectate is grounded in the agent's own numbers, including what the score means at the depth searched. An automatic grounding check runs on every reply and is stored with the call; you can accept, edit or reject it.
  • /llm-arena: an evaluation harness in which a language model plays the minimax agent. It is compared with random, greedy and a scripted first-legal-cell baseline on the same seeds.
  • /ai-log: every call is recorded in IndexedDB with its input, output (including the raw reply when validation failed), latency, tokens, grounding check and human decision, with JSON and CSV export. The key is never recorded.

Second review round

Governance and grounding

  • Key storage: the AI settings dialog keeps every change as a draft until Save. A key saved for this tab only was being moved into local storage after a provider switch; the "remember" box now follows the selected provider's key and storage only changes when you change the box. Closing the dialog changes nothing.
  • Commentary at depth 2+: the facts now say what the score means (score_scope, forced_win). The feature breakdown describes the position one ply after the move, so it no longer gets presented as the reason for a deeper search's score or a forced win. A fifth check fails a reply that misses, misattributes or invents a forced win. Regression test: 4×4, seed 76, a depth-3 forced win whose features sum to −12. The shared empty-hex feature may be called neutral.
  • Audit trail: the grounding result is written into the same audit entry as the output and exported (grounding_passed, grounding_failures). /ai-log shows it above the review buttons, flags and counts failures, and re-checks older entries from the logged prompt.

Statistics

  • Tournament reproducibility: records are analysed and exported in schedule order, so the same seed now gives the same Bradley-Terry intervals and CSV however the Web Workers finish. The reference run is unchanged.
  • Alpha-beta: "92% to 100%" was a range of averages. 444 of 480 searches visit the whole tree, and the 36 that prune visit 0.6% to 92% of it, because the beta bug only allows a cut-off after a forced win for Red. DR-005 supersedes DR-002's sentence; DR-002 itself is unedited.
  • LLM arena: the rejected-answer interval accounts for turns being clustered in games (Wilson on the effective sample size after a design effect, floored at 1, checked against statsmodels). Each baseline is compared with the model game by game, with an exact McNemar test.
  • Display: p-values read "p = 0.012" or "p < 0.001".

Site

  • The AI use statement's subsections are h3 under their h2; header nav focus rings are no longer clipped; wide tables fade at the right edge while columns are hidden.
  • "Source" links point at the deployed commit, so files that exist only on this branch no longer 404 from production. A decision-record page notes when a later record supersedes part of it.
  • DR-005 (report uncertainty at the unit that was sampled) and DR-006 (ground commentary in what the search found, and keep the check with the decision).

First review round

  • Anthropic adapter: checks stop_reason before parsing, so truncated replies and refusals are no longer reported as format errors. It sends a schema that keeps enum (the SDK helper had been turning enums into descriptions).
  • OpenAI adapter: now uses the same token budget.
  • Audit log: failed calls keep their raw reply and the tokens they were billed for.
  • LLM arena scoring: both providers are scored by the same rule. Illegal moves and replies with no usable move are counted separately, and the headline is a per-turn rate with forfeits reported over games. Baselines use only the games the model finished, and the arena warns when colours are unbalanced.
  • Grounding check: now catches invented numbers at the end of a sentence.
  • Spectate: autoplay pauses while commentary loads, and late commentary is kept. Entries left pending can be reviewed on /ai-log.
  • Paired comparisons: Elo differences now come from the same bootstrap refits (original minus greedy: +15, 95% CI −24 to +56; depth 3 minus original: +151, 95% CI 106 to 195). For the A*, the paired optimality difference is −11.3 pp (95% CI −13.3 to −9.1), with an exact McNemar test (1 vs 106 discordant boards). DR-004 records this.
  • New agent-card failure mode: the agent does not block one-move wins. A scripted Blue that fills row 0 wins 50/50 games on 4×4, 43/50 on 5×5 and 34/50 on 6×6, and tests pin these counts.
  • Accessibility:
    • Checked checkboxes are visible in dark mode.
    • The AI settings dialog returns focus to the button that opened it.
    • Scrollable tables and code blocks can be focused and have names.
    • Two-column markdown tables now wrap.
    • The home-page benchmark table shows a Wilson interval on every row.
  • Wording: claims now match what the evidence shows ("no detectable difference" plus the size of difference each check can detect).

Checks

  • pnpm lint && pnpm typecheck && pnpm test && pnpm build all pass on the final commit, with 979 tests (up from 957). pnpm format:check is clean.
  • Playwright on system Chrome against a local production build covered 15 routes in light at 1440 px and dark at 375 px: all 200 (the 404 page 404), no horizontal overflow and no console errors.
  • In the browser:
    • Two in-browser tournaments at seed 4399 gave identical Elo intervals and the same games-CSV order.
    • The review's key-storage reproduction no longer moves the tab-only key, and Escape leaves preferences unchanged.
    • With a mocked Anthropic endpoint, commentary that names the wrong mover is stored with its failed check, shown on /ai-log, and exported in JSON and CSV. An entry logged before this change is re-checked and the check is stored with the decision.
    • A 4-game mocked arena run shows the paired column and the clustered interval.
    • The test key never reaches IndexedDB or the exports, and no request goes anywhere except the intercepted provider endpoint.
  • The AI features have still not been run against a real provider. A manual check with your own Anthropic key before relying on them would confirm that browser CORS and structured outputs work on Haiku 4.5.

The AI use statement is informed by the Australian DTA responsible-AI policy, EU AI Act transparency principles and the NIST AI RMF. It is not a claim of compliance.

🤖 Generated with Claude Code

rNLKJA and others added 18 commits October 6, 2026 00:58
Wilson and Newcombe intervals, seeded percentile bootstrap (plain,
stratified, paired and vector-valued), Wilcoxon signed-rank (exact and
tie-corrected normal approximation) with rank-biserial effect size,
Bradley-Terry by MM with an Elo conversion, type-7 quantiles and
high-precision normal functions. Unit tests pin reference values from
scipy 1.17, statsmodels and R.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- /tournament: seeded round robin of the original agent against
  variants that change one knob (fixed depth 2/3, greedy one-ply) and
  the random baseline, colour-swapped pairs, run in a pool of Web
  Workers. Wilson CIs, Bradley-Terry Elo with stratified bootstrap CIs,
  first-move effect, move times, illegal moves, CSV export.
- Precomputed 1,200-game reference tournament (TypeScript port) and a
  cross-check that replays the feasible pairings from the unchanged
  Python (scripts/crosscheck_tournament.py); the 144/160 benchmark is
  restated with its Wilson interval.
- Alpha-beta efficiency study: nodes with vs without pruning on 120
  positions, plus a clearly labelled textbook variant. Finding: the
  original's inverted beta update leaves 92-100% of the tree searched.
  The agent itself is unchanged.
- /astar: paired Manhattan vs Euclidean study with paired bootstrap CI,
  Wilcoxon signed-rank test, effect sizes and a BFS optimality check,
  live in a worker and as a 980-board reference.
- Agent port gains opt-in fixedDepth and prune options; defaults keep
  parity with the original (all parity tests unchanged and passing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- AI settings dialog: Anthropic (default, Claude Haiku 4.5 or Sonnet
  5.5) or OpenAI (editable model id). The visitor's key is kept in
  sessionStorage by default, localStorage only on opt-in, with a forget
  button. Calls go straight from the browser to the provider; the key
  never reaches this site, the audit log or the repository.
- Provider adapters in lib/ai (official Anthropic SDK with browser
  access, OpenAI Chat Completions with strict JSON schema), zod
  validation and friendly errors (invalid key, rate limit, overload,
  network/CORS, refusal, truncation).
- Move commentator grounded in the agent's own evaluation breakdown,
  with an automatic grounding check and accept / edit / reject review.
- LLM Arena: the model plays the minimax agent with JSON moves checked
  against the legal moves (retry, then forfeit), compared on identical
  seeds with random and greedy baselines; Wilson CIs, illegal-answer
  rate, latency and tokens, CSV export.
- Every call is audit-logged in IndexedDB and viewable at /ai-log with
  JSON and CSV export. All output is labelled AI-generated.
- Tests mock fetch for both providers and assert the key never appears
  in log entries or exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment

- /methods: data provenance, method, evaluation design, assumptions,
  limitations and what I'd change, plus the AI use statement.
- DR-001 (evaluation features and weights), DR-002 (dynamic depth
  allocation) and DR-003 (TypeScript port, Web Workers, parity tests),
  each stating the decision first and reporting weak numbers honestly;
  rendered under /methods/decisions.
- Agent card (docs/model-card.md) with intended use, provenance,
  evaluation with intervals and known weaknesses, at /methods/agent-card.
- docs/ is synced into web/content/docs for the Vercel build; a test
  fails if the copies drift and checks the decision-record format.
- Navigation gains Tournament, LLM Arena and Methods; the landing page
  shows the benchmark's Wilson interval and links the evaluation pages.
- README covers the new features, bring-your-own-key, the audit log,
  methods and decision records, and how every artefact is regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address review findings on the bring-your-own-key layer:

- Anthropic adapter calls messages.create with a JSON schema built from
  the zod schema (enums kept, unsupported numeric bounds dropped) and
  checks stop_reason before parsing, so a reply cut off at max_tokens is
  "truncated" and a refusal is "refusal", not a format error.
- OpenAI adapter sends max_completion_tokens, so both providers answer
  under the same output budget.
- AiError carries the raw reply and token usage; failed calls now log
  both in the audit entry, and the CSV export gains an output_text column.
- LLM arena: truncated, refused and malformed replies are counted as
  "no usable move", separately from illegal moves, and no longer abort
  the run. The headline rate is per turn (first answer only), with the
  forfeit rate over games. Baselines are replayed on exactly the games
  the model finished, the caption says when colours are unbalanced, and
  a scripted first-legal-cell baseline is added.
- Board prompt draws two-character cells shifted half a cell per row.
- Grounding check now catches numbers that end a sentence.
- Spectate pauses autoplay and pins the move when commentary is
  requested; commentary state survives remounts, and /ai-log can review
  entries left pending.
- "Forget key" clears every provider's key; the dialog returns focus to
  whatever opened it; checked checkboxes are visible in dark mode;
  scrollable tables are focusable, named regions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Exact McNemar test (two-sided binomial on discordant units), checked
  against scipy binomtest, statsmodels mcnemar(exact=True) and R
  binom.test.
- A* study: shortest-path rates on the same boards are now compared by a
  paired bootstrap difference (-11.3 pp, 95% CI -13.3 to -9.1) and exact
  McNemar (1 vs 106 discordant boards); copy says "fewer nodes" instead
  of a negative count.
- Tournament: Elo differences between every pair of agents, with
  intervals from the same bootstrap refits as the leaderboard (original
  minus greedy +15 [-24, +56]; depth 3 minus original +151 [106, 195]).
  Shown in a new table, the summary CSV and the findings, which no
  longer argue from overlapping intervals. Per-agent intervals are
  unchanged.
- Leaderboard header reflects the anchor ("vs field mean" when random is
  not in the run); the head-to-head caption says when a stopped run left
  colours unbalanced or pairings without games.
- DR-004 records the decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Agent card: real table headers; "no first-move advantage" no longer
  implies a STEAL effect that was never tested; Elo comparisons and the
  Python cross-check's resolution are stated; new failure mode "does not
  block one-move wins" (a scripted Blue filling row 0 wins 50/50 on 4x4,
  43/50 on 5x5, 34/50 on 6x6; pinned by tests).
- README: "no detectable difference" wording with the resolution of each
  check, paired A* results, DR-004, and the new arena metrics.
- AI use statement: same scoring rule for both providers, raw replies of
  failed calls are logged, pending commentary can be reviewed on /ai-log.
- Home page benchmark table shows Wilson intervals per row.
- Markdown tables and code blocks are focusable, named scroll regions;
  two-column tables wrap instead of scrolling; long inline code wraps.
- Methods page describes the paired analyses and per-turn LLM scoring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The "run your own" leaderboard needs about 820px, but beside the 340px
setup panel it got 802px at 1280-1440px, so the ms/move interval column
was cut off. The setup panel now sits beside the results only from xl
(300px wide), and spans the width in two columns below that. Also say
"boards 4 x 4 to 7 x 7" on the home page instead of "n = 4-7", which
read as a sample size next to "144 of 160 games".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commentator capped replies at 1,500 output tokens. On reasoning models
that cap also has to cover thinking: Claude Sonnet 5.5 runs adaptive thinking
by default, and OpenAI counts reasoning tokens in max_completion_tokens (the
default gpt-5-mini is a reasoning model), so a commentary could be cut off
before its JSON finished. The LLM player already uses 4,096 for this reason.

- Add COMMENTATOR_MAX_TOKENS (4,096, the same as LLM_PLAYER_MAX_TOKENS) and
  commentaryRequest(facts), so the panel and the tests share one request.
- Test what each provider receives: the budget, effort "low" on Sonnet, and
  no thinking or sampling parameters (both are 400s on Sonnet 5.5).

The cap is a ceiling, not a charge; providers bill the tokens generated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grounding check verified feature directions, numbers and moves, but not
who moved. A smoke test with a mocked provider returned "Red played (4, 0)"
for a move Blue made, and the check still passed.

Add a fourth check: when the text says a colour made a move ("Red played",
"Blue has taken"), that colour must be the mover in the facts. The pattern
is narrow on purpose, so naming the other colour ("blocks Blue", "Blue's
tiles") is not treated as a claim about who moved. Tests cover the wrong
mover, the right mover, plain mentions and no mention. The AI use statement
and README now list this check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check results sat in a collapsed <details>, so a reviewer saw only
"Grounding check found problems" and had to expand it to learn what was
wrong before accepting, editing or rejecting. Open it by default when a
check fails; a passing result stays collapsed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its intervals

Web Workers return games in the order they finish, and the seeded
stratified bootstrap indexes into the record arrays in that order, so the
same seed gave the same games but different Bradley-Terry intervals and a
differently ordered games CSV from run to run. summariseTournament and
gamesCsv now sort records into buildSchedule's order (size, pairing,
round, colour leg) first. The reference run was played in that order, so
its numbers do not change; tests shuffle records and check the summary
and CSV are identical, and that the reference order is left as is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k in the audit log

Commentator
- The facts now say what the score means at the depth searched
  (score_scope) and whether the search found a forced win (forced_win).
  The feature breakdown describes the position one ply after the move;
  at depth 2+ the score is a backed-up minimax value, so the prompt no
  longer asks the model to explain the score through the features.
  Regression: 4x4, seed 76, depth 3, +infinity with features summing to -12.
- New check "Forced wins reported as the search found them": the summary
  must report a forced win the search found, for the right player, and
  never claim one it did not (denials are fine).
- The shared empty-hex feature may be called neutral.

Audit trail
- callStructured takes a `check` hook; the grounding result is stored in
  the same audit entry as the output, exported as grounding_passed and
  grounding_failures (CSV) and in full (JSON).
- /ai-log shows the stored check above the review buttons (open when it
  failed), flags failed entries and counts them. Older entries are
  re-checked from the logged prompt, labelled as recomputed, and the
  recomputed check is stored with the reviewer's decision.

AI settings
- The dialog keeps provider, models, key and "remember" as a draft until
  Save; closing it changes nothing. The remember box follows the selected
  provider's saved key and a key only moves between session and local
  storage when the visitor changed the box (a tab-only key was silently
  persisted after a provider switch).

LLM arena statistics
- Rejected first answers: Wilson interval on the effective number of
  turns after a design effect from the between-game spread (floored at 1),
  instead of treating turns as independent. clusteredWilson is checked
  against statsmodels' cluster-robust variance.
- Each baseline is compared with the model game by game: discordant
  games and an exact McNemar test.

Also: p-values are shown as statements ("p = 0.012", "p < 0.001").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x headings and source links

- /tournament: the alpha-beta copy now gives the per-search picture next
  to the averages (in the agent's own order 222 of 240 searches visit
  every node; the 18 that prune visit 12.5% to 86.2% of the tree) and
  the mechanism (beta stays at +infinity, so a cut-off needs a forced win
  for Red).
- /methods: the AI use statement's subsections render as h3 under its
  h2 (Markdown `nested`); evaluation-design bullets describe the paired
  LLM comparison and the clustered rejected-answer interval.
- Scroll regions fade their right edge while more columns are hidden
  (iOS hides scrollbars), dropping the fade while keyboard-focused so
  the focus ring stays whole.
- Header nav links draw their focus ring inside, so the scrolling nav no
  longer clips it.
- "Source" links point at the deployed commit when the build knows it
  (NEXT_PUBLIC_GIT_REF or VERCEL_GIT_COMMIT_SHA), else main: files that
  exist only on an unmerged branch no longer 404 from production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tored grounding checks

- DR-005 (report uncertainty at the unit that was sampled) supersedes
  DR-002's pruning-ratio sentence and DR-004's per-turn arena interval:
  444 of 480 searches visit the whole tree and the 36 that prune visit
  0.6% to 92%; the "92% to 100%" range is of averages. It also records
  the design-effect interval for rejected answers (and why a game-level
  bootstrap was rejected), the paired McNemar arena comparison and the
  schedule-order fix for reproducible tournament intervals.
- DR-006 records grounding commentary in the search's scope (forced
  wins, depth 2+ scores) and storing the check with the human decision.
- Past records are untouched; a decision-record page now notes, from the
  newer record's "Supersedes" line, when part of it has been superseded.
- Agent card, README and AI use statement updated to match; docs/README
  lists DR-004 to DR-006. Synced into web/content/docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JSON and CSV export names used toISOString(), the UTC date, while the
log shows local timestamps. In Adelaide an export made before 10:30 am was
named for the previous day. Use the local calendar date, computed when the
button is pressed, with a unit test that pins the Adelaide case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`pnpm showcase` runs web/e2e/showcase.spec.ts on the system Chrome
(channel "chrome", no browser download; the ffmpeg on PATH records the
video). Three journeys (play the agent, the A* Lab, the tournament and LLM
evaluation) run at a human pace with an on-screen caption banner and cursor
highlight, and assert what they show: the steal, both captures, the recorded
A* output, the seeded tournament, the mocked LLM run and the audit log.
No real key is used: provider requests are intercepted and answered by a
mock that is labelled on screen. The same run captures the screenshots.

scripts/showcase-media.mjs turns the raw output into optimised PNGs, H.264
MP4s with WebVTT captions and posters, and README GIFs that cut waits and
scrolls to stay under 8 MB at 960 px.

/tour shows the walkthroughs (lazy-loaded videos, captions, step
transcripts, the mocked-AI notice) and the screenshots in a lightbox. It is
linked from the header, the footer and the landing page. The header now
matches whole path segments so /tournament does not light up /tour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Recorded with `pnpm showcase` against a production build of this commit's
parent: 13 screenshots (landing light and dark at 1440 x 900, eight key
features, three phone shots at 390 x 844), three captioned MP4s with WebVTT
tracks and posters for /tour, and three README GIFs (960 px, 10 fps, waits
and scrolls cut). The tournament walkthrough's AI steps use a placeholder
key and a mocked reply, labelled as such on screen and in the README.

The README gains a Showcase section: the hero GIF, a two-column screenshot
grid with captions, and the numbered steps of each walkthrough. A test keeps
the media, captions and README steps in sync with src/lib/showcase.ts and
checks the size limits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rNLKJA
rNLKJA merged commit 4a7b492 into main Oct 5, 2026
1 check passed
@rNLKJA
rNLKJA deleted the upgrade/career-aligned branch October 5, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant