Skip to content

feat(client): grounded judge context and per-judge diagnostics - #84

Draft
apucacao wants to merge 1 commit into
alexis/judge-output-format-ignoredfrom
alexis/grounded-judge-context
Draft

feat(client): grounded judge context and per-judge diagnostics#84
apucacao wants to merge 1 commit into
alexis/judge-output-format-ignoredfrom
alexis/grounded-judge-context

Conversation

@apucacao

@apucacao apucacao commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Why

A judge is a second AI call that grades the first one. Three things were wrong with how it ran. This PR fixes all three.

  1. The judge was grading blind. It saw the user request and the model's answer, but not the data the run actually looked at. So it had to guess. It now receives that evidence as context.

  2. A broken judge could sink a good answer. Judges run after the primary call is finished and billed. If the judge's own config or provider failed, the error propagated out and threw away the response the caller already paid for. Each judge is now isolated: its failure is recorded, not raised.

  3. Failures were invisible. A judge that was disabled, timed out, or errored simply vanished from the results, with no way to tell which. Every skip or failure now leaves a JudgeDiagnostic behind.

What changed

  • config(...) gains judge_context (a lazy callback, resolved once after the primary call) and judge_timeout_ms.
  • ProviderResponse, the stream done event, and JudgeTask gain judge_context; those plus the graph response gain judge_diagnostics.
  • The context is validated as JSON under 64 KiB, then injected into each judge's message_history inside UNTRUSTED_ACTUATOR_EVIDENCE_BEGIN / ..._END. It never reaches the primary model and is never recorded in telemetry. A judge's own prompt is a LaunchDarkly AI Config and stays swappable at runtime; the evidence rides in a fixed slot that prompt cannot displace.

Breaking change

run_judges and build_judge_tasks now return RunJudgesResult { judge_results, judge_diagnostics } and BuildJudgeTasksResult { judge_tasks, judge_diagnostics, judge_context } instead of the bare value. The per-entry shape inside is unchanged. This is a clean break on purpose: a wrapper that returned only results would reintroduce the silent-failure hole point 3 closes.

Stacked

Based on #86 (fix(judges): a judge config's outputFormat must not reach the provider), not on main, because both change judges.py. Review #86 first; its diff is small. GitHub retargets this PR to main automatically when #86 merges.

@apucacao
apucacao force-pushed the alexis/grounded-judge-context branch from 1279bfd to 8a96052 Compare September 11, 2026 10:17
Callers can now hand judges evidence about what actually happened during a
request. config() takes a lazy judge_context callback because the value does
not exist yet when config() is called: the caller's tools fill it while the
primary handler runs. The SDK resolves it exactly once, right after the
primary handler succeeds and before output-format parsing, so every request
with a callback freezes the same snapshot whether or not a judge is sampled.

A resolved context must be acyclic JSON of at most 64 KiB encoded. It is
returned unchanged on ProviderResponse.judge_context and reaches a judge only
through that judge's message_history variable, between the
UNTRUSTED_ACTUATOR_EVIDENCE_BEGIN and UNTRUSTED_ACTUATOR_EVIDENCE_END lines.
It never reaches the primary model, the track data, or a span. With no
callback the judge prompt is byte-identical to before.

Judges are now isolated from each other and from the primary result. Config
lookup, provider call, parse and tracking each sit behind their own boundary,
bounded by judge_timeout_ms, and a failure produces one JudgeDiagnostic
instead of discarding work that already succeeded. A judge that beats the
clock and then fails to track keeps its result. A judge that misses the clock
has its late completion consumed silently, so it can neither mutate results
nor emit the score metric. Diagnostics carry codes only, never exception text,
and the same codes are shared with the TypeScript SDK.

run_judges and build_judge_tasks now return result objects carrying both the
results and the diagnostics, which is a breaking change for direct callers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

BREAKING CHANGE: run_judges and build_judge_tasks now return a result
object (RunJudgesResult with judge_results/judge_diagnostics, and
BuildJudgeTasksResult with judge_tasks/judge_diagnostics/judge_context)
instead of the bare value. The per-entry shape inside judge_results is
unchanged. Both are exported from the package.
@apucacao
apucacao changed the base branch from main to alexis/judge-output-format-ignored September 11, 2026 14:09
@apucacao
apucacao force-pushed the alexis/grounded-judge-context branch from 8a96052 to c5635ab Compare September 11, 2026 14:09
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