JuliusBrussee/caveman · error
token budget must be positive, zero (default), or UnlimitedT
Error message
token budget must be positive, zero (default), or UnlimitedTokenBudget
What it means
Recall validates RecallOptions.TokenBudget before running: it accepts a positive budget, 0 (meaning the 2000-token DefaultTokenBudget), or the sentinel UnlimitedTokenBudget (-1). Any other negative number is rejected because it is neither a real cap nor the explicit unlimited sentinel — the store refuses to guess whether you meant bounded or unbounded.
Source
Thrown at mem/store.go:425
// Recall returns the memories most relevant to query, ranked by BM25, filtered
// by the threshold, and compressed for injection. It fails toward no recall: a
// query with no term overlap (or that clears nothing above the threshold)
// returns an empty slice, never a guess.
func (s *Store) Recall(query string, opts RecallOptions) ([]Hit, error) {
limit := opts.Limit
if limit <= 0 {
limit = DefaultLimit
}
threshold := opts.Threshold
if threshold <= 0 {
threshold = DefaultThreshold
}
budget := opts.TokenBudget
unlimited := budget == UnlimitedTokenBudget
if budget == 0 {
budget = DefaultTokenBudget
} else if budget < 0 && !unlimited {
return nil, fmt.Errorf("token budget must be positive, zero (default), or UnlimitedTokenBudget")
}
all, err := s.all()
if err != nil {
return nil, err
}
scores := bm25Scores(query, all)
type scored struct {
mem Memory
score float64
}
ranked := make([]scored, 0, len(all))
for _, m := range all {
if sc := scores[m.ID]; sc >= threshold {
ranked = append(ranked, scored{mem: m, score: sc})
}
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Use mem.UnlimitedTokenBudget (-1) explicitly when you want no cap, or 0/omitted for the 2000-token default
- Clamp computed budgets: if budget < 0 { budget = mem.UnlimitedTokenBudget } (or 0) at the call site
- If the value comes from user config, validate/normalize it at load time so the store never sees garbage
Example fix
// before
res, err := store.Recall(q, mem.RecallOptions{TokenBudget: remaining - requested}) // can be < -1
// after
budget := remaining - requested
if budget < 0 {
budget = mem.UnlimitedTokenBudget // or 0 for the safe default
}
res, err := store.Recall(q, mem.RecallOptions{TokenBudget: budget}) Defensive patterns
Strategy: validation
Validate before calling
if opts.TokenBudget < 0 && opts.TokenBudget != mem.UnlimitedTokenBudget {
opts.TokenBudget = 0 // or mem.UnlimitedTokenBudget, per your intent
}
res, err := store.Recall(q, opts) Type guard
func validTokenBudget(b int) bool {
return b > 0 || b == 0 || b == mem.UnlimitedTokenBudget
} Prevention
- Use the sentinels: 0 for default, mem.UnlimitedTokenBudget (-1) for unlimited — never other negatives
- Clamp arithmetic budgets (remaining - used) before passing them
- Normalize external config values to the three legal forms at load time
When it happens
Trigger: Passing RecallOptions{TokenBudget: -5} or any negative value other than -1. Commonly from computing a budget by subtraction (remaining - used) that underflows below -1, or misreading the API and using an arbitrary negative as 'no limit' instead of mem.UnlimitedTokenBudget.
Common situations: Adapter code mapping external 'unlimited' flags to a hardcoded -2; arithmetic on budgets going negative on small requests; version drift where a caller predates the sentinel and used any negative for unlimited.
Related errors
- cacheengine: invalid scope
- cacheengine: invalid epoch
- cachebench: no providers
- cachebench: invalid trace read limits
- cache-replay: -trace and positive request/token/trace/respon
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/8482ccfe5aed5e22.
Report an issue: GitHub.