Skip to content

Render RewardKit reward design and outputs - #1113

Open
pfbyjy wants to merge 23 commits into
stagingfrom
claude/oddish-reward-rendering-evjp8w
Open

Render RewardKit reward design and outputs#1113
pfbyjy wants to merge 23 commits into
stagingfrom
claude/oddish-reward-rendering-evjp8w

Conversation

@pfbyjy

@pfbyjy pfbyjy commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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 reward number 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.result JSON column. No database migration.

  • _rewards — all the named scores from reward.json.
  • _reward_details — a compact copy of reward-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.reward number is computed exactly as before, so pass@k, baseline gates, and the matrix are unchanged. One rule for task authors: name the strict aggregate reward in reward.toml.

Both fields follow the same safety rules as the existing _verifier field. 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 _rewards key, it is dropped.

Frontend

Three additions. Tasks with a plain single-number reward look exactly the same as before.

  1. Trial drawer: a "Reward Breakdown" card. It lists each dimension and each check inside it. Click a check to see what the judge answered ("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.
  2. Files and Artifacts tabs: reward.json and reward-details.json render as the same tree instead of raw JSON.
  3. Task page: a "Reward Design" section. It shows how the task scores agents before any trial runs. It reads the task's tests/ files: the judge TOML configs and reward.toml are 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

  • 18 new backend tests in test_rewardkit_capture.py; they pass along with the existing test_verifier_metrics.py (39 total).
  • Three failures in test_harbor_ephemeral / test_harbor_runner are pre-existing in this environment — they fail the same way without this change.
  • Frontend: typecheck, lint, prettier, and next build all pass. The TOML parsing was checked against the real judge files from the QA goldens gist.
  • Not done yet: an end-to-end run against a live RewardKit trial. The QA goldens eval would be a good first exercise.

🤖 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 reward scalar 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 from reward.json) and a bounded _reward_details summary into trials.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 settled reward.

Frontend: Adds a Rewards tab in the trial drawer (embedded first, artifact fallback for imports/historical rows), renders reward.json / reward-details.json as reward trees, and a Reward Design section on the task page that reconstructs the program from tests/ 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.

claude added 2 commits August 7, 2026 00:45
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
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
oddish-app Ready Ready Preview Aug 11, 2026 11:45pm

Comment thread oddish/src/oddish/core/harbor_artifacts.py
Comment thread oddish/src/oddish/core/harbor_artifacts.py

@cursor cursor 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.

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Oddish preview

Commit: 94d712b07a9261bd1a0b03798eb769cbe1848337

Surface Link Target
Frontend https://pr-1113.oddish.app Vercel preview for 94d712b
Backend oddish-pr-1113 oddish-pr-1113
Database project nnmbrningmtnwwfnbaok project nnmbrningmtnwwfnbaok

Vercel deployment URL: https://oddish-9lhmekgj0.oddish.app

Plan:

  • Frontend deploy: true
  • Backend deploy: true
  • Migrations: false

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
claude added 3 commits August 7, 2026 02:07
- 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
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
Comment thread frontend/src/lib/reward-kit.ts
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
claude added 2 commits August 7, 2026 06:50
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
Comment thread frontend/src/components/reward-design-card.tsx
Comment thread frontend/src/lib/reward-kit.ts
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
Comment thread frontend/src/lib/reward-kit.ts
Comment thread oddish/src/oddish/core/harbor_artifacts.py
Comment thread frontend/src/components/reward-design-card.tsx
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.
Comment thread frontend/src/lib/reward-kit.ts
Comment thread frontend/src/components/reward-design-card.tsx
Comment thread frontend/src/components/reward-design-card.tsx
Keep embedded reward summaries immediate, but only list historical or truncated artifacts after the Rewards tab is selected or deep-linked.
Comment thread frontend/src/components/trial-detail-panel.tsx
Comment thread oddish/src/oddish/core/harbor_artifacts.py
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.
Comment thread frontend/src/components/task-files-panel.tsx
Comment thread frontend/src/components/reward-breakdown-view.tsx

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread frontend/src/lib/reward-kit.ts
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.

3 participants