Class: RobotLab::Budget::Ledger

Inherits:
Object
  • Object
show all
Defined in:
lib/robot_lab/budget/ledger.rb

Overview

Thread-safe reserve/reconcile ledger for tracking consumption against per-dimension limits (e.g. :tokens, :cost).

Call reserve! before doing billable work, so an already-exhausted budget is caught before the work is attempted rather than after. Once the work completes, reconcile! replaces the reservation with the actual amount consumed (which may be more or less than what was reserved — LLM call sizes aren't known in advance). release! drops an unused reservation without recording any consumption.

A dimension with no configured limit is treated as unlimited: reserve! never raises for it and remaining returns Float::INFINITY.

Examples:

ledger = RobotLab::Budget::Ledger.new(limits: { tokens: 10_000, cost: 0.50 })
ledger.reserve!(:tokens, ledger.remaining(:tokens))
# ... do the billable work ...
ledger.reconcile!(:tokens, ledger.remaining(:tokens), actual_tokens_used)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(limits: {}, consumed: {}) ⇒ Ledger

Returns a new instance of Ledger.

Parameters:

  • limits (Hash{Symbol=>Numeric}) (defaults to: {})

    per-dimension ceilings; a dimension absent from this hash is treated as unlimited

  • consumed (Hash{Symbol=>Numeric}) (defaults to: {})

    starting consumption (e.g. restored from a prior run)



34
35
36
37
38
39
# File 'lib/robot_lab/budget/ledger.rb', line 34

def initialize(limits: {}, consumed: {})
  @mutex = Mutex.new
  @limits = limits
  @consumed = Hash.new(0).merge(consumed)
  @reserved = Hash.new(0)
end

Instance Attribute Details

#consumedObject (readonly)

Returns the value of attribute consumed.



29
# File 'lib/robot_lab/budget/ledger.rb', line 29

attr_reader :limits, :consumed

#limitsHash{Symbol=>Numeric} (readonly)

Returns per-dimension ceilings.

Returns:

  • (Hash{Symbol=>Numeric})

    per-dimension ceilings



29
30
31
# File 'lib/robot_lab/budget/ledger.rb', line 29

def limits
  @limits
end

Instance Method Details

#reconcile!(key, reserved_amount, actual_amount) ⇒ void

This method returns an undefined value.

Replaces a prior reservation with the actual amount consumed.

Parameters:

  • key (Symbol)

    the budget dimension

  • reserved_amount (Numeric)

    the amount previously passed to reserve!

  • actual_amount (Numeric)

    the amount actually consumed



69
70
71
72
73
74
# File 'lib/robot_lab/budget/ledger.rb', line 69

def reconcile!(key, reserved_amount, actual_amount)
  @mutex.synchronize do
    @reserved[key] = [0, @reserved[key] - reserved_amount].max
    @consumed[key] += actual_amount
  end
end

#release!(key, amount) ⇒ void

This method returns an undefined value.

Drops a reservation without recording any consumption (e.g. the reserved work was skipped).

Parameters:

  • key (Symbol)

    the budget dimension

  • amount (Numeric)

    the amount previously passed to reserve!



82
83
84
# File 'lib/robot_lab/budget/ledger.rb', line 82

def release!(key, amount)
  @mutex.synchronize { @reserved[key] = [0, @reserved[key] - amount].max }
end

#remaining(key) ⇒ Numeric

Returns remaining budget for key, floored at 0; Float::INFINITY when unlimited.

Parameters:

  • key (Symbol)

    the budget dimension

Returns:

  • (Numeric)

    remaining budget for key, floored at 0; Float::INFINITY when unlimited



88
89
90
91
92
93
94
95
# File 'lib/robot_lab/budget/ledger.rb', line 88

def remaining(key)
  @mutex.synchronize do
    limit = @limits[key]
    next Float::INFINITY unless limit

    [limit - @consumed[key] - @reserved[key], 0].max
  end
end

#reserve!(key, amount) ⇒ void

This method returns an undefined value.

Reserves amount against +key+'s remaining budget.

A no-op (never raises) when key has no configured limit.

Parameters:

  • key (Symbol)

    the budget dimension

  • amount (Numeric)

    the amount to reserve

Raises:



49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/robot_lab/budget/ledger.rb', line 49

def reserve!(key, amount)
  @mutex.synchronize do
    limit = @limits[key]
    next unless limit

    committed = @consumed[key] + @reserved[key]
    if committed + amount > limit
      raise BudgetExceeded, "budget exceeded for #{key}: #{committed + amount} > #{limit}"
    end

    @reserved[key] += amount
  end
end