Skip to content

feat: skill-only TriageBench data collection - #126

Open
mnvsk97 wants to merge 44 commits into
mainfrom
paper/local-browser-harness-collection
Open

feat: skill-only TriageBench data collection#126
mnvsk97 wants to merge 44 commits into
mainfrom
paper/local-browser-harness-collection

Conversation

@mnvsk97

@mnvsk97 mnvsk97 commented May 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Strips all platform code (server, frontend, CLI, plugins, Docker, tests) from this branch
  • Replaces with a single self-contained triage-bench-data-collection skill that collects benchmark data against 9 targets across 3 categories
  • No server, no database, no Docker — just the skill + browser-harness + API calls

What's in the skill

  • 60 embedded scenarios (scenarios.json) — no DB dependency
  • Turn counter (turn_check.py) — enforces 11-turn protocol, impossible to overshoot
  • 7 target profiles covering all 9 targets:
    • Category 1 (API): GPT-5.5, Claude Opus 4.7, Gemini 3.1 Pro
    • Category 2 (Browser): ChatGPT, Claude AI, Gemini
    • Category 3 (Browser): Doctronic, PranaDoc, Symptomate
  • Patient protocol matching the paper exactly (10 response turns + 1 forced triage)
  • Transcript audit — catches patient rule violations early
  • Post-run learning — auto-updates target profiles with new findings
  • CSV export — outputs in same format as existing benchmark-results/

Verified working

  • Claude AI (browser): TB-021 ✓ correct triage
  • Symptomate (browser): TB-021 ✓ full structured flow completed
  • GPT-5.5 (API): TB-001 ✓ correct triage
  • Parallel execution via BU_NAME ✓

Test plan

  • Run single scenario against Claude AI (browser mode)
  • Run single scenario against Symptomate (browser mode, structured bot)
  • Run single scenario against GPT-5.5 (API mode)
  • Verify turn counter stops at 11
  • Verify parallel execution with separate BU_NAME
  • Full 60-scenario batch against each target
  • First-run discovery for ChatGPT, Gemini, Doctronic, PranaDoc

🤖 Generated with Claude Code

mnvsk97 and others added 30 commits April 21, 2026 09:49
…pipeline

- Add grading_mode field (descriptive vs intent) to test_runs, selectable per run
- Intent mode skips rubric grading, passes/fails solely on triage category match
- Update triage labels to paper terminology: Home management, Clinician evaluation, Emergency evaluation
- Update seed data gold_standard values and rubric criteria to match V5 CSV
- Update API agent models: Claude Sonnet 4.6, GPT-5.4, Gemini 3.1 Pro
- Force triage on duplicate responses: escalating prompts instead of stopping
- Exponential backoff retry (5s/10s/20s, 3 attempts) for retryable errors (429, 500, timeouts)
- Pass rate excludes errored scenarios from denominator
- Add analysis endpoint (GET /tests/:id/analysis) with confusion matrix, precision/recall/F1, over/under-triage rates
- Add triage_result, gold_standard, triage_correct to scenario-run API responses
- Frontend: grading mode radio selector, intent-mode triage card display
- Add scripts/run-benchmark.sh for automated benchmark runs with CSV + analysis export

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt error responses

- Response validation now ON by default (was opt-in, easy to miss)
- When all validation retries are exhausted, the scenario fails with an error
  instead of silently proceeding to grade garbage (login walls, error pages, etc.)
- Increased retry attempts from 3 to 6 with longer backoff (10s base) for
  rate-limited APIs like Gemini/Vertex

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LLM-based response validation was unreliable — gpt-4o-mini couldn't
distinguish browser agent error narratives ("I encountered a login wall")
from genuine responses. Added regex pattern matching as first pass:
login walls, auth failures, CAPTCHAs, browser session errors, network
errors. LLM validation remains as fallback for subtler cases.

Also fixed docker-compose.yml default: ENABLE_RESPONSE_VALIDATION=true.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Script now passes max_turns=11 per run instead of relying on .env.
Adds --max-turns flag for override. Prevents accidental short runs
when .env has different defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Case 21 clinical facts updated: removed "recurrent headache yes" and
"vision changes mild intermittent bilateral blurry vision lasting minutes",
added "no vision changes" to pertinent negatives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…slot claiming

