Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions qa/run_duo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -506,14 +506,22 @@ jq -rs 'map((.role|ascii_upcase) + ": " + (.text // "")) | join("\n\n")' "$CHAT"
# a lock-only dir with no snapshot, which head -1 may grab -> false "no state" RED).
SNAP="$(find "$STATE_DIR/campaigns" -mindepth 2 -maxdepth 2 -name snapshot.json -size +1c -exec ls -S {} + 2>/dev/null | head -1)"
if [ -n "$SNAP" ]; then cp "$SNAP" "$T/$RUN.state.json"; else echo '{"warning":"no state"}' > "$T/$RUN.state.json"; fi
# Three lenses, run CONCURRENTLY (background + wait) so the third pass adds no wall-clock.
# Mechanical + Angry-DM (5e rules-fidelity) score the DM distill `$RUN.md` — the tool
# Lenses. Mechanical + Angry-DM (5e rules-fidelity) score the DM distill `$RUN.md` — the tool
# stream (→ tool / ← result) where the MECHANICS live; Tolkien scores the two-sided $PLAY
# (scene-craft must be judged on the actual back-and-forth).
#
# #1040: the two LIGHT lenses (mechanical ~4 KB rubric, tolkien ~12 KB) run CONCURRENTLY (they
# finish in ~60–150s, so the second adds no wall-clock). The Angry-DM lens (rubric ~32 KB) is the
# HEAVY one — it LEGITIMATELY takes ~400s on a combat-dense transcript (single-turn generation,
# MEASURED 402s). Running it concurrently with the others made the calls share API throughput so the
# heaviest one routinely blew past the timeout and produced NOTHING (the false "combat-scorer hang").
# So: score the two light lenses in parallel, WAIT, THEN score Angry-DM ALONE — full throughput, lands
# near its ~400s baseline, comfortably under score.sh's 600s guard. Adds ~the angrydm time in extra
# wall-clock vs the old all-parallel, but the old way silently LOST the mech lens on every combat run.
[ -f "$T/$RUN.md" ] && "$SCORE_SCRIPT" "$T/$RUN.md" "$T/$RUN.state.json" qa/rubric.md qa/score_schema.json "$T/$RUN.score.json" 1.50 &
[ -s "$PLAY" ] && "$SCORE_SCRIPT" "$PLAY" "$T/$RUN.state.json" qa/rubric_tolkien.md qa/score_schema_tolkien.json "$T/$RUN.tolkien.json" 1.50 &
[ -f "$T/$RUN.md" ] && "$SCORE_SCRIPT" "$T/$RUN.md" "$T/$RUN.state.json" qa/rubric_angry_dm.md qa/score_schema_angry_dm.json "$T/$RUN.angrydm.json" 1.50 &
wait
[ -f "$T/$RUN.md" ] && "$SCORE_SCRIPT" "$T/$RUN.md" "$T/$RUN.state.json" qa/rubric_angry_dm.md qa/score_schema_angry_dm.json "$T/$RUN.angrydm.json" 1.50
# #842 Fix F (caller half): score.sh now FAILS FAST on a 429, writing a {"quota_exhausted":true,…}
# sentinel into its OUT and exiting rc=2 — so the scorer can quota-trip even when the DM cold-open
# itself didn't (e.g. the account hits the limit AFTER the play, during scoring). Any lens carrying
Expand Down
19 changes: 12 additions & 7 deletions qa/score.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,18 @@ while [ "$attempt" -lt 3 ]; do
# so it uses the default ~/.claude (Claude OAuth) + api.anthropic.com. On a normal Claude run
# these vars are unset, so `env -u …` is a NO-OP → byte-identical to today.
# TIMEOUT GUARD: `claude -p` occasionally HANGS (a stuck stream / a slow response that never
# returns) — without a wall-clock bound that blocks the ENTIRE run forever (seen repeatedly on
# combat-sprint + north-star scoring; the run fights+gates fine, then scoring hangs). `timeout`
# kills a hung call so the retry loop below catches it (empty $RAW → the EMPTY branch → retry).
# Default 300s; override via WORLDOS_SCORE_TIMEOUT. A healthy score is ~60–150s, so this never
# fires on a good call — it only rescues a genuine hang.
# returns) — without a wall-clock bound that blocks the ENTIRE run forever. `timeout` kills a hung
# call so the retry loop below catches it (empty $RAW → the EMPTY branch → retry).
# Default 600s (#1040): the social lenses (tolkien/mechanical) finish in ~60–150s, but the
# Angry-DM 5e-fidelity lens (rubric_angry_dm.md is ~32 KB, ~3× tolkien) LEGITIMATELY takes ~400s
# to grade a COMBAT-DENSE transcript — a single-turn generation, MEASURED 402s on csmed-1 (num_turns=1,
# valid 7.7 KB scorecard). The old 300s default KILLED that mid-generation → empty stdout that LOOKED
# like a hang (the #1040 "combat-scorer hang" was a too-short timeout, not a true hang). 600s covers it
# with headroom; the fast lenses are unaffected (the bound only fires on a genuinely slow/stuck call).
# run_duo.sh ALSO isolates the angrydm lens (scores it alone, not concurrent with the 2 light lenses)
# so it gets full API throughput and lands near the ~400s baseline rather than slower under contention.
printf '%s' "$INPUT" | env -u ANTHROPIC_BASE_URL -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN \
-u API_TIMEOUT_MS -u CLAUDE_CONFIG_DIR timeout "${WORLDOS_SCORE_TIMEOUT:-300}" claude -p \
-u API_TIMEOUT_MS -u CLAUDE_CONFIG_DIR timeout "${WORLDOS_SCORE_TIMEOUT:-600}" claude -p \
--model "$SCORER_MODEL" --permission-mode bypassPermissions \
--max-budget-usd "$BUDGET" \
--output-format json > "$RAW" 2> "$ERR"
Expand Down Expand Up @@ -130,7 +135,7 @@ while [ "$attempt" -lt 3 ]; do
fi
if [ ! -s "$RAW" ]; then
# No envelope at all → claude itself never produced output (E2BIG, killed, exec fail).
echo "[score] attempt $attempt: EMPTY output for $(basename "$OUT") — claude wrote NOTHING to stdout (E2BIG / killed / TIMED OUT at ${WORLDOS_SCORE_TIMEOUT:-300}s). Retrying. stderr tail:" >&2
echo "[score] attempt $attempt: EMPTY output for $(basename "$OUT") — claude wrote NOTHING to stdout (E2BIG / killed / TIMED OUT at ${WORLDOS_SCORE_TIMEOUT:-600}s). Retrying. stderr tail:" >&2
tail -n 20 "$ERR" >&2 2>/dev/null || echo "[score] (no stderr captured at $ERR)" >&2
elif [ -n "$api_err" ]; then
# A real API-error envelope (e.g. 401 auth, 400, overload). Surface it — don't bury it.
Expand Down
Loading