Skip to content

fix(synthesis): tolerate string list fields in map-reduce partials; synthesis failure no longer invalidates a complete run#588

Merged
the-data-viking merged 2 commits into
mainfrom
claude/happy-austin-5d2bb0
Jul 20, 2026
Merged

fix(synthesis): tolerate string list fields in map-reduce partials; synthesis failure no longer invalidates a complete run#588
the-data-viking merged 2 commits into
mainfrom
claude/happy-austin-5d2bb0

Conversation

@the-data-viking

Copy link
Copy Markdown
Collaborator

Summary

Fixes two defects observed on a real run (2026-07-19), plus a pricing refresh flagged in the same log.

1. Map-reduce typed validation rejected newline-bulleted strings

The map model sometimes emits themes/agreements/disagreements/surprises as one newline-bulleted string ("\n- A\n- B") instead of a JSON array — drift the single-pass synthesis path already tolerates. PartialSummary now coerces a string into a list via a before-mode field validator (bullet/enumeration markers stripped), and synthesize_panel_mapreduce writes the coerced lists back onto each map result before reduce.

2. Synthesis-stage failure invalidated an otherwise-complete run

A synthesis failure set run_invalid: true + message="PANEL RUN INVALID" + exit 2 even though every panelist response completed and was present in the output. Synthesis failure now degrades gracefully across CLI/SDK/MCP: synthesis: null (or the fallback payload), structured synthesis_error kept, a synthesis_failed warning surfaced, run_invalid: false, exit 0. Panelist-level invalidity (failure rate, total failure, cost gate, SIGINT, missing-input refusals) still exits 2, and panel synthesize keeps exit 2 — synthesis is that command's sole deliverable. Docs updated (docs/production-operations.md exit-code table).

3. Pricing refresh (separate commit)

llama-3.3-70b was stale vs the OpenRouter feed ($0.10/$0.32 → $0.13/$0.40, matching the +33% divergence warning). gpt-4o-mini was flagged too (-26%) but its entry exactly matches the current feed and OpenAI published rates — that divergence was downstream provider routing, so it's unchanged.

Tests

  • New regression tests: string-valued list fields parse at the map boundary (unit + end-to-end through synthesize_panel_mapreduce); synthesis exception / pre-flight overflow / chunk overflow / judge fallback no longer invalidate a complete run.
  • Former fail-loud tests (sp-avmm/sy-549) rewritten to the new contract.
  • Full suite: 3458 passed locally; the 7 remaining failures (test_mcp_stdio_sampling, test_seed, one test_structured_output) fail identically on clean HEAD in this environment (pre-existing).

Labeled semver:minor since the exit-code/run_invalid envelope semantics are a documented behavioral contract change.

🤖 Generated with Claude Code

openclaw-dv and others added 2 commits July 19, 2026 21:11
…rade gracefully on synthesis failure

Two defects observed on a real run (2026-07-19):

1. PartialSummary rejected map partials whose list fields came back as
   newline-bulleted strings ("\n- A\n- B") — drift the single-pass
   synthesis path already tolerates. The model now coerces a string
   into a list via a before-mode field validator (bullet/enumeration
   markers stripped), and synthesize_panel_mapreduce writes the coerced
   lists back onto the map result before reduce.

2. A synthesis-stage failure marked an otherwise-complete run invalid
   (run_invalid=true, message="PANEL RUN INVALID", exit 2) even though
   every panelist response completed and was present in the output.
   Synthesis failure now degrades gracefully across CLI/SDK/MCP:
   synthesis stays null (or the fallback payload), the structured
   synthesis_error is kept, a synthesis_failed warning is surfaced, and
   the run exits 0 with run_invalid=false. Panelist-level failures
   (failure rate, total failure, cost gate, SIGINT, missing-input) still
   invalidate. `panel synthesize` keeps exit 2 — synthesis is that
   command's sole deliverable.

Regression tests: string-valued list fields parse through the map
boundary end-to-end; synthesis exception / pre-flight overflow / chunk
overflow / judge fallback no longer invalidate a complete run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live-run divergence warning (2026-07-19) flagged provider-reported cost
~33% above the local estimate. The OR feed confirms the blended rate
moved from $0.10/$0.32 (2026-05-10 snapshot) to $0.13/$0.40.

gpt-4o-mini was flagged in the same log (-26%) but its table entry
exactly matches both the OR feed and OpenAI's published rates today —
that divergence reflected downstream provider routing on the run, not a
stale entry, so it is left unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@the-data-viking the-data-viking added the semver:minor Bump minor version on merge label Jul 20, 2026
@the-data-viking the-data-viking added the semver:minor Bump minor version on merge label Jul 20, 2026
@the-data-viking
the-data-viking merged commit 88730c7 into main Jul 20, 2026
22 of 24 checks passed
@the-data-viking
the-data-viking deleted the claude/happy-austin-5d2bb0 branch July 20, 2026 03:31

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23c3cbf459

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/synth_panel/sdk.py
# structured output) at the top level so MCP consumers don't have to
# walk into the nested synthesis dict to find them.
synthesis_warnings = list(synthesis_dict.get("warnings") or []) if isinstance(synthesis_dict, dict) else []
if isinstance(synthesis_error, dict):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle fallback results in SDK and MCP envelopes

When run_panel_sync returns a normal SynthesisResult(is_fallback=True) after exhausting schema retries, its dictionary contains is_fallback and error but no nested synthesis_error. Consequently this new warning block never runs and the SDK returns neither a top-level synthesis_error nor synthesis_failed warning; _run_panel_async in the MCP server uses the same nested-key-only test. CLI explicitly converts this case into a structured synthesis_fallback error, so derive the same payload from is_fallback here and in MCP to avoid presenting an incomplete synthesis as healthy to programmatic callers.

Useful? React with 👍 / 👎.

Comment on lines 2833 to +2834
# succeeded, hiding the failure behind mostly-empty fields. Surface a
# loud warning + a machine-detectable error payload and mark the run
# invalid, mirroring the sp-avmm hard-fail on synthesis API errors.
# loud warning + a machine-detectable error payload.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report fallback synthesis as failed in text output

When the judge returns is_fallback=True in the default text-output path, this branch now permits exit 0 but leaves synthesis_dict truthy. The run summary later at lines 3172-3173 therefore prints Synthesis: completed even though this branch just reported it as incomplete; with the invalid-run banner and nonzero exit removed, the final status is especially likely to mislead users. Select the text status using synthesis_error_payload or is_fallback before treating a synthesis dictionary as completed.

Useful? React with 👍 / 👎.

the-data-viking pushed a commit that referenced this pull request Jul 20, 2026
Resolutions: took main's graceful-degradation semantics for
tests/test_synthesis_failure.py with renamed module paths; renamed
merge-introduced synth_panel/synthpanel references in the #588 test,
docs hint, and CLI hint; noqa'd the intentional en-dash bullet regex
(RUF001 — newer ruff flags it; heals main's currently-red lint).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver:minor Bump minor version on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants