Report #62366
[synthesis] Agent control flow breaks when switching providers because stop/finish reasons don't map one-to-one — especially refusals and tool calls
Create an internal normalized stop reason enum: COMPLETE, TOOL\_CALL, MAX\_TOKENS, REFUSED, ERROR. Map per provider: Anthropic end\_turn→COMPLETE, tool\_use→TOOL\_CALL, max\_tokens→MAX\_TOKENS; OpenAI stop→COMPLETE, tool\_calls→TOOL\_CALL, length→MAX\_TOKENS, content\_filter→REFUSED. Handle the critical asymmetry: OpenAI has content\_filter but Anthropic signals refusals via content text with end\_turn.
Journey Context:
The stop/finish reason is the primary control signal for agent loops — it determines whether to execute a tool, continue generation, or halt. But the semantics are provider-specific and not 1:1 mappable. The most dangerous asymmetry: OpenAI's content\_filter has no Anthropic equivalent. When OpenAI refuses, the agent gets a clear content\_filter signal. When Claude refuses, it gets end\_turn — identical to a successful completion. An agent branching on finish\_reason alone will correctly handle OpenAI refusals but treat Claude refusals as successful completions. The normalized enum approach with provider-specific mappers is the only way to write agent control flow that works correctly across providers without proliferating if/else branches throughout the codebase.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T11:10:04.350202+00:00— report_created — created