Skip to content

Judge feedback batch: job-backed run of a pre-existing batch (eval parity) + deep-review fixes#1542

Merged
tawnymanticore merged 10 commits into
integration/auto-mode-e2efrom
leonard/judge-feedback-batch-job-polish
Jul 2, 2026
Merged

Judge feedback batch: job-backed run of a pre-existing batch (eval parity) + deep-review fixes#1542
tawnymanticore merged 10 commits into
integration/auto-mode-e2efrom
leonard/judge-feedback-batch-job-polish

Conversation

@leonardmq

@leonardmq leonardmq commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Brings the judge_feedback_batch background-job worker to parity with the eval job worker, reshapes it to run a pre-existing batch (eval parity), and applies fixes from a deep multi-phase code review of the whole judge_feedback_batch feature. Targets integration/auto-mode-e2e (the auto-mode e2e integration branch). Includes the work merged from #1543.

Job shape: run a pre-existing batch (eval parity)

The job was reshaped from "create-and-run" to "run an existing batch", mirroring EvalJobWorker:

  • The batch config is created first via the synchronous POST .../judge_feedback_batches (persisted under the git-sync write lock); the job then just runs it by id.
  • JudgeFeedbackBatchJobParams is now {project_id, task_id, judge_feedback_batch_id} (was a full CreateJudgeFeedbackBatchRequest). run()/describe() load the batch by id and read its config off disk.
  • Because the id is caller-supplied and lives in job.params, it's retrievable via GET /api/jobs/{id} even if the run fails (the old create-and-run job only surfaced the minted id in its success result). The job result now echoes judge_feedback_batch_id from params.
  • The worker no longer writes the config, so the earlier "wrap the batch save in the git-sync context" worker workaround is gone — the create endpoint owns that write.

Worker parity

  • Progress: the worker previously reported progress once at the end and used train_set_size as the denominator, so the bar stalled below 100% when the matching set exceeded max_samples. The runner now takes an optional progress_callback and streams (num_judged, error_count, planned_total) per judged chunk; live + final progress use the capped denominator min(train_set_size, max_samples), reaching 100% on full coverage (mirrors the eval worker). success excludes errors (num_judged - error_count) so processed = success + error holds and the bar can't overshoot.
  • Metadata / jobs UI: added JudgeFeedbackBatchJobProperties + describe() (mirrors EvalJobProperties) — judge/eval names, algorithm, model, run config (only when generating outputs), tags, max_samples, stop_after_failures — and render them in the jobs table (was a raw Judge_feedback_batch label).
  • Errors: per-item errors reach the View Errors UI, and are now logged live via an error_callback the moment each is collected — previously the button appeared mid-run (gated by the streamed error count) but the messages only landed in a post-run loop, so the log read "No error messages recorded" until completion. Message quality improved by unwrapping KilnRunError.original like the eval worker.
  • Field descriptions: added to JudgeFeedbackBatchJobResult, JudgeFeedbackBatchJobParams, and the project_id/task_id params.

Deep code review fixes

Multi-phase review (runner / worker / data model / REST API / UI / tests). 0 critical, 6 moderate (all fixed), 24 mild (12 fixed, rest documented). Highlights:

  • Progress double-counting: num_judged already includes errored items, so reporting success = num_judged alongside a separate error count breached the processed = success + error contract — now success = num_judged - error_count in both the streamed callback and the final snapshot.
  • Gate-mode determinism: gate mode now samples deterministically (sorted by id) before capping, so two runs cover the same task_run_ids — the pairing the gate exists for (a random shuffle previously picked disjoint subsets past max_samples). Train-signal mode keeps the random minibatch.
  • Data model: added a JudgeFeedbackBatch model_validator (generate_outputsrun_config_id, non-empty target_tags) as defense-in-depth (mirrors EvalRun).
  • Runner tests for the empty candidate set and gate-mode hit_cap=True (incl. proof that two gate runs cover the same ids), plus a test asserting errors are delivered live (interleaved with progress).
  • Polish: _error_detail on save errors, concurrency = max(1, concurrency) guard, 404 message/wording (Judge feedback batch not found), judge_feedback_batch_from_id accepts a preloaded task (removes the run endpoint's double task load), docstrings covering generate-outputs mode.

Agent policy

The synchronous run / create-and-run endpoints are superseded by the create + job flow, so the agent goes through POST /api/jobs/judge_feedback_batch/run for dashboard visibility:

  • create → allow (cheap, no model calls — the agent creates the batch, then runs it as a job)
  • run (existing, sync) → deny
  • create-and-run (sync) → deny
  • run as job (/api/jobs/...) → allow + approval
  • list / get / runs (GET) → allow

Regenerated the agent-check annotation JSONs so the policy is actually enforced (the chat backend reads the dumped JSONs, not the live spec) — fixes the check_api_bindings CI job. The synchronous create-and-run / run endpoints are left in place for callers not yet migrated.

UI hardening

  • Render judge-batch properties in the jobs table (eval_name, stop_after_failures, tags, etc.).
  • Guard the target_tags render with optional chaining so a job record with incomplete properties can't throw and crash the whole jobs table (per gemini-code-assist review).

Verification

Full uv run ./checks.sh --agent-mode green (lint, format, typecheck, py + web tests, schema, build). api_schema.d.ts regenerated.

🤖 Generated with Claude Code

leonardmq and others added 2 commits July 1, 2026 18:50
…ops, describe

Bring the judge_feedback_batch job worker to parity with the eval job worker
(the auto-mode e2e integration flagged three gaps):

- Progress: the worker reported progress only once at the end, and used
  train_set_size as the denominator — so when the matching set exceeded
  max_samples the bar stalled below 100% even on full success. The runner now
  takes an optional progress_callback and streams (num_judged, error_count,
  planned_total) per judged chunk; the worker reports live progress and its
  final snapshot against the planned (capped) count min(train_set_size,
  max_samples), so success reaches total on full coverage.
- Metadata: publish JudgeFeedbackBatchJobProperties via describe() (mirrors
  EvalJobProperties) — judge/eval names, algorithm, model, mode, run config,
  tags, max_samples — and render them in the jobs table (previously just a raw
  "Judge_feedback_batch" label).
- Errors: unchanged wiring already surfaces per-item errors to the View Errors
  UI; improved message quality by unwrapping KilnRunError.original (mirrors
  EvalJobWorker._error_detail).
- API models: add field descriptions to JudgeFeedbackBatchJobResult and the
  project_id/task_id params. Regenerated api_schema.d.ts.

Tests: runner progress_callback streaming, worker describe() + capped-total
progress, frontend judge-property rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
…ish)

