Skip to content

fix: the default budget path cannot block the first invocation #43

Description

@briananderson1222

Problem

The README describes Dispatch as applying "capability constraints and budgets," which reads as enforcement. In the default configuration, at least one call is always paid for before anything can stop it.

src/engine.ts:89-97 — the pre-loop budget check compares elapsedBefore / tokensBefore / costBefore, which are reductions over already-completed attempts. On the first iteration attempts is [], so those sums are all 0 and cannot trip maxTotalTokens or maxCostUsd regardless of the candidate's worstCaseUsage. runtime.invoke() fires at :146; the only usage-based check runs after invocation at :181-185.

Genuine pre-call gating exists only on the separate, opt-in authorizationLedger path (:103-140, reserving via assertCapacity in src/authorization.ts:195-210) — which is solid, race-safe work. The README concedes the gap in one easily-skimmed line ("Per-invocation budgets are measured from one Dispatch call").

For an expensive model, "cannot prevent the first call" is most of the budget.

Acceptance criteria

  • The default path compares candidate.worstCaseUsage against the remaining budget before the first runtime.invoke().
  • A candidate whose worst case exceeds the budget is skipped, with that recorded on the receipt.
  • Test: a plan whose budget is smaller than the single candidate's worst case performs zero invocations.
  • If pre-call gating is intended to remain ledger-only, the README says so where the budget claim is made.

From a read-only adversarial audit, 2026-07-29. All evidence below was re-verified against origin/main (an earlier pass read stale checkouts and produced four false findings; those were discarded). Nothing was modified in any repository.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions