Skip to content

chore(score): env-overridable scorer model + the Opus-vs-Sonnet calibration finding - #687

Merged
100yenadmin merged 3 commits into
mainfrom
chore/scorer-model-override
Jun 6, 2026
Merged

chore(score): env-overridable scorer model + the Opus-vs-Sonnet calibration finding#687
100yenadmin merged 3 commits into
mainfrom
chore/scorer-model-override

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented Jun 6, 2026

Copy link
Copy Markdown
Member

Adds CLAWDND_SCORER_MODEL to qa/score.sh (default sonnet — the gate scorer stays constant by default; never flipped casually). This enabled a scorer-calibration probe that produced a pivotal finding.

Finding: the Sonnet gate scorer UNDER-READS Opus story craft

Re-scored 3 existing transcripts with both judges:

transcript Sonnet Opus
storyhi (story) 4.0 (uniform 4s) 4.4 (scene_craft 5, prose 5)
leandirfix2 (story) 4.0 4.4
csopus2 (mech) 3.9 3.6

Not a self-favoring bias — the Opus judge reads story higher but mech lower, so it isn't inflating Opus output; it's a more discerning judge. On subjective prose craft the Sonnet scorer flatlines at uniform 4s; on objective rules-fidelity both agree (Opus stricter). The Opus rationale is specific and critical ("Near BG3-tier… the single biggest lever is restraint — stop narrating the hero's interior realizations"), corroborated by the GUI persona's lived ~4.5.

Implication

Caveat: a fully rigorous story-scorer recalibration wants a 3rd independent judge (GPT-5.4) or a human anchor — the #643 scorer-audit's job. This PR is the tooling + the evidence.

Summary by CodeRabbit

  • Chores

    • QA scoring now supports configurable model/agent selection via environment variables while keeping previous defaults.
    • Per-run session ID prefixes introduced and model override options made conditional.
  • Bug Fixes

    • Prompt payloads are now size-limited (configurable) to prevent oversized gateway requests; truncated state includes a clear marker when cut.

…gate-constant)

Adds CLAWDND_SCORER_MODEL (default sonnet) so the gate scorer stays constant by default, but a
deliberate scorer-calibration probe can re-score with a stronger judge. Motivated by the
analytical-Sonnet (story 4.0) vs lived-persona (~4.5 'best AI fiction') gap on Opus craft.
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

Warning

Review limit reached

@100yenadmin, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 1 hour, 7 minutes, and 39 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f402eb8-812d-4665-a137-ae3ee66f8bd0

📥 Commits

Reviewing files that changed from the base of the PR and between cd98c5b and bc607b4.

📒 Files selected for processing (1)
  • qa/vm/sweep_v2.sh
📝 Walkthrough

Walkthrough

QA scoring scripts: qa/score.sh now reads SCORER_MODEL from CLAWDND_SCORER_MODEL (default sonnet) and passes it to claude. qa/score_openclaw.sh changes the default agent to main, conditionally passes model args, adds per-run session prefixes, and caps/truncates engine state sent in prompts.

Changes

Scorer + OpenClaw gateway updates

Layer / File(s) Summary
Scorer model env config
qa/score.sh
Adds SCORER_MODEL (default sonnet) from CLAWDND_SCORER_MODEL and uses --model "$SCORER_MODEL" in the claude CLI call.
OpenClaw gateway args and state truncation
qa/score_openclaw.sh
Changes default agent to main, only includes model override when CLAWDND_SCORER_MODEL is set (via MODEL_ARGS), adds a per-run session id prefix, introduces STATE_CAP, and truncates the engine state included in the gateway prompt. Also switches openclaw agent invocation to expand MODEL_ARGS conditionally.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I tweak the scorer with a gentle hop,
Env keys choose models, defaults don't stop,
Truncate the state so prompts stay light,
Agents set to main, session names take flight,
A rabbit's cheer for scripts done right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides a comprehensive summary of changes and their implications, including a detailed calibration study, but lacks explicit completion of the CLA and validation checklist sections from the template. Complete the checklist items under 'Licensing / CLA' and 'Validation' sections to confirm CLA compliance and document testing performed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: adding an environment variable for scorer model selection and presenting calibration findings comparing Sonnet vs. Opus.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…esh session, state cap

The gateway scorer was broken on the VM: default agent clawdnd-qa isn't configured (main is, model
gpt-5.5), --model openai/gpt-5.4 is REJECTED for main, and the single --message argv tripped E2BIG
(state.json ~137KB > MAX_ARG_STRLEN ~128KB). Fixes: default agent=main, pass --model only when
explicitly set+allowed, fresh --session-id per run (no main-session pollution), and cap the state
(CLAWDND_SCORER_STATE_CAP=75000; transcript carries the prose). Enables the GPT-5.5 3rd judge + the
credit-saving gateway scorer the owner asked for.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@qa/score_openclaw.sh`:
- Line 55: Wrap the conversion cap = int(sys.argv[5]) in a try/except to catch
ValueError, validate the provided sys.argv[5] (CLAWDND_SCORER_STATE_CAP) is an
integer, and on failure print a clear error to stderr mentioning the env var
name and the invalid value then exit with non-zero status (e.g., sys.exit(1));
ensure you still assign cap on success and use the same variable name cap and
input sys.argv[5] so callers remain unchanged.
- Around line 55-57: The current truncation and cap parsing are unsafe: replace
the direct parse cap = int(sys.argv[5]) with a guarded parse (try/except) that
validates CLAWDND_SCORER_STATE_CAP and falls back to a sane default or exits
with a clear error, and change the truncation of st so it preserves JSON
validity—attempt to parse st as JSON and if successful prune large fields
(truncate long strings, reduce arrays, or remove optional keys) until the
serialized JSON length <= cap, otherwise treat st as opaque text and append the
truncation marker outside any JSON braces; update the logic around variable st
and cap to use these safe parsing/pruning steps and ensure the truncation marker
is not injected inside JSON content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dd1fe909-7b55-4e4a-855b-7a77accebabd

📥 Commits

Reviewing files that changed from the base of the PR and between 41fd9d1 and cd98c5b.

📒 Files selected for processing (1)
  • qa/score_openclaw.sh

Comment thread qa/score_openclaw.sh
s = open(sys.argv[2]).read()
m = open(sys.argv[3]).read()
st = open(sys.argv[4]).read()
cap = int(sys.argv[5])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add error handling for invalid STATE_CAP values.

The int(sys.argv[5]) call will raise ValueError if CLAWDND_SCORER_STATE_CAP is set to a non-integer string, producing a cryptic Python traceback instead of a clear error message.

🛡️ Proposed fix to add validation
-cap = int(sys.argv[5])
+try:
+    cap = int(sys.argv[5])
+except ValueError:
+    sys.stderr.write(f'ERROR: STATE_CAP must be an integer, got: {sys.argv[5]}\n')
+    sys.exit(1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cap = int(sys.argv[5])
try:
cap = int(sys.argv[5])
except ValueError:
sys.stderr.write(f'ERROR: STATE_CAP must be an integer, got: {sys.argv[5]}\n')
sys.exit(1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/score_openclaw.sh` at line 55, Wrap the conversion cap = int(sys.argv[5])
in a try/except to catch ValueError, validate the provided sys.argv[5]
(CLAWDND_SCORER_STATE_CAP) is an integer, and on failure print a clear error to
stderr mentioning the env var name and the invalid value then exit with non-zero
status (e.g., sys.exit(1)); ensure you still assign cap on success and use the
same variable name cap and input sys.argv[5] so callers remain unchanged.

Comment thread qa/score_openclaw.sh
Comment on lines +55 to +57
cap = int(sys.argv[5])
if len(st) > cap:
st = st[:cap] + '\n…[FINAL STATE truncated to fit the gateway message size limit]…\n'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find state.json files and check sizes against the 75KB cap
fd -e json -x sh -c 'size=$(wc -c < "{}"); if [ "$size" -gt 75000 ]; then echo "{}: $size bytes (exceeds 75KB cap)"; fi' \; | head -20

Repository: electricsheephq/WorldOS

Length of output: 1043


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== qa/score_openclaw.sh lines 45-90 =="
nl -ba qa/score_openclaw.sh | sed -n '45,90p'

echo
echo "== Search in qa/score_openclaw.sh for key strings =="
rg -n "sys\.argv\[5\]|CLAWDND_SCORER_STATE_CAP|st\s*\[:cap\]|FINAL STATE|ground truth|json\.loads|json\.dumps" qa/score_openclaw.sh || true

echo
echo "== Find files that match state.json exactly (and *state*.json) and check size > 75KB =="
# exact state.json
fd -i 'state.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

echo
# any file containing "state" in name (useful if naming differs)
fd -i '*state*.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

Repository: electricsheephq/WorldOS

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== qa/score_openclaw.sh lines 45-90 =="
nl -ba qa/score_openclaw.sh | sed -n '45,90p'

echo
echo "== Search in qa/score_openclaw.sh for key strings =="
rg -n "sys\.argv\[5\]|CLAWDND_SCORER_STATE_CAP|st\s*\[:cap\]|FINAL STATE|ground truth|json\.loads|json\.dumps" qa/score_openclaw.sh || true

echo
echo "== Find files that match state.json exactly (and *state*.json) and check size > 75KB =="
# exact state.json
fd -i 'state.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

echo
# any file containing "state" in name (useful if naming differs)
fd -i '*state*.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

Repository: electricsheephq/WorldOS

Length of output: 167


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== qa/score_openclaw.sh lines 45-90 =="
sed -n '45,90p' qa/score_openclaw.sh | awk '{printf "%4d\t%s\n", NR+44, $0}'

echo
echo "== Search in qa/score_openclaw.sh for key strings =="
rg -n "sys\.argv\[5\]|CLAWDND_SCORER_STATE_CAP|st\s*\[:cap\]|json\.loads|json\.dumps" qa/score_openclaw.sh || true

echo
echo "== Find files that match state.json exactly (and *state*.json) and check size > 75KB =="
fd -i 'state.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

echo
fd -i '*state*.json' -e json -t f --exec sh -c 's=$(wc -c < "{}"); if [ "$s" -gt 75000 ]; then echo "{}: ${s} bytes"; fi' \; 2>/dev/null | head -50 || true

Repository: electricsheephq/WorldOS

Length of output: 2535


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== qa/score_openclaw.sh (head) =="
sed -n '1,120p' qa/score_openclaw.sh

echo
echo "== qa/score_openclaw.sh (rest) =="
sed -n '120,260p' qa/score_openclaw.sh

echo
echo "== Find how score_openclaw is invoked / STATE arg meaning =="
rg -n "score_openclaw\.sh|score_openclaw" -S . || true

Repository: electricsheephq/WorldOS

Length of output: 7500


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "CLAWDND_SCORER_STATE_CAP" -S . || true
rg -n "SCORER_STATE_CAP|STATE_CAP" -S qa WorldOS-RUNBOOK.md qa/SCORING.md qa/QA_TOOLS.md || true

Repository: electricsheephq/WorldOS

Length of output: 548


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== score_openclaw call sites =="
rg -n "score_openclaw\.sh" -S . || true

echo
echo "== QA_TOOLS.md context =="
sed -n '60,120p' qa/QA_TOOLS.md || true

echo
echo "== qa/SCORING.md context =="
sed -n '1,120p' qa/SCORING.md || true

echo
echo "== prompt section mentions state parsing? (search for 'FINAL ENGINE STATE' or 'ground truth') =="
rg -n "FINAL ENGINE STATE|ground-truth|ground truth|state.*json|parse.*state" -S qa/score_openclaw.sh qa/SCORING.md qa/QA_TOOLS.md WorldOS-RUNBOOK.md || true

echo
echo "== Find any files literally named state.json in repo =="
fd -i 'state.json' || true

Repository: electricsheephq/WorldOS

Length of output: 8905


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "qa/score_openclaw\.sh|score_openclaw\.sh" -S . || true

echo
echo "== Find runner scripts for openclaw scoring =="
fd -e sh -t f qa | xargs -I{} rg -n "openclaw|score_openclaw\.sh|run_duo_openclaw|run_party_openclaw" "{}" || true

Repository: electricsheephq/WorldOS

Length of output: 841


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== rg for openclaw usage across repo =="
rg -n "openclaw|score_openclaw\.sh|run_duo_openclaw|run_party_openclaw" -S . || true

echo
echo "== List qa scripts that look openclaw-related =="
fd -i -t f qa -g '*openclaw*' -e sh -e py -e ts -e js || true

echo
echo "== Find where qa/score_openclaw.sh is executed (not just referenced) =="
rg -n "score_openclaw\.sh\s" -S qa . || true

Repository: electricsheephq/WorldOS

Length of output: 40529


Fix prompt state truncation to preserve JSON validity (and guard STATE_CAP parsing).

  • In qa/score_openclaw.sh lines 55-57, st = st[:cap] slices the state text mid-document, so the “FINAL ENGINE STATE (ground truth)” section is often syntactically invalid JSON when truncated; use JSON-aware truncation/pruning (or explicitly document that the model must treat this as non-JSON text).
  • cap = int(sys.argv[5]) (from CLAWDND_SCORER_STATE_CAP) has no validation; non-integer values will raise ValueError and crash—add a safe parse with a clear error or fallback to the default.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qa/score_openclaw.sh` around lines 55 - 57, The current truncation and cap
parsing are unsafe: replace the direct parse cap = int(sys.argv[5]) with a
guarded parse (try/except) that validates CLAWDND_SCORER_STATE_CAP and falls
back to a sane default or exits with a clear error, and change the truncation of
st so it preserves JSON validity—attempt to parse st as JSON and if successful
prune large fields (truncate long strings, reduce arrays, or remove optional
keys) until the serialized JSON length <= cap, otherwise treat st as opaque text
and append the truncation marker outside any JSON braces; update the logic
around variable st and cap to use these safe parsing/pruning steps and ensure
the truncation marker is not injected inside JSON content.

… budgets + lean-ON)

For the scheduled Tue-4am full sweep on the credit refresh: per-persona timeout 1500->2400s + run
budget $12->$18 (Opus cold-open ~$2.4 + slower beats); duo timeout 2700->3600s + budget $2->$5.
Flip lean ON (was a stale 'intentionally OFF' from the 2026-06-05 finding; #683 fixed the
cross-campaign contamination + #685 the output-discipline) — lean-ON matches production + gives fast
Opus beats so the sweep completes without latency give-ups/timeouts (the wasted-run vector).
@100yenadmin
100yenadmin merged commit e5c0a5f into main Jun 6, 2026
14 checks passed
@100yenadmin
100yenadmin deleted the chore/scorer-model-override branch June 6, 2026 10:08
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