Render RewardKit reward design and outputs - #1113
Conversation
RewardKit verifiers report multiple named rewards (dimensions plus reward.toml aggregates) in reward.json and write a per-criterion breakdown with judge reasoning to verifier/reward-details.json. The worker previously collapsed the rewards dict to the headline scalar and discarded everything else. Persist both in the trials.result JSONB alongside the existing verifier envelopes: the full named-rewards map under the reserved _rewards key, and a bounded summary of reward-details.json (long reasoning truncated through progressively tighter tiers, criterion lists capped) under _reward_details with the artifact path of the full document. The headline trials.reward scalar and its extraction are unchanged, and both keys follow the _verifier rules: oddish-owned, spoofed task-authored copies stripped, and any malformed/oversized source file ignored so a details problem can never change a settled reward. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Three new surfaces, all no-ops for tasks that report a bare scalar: - Trial drawer: a Reward Breakdown card showing the named rewards next to the headline scalar and each dimension with its criteria — the judge's raw answer, normalized value, weight, full reasoning, errors, and the exact files the judge read. Renders from the embedded _rewards/_reward_details summaries and lazily loads the full verifier/reward-details.json through the scoped trial files API when the embedded copy is missing (imports) or truncated, mirroring the CTRF fallback. - Files/Artifacts tabs: reward.json and reward-details.json render as reward trees via a dedicated renderer instead of raw JSON. - Task page: a Reward Design section that reconstructs the reward program client-side from tests/reward.toml and the judge TOMLs (smol-toml) plus a best-effort static scan of python criteria, enriched with criteria observed in a completed trial's breakdown. Includes a what-if mode that toggles criteria pass/fail and recomputes scores through RewardKit's own aggregation semantics. Documents the authoring contract (name the strict aggregate "reward") in DOCS.md and the rendering invariants in AGENTS.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Risk: medium. Left a non-blocking comment: Cursor Bugbot reported unresolved findings (including high severity) and its check finished as skipping, so this is not auto-approved. Two reviewers are already requested; no additional reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver
Oddish previewCommit:
Vercel deployment URL: https://oddish-9lhmekgj0.oddish.app Plan:
This comment is updated by the PR Preview workflow. |
A local run of harbor-rewardkit 0.1.7 shows reward.toml aggregates weight
each dimension by its reward_weight — the judge's weight for judge
dimensions and 1.0 for programmatic ones. The Harbor docs' "sum of
criterion weights" wording does not match the implementation, and the
what-if simulation in the design card must reproduce the real engine.
Verified against the demo task: {completeness: 0.0, files: 1.0,
style: 0.7} -> soft_score 0.5667 = (0 + 1 + 0.7) / 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Three programmatic dimensions (pass / fail / partial against the reference solution) plus the standard reward.toml aggregates, so one oracle run shows every render state the dashboard has to handle. Expected scores documented in the README and verified against harbor-rewardkit 0.1.7 locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
- Trial drawer: the reward breakdown moves out of the Summary tab into its own top-level Rewards tab. The tab only appears when the trial actually has RewardKit output (embedded or discovered artifacts), so scalar-reward trials keep their existing tab set. - Task drawer: a Reward entry joins Overview in the task-overview group, rendering the reward design pane. Gated on a RewardKit marker in the file listing (a TOML under tests/) so ordinary tasks never grow the entry. - RewardDesignCard accepts a filesUrl override and an empty state so the files pane can host it in filesUrl-driven contexts, and tolerates both JSON and raw-text file content responses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Harbor's agent-setup phase installs the CLI agent's prerequisites from public package registries inside the sandbox, which no-network blocks (the oracle and nop baselines never run agent setup, so they passed). Match the QA goldens tasks: public network with a justification; the verifier itself stays offline since rewardkit is baked into the image. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Resolves the task-files-panel conflict against the on-demand file loading refactor (#1108): took staging's version of the panel and re-applied the Reward entry on its new selectedFilePath-based structure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Extends the existing addressing contract to the reward views: - Trial drawer: while ?tab=rewards is active, ?file= addresses a single criterion as dimension/criterion — the same per-tab ?file= scoping the files and artifacts tabs already use. The breakdown view's criterion expansion becomes controlled in the drawer (deep links scroll to and expand the addressed criterion); file-renderer usages stay local. - Task pane: ?taskView=reward addresses the reward-design view beside ?taskFile=/?taskLines=, wired on both hosts that own those params (the task page and the experiment page). A reward address on a task without a reward design falls back to the overview. - Task page: the inline design section anchors as #reward-design and #reward-design-<dimension>, scrolling into place once the async design loads. Documents the contract in AGENTS.md: new reward surfaces must take an address in one of these channels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
…vjp8w' into claude/oddish-reward-rendering-evjp8w
- The drawer's artifact fallback now applies the same rule as the backend embed: a reward.json holding only the headline reward key is an ordinary scalar verifier, so plain Harbor trials no longer grow a Rewards tab after the files fetch. - The task drawer's Reward entry gate excludes well-known tool-config TOMLs (pyproject.toml and friends) under tests/, so only reward.toml or judge-style configs surface the entry; the empty state remains the backstop for anything the heuristic still lets through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
RewardKit collapses same-named directory groups (judge + programmatic) into one named score weighted by reward_weight before reward.toml aggregation. The what-if now performs that collapse instead of feeding each group into the aggregate separately — previously a failing group could zero an all_pass that the collapsed name would survive. Observed enrichment pairs design groups with trial dimensions by name AND kind, so the judge group no longer absorbs the programmatic group's criteria (or vice versa) on mixed directories. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
wait_for_supabase_branch.sh rotates the branch DB password on every prepare-preview-database run, and that job runs on frontend-only pushes whenever a preview backend is live (to keep the branch smoke-tested). The live backend then holds the pre-rotation password and 500s every DB-backed request until a backend-scoped push happens to redeploy it — reproduced twice on this PR's preview stack. Mirror the existing migrations rule: a run that prepares the branch against an existing backend also redeploys that backend so the rotated password is re-published to the Modal secret. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
- Each judge TOML becomes its own same-named reward group in the design builder, matching rewardkit's one-Reward-per-judge-config model; two judge files in a directory no longer merge into one card with the last file's aggregation and weight. - The design-file cap fetches reward.toml, test.sh, and TOML rubrics before python criteria so the anchors can never be dropped on tasks with many check files. - Capping or shortening the judge's input file list marks the embed truncated, so the drawer fetches the full report instead of showing an incomplete "Judge read" list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Resolve the trial detail conflict by preserving canonical trial refresh and lazy tab mounting while retaining URL-addressable RewardKit output.
Keep embedded reward summaries immediate, but only list historical or truncated artifacts after the Rewards tab is selected or deep-linked.
Preserve lazy artifact loading while exposing imported-trial discovery, persist RewardKit fields during CLI import, and correct incomplete criteria, reload state, and zero-weight what-if behavior.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ff1db78. Configure here.



Some Harbor tasks now use RewardKit to score agents. A RewardKit verifier does not produce one number. It produces several named scores (for example
exact,llmj,reward,soft_score) and a detailed report that lists every check, its weight, and what the LLM judge said.Until now, oddish kept only the single
rewardnumber and threw the rest away. The detailed report reached S3, but you could only view it as raw JSON.This PR keeps that data and shows it in the dashboard.
Backend (
oddish/)The worker stores two extra fields inside the existing
trials.resultJSON column. No database migration._rewards— all the named scores fromreward.json._reward_details— a compact copy ofreward-details.json: each dimension, its checks, their weights, and the judge's reasoning. Long reasoning is shortened so the row stays small, and the path of the full file is stored so the UI can fetch it later.The single
trial.rewardnumber is computed exactly as before, so pass@k, baseline gates, and the matrix are unchanged. One rule for task authors: name the strict aggregaterewardinreward.toml.Both fields follow the same safety rules as the existing
_verifierfield. If the source file is missing, broken, or too large, oddish ignores it and the trial's reward is not affected. If a task tries to write its own_rewardskey, it is dropped.Frontend
Three additions. Tasks with a plain single-number reward look exactly the same as before.
"no"), the score that became (0.00), the judge's full reasoning, and which files the judge read. If the stored copy was shortened, or the trial was imported and has no stored copy, the full report is fetched from the trial's files using the existing scoped files API.reward.jsonandreward-details.jsonrender as the same tree instead of raw JSON.tests/files: the judge TOML configs andreward.tomlare parsed (new dependency:smol-toml), and Python check files are scanned as text — never executed. It also has a what-if mode: toggle checks pass/fail and watch the final scores change, using the same aggregation math RewardKit uses.Docs
DOCS.md: a new section on writing RewardKit tasks for oddish, next to the metrics.json and ctrf.json sections.AGENTS.md: notes on how the UI loads this data.Testing
test_rewardkit_capture.py; they pass along with the existingtest_verifier_metrics.py(39 total).test_harbor_ephemeral/test_harbor_runnerare pre-existing in this environment — they fail the same way without this change.next buildall pass. The TOML parsing was checked against the real judge files from the QA goldens gist.🤖 Generated with Claude Code
https://claude.ai/code/session_01PKitApumfgZMroHfnNvGsH
Note
Medium Risk
Touches trial result persistence and major trial/task UI surfaces, but follows the existing CTRF embed pattern, leaves the headline
rewardscalar unchanged, and needs no schema migration.Overview
Captures and displays RewardKit multi-dimensional rewards instead of discarding everything beyond the headline scalar.
Backend: Workers now embed
_rewards(named scores fromreward.json) and a bounded_reward_detailssummary intotrials.result, with the full details file left in object storage for lazy load. Same forgiving rules as_verifier— missing/malformed/oversized input never affects the settledreward.Frontend: Adds a Rewards tab in the trial drawer (embedded first, artifact fallback for imports/historical rows), renders
reward.json/reward-details.jsonas reward trees, and a Reward Design section on the task page that reconstructs the program fromtests/TOMLs plus a static Python scan — including what-if pass/fail simulation. All surfaces are URL-addressable (?tab=rewards,?taskView=reward,#reward-design).Also forces preview backend redeploy when a live backend would otherwise keep a rotated DB password, and adds a small RewardKit demo task plus docs.
Reviewed by Cursor Bugbot for commit 94d712b. Bugbot is set up for automated code reviews on this repo. Configure here.