fix(dispatcher): keep a bind alive when reasoning fills the output cap - #2231
Conversation
|
Expanding the second Feedback-wanted question, because it is the difference between a fix and a better-labelled version of the outage it closes. The A second cut takes a typed That makes the retry, not the floor, the thing worth getting right. The question is what the second attempt should change. Raising the ceiling is what this PR does. It converges only if reasoning happens to fit the next ceiling, and it costs more tokens each time. Lowering the effort is the stronger answer. The operator's job is to pick a preset and a repository from a message. That is classification, not deliberation. A retry at low or no effort spends almost nothing on hidden reasoning and leaves effectively the whole budget for the visible answer, so it converges rather than escalating, and it costs less rather than more. The design question underneath, which is why this is worth asking rather than just patching: the operator inherits None of this is a request to hold the PR. It closes a real defect, the derivation and the retry are both right, and a typed floor with a named reason is strictly better than what is on main today. The ask is for the reviewer's judgement on whether the second attempt should lower effort instead of raising the ceiling, and whether the operator's effort should be its own rather than inherited — either now or as a named follow-up. |
87ac6eb to
f2d668b
Compare
|
Live receipt for the defect this fixes, from production twenty minutes ago rather than from a fixture. Door run Worth stating for the reviewer, since it bears on how hard to push on the two Feedback-wanted questions: this is not a latent defect being fixed prophylactically. It is firing on the current production build several times a day, and it fires harder after the provider change this release is gating, because the Responses wire counts reasoning tokens inside the same The release is being held for this pull request on Justin's instruction to get every open pull request into it, so the review round here is the last gate before both this fix and the provider switch reach production together. |
There was a problem hiding this comment.
Changes requested: The retry path is sound, but unset effort still leaves reasoning-capable Responses calls on the undersized initial cap.
Warning
Changes requested · head f2d668b · 1 finding: 1 minor
| Severity | Finding | Where |
|---|---|---|
| minor | F1 Reasoning allowance disappears when effort uses the model default | src/core/dispatch/route.ts:60 |
Full review
F1 — providerStructuredModel explicitly delegates to the model’s default when effort is unset, so a reasoning-capable OpenAI Responses model can still consume hidden reasoning while outputCapWithReasoning leaves its initial ceiling at 374 tokens. That guarantees an avoidable first truncation and may spend the shared 20-second timeout before the larger retry runs. Gate the allowance on the reasoning-counting wire/model capability rather than explicit effort presence, and add an unset-effort Responses regression.
- Keep the flat 4,096-token allowance rather than scaling it by effort level, but effort presence is the wrong gate because unset does not mean no reasoning.
- Keep raising the retry ceiling. Lowering effort changes classification semantics and is not reliable truncation recovery. Giving the operator its own low-effort configuration may be worthwhile separately, but should not replace this retry behavior.
f2d668b to
24877eb
Compare
Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
24877eb to
74a8ed0
Compare
|
Re-review requested at F1, the reasoning allowance disappearing when effort is unset. The regression is a second row on the existing cap-conformance table — Both answers from the last round were taken as given. The allowance is still a flat 4,096 rather than scaled by effort level, and the retry still raises the ceiling rather than lowering the effort. Only the gate changed. The separate question — whether the door should have its own effort instead of inheriting the general preset's by sharing a key — is filed on its own and deliberately not in this pull request. About the head. One commit. It differs from the previously reviewed |
The operator's output cap was sized for the visible answer alone, but on a reasoning wire the hidden reasoning is counted under the same ceiling, so a turn could be cut before it emitted any tool call — and a cut ended the pipeline. The cap now carries a reasoning allowance when the wire and the bound effort call for one, a cut retries once at a larger ceiling, and a second cut falls to a typed
generalbind instead of ending an owned pipeline.Why: Closes #2099. It is the third gate on moving the model defaults to first-party OpenAI, with #2189 and #2190; after its rebase it now sits on main with #2189's evidence-gated schema re-ask in the same door loop, and it already cost pipelines today: two ship parents aborted with
the operator failed: answer cut at the output cap (374 tokens). #2106 removed the request re-typing, so the length of the ask is close to irrelevant now — what spends the budget is reasoning, which is why this survived that fix and why the flip makes it systematic.Where to look
output_cap. ⚠ This is the line that stops a cut ending a pipeline; the floor must stay a real bind, not anon_decision.Feedback wanted: Two, both about the second attempt. The allowance is a flat 4,096 gated on effort being requested at all, not scaled by its level — is effort-presence the right gate? And should the retry lower the effort rather than raise the ceiling, given that the
generalfloor behind it is a real failure for a write ask? See the comment below.Risk: Too small an allowance and the door still gets cut — but now it retries and then floors to
generalrather than ending the pipeline, so the worst case is a mis-binding, not a dead run. Too large and a runaway turn costs more tokens before the twenty-second door timeout stops it. Blast radius is every operator turn on a reasoning wire; rollback is a revert, nothing persists.Verified: Scoped operator tests, typecheck,
hygiene:checkandspecs:checkpassed on the branch; full suite is CI-gated. The live proof on the real Responses wire is human-gated below.Decisions (3)
route.tspreviously threw a plainErrorwhose message the caller would have had to match.OutputCapErrorcarries the ceiling that was hit, so the retry can compute the next one and the loop can tell a cut from a provider refusal.generalbind with reasonoutput_capkeeps an owned pipeline alive and leaves the reason on the record.Validation (5 criteria)
src/core/dispatch/operator.test.ts::an answer cut at the cap retries once at a larger cap and accepts the bindsrc/core/dispatch/operator.test.ts::a second cut floors through the typed general bind, so a cut answer never ends a pipelinesrc/core/dispatch/operator.test.ts::operatorStage — the operator's effort from defaults.efforts.general::cap conformance: openai-responses at configured medium carries reasoning plus one bindoutputCapRetryreturnsmax(current * 2, 4096)For agents
The head
87ac6ebdis the coding child's own commit, unmodified. This pull request was opened from the operator session rather than by the runner: the ship parent aborted on a clean handoff without opening one (#2197), the fourth time today across three branches. The push is recordedby: "salvage". No code was written or amended from the session.The commit subject is
fix(routing): …, whose scoperoutingis not a code-map area and would failcheck:pr-title. This pull request's title usesdispatcher, which is the areasrc/core/dispatch/belongs to, and the squash subject comes from the title.Merge order: this branch overlaps #2227 in
src/core/dispatch/operator.ts. #2227 merges first and this branch rebases onto it before its review round, so an approval is not invalidated by a later rebase.The defect's own mechanism is worth holding while reviewing: #2106 already made
bind_presetcarry the request by reference, so the visible answer no longer grows with the ask. What remained was reasoning inside the same budget. A fix that only enlarged the cap by a fixed amount would not be sufficient on its own, because reasoning has no fixed size — hence both the derivation and the retry.