Documentation
¶
Overview ¶
Package budgetguard stops an agent loop before it runs away.
A ReAct loop is closed by the graph: a tool result folds back into the model, which may call another tool, forever. Nothing in that circuit counts, so a model that will not converge — or a tool that keeps returning something it finds interesting — bills every iteration until someone notices. This project has already paid for that once, which is why implicit retries were removed from the scheduler in May 2026.
Put this in the loop and it becomes the one place with a ceiling.
Index ¶
- Constants
- type Component
- func (c *Component) GetInfo() module.ComponentInfo
- func (c *Component) Handle(ctx context.Context, handler module.Handler, port string, msg any) module.Result
- func (c *Component) Instance() module.Component
- func (c *Component) OnSettings(_ context.Context, msg any) error
- func (c *Component) Ports() []module.Port
- type Context
- type Exceeded
- type Proceed
- type Request
- type Settings
Constants ¶
const ( ComponentName = "budget_guard" RequestPort = "request" ProceedPort = "proceed" ExceededPort = "exceeded" )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
func (*Component) GetInfo ¶
func (c *Component) GetInfo() module.ComponentInfo
type Exceeded ¶
type Exceeded struct {
Context Context `` /* 161-byte string literal not displayed */
Reason string `json:"reason" title:"Reason" description:"Which ceiling was reached: iterations, tokens, or cost."`
Iteration int `json:"iteration" title:"Iteration"`
SpentTokens int `json:"spentTokens" title:"Spent Tokens"`
SpentUSD float64 `json:"spentUSD" title:"Spent (USD)"`
}
Exceeded is the stop. It says which ceiling was hit, because "the agent stopped" is not something anyone can act on.
type Proceed ¶
type Proceed struct {
Context Context `json:"context,omitempty" configurable:"true" title:"Context"`
Iteration int `json:"iteration" title:"Iteration" description:"Incremented. Map back into the guard on the next pass."`
SpentTokens int `json:"spentTokens" title:"Spent Tokens" description:"Running total including this iteration."`
SpentUSD float64 `json:"spentUSD" title:"Spent (USD)" description:"Running total including this iteration."`
Remaining int `json:"remainingIterations" title:"Remaining Iterations"`
}
Proceed means the loop is still within budget. Its fields are the next iteration's counters, ready to be mapped back into the guard.
type Request ¶
type Request struct {
Context Context `` /* 152-byte string literal not displayed */
// Iteration is absent on the first pass and echoed back from proceed
// afterwards. A caller that forgets to thread it through gets a loop that
// never counts, so proceed emits it ready to map straight back in.
Iteration int `` /* 142-byte string literal not displayed */
InputTokens int `` /* 151-byte string literal not displayed */
OutputTokens int `` /* 131-byte string literal not displayed */
CostUSD float64 `json:"costUSD,omitempty" title:"Cost (USD)" description:"Cost of THIS iteration, if you price it. Accumulated by the guard."`
// Spent totals arrive back from proceed; the guard adds this iteration's
// usage to them.
SpentTokens int `json:"spentTokens,omitempty" title:"Spent Tokens" description:"Running total so far. Map from proceed."`
SpentUSD float64 `json:"spentUSD,omitempty" title:"Spent (USD)" description:"Running total so far. Map from proceed."`
}
Request carries the running totals. They live in the payload, not in the component, so one guard supervises many concurrent loops without their counts bleeding into each other — the same reason retry keeps its attempt count on the message.
type Settings ¶
type Settings struct {
// MaxIterations is the ceiling that always applies. Tokens are optional
// because not every loop calls a model, but a loop with no iteration limit
// is a loop with no limit at all.
MaxIterations int `` /* 132-byte string literal not displayed */
MaxTokens int `` /* 158-byte string literal not displayed */
// MaxCostUSD is priced by the caller rather than here: rates change per
// model and per provider, and a table baked into a component would be
// wrong within a month.
MaxCostUSD float64 `` /* 162-byte string literal not displayed */
}