Documentation
¶
Overview ¶
Package budget is the agent's spend ceiling: a per-run pool of tokens and cost that every model and tool call is charged against at the dispatch waist, so a run, or a whole fan-out of runs sharing one pool, cannot exceed the limit set for it. It is the enforcement half of the README's "shared budget pool"; the metering it charges is the dispatch.Metering each governed action already reports.
A budget is an ordinary Resource (kind "Budget") keyed by the run id, so it is durable, replayable, and shared: concurrent runs charge the same record under optimistic concurrency, and a crash-resumed run reads the spend already made rather than a fresh pool. A run with no budget bound is unlimited, which keeps the standalone agent zero-config; binding one switches the posture to default-deny once the ceiling is reached.
Index ¶
- Constants
- func FromContext(ctx context.Context) (string, bool)
- func Into(ctx context.Context, id string) context.Context
- func RegisterKind(reg *resource.Registry) error
- type Hook
- type HookOption
- type Ledger
- func (l *Ledger) Available(ctx context.Context, id string, scope resource.Scope) (bool, error)
- func (l *Ledger) Charge(ctx context.Context, id string, scope resource.Scope, m dispatch.Metering) error
- func (l *Ledger) Open(ctx context.Context, id string, scope resource.Scope, limits Limits, ...) (resource.Resource, error)
- func (l *Ledger) Release(ctx context.Context, id string, scope resource.Scope, est Spent) error
- func (l *Ledger) Reserve(ctx context.Context, id string, scope resource.Scope, est Spent) (bool, error)
- func (l *Ledger) Settle(ctx context.Context, id string, scope resource.Scope, est Spent, ...) error
- type Limits
- type Spec
- type Spent
- type Status
Constants ¶
const ( // GroupVersion is the Budget kind's API group and version. GroupVersion = "budget.ionagent.io/v1alpha1" // Kind is the resource kind name. Kind = "Budget" )
Variables ¶
This section is empty.
Functions ¶
func FromContext ¶
FromContext returns the budget id bound to ctx and whether one was present. Absent an id the run is unbudgeted (unlimited), the zero-config default.
func Into ¶
Into returns a context carrying the budget id (the run id) a run charges against, so the dispatch waist's Hook reads the pool from the context rather than from a parameter. Binding it once at the top of a run applies the budget to every action that run dispatches.
func RegisterKind ¶
RegisterKind registers the Budget kind so budgets can be stored and admitted like any other resource.
Types ¶
type Hook ¶
type Hook struct {
// contains filtered or unexported fields
}
Hook enforces a run's budget at the dispatch waist. Before rejects an action when the run's pool is already spent to its ceiling; After charges the action's metering onto the pool. Because the waist governs every model and tool call, one Hook caps the whole run with no per-call wiring. It composes with the capability admitter rather than replacing it: capability decides what may run, budget decides whether there is still budget to run it.
With a reservation configured (WithReservation), Before instead reserves an upper-bound estimate against the pool before admitting, and After settles the reservation into the actual metered spend. This closes the concurrent-overshoot gap a plain check-then-charge leaves: when many actions share one pool (a fan-out of children), each would otherwise read the same under-budget snapshot and all pass; the atomic reserve makes them admit against one consistent view.
func NewHook ¶
func NewHook(store resource.Store, opts ...HookOption) *Hook
NewHook returns a budget Hook backed by store. Add it to a dispatcher with dispatch.WithHook to budget every action that dispatcher governs.
func (*Hook) After ¶
After records the action's spend. In reservation mode it settles the reservation into the actual metering (releasing the estimate and charging the actual in one write, so a zero metering simply releases the reservation); otherwise it charges the metering. It is best effort: the work has already run, so a write that fails to persist is surfaced through the dispatcher's observability rather than failing the action.
type HookOption ¶
type HookOption func(*Hook)
HookOption configures a budget Hook.
func WithReservation ¶
func WithReservation(est Spent) HookOption
WithReservation makes the Hook reserve an upper-bound estimate against the pool before each action and settle the actual after, instead of a plain check-then- charge. The estimate should be an upper bound on a single action's spend (for a model call, the input plus the max output tokens), so the pool can never be exceeded even under a concurrent fan-out. A zero estimate (the default) keeps the original check-then-charge behaviour.
type Ledger ¶
type Ledger struct {
// contains filtered or unexported fields
}
Ledger reads and writes run budgets on the resource store. It is the durable home of a run's spend: concurrent charges converge on one record under the store's optimistic concurrency, so a shared pool stays correct across a fan-out and across a crash.
func (*Ledger) Available ¶
Available reports whether the run identified by id still has budget: true when no budget is bound (unlimited), and true until the recorded spend reaches a set limit. It is the pre-execution check the dispatch waist gates an action on.
func (*Ledger) Charge ¶
func (l *Ledger) Charge(ctx context.Context, id string, scope resource.Scope, m dispatch.Metering) error
Charge adds m to the run's recorded spend, retrying under optimistic concurrency so concurrent charges against a shared pool all land. A run with no budget bound is a no-op (unlimited). Charging more than the limit is allowed: the limit is enforced before an action runs (see Available), and the actual cost is only known after, so the recorded spend is the truth and can settle slightly past the ceiling.
func (*Ledger) Open ¶
func (l *Ledger) Open(ctx context.Context, id string, scope resource.Scope, limits Limits, owners ...resource.OwnerReference) (resource.Resource, error)
Open creates (or returns the existing) budget for run id in scope, capped by limits. Pass owners to bind the budget to the run that owns it (an OwnerReference to the run's goal), so the budget is garbage-collected when the run ends rather than outliving it.
func (*Ledger) Release ¶
Release returns a reservation to the pool without spending it, for an action that was admitted but did not run (rejected downstream, cancelled). It floors the reserved total at zero so a doubled release under a race cannot drive it negative.
func (*Ledger) Reserve ¶
func (l *Ledger) Reserve(ctx context.Context, id string, scope resource.Scope, est Spent) (bool, error)
Reserve atomically holds est against the run's pool before an action runs: the reserve half of reserve-before-dispatch. It admits (returns true) while the pool still has budget left, where "left" means the committed total (spent plus already-reserved) has not reached a set limit, and records the reservation; it refuses (false) once the pool is fully committed. Because the check and the reservation are one compare-and-set, concurrent actions sharing a pool admit against one consistent view rather than each reading an under-budget snapshot and overshooting together. A run with no budget bound is unlimited: always admits, records nothing. With an upper-bound estimate the ceiling cannot be exceeded; with a smaller estimate the overshoot is bounded by the in-flight estimates.
func (*Ledger) Settle ¶
func (l *Ledger) Settle(ctx context.Context, id string, scope resource.Scope, est Spent, m dispatch.Metering) error
Settle converts a reservation into actual spend once an action finishes: it releases est and charges the metered actual in one atomic write, so the pool never briefly double-counts (reserved and spent) nor under-counts (released before charged). A zero est with a real metering behaves like Charge.
type Limits ¶
Limits is a run's spend ceiling. A zero field means no limit on that axis, so the zero Limits is unlimited (a budget that only tracks spend without capping it). Tokens is a total token count; Cost is in the provider's currency unit.
type Spec ¶
type Spec struct {
Limits Limits `json:"limits"`
}
Spec is a budget's desired state: the ceiling the run may spend up to.
type Spent ¶
Spent is what a run has consumed so far, accumulated from the metering of every governed action it ran.
type Status ¶
Status is a budget's observed state: what the run has spent, and what it has reserved but not yet spent. The ledger accumulates both under optimistic concurrency. Reserved is the in-flight commitment a reserve-before-dispatch holds against the pool so concurrent actions (a fan-out of children sharing one budget) admit against one consistent view and cannot all pass an under-budget check and then overshoot together. An action settles its reservation into Spent when it finishes.
func DecodeStatus ¶
DecodeStatus reads the typed status from a resource.