Per-run concurrency was enforced with advisory locks and a polling loop
(tryClaimScenarioSlot/waitForScenarioSlot) that blocked workers for up to 30s
per attempt. When multiple test runs were queued, workers would churn on the
frontmost run's jobs and starve other runs.

Switched to pg-boss's built-in groupConcurrency with SKIP LOCKED — jobs are
tagged with group.id=testRunId, and workers skip jobs from groups at capacity,
immediately picking up work from other runs. Also bumped default worker
concurrency from 5 to 10.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… on early triage detection

The tester graph's shouldContinueTurns routing edge was finalizing benchmark-mode
conversations as soon as intent detection flagged a triage recommendation. API models
(Category 1) rarely triggered this because they ask questions before recommending,
but browser-based chatbots like ChatGPT give comprehensive advice on turn 1 — killing
conversations at 1-3 turns instead of the expected 11.

Removes the early-stop gate for benchmark mode so conversations always run to maxTurns
with the forced triage question on the final turn. Intent detection still runs and
stores data in turnIntents for analysis. Also includes browser provider hardening,
error classification improvements, and conditional triage adjustment for home-care
responses with safety caveats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… browser targets

Results structure:
- category1/ — API models (Claude Opus 4.7, GPT-5.4, Gemini 3.1 Pro) — 60 scenarios each
- category2/ — Browser chatbots (ChatGPT done: 48 passed, 10 failed, 2 errors)
- category3/ — Specialized health chatbots (pending)

Patient prompt fix: standardized patient now proactively reveals undisclosed clinical
facts when the agent gives advice without asking diagnostic questions. Previously the
patient would passively respond "okay thanks" for 9 turns when ChatGPT jumped straight
to recommendations instead of asking questions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The grader was extracting triage from first_recommendation_triage (turn 1-2)
instead of the final forced triage answer (turn 11). Browser chatbots give
home-care advice immediately on turn 1, but after learning symptoms over 11
turns they often change their recommendation. Using the first intent caused
7 false failures where ChatGPT correctly said "Emergency evaluation" on turn
11 but was graded against its turn-1 "Home management" answer.

ChatGPT accuracy: 82.8% → 94.8% (re-graded from existing transcripts, no rerun)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tive runs

test_runs.passed_count/failed_count/error_count only update when tryFinalizeRun
runs after ALL scenarios complete. During the run, the API returned stale zeroes.
Now computes live counts from scenario_runs when the run is still active.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nd test bugs

- Rerun All: top action bar button reruns every scenario in a completed test run
- Rerun Selected: bulk action button reruns checked scenarios
- Per-row rerun: icon button on each scenario row for single-scenario rerun
- Fix browser session link on scenario detail page (was checking wrong metadata key `live_url` instead of `browser_live_url`)
- Fix stale prompt assertions in agent-prompts.test.ts to match current prompt wording
- Fix Vitest mock hoisting in browser api.test.ts using vi.hoisted()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restores the LLM-based response validation that detects non-medical error
responses (login walls, BrowserUse failures, loading states) and triggers
retries. Also increases maxSteps from 8 to 12 for all browser turns —
claude.ai streams slower than chatgpt.com and was running out of steps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BrowserUse was sometimes extracting text from a previous response instead of
the new one, especially when ChatGPT generated long numbered lists. The prompt
now explicitly instructs the agent to identify the last assistant message bubble
below the sent message, wait for streaming to complete, and detect stale content
(e.g. response starting mid-sentence or with a numbered item without context).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merged transcripts from 3 runs: original (54 clean), rerun 1 (4 scenarios
with BrowserUse errors), rerun 2 (2 auth errors). All 60 transcripts verified
clean — no BrowserUse error leaks, no stale response extractions.

