-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(combos): preserve declared targets under the send budget #4763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { isDeclaredReasoningEffort } from "../../reasoning-effort"; | |
| import { recordAttemptRequestedEffort } from "../request-log"; | ||
| import { | ||
| CODEX_TEXT_GUARDED_BUDGET_POLICY, | ||
| createRequestExecutionBudget, | ||
| deriveRequestExecutionBudget, | ||
| isRequestExecutionBudget, | ||
| } from "../../lib/request-execution-budget"; | ||
| import type { | ||
|
|
@@ -107,25 +107,27 @@ export function comboExecutionBudgetPolicy(declaredTargets: number): RequestExec | |
| /** | ||
| * A budget scope that keeps its own recovery ledgers but spends the SAME request-wide counter. | ||
| * | ||
| * `used` is redefined as an accessor onto the parent because the factory reads it back off this | ||
| * object -- `remainingBaseSends` and the total check both do -- so a copied number would let a | ||
| * combo target run its ladder against a stale total, which is precisely the per-layer counting | ||
| * this work exists to remove. The reserve, alternate-target and transition ledgers stay | ||
| * per-scope on purpose: a combo target's account failover is its own recovery decision, while | ||
| * the request total still bounds every target together. | ||
| * The sharing has to happen inside the factory. Redefining `used` as an accessor onto the parent | ||
| * only shared what callers read from the outside: `remainingBaseSends`, the total check and the | ||
| * reserve test all consult the factory's own private counter, which an overridden property | ||
| * cannot reach. Each derived scope therefore admitted dispatches as though the request had spent | ||
| * nothing, and the per-target holdback below -- expressed against `maxTotalModelSends` -- had | ||
| * nothing to hold back from. | ||
| * | ||
| * `deriveRequestExecutionBudget` binds the scope to the parent's real ledger, including pending | ||
| * externally-counted bookings and the durable-spend observer, all of which must travel together. | ||
| * A pending booking is a send already counted in the total and waiting for its reporter, and the | ||
| * observer books by watching that same counter move (#4707) -- so a scope that spent the counter | ||
| * without carrying the observer would move it without booking, and this combo's child sends | ||
| * would go missing from the spend ledger. The reserve, alternate-target and transition ledgers | ||
| * stay per-scope on purpose: a combo target's account failover is its own recovery decision, | ||
| * while the request total still bounds every target together. | ||
| */ | ||
| export function deriveSendBudgetScope( | ||
| parent: RequestExecutionBudget, | ||
| policy: RequestExecutionBudgetPolicy, | ||
| ): RequestExecutionBudget { | ||
| const scope = createRequestExecutionBudget(policy, parent.logicalRequestId); | ||
| Object.defineProperty(scope, "used", { | ||
| get: () => parent.used, | ||
| set: (value: number) => { parent.used = value; }, | ||
| enumerable: true, | ||
| configurable: true, | ||
| }); | ||
| return scope; | ||
| return deriveRequestExecutionBudget(parent, policy); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a combo target uses an adapter-owned transport such as Kiro or Cursor, this shared derivation makes the combo's AGENTS.md reference: AGENTS.md:L376-L379 Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 22703
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 11377
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 50375
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 23579
🏁 Script executed:
sed -n '526,590p' src/combos/failover.tsRepository: lidge-jun/opencodex
Length of output: 4666
🏁 Script executed:
sed -n '590,640p' src/combos/failover.tsRepository: lidge-jun/opencodex
Length of output: 1387
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 24530
🏁 Script executed:
Repository: lidge-jun/opencodex
Length of output: 6090
🏁 Script executed:
rg -n -C 10 "waitForProviderRequestSlot" srcRepository: lidge-jun/opencodex
Length of output: 21379
Release the combo reservation when pacing rejects a child before dispatch.
core-combo.ts:403-408reserves acountedExternallydispatch and callspermit.use(). This closes the permit but leavespendingExternalSendsset.adapter-dispatch.ts:433-438waits for a provider slot before callingonDispatch(). A pacing rejection returns an error without reaching the wire. The adapter maps it to a 502 response, andcomboFailureDecision()classifies 5xx responses as"hop", socore-combo.ts:692-706can advance to another target.When that later child sends,
request-send-budget.ts:34-37settles the stale pending booking before charging the reported send. The ledger therefore keepsspentone higher than the number of physical sends and can deny one later retry or fallback. The evidence does not show actual sends exceedingmaxTotalModelSends.Associate each reservation with its child dispatch. Settle it at the physical-send boundary, and release that reservation on every pre-dispatch exit. Use identity-based bookkeeping instead of the shared pending count.
🤖 Prompt for AI Agents