Deep multi-phase review of the judge_feedback_batch feature (0 critical). Fixes:

Moderate:
- Progress double-counted errored items: the runner's num_judged already
  includes errored items, so reporting success=num_judged alongside a separate
  error count breached the processed=success+error contract and overshot 100%.
  Report success=num_judged-error_count in both the streamed callback and the
  final snapshot (worker), matching the eval reference's disjoint counters.
- Gate-mode task_run_id pairing was defeated by the random shuffle when the tag
  set exceeded max_samples (two runs sampled disjoint subsets). Gate mode now
  selects deterministically (sorted by id) before capping; train-signal mode
  keeps the random minibatch.
- Added a JudgeFeedbackBatch model_validator coupling generate_outputs=True to a
  required run_config_id and rejecting empty target_tags (defense in depth,
  mirrors EvalRun) — the API request model already validated both.
- Documented that num_judged counts attempts (errors/cache included); use
  len(judged_runs) for a scored-item count.
- Added runner tests for the empty candidate set and gate-mode hit_cap=True
  (set > max_samples), incl. proof that two gate runs cover the same ids.

Polish:
- Runner: save-error uses _error_detail; concurrency=max(1, concurrency) guard;
  mean-of-means comment; "Judge feedback batch" wording.
- API: judge_feedback_batch_from_id 404 message fixed + accepts a preloaded task
  (removes the run endpoint's double task load); run docstring covers generate
  mode.
- UI: surface eval_name + stop_after_failures in the jobs table.
- Tests: worker run stub gets the full signature; judge-only describe() test.

Full checks.sh green. Regenerated api_schema.d.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Updates judge feedback batch jobs to stream progress and errors, expose job properties for describe/UI use, enforce batch configuration invariants, and align backend API and web UI rendering with the new job shape.

Changes

Judge Feedback Batch Progress, Describe, and Validation

Layer / File(s) Summary
Runner progress callback and error detail extraction
libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py, libs/core/kiln_ai/adapters/eval/test_judge_feedback_batch_runner.py
Runner adds async progress and error callbacks, deterministic gate-mode candidate ordering, concurrency clamping, and unified per-item error formatting; tests cover streaming, cap handling, empty candidates, and live error delivery.
Worker describe, run progress, and API reuse
app/desktop/studio_server/jobs/workers/judge_feedback_batch.py, app/desktop/studio_server/jobs/workers/test_judge_feedback_batch.py, app/desktop/studio_server/judge_feedback_batch_api.py
Worker adds async describe()/_describe_sync() for properties, run() forwards progress and error callbacks while reporting final capped totals, and the API reuses a preloaded task while updating its not-found text and git-sync save handling.
JudgeFeedbackBatch model validation
libs/core/kiln_ai/datamodel/judge_feedback_batch.py, libs/core/kiln_ai/datamodel/test_judge_feedback_batch.py
Adds a model validator requiring run_config_id when generate_outputs is true and at least one target_tags entry, with tests for both cases.
Frontend job properties and table rendering
app/web_ui/src/lib/stores/jobs_api.ts, app/web_ui/src/lib/components/jobs_table.svelte, app/web_ui/src/lib/components/jobs_table.test.ts, app/web_ui/src/lib/api_schema.d.ts
The web UI adds typed judge feedback batch job properties, a dedicated jobs-table rendering branch, matching test coverage, and schema/docs updates for the endpoint and params.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • Kiln-AI/Kiln#1530: Both PRs touch the worker/job-properties contract used by the jobs table UI.

Suggested reviewers: scosman, chiang-daniel

Poem

A rabbit hopped through batch and log,
With progress ticks and tidy fog 🐇
New fields bloom, and tables sing,
Judge the batch — let metrics ping!
Hop, hop, hooray, the bits align 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers the PR goal, but it omits required template sections for Related Issues, CLA, and checklists. Add the missing template sections: Related Issues, CLA confirmation, and the test/checklist items, even if briefly filled out.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main change: judge feedback batch jobs now run an existing batch with parity and review fixes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch leonard/judge-feedback-batch-job-polish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

📊 Coverage Report

Overall Coverage: 92%

Diff: origin/integration/auto-mode-e2e...HEAD

  • app/desktop/studio_server/jobs/workers/judge_feedback_batch.py (100%)
  • app/desktop/studio_server/judge_feedback_batch_api.py (100%)
  • libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py (86.4%): Missing lines 61,235,380
  • libs/core/kiln_ai/datamodel/judge_feedback_batch.py (100%)

Summary

  • Total: 100 lines
  • Missing: 3 lines
  • Coverage: 97%

Line-by-line

View line-by-line diff coverage

libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py

Lines 57-65

  57     (often generic) `format_error_message` text; the underlying cause survives on `.original`, so
  58     surface that for the developer-facing error log. Mirrors `EvalJobWorker._error_detail`.
  59     """
  60     if isinstance(error, KilnRunError) and error.original is not None:
! 61         return str(error.original)
  62     return str(error)
  63 
  64 
  65 def score_passes(

Lines 231-239

  231         retry_delay: float = 2.0,
  232     ):
  233         task = judge_feedback_batch.parent_task()
  234         if task is None:
! 235             raise ValueError("Judge feedback batch must have a parent task")
  236         eval = eval_config.parent_eval()
  237         if eval is None:
  238             raise ValueError("Eval config must have a parent eval")

Lines 376-384

  376                         error=f"Unexpected error judging item: {_error_detail(scored)}",
  377                     )
  378                     errors.append(unexpected_error)
  379                     if error_callback is not None:
! 380                         await error_callback(unexpected_error)
  381                     continue
  382                 if scored.error is not None:
  383                     errors.append(scored.error)
  384                     if error_callback is not None:


@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces live progress streaming and detailed job properties for judge feedback batch jobs, enhancing both the backend runner and the frontend jobs UI. It also adds deterministic candidate selection in gate mode to ensure stable subsets across runs, along with model validation for batch configurations. The review feedback highlights a potential UI crash in the jobs table if target_tags is undefined for older or malformed jobs, suggesting optional chaining as a safeguard.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread app/web_ui/src/lib/components/jobs_table.svelte Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@app/desktop/studio_server/jobs/workers/judge_feedback_batch.py`:
- Around line 216-251: The judge/save errors are only being forwarded to
ctx.report_error after runner.run completes, so the error count can update
before any messages exist in the View Errors UI. Update judge_feedback_batch.py
so errors are reported incrementally during execution by wiring an error
callback or observer through the runner path used by report_progress and
result.errors, similar to EvalJobWorker, and keep the final post-run snapshot
only as a catch-up for any remaining errors.

In `@libs/core/kiln_ai/datamodel/judge_feedback_batch.py`:
- Around line 113-124: The after-model validation in
JudgeFeedbackBatch.validate_config is now rejecting older persisted batches when
load_from_file() calls model_validate() directly. Update the loading path or add
a backward-compatibility migration so legacy files can still be read, and keep
the stricter checks only for newly created/edited batches; use the
JudgeFeedbackBatch.validate_config and load_from_file entry points to locate the
fix.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 34d8f9c9-c528-4eb2-8880-e3c2ac794eb5

📥 Commits

Reviewing files that changed from the base of the PR and between 16f3933 and 938fdda.

📒 Files selected for processing (11)
  • app/desktop/studio_server/jobs/workers/judge_feedback_batch.py
  • app/desktop/studio_server/jobs/workers/test_judge_feedback_batch.py
  • app/desktop/studio_server/judge_feedback_batch_api.py
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/lib/components/jobs_table.svelte
  • app/web_ui/src/lib/components/jobs_table.test.ts
  • app/web_ui/src/lib/stores/jobs_api.ts
  • libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py
  • libs/core/kiln_ai/adapters/eval/test_judge_feedback_batch_runner.py
  • libs/core/kiln_ai/datamodel/judge_feedback_batch.py
  • libs/core/kiln_ai/datamodel/test_judge_feedback_batch.py

Comment thread app/desktop/studio_server/jobs/workers/judge_feedback_batch.py
Comment thread libs/core/kiln_ai/datamodel/judge_feedback_batch.py
… works mid-run

The streamed progress callback bumps progress.error per chunk, which makes the
"View Errors" button appear while the job is still running. But the error
MESSAGES were only written to the job error log in a post-run loop over
result.errors — after runner.run() returned. So mid-run the button showed but
the log was empty ("No error messages recorded"); the messages only appeared
once the job completed. This diverged from the eval worker, which logs each
error live via an observer.

Add an error_callback to JudgeFeedbackBatchRunner.run(), fired the moment each
per-item error is collected (both the judge/save error and the unexpected-throw
paths), and wire the worker to write it to the error log immediately. Errors
still appear in the returned result.errors for the synchronous endpoint (which
passes no callback). Removed the worker's post-run loop to avoid double-logging.

Tests: runner test asserting errors are delivered live (interleaved with
progress, not batched to the end); worker stub updated to emit errors via the
callback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py (1)

374-393: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unguarded callback exceptions can abort the whole run and discard already-judged results.

error_callback and progress_callback are awaited directly with no try/except. If the caller's callback raises (e.g. the worker's report_progress/report_item_error in judge_feedback_batch.py doing I/O against the job store), the exception propagates out of run(), and none of the already-collected judged_runs/errors/failing_runs from prior chunks get returned — a purely cosmetic reporting failure would fail the entire batch job. The worker layer doesn't wrap await runner.run(...) in a try/except either, so nothing catches this downstream.

Since the whole point of these hooks is to report progress/errors without changing outcome semantics, consider isolating callback failures so a broken reporter can't sink the run.

🛡️ Proposed fix: isolate callback exceptions
                     errors.append(unexpected_error)
                     if error_callback is not None:
-                        await error_callback(unexpected_error)
+                        await self._safe_callback(error_callback, unexpected_error)
                     continue
                 if scored.error is not None:
                     errors.append(scored.error)
                     if error_callback is not None:
-                        await error_callback(scored.error)
+                        await self._safe_callback(error_callback, scored.error)
                 if scored.run is not None:
                     judged_runs.append(scored.run)
                     if not scored.passed:
                         failing_runs.append(scored.run)
             # Stream live progress against the planned (capped) count so a background job's bar
             # advances per chunk and reaches 100% on full coverage.
             if progress_callback is not None:
-                await progress_callback(num_judged, len(errors), len(candidates))
+                await self._safe_callback(
+                    progress_callback, num_judged, len(errors), len(candidates)
+                )
async def _safe_callback(self, callback, *args) -> None:
    try:
        await callback(*args)
    except Exception:
        logger.exception(
            "Callback failed for judge feedback batch %s; continuing run",
            self.judge_feedback_batch.id,
        )
🤖 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 `@libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py` around lines
374 - 393, The callback hooks in judge feedback batch execution can throw and
abort the whole run, so isolate failures in the `JudgeFeedbackBatchRunner.run`
flow instead of letting them propagate. Wrap both `error_callback` and
`progress_callback` awaits in a small safe helper or local try/except, log the
callback failure, and continue so already collected `judged_runs`, `errors`, and
`failing_runs` are still returned. Use the existing `error_callback`,
`progress_callback`, and `run()` symbols to locate the change.
🤖 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.

Outside diff comments:
In `@libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py`:
- Around line 374-393: The callback hooks in judge feedback batch execution can
throw and abort the whole run, so isolate failures in the
`JudgeFeedbackBatchRunner.run` flow instead of letting them propagate. Wrap both
`error_callback` and `progress_callback` awaits in a small safe helper or local
try/except, log the callback failure, and continue so already collected
`judged_runs`, `errors`, and `failing_runs` are still returned. Use the existing
`error_callback`, `progress_callback`, and `run()` symbols to locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e33f0b02-50d5-486d-b245-00c77c3e6739

📥 Commits

Reviewing files that changed from the base of the PR and between d495639 and 5f60d43.

📒 Files selected for processing (4)
  • app/desktop/studio_server/jobs/workers/judge_feedback_batch.py
  • app/desktop/studio_server/jobs/workers/test_judge_feedback_batch.py
  • libs/core/kiln_ai/adapters/eval/judge_feedback_batch_runner.py
  • libs/core/kiln_ai/adapters/eval/test_judge_feedback_batch_runner.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/core/kiln_ai/adapters/eval/test_judge_feedback_batch_runner.py
  • app/desktop/studio_server/jobs/workers/test_judge_feedback_batch.py
  • app/desktop/studio_server/jobs/workers/judge_feedback_batch.py

leonardmq and others added 6 commits July 2, 2026 16:16
Defensive: use optional chaining on jp.target_tags so a job record with
incomplete properties can't throw a TypeError and crash the whole jobs table
render. (Per gemini-code-assist PR review.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
The batch config was written with a raw save_to_file() outside any atomic_write
— in both the job worker and the synchronous create-and-run endpoint. Under auto
git-sync that new (untracked) file is dirty working-tree state, so the runner's
first per-item child write enters atomic_write, whose ensure_clean() treats it as
a crashed session and stashes it away (include_untracked=True). Net effect: the
batch config is never committed/pushed and is removed from the working tree,
orphaning the committed child runs.

Wrap the batch-config save in the same save_context used for the child writes
(coalescing None -> no-op default_save_context) so it gets its own atomic_write /
commit before the runner starts. The batch save and each child save are separate,
non-nested atomic_write blocks, so re-entrancy is not a concern. Mirrors how
EvalRunner already wraps every per-item write (the eval worker creates no parent
entity, so it was already correct).

- worker: judge_feedback_batch.py run() wraps the save.
- sync endpoint: create_and_run_judge_feedback_batch (@no_write_lock) wraps it via
  build_save_context(request).
- test: asserts the batch save goes through the git context and closes before the
  runner runs (enter -> exit -> run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
…arity)

Reshape the judge_feedback_batch job from "create-and-run" to "run an existing
batch", mirroring EvalJobWorker. The batch config is created first via the
synchronous POST .../judge_feedback_batches (which persists it under the git-sync
write lock), then the job just runs it by id.

Why:
- The batch id is now caller-supplied and lives in job.params, so it's
  retrievable via GET /api/jobs/{id} even if the run fails — closing the gap
  where a create-and-run job only surfaced the minted id in its success result.
- The worker no longer writes the config at all, so the earlier "wrap the batch
  save in the git-sync context" workaround is gone — the create endpoint owns
  that write. One less special case.
- Structurally identical to the eval job (params carry the entity id; results
  live on disk), which sets up deriving a disk summary + a real compute_state
  later.

Changes:
- JudgeFeedbackBatchJobParams: now {project_id, task_id, judge_feedback_batch_id}
  (was a full CreateJudgeFeedbackBatchRequest). run()/describe() load the batch
  by id and read its config off disk.
- Job result echoes judge_feedback_batch_id from params (no longer "created").
- Route docstring updated; regenerated api_schema.d.ts.
- Tests: run loads an existing batch, missing-batch 404s, describe/props derived
  from the persisted batch. Dropped the now-obsolete config-save-wrap test.

The synchronous create-and-run / run endpoints are left in place (now redundant
with create + job) for callers not yet migrated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
…ints

The synchronous create / run / create-and-run endpoints are being superseded by
the create + job flow, so mark them DENY_AGENT (the chat assistant should go
through POST /api/jobs/judge_feedback_batch/run for dashboard visibility). GETs
stay ALLOW_AGENT. Drops the now-unused agent_policy_require_approval import.

NOTE: not enforced until the agent-check annotation JSONs are regenerated (the
policy lookup reads those, not the live spec). See PR notes re: keeping the
create endpoint agent-callable for the new create-then-run-job flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
…nnotations

Follow-up to the DENY_AGENT change:
- Flip the sync create endpoint back to ALLOW_AGENT — the agent creates the batch
  here (cheap, no model calls) and then runs it via POST /api/jobs/judge_feedback_batch/run.
  Only the sync run / create-and-run stay DENY_AGENT (superseded by the job).
- Regenerate the agent-check annotation JSONs so the policy is actually enforced
  (the chat backend reads the dumped JSONs, not the live spec) — fixes the
  check_api_bindings CI job. run / create-and-run now dump as "deny"; create and
  the GETs stay "allow".

Final judge_feedback_batch agent policy:
  create                    -> allow
  run (existing, sync)      -> deny
  create-and-run (sync)     -> deny
  run as job (/api/jobs/...) -> allow + approval
  list / get / runs (GET)   -> allow

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N
…n-existing

Judge feedback batch: job runs a pre-existing batch (eval parity)
@leonardmq leonardmq changed the title Judge feedback batch jobs: worker parity + deep-review fixes Judge feedback batch: job-backed run of a pre-existing batch (eval parity) + deep-review fixes Jul 2, 2026
@tawnymanticore tawnymanticore merged commit cc5347f into integration/auto-mode-e2e Jul 2, 2026
12 checks passed
tawnymanticore added a commit that referenced this pull request Jul 2, 2026
…1536)

* Add failing-train-examples eval API for reflective optimization

New endpoint POST .../evals/{eval_id}/eval_config/{eval_config_id}/failing_train_examples
that samples an Eval's train set, runs its judge (EvalConfig), and returns the
datapoints that fail — with the judge's plaintext feedback — to feed GEPA-style
reflection loops.

- libs/core/.../eval/failing_examples.py: find_failing_train_examples() shuffles the
  train set (eval.train_set_filter_id), judges items in concurrent batches via the
  eval_config_eval path, and stops once `count` failures are found or `max_samples`
  items are judged ("oversample, return the requested amount"). An example fails only
  when all output scores fall below the bar (normalize_rating < threshold, default
  0.75). Results are persisted as EvalRuns and reused on later calls.
- eval_api.py: thin endpoint + request/response models, allowed for the Kiln assistant
  (ALLOW_AGENT) with a detailed OpenAPI description. Committed the agent-policy
  annotation so the assistant's policy lookup permits the call.
- Regenerated app/web_ui api_schema.d.ts.
- Tests: 13 core + 6 API.

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

* Address review: reuse loaded eval, resilient persistence, more tests

- eval_api.py: resolve the eval config from the already-loaded eval instead of
  eval_config_from_id (which re-reads the task/eval from disk). Keeps the same 404.
- failing_examples.py: wrap EvalRun persistence in its own try/except so a save
  failure logs and is skipped instead of crashing the whole concurrent batch; the
  computed scores are still returned.
- tests: cover missing scores in example_fails, and judge errors being skipped
  (still counted as examined) during orchestration.

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

* Rework into a persistent Judge Job (config + run + poll)

Replace the stateless failing_train_examples endpoint with a durable, runnable
JudgeJob model per review feedback. A JudgeJob samples dataset items by tag,
judges their existing outputs with an eval config (the judge), and records each
item's pass/fail + the judge's feedback as child JudgeJobRuns — surfacing failing
examples for reflective optimization.

- datamodel/judge_job.py: JudgeJob (child of Task, parent of JudgeJobRun) with
  target_tags, eval_config_id, run_config_id (metadata), count/max_samples/threshold,
  latest_status, and an outcome summary. Registered on Task + datamodel __init__.
- adapters/eval/judge_job_runner.py: JudgeJobRunner mirrors EvalRunner — judges in
  eval_config_eval mode, yields Progress for SSE, persists child runs, reuses cached
  results, and updates status/outcome (running -> succeeded/failed). Keeps the
  example_fails/score_passes/feedback helpers from the prior engine.
- studio_server/judge_job_api.py: create / run (SSE) / create-and-run (SSE) / get /
  runs / list. The model id is the job id; GET is the poll. Registered in
  desktop_server; "Judge Jobs" tag added; agent-policy annotations regenerated
  (ALLOW_AGENT). Removed the standalone endpoint and its annotation; regenerated
  api_schema.d.ts.
- Tests: datamodel, runner, and API (incl. SSE).

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

* Address review: cancel on disconnect, validation, defensive hardening

- judge_job_runner: catch GeneratorExit/CancelledError and mark the job `cancelled`
  (an SSE disconnect previously left it stuck in `running`); add a test.
- judge_job_runner: harden score_passes (catch TypeError), feedback extraction
  (skip non-str values), and tag matching (None-safe).
- judge_job_api: validate run_config_id (if provided) and reject a second run with
  409 when the job is already running; add tests.
- judge_job datamodel: constrain count/max_samples (ge=1) and threshold (0-1).

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

* Make judge jobs synchronous (per Leonard's feedback)

The chat tool drops SSE event payloads and a judge minibatch is short, so SSE buys
nothing here. Switch run / create-and-run to synchronous JSON and drop the
job-status/poll/cancel layer.

- judge_job datamodel: remove latest_status, outcome, JudgeJobStatus, JudgeJobOutcome.
  A JudgeJob is now just a config (+ JudgeJobRun children).
- judge_job_runner: run() returns a JudgeJobRunResult (failing_runs + counts) instead
  of streaming Progress; no status writes, lock, or GeneratorExit handling.
- judge_job_api: run / create-and-run block and return JudgeJobRunResponse
  (judge_job + failing_runs + counts). Counts are FYI for the caller, not persisted.
  Removed the SSE helper and the 409 already-running guard.
- Tests + api_schema.d.ts updated.

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

* Address Leonard's review: rename task_run_id, collect per-item errors

- Fix CI lint failure (ruff format test_judge_job.py).
- Rename JudgeJobRun.dataset_id -> task_run_id (more descriptive of the TaskRun
  it points at). Updates model, runner, API, tests, and regenerated TS schema.
- Collect per-item judge/save errors during a run and return them in the sync
  response (new JudgeJobItemError + JudgeJobRunResult.errors / run-response
  `errors`). Errors no longer silently swallowed: one bad item doesn't abort the
  run, the item is left un-persisted, and re-running retries only un-persisted
  items. A non-empty `errors` list signals partial success to the caller.

Per-run aggregate counts stay derived (returned, not persisted) — the durable
record remains the per-item JudgeJobRun children.

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

* Judge jobs: full-coverage gate + return all judged runs

Replace `count` (always early-stops) with `stop_after_failures: int | None`:
- None (default) = judge the whole matching set up to max_samples (full
  coverage), so a val gate can pair results by task_run_id.
- set = stop once that many failures are found (the cheap train-signal minibatch).

Add `judged_runs` (every item judged this run, pass and fail) to the run result
and response, keyed by task_run_id — the piece that makes a paired
baseline-vs-candidate gate computable instead of an aggregate-count approximation.

hit_cap now means coverage was capped (max_samples reached before
stop_after_failures, or the matching set exceeded max_samples).

Regenerated api_schema.d.ts.

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

* Judge jobs: retry transient per-item errors

Generating/judging invokes a model, so transient rate-limit/connection blips are
expected; without a retry they were collected as per-item errors and silently
shrank coverage (skewing a gate). Route the judge call through _judge_with_retry,
reusing the eval runner's transient-error classification (max_retries=2). Non-
transient errors are still collected once, not retried.

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

* Judge jobs: generate-and-judge mode (scoped candidate gate)

Add generate_outputs: when true, run run_config_id on each tagged item to produce
a fresh output and judge that — gating a candidate config scoped to the tagged
items, in one sync call (no run_comparison, no full-eval scores). run_config_id is
required in this mode.

- Runner resolves the run config's RunConfigProperties + preloads its skills
  (mirrors EvalRunner.run_job) and instantiates the evaluator with them.
- _judge_with_retry branches to run_task_and_eval; the fresh TaskRun is discarded
  (allow_saving=False) so the dataset is never polluted.
- Skip the result cache in generate mode (generation is non-deterministic).
- Record run_config_id on each JudgeJobRun for provenance; lower default
  concurrency when generating.

Regenerated api_schema.d.ts.

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

* Judge jobs: address review (approval, robustness, eval-type, naming)

Per Leonard's review:
- Run / Create-and-run endpoints now require agent approval
  (agent_policy_require_approval) — they make model calls, so the bot shouldn't
  kick them off without consent (auto-mode still bypasses). Create + GETs stay
  ALLOW_AGENT. Regenerated the two annotation files.
- run()'s asyncio.gather now uses return_exceptions=True: an unexpected throw in
  one item is converted to a per-item error instead of discarding the whole
  chunk's results.
- Reject reference-answer evals when generate_outputs=false (no reference to
  compare a pre-existing output against; the judge would error per item).
  Validated in the API (422) and the runner constructor (last line of defense).
- Rename eval_config_for_id -> eval_config_from_id to match the *_from_id
  convention.

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

* Rename JudgeJob -> JudgeFeedbackBatch

Avoid collision with the upcoming Job management system (and GEPA's "job"), per
Leonard's review. Mechanical rename across the datamodel (JudgeFeedbackBatch /
JudgeFeedbackBatchRun), runner, API (paths: /judge_jobs -> /judge_feedback_batches),
the Task accessor (judge_feedback_batches()), OpenAPI tag, files, tests. Regenerated
api_schema.d.ts and the agent-policy annotations (removed the stale judge_jobs
files — the endpoints never shipped). Zero external callers, so no compat shim.

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

* Judge feedback batch: return continuous per-dimension scores (P1)

The pass/fail bit (example_fails at the 0.75 threshold) discards the continuous
signal, so a 2★→3★ improvement reads as zero gradient and a small val slice
quantizes to a few loss levels. Add aggregate_normalized_scores() and return
mean_normalized_scores (per-dimension mean over judged_runs, 0-1 higher=better)
+ mean_normalized_score (overall) in the run response — a usable gate/loss metric
the caller no longer has to hand-compute from judged_runs[].scores.

Part of the loss-function API review; P2/P3/P4/P7 are a separate follow-up.

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

* judge_feedback_batch: surface generation usage (tokens/cost/latency)

Stop discarding the candidate run's Usage in generate_outputs mode.
JudgeFeedbackBatchRun now carries a per-item `usage`; the runner aggregates
it (aggregate_usage) into total_usage / mean_cost / mean_latency_ms on
JudgeFeedbackBatchRunResult, and the run API exposes both per-run and
aggregate usage. Lets the auto-optimize loop read deterministic cost/latency
signals from the same call that gates quality. None on the judge-only path.

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

* judge_feedback_batch: exponential backoff on transient/rate-limit retries

Fixed 1s gaps don't let a 429 clear and just re-flood a throttled provider —
back off progressively (delay, 2x, 4x) in _judge_with_retry, and raise the
default retry_delay 1.0->2.0 so the server-side backoff is gentler. Surfaced
by an auto-optimize smoke whose single-sample judge_feedback_batches calls hit
provider rate limits (Cerebras/Gemini-Flash candidates).

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

* feat(jobs): add judge_feedback_batch job type (parallel kick-offs)

Make judge feedback batch runs job-backed, alongside the existing synchronous
endpoints, so an agent can fire many gates and POST /api/jobs/wait on all of
them at once instead of blocking on each synchronous call serially.

- JudgeFeedbackBatchJobWorker wraps JudgeFeedbackBatchRunner unchanged (mirrors
  EvalJobWorker). Single-shot: progress reported once, supports_pause=False.
- POST /api/jobs/judge_feedback_batch/run (two-segment, approval-gated) — the
  job-backed counterpart to POST /judge_feedback_batches/run.
- Result carries the aggregate scores/usage/latency + the batch id; per-item
  runs (with the judge's feedback) are persisted, fetched via .../runs.
- Worker tests + regenerated api_schema.d.ts.

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

* feat(jobs): agent-check annotation for the judge_feedback_batch job route

The chat backend gates call_kiln_api via AgentPolicyLookup, which reads the
static agent-check annotation JSON (dumped from the OpenAPI), NOT the live
spec. Without an annotation the new POST /api/jobs/judge_feedback_batch/run is
rejected as "not allowed", so the assistant falls back to the synchronous
endpoint. Regenerated via `make annotations`; marks it allow + requires_approval.

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

* Judge feedback batch: job-backed run of a pre-existing batch (eval parity) + deep-review fixes (#1542)

* feat(jobs): judge_feedback_batch worker — stream progress, publish props, describe

Bring the judge_feedback_batch job worker to parity with the eval job worker
(the auto-mode e2e integration flagged three gaps):

- Progress: the worker reported progress only once at the end, and used
  train_set_size as the denominator — so when the matching set exceeded
  max_samples the bar stalled below 100% even on full success. The runner now
  takes an optional progress_callback and streams (num_judged, error_count,
  planned_total) per judged chunk; the worker reports live progress and its
  final snapshot against the planned (capped) count min(train_set_size,
  max_samples), so success reaches total on full coverage.
- Metadata: publish JudgeFeedbackBatchJobProperties via describe() (mirrors
  EvalJobProperties) — judge/eval names, algorithm, model, mode, run config,
  tags, max_samples — and render them in the jobs table (previously just a raw
  "Judge_feedback_batch" label).
- Errors: unchanged wiring already surfaces per-item errors to the View Errors
  UI; improved message quality by unwrapping KilnRunError.original (mirrors
  EvalJobWorker._error_detail).
- API models: add field descriptions to JudgeFeedbackBatchJobResult and the
  project_id/task_id params. Regenerated api_schema.d.ts.

Tests: runner progress_callback streaming, worker describe() + capped-total
progress, frontend judge-property rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* fix(judge-feedback-batch): address deep code review (6 moderate + polish)

Deep multi-phase review of the judge_feedback_batch feature (0 critical). Fixes:

Moderate:
- Progress double-counted errored items: the runner's num_judged already
  includes errored items, so reporting success=num_judged alongside a separate
  error count breached the processed=success+error contract and overshot 100%.
  Report success=num_judged-error_count in both the streamed callback and the
  final snapshot (worker), matching the eval reference's disjoint counters.
- Gate-mode task_run_id pairing was defeated by the random shuffle when the tag
  set exceeded max_samples (two runs sampled disjoint subsets). Gate mode now
  selects deterministically (sorted by id) before capping; train-signal mode
  keeps the random minibatch.
- Added a JudgeFeedbackBatch model_validator coupling generate_outputs=True to a
  required run_config_id and rejecting empty target_tags (defense in depth,
  mirrors EvalRun) — the API request model already validated both.
- Documented that num_judged counts attempts (errors/cache included); use
  len(judged_runs) for a scored-item count.
- Added runner tests for the empty candidate set and gate-mode hit_cap=True
  (set > max_samples), incl. proof that two gate runs cover the same ids.

Polish:
- Runner: save-error uses _error_detail; concurrency=max(1, concurrency) guard;
  mean-of-means comment; "Judge feedback batch" wording.
- API: judge_feedback_batch_from_id 404 message fixed + accepts a preloaded task
  (removes the run endpoint's double task load); run docstring covers generate
  mode.
- UI: surface eval_name + stop_after_failures in the jobs table.
- Tests: worker run stub gets the full signature; judge-only describe() test.

Full checks.sh green. Regenerated api_schema.d.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* fix(judge-feedback-batch): report per-item errors live so View Errors works mid-run

The streamed progress callback bumps progress.error per chunk, which makes the
"View Errors" button appear while the job is still running. But the error
MESSAGES were only written to the job error log in a post-run loop over
result.errors — after runner.run() returned. So mid-run the button showed but
the log was empty ("No error messages recorded"); the messages only appeared
once the job completed. This diverged from the eval worker, which logs each
error live via an observer.

Add an error_callback to JudgeFeedbackBatchRunner.run(), fired the moment each
per-item error is collected (both the judge/save error and the unexpected-throw
paths), and wire the worker to write it to the error log immediately. Errors
still appear in the returned result.errors for the synchronous endpoint (which
passes no callback). Removed the worker's post-run loop to avoid double-logging.

Tests: runner test asserting errors are delivered live (interleaved with
progress, not batched to the end); worker stub updated to emit errors via the
callback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* fix(jobs-table): guard target_tags render against missing properties

Defensive: use optional chaining on jp.target_tags so a job record with
incomplete properties can't throw a TypeError and crash the whole jobs table
render. (Per gemini-code-assist PR review.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* fix(judge-feedback-batch): wrap batch-config save in git-sync context

The batch config was written with a raw save_to_file() outside any atomic_write
— in both the job worker and the synchronous create-and-run endpoint. Under auto
git-sync that new (untracked) file is dirty working-tree state, so the runner's
first per-item child write enters atomic_write, whose ensure_clean() treats it as
a crashed session and stashes it away (include_untracked=True). Net effect: the
batch config is never committed/pushed and is removed from the working tree,
orphaning the committed child runs.

Wrap the batch-config save in the same save_context used for the child writes
(coalescing None -> no-op default_save_context) so it gets its own atomic_write /
commit before the runner starts. The batch save and each child save are separate,
non-nested atomic_write blocks, so re-entrancy is not a concern. Mirrors how
EvalRunner already wraps every per-item write (the eval worker creates no parent
entity, so it was already correct).

- worker: judge_feedback_batch.py run() wraps the save.
- sync endpoint: create_and_run_judge_feedback_batch (@no_write_lock) wraps it via
  build_save_context(request).
- test: asserts the batch save goes through the git context and closes before the
  runner runs (enter -> exit -> run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* refactor(judge-feedback-batch): job runs a pre-existing batch (eval parity)

Reshape the judge_feedback_batch job from "create-and-run" to "run an existing
batch", mirroring EvalJobWorker. The batch config is created first via the
synchronous POST .../judge_feedback_batches (which persists it under the git-sync
write lock), then the job just runs it by id.

Why:
- The batch id is now caller-supplied and lives in job.params, so it's
  retrievable via GET /api/jobs/{id} even if the run fails — closing the gap
  where a create-and-run job only surfaced the minted id in its success result.
- The worker no longer writes the config at all, so the earlier "wrap the batch
  save in the git-sync context" workaround is gone — the create endpoint owns
  that write. One less special case.
- Structurally identical to the eval job (params carry the entity id; results
  live on disk), which sets up deriving a disk summary + a real compute_state
  later.

Changes:
- JudgeFeedbackBatchJobParams: now {project_id, task_id, judge_feedback_batch_id}
  (was a full CreateJudgeFeedbackBatchRequest). run()/describe() load the batch
  by id and read its config off disk.
- Job result echoes judge_feedback_batch_id from params (no longer "created").
- Route docstring updated; regenerated api_schema.d.ts.
- Tests: run loads an existing batch, missing-batch 404s, describe/props derived
  from the persisted batch. Dropped the now-obsolete config-save-wrap test.

The synchronous create-and-run / run endpoints are left in place (now redundant
with create + job) for callers not yet migrated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* chore(judge-feedback-batch): deny agent access to the sync POST endpoints

The synchronous create / run / create-and-run endpoints are being superseded by
the create + job flow, so mark them DENY_AGENT (the chat assistant should go
through POST /api/jobs/judge_feedback_batch/run for dashboard visibility). GETs
stay ALLOW_AGENT. Drops the now-unused agent_policy_require_approval import.

NOTE: not enforced until the agent-check annotation JSONs are regenerated (the
policy lookup reads those, not the live spec). See PR notes re: keeping the
create endpoint agent-callable for the new create-then-run-job flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

* chore(judge-feedback-batch): keep create agent-callable; regenerate annotations

Follow-up to the DENY_AGENT change:
- Flip the sync create endpoint back to ALLOW_AGENT — the agent creates the batch
  here (cheap, no model calls) and then runs it via POST /api/jobs/judge_feedback_batch/run.
  Only the sync run / create-and-run stay DENY_AGENT (superseded by the job).
- Regenerate the agent-check annotation JSONs so the policy is actually enforced
  (the chat backend reads the dumped JSONs, not the live spec) — fixes the
  check_api_bindings CI job. run / create-and-run now dump as "deny"; create and
  the GETs stay "allow".

Final judge_feedback_batch agent policy:
  create                    -> allow
  run (existing, sync)      -> deny
  create-and-run (sync)     -> deny
  run as job (/api/jobs/...) -> allow + approval
  list / get / runs (GET)   -> allow

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ZXQFWTXbstDj7vBihcx5N

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(judge-feedback-batch): expose concurrency in job params

Add an optional `concurrency` field to JudgeFeedbackBatchJobParams and
forward it to JudgeFeedbackBatchRunner.run. Null keeps the runner's
mode-aware default (5 when generating outputs, 25 when judging existing
ones); values below 1 are clamped to 1 by the runner. Regenerated
api_schema.d.ts and added a param-forwarding test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019w3CFeeewrMyMRdTv3SgQm

* feat(judge-feedback-batch): validate concurrency >= 1 at schema level

Add ge=1 to the concurrency param so invalid input returns a 422 up
front (consistent with max_samples / stop_after_failures) instead of
being silently clamped by the runner. Update the description, regenerate
api_schema.d.ts, and add a validation test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019w3CFeeewrMyMRdTv3SgQm

* feat(eval-job): expose concurrency in job params

Add an optional `concurrency` field to EvalJobParams and forward it to
EvalRunner.run, mirroring the judge feedback batch job param. Null keeps
the runner's default (25); ge=1 rejects invalid values with a 422 at the
API boundary (matching max_samples-style validation) rather than a
runner-side ValueError. EvalRunner.run now accepts int | None and
resolves the default internally, keeping 25 a single source of truth.
Regenerated api_schema.d.ts; added forwarding + validation tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019w3CFeeewrMyMRdTv3SgQm

* fix(eval-job): CI + review — params round-trip and single-source default

- test_api: _EVAL_PARAMS now includes the defaulted concurrency=None, so
  the stored-params round-trip assertion in test_run_eval_job_creates_typed_eval_job
  matches again (the new optional field is serialized into job.params).
- Address gemini review: extract DEFAULT_EVAL_CONCURRENCY (=25) in eval_runner
  as the single source of truth for the default, use it in EvalRunner.run and
  interpolate it into the EvalJobParams.concurrency field description so the doc
  can't drift from the runner default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019w3CFeeewrMyMRdTv3SgQm

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Leonard Q. Marcq <marcqleonard@gmail.com>
Co-authored-by: Leonard Q. Marcq <leonard@getkiln.ai>
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