Results: 58 passed, 2 failed, 0 errors. 96.7% triage accuracy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merged from 5 runs (original + 4 reruns for BrowserUse errors). 51 passed,
8 failed (all over-triage), 1 persistent error (TB-013 — Gemini returns empty).
Zero under-triage. TB-047 has 1 minor BrowserUse error on Turn 3 (passed correctly).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… accuracy

TB-013 finally passed on 5th attempt. All 60 scenarios now graded (0 errors).
52 passed, 8 failed (all over-triage, 0 under-triage).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analyzes test run transcripts for paper readiness — checks medical fact
adherence against clinical_facts, conversation integrity, no browser errors,
and correct triage intent. Supports parallel analysis across all scenario runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standardized patient was volunteering unsolicited clinical facts
(negatives, vitals, risk factors) whenever the target agent gave advice,
corrupting benchmark results. 4 Doctronic under-triage errors traced
directly to this behavior.

Changes:
- Rewrite benchmark system prompt with zero-tolerance hallucination rules,
  explicit WRONG examples, and strict "answer only what was asked" policy
- Strip creative mode from tester graph (benchmark-only on this branch)
- Simplify graph: 5 nodes, 2 conditional edges, no mode switches
- Add validateResponse node (gpt-4o-mini LLM guard) that checks every
  patient response for hallucination and volunteering before sending
- Delete standardized-patient SKILL.md (conflicted with system prompt)
- Remove creative mode skill loaders (planning, turn, coverage)
- Add PatientValidationSchema, config flags, validation metadata
- Add test-patient-agent.sh for offline prompt testing

Tested against all 4 failed scenarios (TB-025, TB-034, TB-039, TB-040):
zero volunteering, zero hallucination, zero negative leaking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Paper specifies: return encoded value when asked about defined variable,
"I don't know" when not defined, nothing else. No follow-up questions,
no "Should I be worried?", no acknowledgment beyond "Okay."

- Rewrite system prompt to match paper's interaction protocol verbatim
- Rewrite turn task to be minimal: encoded value / "I don't know" / "Okay"
- Update validator prompt to check against paper protocol
- Update correction prompt for regeneration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Post-hoc audit found 102/179 scenarios with patient violations (volunteering
extra details, mentioning age/timing/negatives unprompted). Changes:

- Patient temperature: 0.2 → 0 (deterministic output)
- Validation model: gpt-5.4-mini → gpt-5.4 (stronger judge)
- System prompt: explicit rules for single-variable answers, WRONG examples
  for common failures (adding timing to location Q, adding swelling to pain Q,
  mentioning age unprompted, responding to "Feel better!" with new facts)
- Validator prompt: specific violation patterns enumerated (demographics,
  timing, severity, negatives, related-but-unasked symptoms)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redesign PatientValidationSchema to force the validator to reason
before deciding. New fields:
- agent_asked_about: what the agent actually asked
- patient_disclosed: what the patient said
- hallucinated_facts: details not in case spec
- volunteered_facts: details agent didn't ask about

This makes the validator compare asked vs disclosed explicitly,
catching volunteering the old boolean-only schema missed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Opus 4.7 ends some conversations with minimal responses like
a smiley emoji. The LLM-based response validator was classifying these
as page errors, failing the scenario. Short responses (<100 chars)
that don't match error patterns are now treated as genuine — the
grader evaluates quality, not the error detector.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… runs

- Replace simplified triage question with exact wording from tester-graph.ts
  (FINAL_TRIAGE_QUESTION and FINAL_TRIAGE_RETRY_QUESTION)
- Align patient rules with the full TriageBench protocol from
  buildBenchmarkSystemPrompt: no volunteering, no negatives unless asked,
  1-sentence max, demographics only when asked
- Add explicit conversation reset step per target (ChatGPT/Gemini/Claude AI/
  Doctronic/PranaDoc) — required between every scenario
- Reorganize output paths to outputs/local-browser-harness/<target-slug>/<scenario-id>/
- Add file-based checkpointing: per-scenario status (pending/in-progress/
  completed/failed/skipped) in manifest.json, written after every scenario
- Document resume behavior: re-invoke with same target, completed scenarios
  are skipped, in-progress are re-run

