Skip to content

fix: reject duplicate panel labels, correct tool_choice:none for Anthropic - #143

Open
shrdgn wants to merge 1 commit into
mainfrom
claude/vibrant-archimedes-85b9fq
Open

fix: reject duplicate panel labels, correct tool_choice:none for Anthropic#143
shrdgn wants to merge 1 commit into
mainfrom
claude/vibrant-archimedes-85b9fq

Conversation

@shrdgn

@shrdgn shrdgn commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

Scheduled repo-quality review. Two independent correctness bugs, plus a documentation clarification tied to an already-open issue.

  • Duplicate panel labels silently collide. OpenFusionConfig now rejects (at config load) a panel where two members resolve to the same effective label — an explicit duplicate label, or two entries sharing a model with no label set. Every downstream consumer keys off this label and assumed uniqueness with no validation:

    • panel.py::_run_debate_round's peer/self split (peers = [r for r in current if r.label != response.label]) excludes every response with a shared label, not just "self" — duplicate-labeled members never see each other's answer during debate, and both get routed to the same (last-registered) member config for revision, silently degrading debate quality (or using the wrong upstream credentials if the two entries differ beyond the label).
    • synthesize.py's judge prompt shows the judge two identically-labeled answer blocks with no way to tell them apart.
    • metrics.py::record_panel_member_latency merges their latency series.

    Self-fusion is exempt — it only reads panel[0] and mints its own unique {label}-{n} suffixes.

  • tool_choice: "none" mistranslated to Anthropic "auto". upstream._TOOL_CHOICE_MAP mapped "none" to {"type": "auto"} — the same as an unset/"auto" choice — instead of Anthropic's own {"type": "none"}. A client that explicitly disabled tool use for one turn could still get a tool_use block back from an Anthropic-backed panel/judge member, breaking any client-side assumption that no tool call would be returned.

  • Docs clarification: TimeoutsConfig.total_seconds (config.py) now has a docstring noting it's validated but not currently enforced anywhere in the request path — every example config sets it as if it were a wall-clock cap, but nothing reads it outside config.py itself. This is the scoped, non-controversial part of timeouts.total_seconds is documented and set in every example config, but never enforced #140 (full enforcement was explicitly deferred there pending a product decision on streaming semantics); this PR only prevents operators from being misled by the field's presence.

Test plan

  • Added test_duplicate_panel_label_rejected, test_duplicate_panel_model_without_label_rejected, test_self_fusion_exempt_from_duplicate_label_check (tests/test_config.py)
  • Added test_tool_choice_none_mapped_to_none (tests/test_upstream_anthropic.py)
  • Verified every shipped examples/*.yaml.example still passes the new panel-label validation
  • pytest -q — 494 passed
  • ruff check . — all checks passed
  • mypy openfusion/ — no issues

🤖 Generated with Claude Code

https://claude.ai/code/session_01HSnQZ5XidJbUBUg5KC3oQi


Generated by Claude Code

…ropic

Two independent correctness bugs found in a repo-quality review:

- Panel configs where two members resolve to the same effective label
  (explicit duplicate `label`, or two entries sharing a `model` with no
  `label`) silently collided: debate's peer/self split treated every
  same-labeled response as "self" so duplicate members never debated
  each other, and the judge synthesis prompt showed the judge two
  identically-labeled answers with no way to distinguish them. Now
  validated at config load with a clear error (self-fusion exempt, since
  it only reads panel[0] and mints its own unique suffixes).

- `tool_choice: "none"` on an OpenAI-style request was translated to
  Anthropic's `{"type": "auto"}` instead of `{"type": "none"}`, silently
  discarding a client's explicit "don't call tools this turn" for any
  Anthropic-backed panel/judge member.

Also clarifies in TimeoutsConfig's docstring that `total_seconds` is
accepted/validated but not currently enforced anywhere (see #140), so
operators don't get a false sense of a wall-clock guarantee from it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HSnQZ5XidJbUBUg5KC3oQi
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