Skip to content

Regenerated complete branches fail closed; evaluator has no branch-selection tie-break #2

Description

@StartupBros

evaluateProTurnCompletion never receives a branch-selection pointer. When a conversation mapping contains two independent, both-complete recap -> terminal-text branches under the same turn_exchange_id — the shape produced by a regenerate/retry — the leaf-ambiguity check rejects both because neither is a graph descendant of the other:

const leafCandidates = structurallySafe.filter((candidate) =>
  !structurallySafe.some((other) =>
    other.key !== candidate.key && isDescendantOf(mapping, other.key, candidate.key)
  )
);
if (leafCandidates.length !== 1) {
  return {
    done: false,
    reason: `ambiguous terminal text branches (${leafCandidates.length})`,
  };
}

That is correct for a genuinely stray/abandoned branch, but it is a false negative when both branches are finished and the UI is currently displaying one specific branch. From mapping alone the function cannot distinguish those cases.

Reproduction

root
├─ recapA(reasoning_ended)
│  └─ finalA(finished_successfully, end_turn, stop)
└─ recapB(reasoning_ended)
   └─ finalB(finished_successfully, end_turn, stop)

Both branches use the same turn_exchange_id. Current result (executed against a logic-identical port of pro-final.ts):

{
  "done": false,
  "reason": "active or graph-incomparable reasoning remains for final text candidate"
}

(The exact reason is the active-reasoning/graph-incomparability veto rather than the later ambiguous terminal text branches (2) check, but the underlying issue is the same: there is no branch-selection signal.)

Possible direction

ChatGPT conversation responses are commonly understood to include a top-level current_node pointer identifying the branch the UI displays, but I have not verified that field against a captured response from rosetta's current endpoint, so treating it as available would be premature. client.ts currently narrows the response to {mapping?: ConversationMapping}, and neither src/ nor tests/ references current_node.

Could you confirm whether the raw /backend-api/conversation/<id> response you see carries current_node? If it does, one option is to widen the response type and pass that pointer into the evaluator as an optional tie-break. If it does not, this may be an unavoidable fail-closed case and worth documenting as such.

I don't have a patch ready for this one because the right API depends on that wire fact. Filed separately from #1 because #1 is a one-line fail-open fix with a ready regression, while this is a caller/API design question.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions