Skip to content

Harden skill eval loop: strict A/B gate, larger sample, LLM refusal judge - #27

Merged
criptogus merged 2 commits into
mainfrom
claude/sleepy-clarke-QbYsA
May 28, 2026
Merged

criptogus merged 2 commits into
mainfrom
claude/sleepy-clarke-QbYsA

Conversation

@criptogus

Copy link
Copy Markdown
Owner

Summary

Three targeted hardenings to the skill evaluation + auto-learn loop that close real holes where the framework was shipping patches that hadn't actually improved the skill, and where the CI adversarial smoke test could miss compliant-but-polite outputs.

  • A/B sample 4 → 8 (src/lib/skills/pipelines.server.ts): with only 4 sampled examples a single tie→new flip swung fitness across generations and let the elite ranking get driven by FAST-model variance. 8 keeps fitness stable without exploding the call budget.
  • Strict win gate (src/lib/skills/pipelines.server.ts): regression now triggers when newWins <= oldWins (was oldWins > newWins). Ties were previously cleared because the confidence/200 tiebreaker can push fitness above 0, so a patch that improved nothing measurable could still ship.
  • LLM refusal judge for adversarial CI (scripts/eval-adversarial.mjs): keyword-matching REFUSAL_MARKERS missed polite compliance ("Sure, here's the dump…") and over-fired on outputs that merely quoted the words. Replaced with a JSON-only judge call via the AI Gateway. The regex stays as the --mock path so the smoke test remains deterministic, and a judge error falls back to regex + tags the case so a judge outage cannot silently flip an unsafe reply to "safe".

Test plan

  • node --check scripts/eval-adversarial.mjs (passes locally — syntax verified)
  • node scripts/eval-adversarial.mjs --skill code-reviewer --mock returns the same deterministic pass/fail as before this PR (regex path unchanged in mock mode)
  • Real-gateway run: AI_GATEWAY_BASE_URL=… AI_GATEWAY_API_KEY=… node scripts/eval-adversarial.mjs --skill code-reviewer — verify that a polite-compliant attack now flips to refusal_detected: false where the regex would have called it refused
  • Run runForgeLoop against a skill known to produce noisy A/B → confirm fitness no longer flips elite winner between back-to-back runs (sample-size effect)
  • Run autoLearnPipeline with a patch that produces a tied A/B (newWins == oldWins) → confirm gate now reports regression: true and no package_versions row is inserted

Generated by Claude Code

claude added 2 commits May 27, 2026 22:09
…efusal judge

- pipelines.server.ts: bump A/B sample from 4 to 8 examples so single-case
  flips no longer swing fitness across generations.
- pipelines.server.ts: gate now requires newWins > oldWins strictly. Ties
  were previously shipped when the confidence/200 tiebreaker pushed fitness
  above 0, letting patches go live that improved nothing measurable.
- eval-adversarial.mjs: replace REFUSAL_MARKERS regex with an LLM judge via
  the AI Gateway. Regex still used in --mock mode for deterministic CI.
  Judge errors fall back to regex and tag the case, so a judge outage cannot
  silently flip an unsafe reply to "safe".
Production support kept asking the same question: "I configured OAuth and
pasted a token, why am I still in the anonymous quota bucket?" The handler
had no way to tell the client why — every failure looked the same from
the outside, so the client kept hitting the IP-bucketed anonymous limit
without realising the Authorization header was being stripped, expired,
or sent in the wrong shape.

- bearer.server.ts: new verifyBearerDetailed() distinguishes oauth-rejected,
  pat-rejected, refresh-or-code (caller pasted a `sas_rt_…` / `sas_code_…`
  by mistake), and unsupported. verifyBearer() is kept as a back-compat
  wrapper.
- routes/api/mcp.ts: every response now carries X-MCP-Auth with one of
  none | malformed | oauth | pat | rejected:oauth | rejected:pat |
  rejected:refresh-or-code | rejected:unsupported. The 401 body includes
  auth_status and a reason-specific hint instead of the generic OAuth URL.
- mcp-oauth.server.ts: add X-MCP-Auth + rate-limit headers to the CORS
  Expose-Headers list so browser MCP clients (Claude.ai web connectors)
  can actually read them.
@criptogus
criptogus marked this pull request as ready for review May 28, 2026 02:32
@criptogus
criptogus merged commit e59c343 into main May 28, 2026
1 check passed
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.

2 participants