limits

package
v0.1.0-beta.15 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package limits enforces the stack.yaml `limits:` block: dollar budget caps with calendar-aligned windows and token-bucket rate limits, both scoped to one client, server, or tool. It implements the gateway's CallGate seam for pre-call checks and CostSettler for post-call spend settlement, and owns a small durable ledger so budget spend survives daemon restarts.

Enforcement is check-then-settle: a call is admitted against spend already recorded, and its own cost lands after it completes. Concurrent or in-flight calls can therefore overshoot a cap by their own cost; the next call after the cap is reached is denied. Budgets govern attributed cost only — a call whose model cannot be priced settles nothing.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultBurst

func DefaultBurst(callsPerMinute int) int

DefaultBurst returns the bucket capacity for a rate limit that does not set one: a few seconds of the sustained rate, never below five.

Types

type BudgetStatus

type BudgetStatus struct {
	MaxUSD        float64   `json:"max_usd"`
	SpentUSD      float64   `json:"spent_usd"`
	Percent       float64   `json:"percent"`
	Period        string    `json:"period"`
	WarnAtPercent int       `json:"warn_at_percent,omitempty"`
	WindowStart   time.Time `json:"window_start"`
	WindowEnd     time.Time `json:"window_end"`
}

BudgetStatus is one budget's consumption within its current window. All numeric fields are always present: a zero spent_usd is a real zero, not an unknown (the UI's em-dash convention is for absent data only).

type EntryStatus

type EntryStatus struct {
	// Kind is "budget" or "rate".
	Kind string `json:"kind"`
	// Scope is "client", "server", or "tool"; Key is the configured value.
	Scope string `json:"scope"`
	Key   string `json:"key"`
	// State is "ok", "warn" (budget past its warn threshold), or "exceeded".
	State string `json:"state"`

	Budget *BudgetStatus `json:"budget,omitempty"`
	Rate   *RateStatus   `json:"rate,omitempty"`
}

EntryStatus is one limit's snapshot, shared by GET /api/limits and `gridctl limits`. Exactly one of Budget or Rate is set, matching Kind.

type Period

type Period string

Period is a budget's calendar reset cadence.

const (
	PeriodDaily   Period = "daily"
	PeriodWeekly  Period = "weekly"
	PeriodMonthly Period = "monthly"
)

Budget reset periods. Windows are calendar-aligned in the supplied time's location (in production, the daemon's local timezone): daily resets at midnight, weekly on Monday 00:00, monthly on the 1st at 00:00. This is a deliberate divergence from cloud gateways' UTC alignment: a single operator's "five dollars a day" means their day.

type Policy

type Policy struct {
	// contains filtered or unexported fields
}

Policy is the compiled, enforcement-ready form of a config.LimitsConfig. A nil *Policy means no limits block was configured; every method is nil-safe and permissive. Entries are immutable after compile; only the per-entry window state mutates, under per-entry locks.

func NewPolicy

func NewPolicy(cfg *config.LimitsConfig, ledgerPath string, logger *slog.Logger) *Policy

NewPolicy compiles the limits block. A nil or empty config returns a nil policy (no limits, byte-identical legacy behavior). When budgets exist and ledgerPath is non-empty, prior spend is loaded from the ledger: entries whose stored window matches the current one resume, stale windows reset. A corrupt or missing ledger logs a WARN and starts fresh; it never fails.

func (*Policy) CarryOver

func (p *Policy) CarryOver(old *Policy)

CarryOver adopts state from a retiring policy so a hot reload never resets enforcement: budget windows carry for entries whose scope, key, and period are unchanged (cap changes deliberately keep the counter, and spend merges by maximum so a settlement that raced the swap is never lost), and rate limiters are reused for entries whose scope, key, rate, and burst are unchanged (an unrelated stack edit must not refill a drained bucket). It then marks the old policy retired, forwarding any late settlements here.

func (*Policy) Flush

func (p *Policy) Flush(_ context.Context)

Flush writes the ledger atomically (temp file + rename). Failures WARN and are retried on the next dirty signal; spend is never worth crashing over.

func (*Policy) Gates

func (p *Policy) Gates() []mcp.CallGate

Gates returns the policy's pre-call gates in canonical order: rate limits before budgets, so a rate-limited caller gets the cheaper check's message. A nil policy returns nil.

func (*Policy) SettleToolCallCost

func (p *Policy) SettleToolCallCost(ctx context.Context, call mcp.GateCall, costUSD float64)

SettleToolCallCost implements mcp.CostSettler: it adds the call's priced cost to every matching budget window. Runs synchronously on the dispatch path, so it is a few short critical sections and a channel nudge; the ledger write happens on the flusher goroutine.

func (*Policy) Start

func (p *Policy) Start(ctx context.Context)

Start launches the debounced ledger flusher. It is a no-op for a nil policy, a policy with no budgets, or an empty ledger path, so a stack without budgets gains zero goroutines. Idempotent: only the first call starts the goroutine. ctx cancellation and Stop both terminate the flusher after a final flush.

func (*Policy) Status

func (p *Policy) Status() StatusReport

Status snapshots every configured limit. Budget windows are rolled first so a report requested after midnight never shows yesterday's spend. A nil policy reports Configured: false with an empty (non-nil) entry list.

func (*Policy) Stop

func (p *Policy) Stop()

Stop terminates the flusher after a final flush. Safe to call more than once and on a policy that never started: without a running flusher it performs the final flush inline instead of waiting on the done channel.

type RateStatus

type RateStatus struct {
	CallsPerMinute int `json:"calls_per_minute"`
	Burst          int `json:"burst"`
}

RateStatus is one rate limit's configuration snapshot.

type StatusReport

type StatusReport struct {
	Configured bool          `json:"configured"`
	Entries    []EntryStatus `json:"entries"`
}

StatusReport is the full limits status payload.

Jump to

Keyboard shortcuts

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