Rulvar API reference / @rulvar/core / BudgetHooks
Interface: BudgetHooks
Defined in: packages/core/src/runtime/agent-loop.ts:336
Budget hooks bound by the three-layer budget.
Properties
| Property | Type | Description | Defined in |
|---|---|---|---|
admitTurnExposure? | (servedBy, estimatedInputTokens, plannedOutputTokens) => (() => void) | undefined | - | packages/core/src/runtime/agent-loop.ts:391 |
assertPricedDispatch? | (servedBy) => void | The strict pre-egress pricing gate (RV1508): wired only when RunOptions.strictPricing armed it; throws typed BEFORE the wire call for a model whose price row is missing, malformed, or stale. | packages/core/src/runtime/agent-loop.ts:390 |
awaitExposureRelease? | (signal?) => Promise<"aborted" | "released" | "drained"> | Parks until the next in-flight exposure hold releases (RV1902): 'released' on that wake, 'drained' immediately when no hold is live, 'aborted' when the signal fires first. Wired beside admitTurnExposure when the cap is configured; consumed only by invocations that opted into the exposure wait. | packages/core/src/runtime/agent-loop.ts:403 |
liveExposureUsd? | () => number | Live in-flight exposure currently held by open dispatches (RV1902). | packages/core/src/runtime/agent-loop.ts:405 |
maxAffordableOutputTokens? | (servedBy, estimatedInputTokens) => number | undefined | Layer 2b, the pre-dispatch output bound: the output tokens the remaining budget still affords from servedBy for a prompt of estimatedInputTokens. The dispatch clamps the request's maxOutputTokens to it and denies the turn entirely when not even one output token fits. Undefined = unbounded (no ceiling, no price row, or free output). | packages/core/src/runtime/agent-loop.ts:347 |
maxExposureOutputTokens? | (servedBy, estimatedInputTokens) => number | undefined | Layer 2b asked of the IN-FLIGHT EXPOSURE ceiling (RV2503), wired only when the cap is configured: the output tokens the exposure room still affords for this prompt. The dispatch clamps to it too, so a turn whose full plan overshoots the exposure line is SHORTENED rather than refused while the budget can still pay for it. An answer below the serving model's output floor is ignored, so a genuine exposure exhaustion still refuses through admitTurnExposure with its own typed reason. | packages/core/src/runtime/agent-loop.ts:368 |
openCallMeter? | (servedBy) => (delta) => void | Opens the per-call marginal meter (RV1101): one meter per provider call, fed every mid-stream delta and the settle remainder of THAT call. The budget prices the call's ACCUMULATED usage and debits the increment over what the call already paid, so a long-context tier crossed by the accumulation re-prices the whole call live exactly as the settled fold will; per-slice pricing can never see that crossing (no single slice crosses the threshold). Optional: hooks without it keep the historical per-slice debit into onUsage. | packages/core/src/runtime/agent-loop.ts:418 |
remainingUsd? | () => number | undefined | The remaining chain headroom in USD (RV301): the same arithmetic the output bound above reads, before pricing. Undefined = no ceiling anywhere on the chain. The tool budget extension admits a grant against it. | packages/core/src/runtime/agent-loop.ts:357 |
signal? | AbortSignal | Layer 3: the ceiling AbortSignal. | packages/core/src/runtime/agent-loop.ts:420 |
Methods
beforeTurn()
ts
beforeTurn(): void;Defined in: packages/core/src/runtime/agent-loop.ts:338
Layer 2: before every turn; throws BudgetExhaustedError to block dispatch.
Returns
void
onUsage()
ts
onUsage(usage, servedBy): void;Defined in: packages/core/src/runtime/agent-loop.ts:407
Live usage accounting; layer 3 may respond by aborting signal.
Parameters
| Parameter | Type |
|---|---|
usage | Usage |
servedBy | `${string}:${string}` |
Returns
void