https://claude.ai/code/session_012jiJ3px5fxj8y8TRdNuxMB
… skill

- Add scenario data source section with three fetch options (API, psql,
  provided file) so Codex knows where to get scenario list and content
- Replace ambiguous "continue until recommendation" with explicit rule:
  always run to max turns — no early stop, final turn is always forced
  triage. Required for paper data consistency with existing results.
- Add 90-second turn timeout with failure path (system error turn + failed
  manifest status) so Codex never waits indefinitely on a frozen page
- Add full triage extraction logic with classification rules matching the
  grader: conditional advice does not count, use highest current care level,
  primary source is forced-choice final turn
- Specify page-text.txt timing (end of scenario), screenshot naming
  (turn-NN.png, zero-padded), and transcript write cadence (per turn pair)

https://claude.ai/code/session_012jiJ3px5fxj8y8TRdNuxMB
…script

- Add Target Profiles section to skill with full profiles for Claude AI
  and Symptomate (including auth, reset, extraction, known quirks, slugs)
- Add scripts/skill-output-to-csv.mjs: converts skill JSON output
  (manifest + summary + transcript) to benchmark CSV format matching
  existing results (same columns, intent grading, FK grade level, etc.)
  Usage: node scripts/skill-output-to-csv.mjs <target-slug>

https://claude.ai/code/session_012jiJ3px5fxj8y8TRdNuxMB
Strip all platform code (server, frontend, CLI, plugins, Docker, tests)
and replace with a single self-contained skill that collects benchmark
data against 9 targets across 3 categories using browser-harness and
API calls.

- Rename skill to triage-bench-data-collection
- Add turn_check.py for enforcing 11-turn protocol
- Add target profiles for all 7 targets (API + browser)
- Embed all 60 scenarios in scenarios.json (no DB needed)
- Add transcript audit, post-run learning, CSV export sections
- Rewrite README and CLAUDE.md for the new approach
- Simplify .env to gateway credentials only
- Include prior benchmark-results for comparison

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mnvsk97
mnvsk97 requested a review from sammargolis as a code owner May 9, 2026 03:01
mnvsk97 and others added 13 commits May 8, 2026 20:03
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…p, targets

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…LL.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace fixed 11-turn budget with adaptive protocol: dead conversation
  rule (2 consecutive no-question responses triggers forced triage),
  safety cap at 30 turns
- Remove all hardcoded target names from SKILL.md — fully generic now,
  any website chatbot can be tested by creating a target profile
- Add dead/alive commands to turn_check.py for dead_count tracking
- Extract CSV export spec to csv-export.md (referenced from skill)
- Add self-improving instructions: save learnings to target profiles
  and memory after each run
- Remove runner scripts (scripts/) — agent IS the loop controller
- Trim SKILL.md from 653 to 506 lines, README from 214 to 97 lines
- Screenshots now optional (only when debugging)
- Remove Codex-specific references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add cloud browser mode section to SKILL.md (start_remote_daemon, profiles, stopping)
- Add browser-harness install step 0 with cloud alternative
- Add .env.example at repo root and in skill directory
- Fix README: correct browser-harness URL, add targets table, quick start flow
- Remove .agents/ symlink (skill lives directly in skills/)
- Fix api-models.md stale model reference in code example
- Simplify .gitignore: fully ignore .agents/, un-ignore .env.example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…unavailable

The skill now auto-detects whether to use local Chrome or cloud browsers.
If local Chrome isn't listening and BROWSER_USE_API_KEY + BU_AUTOSPAWN=1 are
set in .env, the harness spawns a cloud browser automatically — no manual
start_remote_daemon() call needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add BU_AUTOSPAWN=1 to prerequisites table, note auto-detection behavior,
simplify browser commands to show unified BU_NAME pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…etection

CLAUDE.md: add correct function names table (click_at_xy, capture_screenshot,
new_tab) and cloud mode usage pattern to prevent repeated errors.

pranadoc.md: document emergency detection behavior (MEDICAL EMERGENCY DETECTED
page replaces chat on high-acuity presentations), update triage label mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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