budgetguard

package
v0.7.25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 6 Imported by: 0

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

View Source
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

func (*Component) Handle

func (c *Component) Handle(ctx context.Context, handler module.Handler, port string, msg any) module.Result

func (*Component) Instance

func (c *Component) Instance() module.Component

func (*Component) OnSettings

func (c *Component) OnSettings(_ context.Context, msg any) error

func (*Component) Ports

func (c *Component) Ports() []module.Port

type Context

type Context any

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 */
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL