Skip to content

fix(orchestrator): log why a structured-output candidate was rejected - #1254

Draft
seonghobae wants to merge 2 commits into
mainfrom
fix/log-structured-candidate-rejection
Draft

seonghobae wants to merge 2 commits into
mainfrom
fix/log-structured-candidate-rejection

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Problem

On the strict structured-output path, a candidate whose synthesis and repair were both rejected leaves only circuit_failure agent_id=… failures=… threshold=… in the sidecar log — the same line a transport failure leaves. In .github Noema run 36166447802 four NVIDIA candidates (llama-3.2-11b-vision ×2, muse-glimmer-30b ×2) failed in 12–98 s with two provider_attempt lines each, no provider_attempt_failed, and no recorded reason, so "unusable structured output" could not be confirmed or ruled out.

The reason already exists in the trace (validation_outcome from _structured_output_error: schema_missing / invalid_json / invalid_json_object / schema_violation) but is never logged.

Change

  • New _log_structured_candidate_rejected(agent, stage, reason) next to _log_provider_attempt_failed: one DEBUG line structured_candidate_rejected agent_id=… model=… stage=synthesis|repair reason=… request_id=…. reason must match ^[a-z][a-z0-9_]{0,63}$, otherwise unclassified; provider content is never read into it.
  • Emitted before every circuit failure on the structured path: repair rejection (repair_error), repair ProviderResponseError (provider_error), and record_synthesis_failure (now takes a fixed reason: provider_error, provider_upstream_error, synthesis_exception).
  • No change to failover, breaker, budget, or response behavior.

Tests

  • tests/test_structured_candidate_rejection_log.py: a twice-invalid candidate logs exactly stage=repair reason=schema_violation and no response text (canary); free-text reasons become unclassified; nothing is emitted above DEBUG.
  • Local, focused only (heavy local runs are restricted on this host): the new file + test_structured_output_distinct_fallback.py + test_structured_output_malformed_synthesis_usage.py → 42 passed. test_telemetry.py + test_provider_reliability.py: 95 passed / 4 failed, and the same 4 fail on unmodified main (test_failover_to_backup_agent_when_primary_fails, test_unallowlisted_provider_host_is_classified_upstream_error, test_passthrough_allowlist_failure_reports_passthrough_transport, test_free_model_exhausted_allowlist_pool_fails_closed_as_502) — environment-dependent, not introduced here. Full suite and coverage are left to CI.

Scope

Draft. The .github review sidecar only picks this up after a later pin update; this PR does not change any pin. Companion: ContextualWisdomLab/.github#2381 (candidate-trail summary on the .github side).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EAXNYEtrwwF5dLCZb7Bx2V

A strict structured-output candidate whose synthesis and repair were both
rejected produced only a circuit_failure line, indistinguishable from a
transport failure. .github Noema run 36166447802 showed four NVIDIA candidates
failing this way with no reason recorded.

Add _log_structured_candidate_rejected, a DEBUG line with agent_id, model,
stage and a regex-bounded reason (the existing validation code, or a fixed
label), emitted before every circuit failure on the structured synthesis and
repair paths. Provider content is never read into the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EAXNYEtrwwF5dLCZb7Bx2V
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 10 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: bb4545d1-f940-4ea3-b8d9-d6f2a82b1df8

📥 Commits

Reviewing files that changed from the base of the PR and between 8e1f1a8 and 5be8f3f.

📒 Files selected for processing (3)
  • CHANGELOG.d/structured-candidate-rejection-log.md
  • contextual_orchestrator/orchestrator.py
  • tests/test_structured_candidate_rejection_log.py

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.

A strict structured-output rejection was only visible as circuit_failure.
Log a bounded stage and reason before that circuit failure, and keep
provider response text out of the line. HTTP 429 stays quota cooldown:
it does not emit structured_candidate_rejected and does not record a
circuit failure.
@seonghobae
seonghobae marked this pull request as ready for review September 28, 2026 11:46
@opencode-agent
opencode-agent Bot disabled auto-merge September 28, 2026 14:02

Copy link
Copy Markdown
Contributor Author

Exact-head admission correction — 5be8f3f9ec57c306936fbe14aeceffcb7ea1c610

Ready is review admission only. Fresh audit against base 8e1f1a8bf3e96e56dc8fcc90ec777883a1d56ce6 found:

  • latest terminal workflow blockers: CodeQL PR 36417643634=failure

This PR is moved to Draft/Proposed until the causal owner repair is present on a successor exact head and re-audited. Queued/pending work is neither an additional blocker nor passing evidence. No Close, force push, destructive rebase, manual rerun, synthetic status/approval, merge, auto-merge, or bypass was performed.

@seonghobae
seonghobae marked this pull request as draft September 30, 2026 05:21

This branch has not been deployed

No deployments
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