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

  1. Use mem.UnlimitedTokenBudget (-1) explicitly when you want no cap, or 0/omitted for the 2000-token default
  2. Clamp computed budgets: if budget < 0 { budget = mem.UnlimitedTokenBudget } (or 0) at the call site
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/8482ccfe5aed5e22. Report an issue: GitHub.