Class: ActiveAgent::Delegation::Budget
- Inherits:
-
Object
- Object
- ActiveAgent::Delegation::Budget
- Defined in:
- lib/active_agent/delegation/budget.rb
Overview
Cost and latency limits for delegated work.
A sub-agent is a loop inside a loop: the parent model decides how often to call it, and each call spends tokens and wall-clock time nobody explicitly authorized. A budget puts a ceiling on that — per delegation and across the agent as a whole — so a runaway hand-off degrades into a bounded answer instead of an unbounded bill.
Every limit is optional; an empty budget imposes nothing.
Defined Under Namespace
Classes: Violation
Constant Summary collapse
- POLICIES =
What to do when a limit is reached.
:stop— return a structured "budget exhausted" result to the calling model so it can finish with what it already has (default).:raise— raise ActiveAgent::Delegation::BudgetExceededError and abort the generation. %i[stop raise].freeze
- LIMITS =
%i[max_calls max_tokens max_cost max_duration].freeze
- KEYS =
(LIMITS + %i[timeout rates on_exceeded]).freeze
Instance Attribute Summary collapse
-
#max_calls ⇒ Integer?
readonly
Maximum number of delegated calls.
-
#max_cost ⇒ Float?
readonly
Maximum cumulative spend in USD.
-
#max_duration ⇒ Float?
readonly
Maximum cumulative wall-clock seconds.
-
#max_tokens ⇒ Integer?
readonly
Maximum cumulative tokens across delegated calls.
-
#on_exceeded ⇒ Symbol?
readonly
:stop or :raise.
-
#rates ⇒ Hash?
readonly
Inline token rates in USD per 1M tokens.
-
#timeout ⇒ Float?
readonly
Per-call wall-clock timeout in seconds.
Class Method Summary collapse
-
.build(spec = nil) ⇒ Budget
Coerces a budget spec into a Budget.
Instance Method Summary collapse
-
#initialize(**options) ⇒ Budget
constructor
A new instance of Budget.
-
#limited? ⇒ Boolean
Whether any limit is set.
-
#merge(other) ⇒ Budget
Returns a budget where +other+'s settings win over this one's.
-
#policy ⇒ Symbol
The effective policy.
-
#to_h ⇒ Hash
Only the settings that were actually provided.
-
#violation_for(ledger) ⇒ Violation?
Finds the first limit the ledger has already reached.
Constructor Details
#initialize(**options) ⇒ Budget
Returns a new instance of Budget.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/active_agent/delegation/budget.rb', line 77 def initialize(**) unknown = .keys - KEYS raise ArgumentError, "Unknown delegation budget keys: #{unknown.join(", ")}. Valid keys: #{KEYS.join(", ")}" if unknown.any? @max_calls = [:max_calls] @max_tokens = [:max_tokens] @max_cost = [:max_cost] @max_duration = [:max_duration] @timeout = [:timeout] @rates = [:rates] @on_exceeded = [:on_exceeded]&.to_sym if @on_exceeded && !POLICIES.include?(@on_exceeded) raise ArgumentError, "Unknown delegation budget policy #{@on_exceeded.inspect}. Valid policies: #{POLICIES.join(", ")}" end end |
Instance Attribute Details
#max_calls ⇒ Integer? (readonly)
Returns maximum number of delegated calls.
48 49 50 |
# File 'lib/active_agent/delegation/budget.rb', line 48 def max_calls @max_calls end |
#max_cost ⇒ Float? (readonly)
Returns maximum cumulative spend in USD.
52 53 54 |
# File 'lib/active_agent/delegation/budget.rb', line 52 def max_cost @max_cost end |
#max_duration ⇒ Float? (readonly)
Returns maximum cumulative wall-clock seconds.
54 55 56 |
# File 'lib/active_agent/delegation/budget.rb', line 54 def max_duration @max_duration end |
#max_tokens ⇒ Integer? (readonly)
Returns maximum cumulative tokens across delegated calls.
50 51 52 |
# File 'lib/active_agent/delegation/budget.rb', line 50 def max_tokens @max_tokens end |
#on_exceeded ⇒ Symbol? (readonly)
Returns :stop or :raise.
60 61 62 |
# File 'lib/active_agent/delegation/budget.rb', line 60 def on_exceeded @on_exceeded end |
#rates ⇒ Hash? (readonly)
Returns inline token rates in USD per 1M tokens.
58 59 60 |
# File 'lib/active_agent/delegation/budget.rb', line 58 def rates @rates end |
#timeout ⇒ Float? (readonly)
Returns per-call wall-clock timeout in seconds.
56 57 58 |
# File 'lib/active_agent/delegation/budget.rb', line 56 def timeout @timeout end |
Class Method Details
.build(spec = nil) ⇒ Budget
Coerces a budget spec into a Budget.
67 68 69 70 71 72 73 74 75 |
# File 'lib/active_agent/delegation/budget.rb', line 67 def self.build(spec = nil) case spec when Budget then spec when nil then new when Hash then new(**spec.symbolize_keys) else raise ArgumentError, "Delegation budget must be a Hash or #{name}, got #{spec.inspect}" end end |
Instance Method Details
#limited? ⇒ Boolean
Returns whether any limit is set.
105 106 107 |
# File 'lib/active_agent/delegation/budget.rb', line 105 def limited? LIMITS.any? { |limit| public_send(limit) } end |
#merge(other) ⇒ Budget
Returns a budget where +other+'s settings win over this one's.
98 99 100 101 102 |
# File 'lib/active_agent/delegation/budget.rb', line 98 def merge(other) return self if other.nil? self.class.new(**to_h.merge(other.to_h)) end |
#policy ⇒ Symbol
Returns the effective policy.
110 111 112 |
# File 'lib/active_agent/delegation/budget.rb', line 110 def policy on_exceeded || :stop end |
#to_h ⇒ Hash
Returns only the settings that were actually provided.
133 134 135 |
# File 'lib/active_agent/delegation/budget.rb', line 133 def to_h KEYS.index_with { |key| public_send(key) }.compact end |
#violation_for(ledger) ⇒ Violation?
Finds the first limit the ledger has already reached.
Limits are checked before a call runs, because token spend can only
be measured after the fact. A budget of max_tokens: 8_000 therefore
means "stop delegating once 8,000 tokens have been spent", not "never
exceed 8,000 tokens".
123 124 125 126 127 128 129 130 |
# File 'lib/active_agent/delegation/budget.rb', line 123 def violation_for(ledger) return Violation.new(limit: :max_calls, allowed: max_calls, used: ledger.calls) if max_calls && ledger.calls >= max_calls return Violation.new(limit: :max_tokens, allowed: max_tokens, used: ledger.tokens) if max_tokens && ledger.tokens >= max_tokens return Violation.new(limit: :max_cost, allowed: max_cost, used: ledger.cost) if max_cost && ledger.cost >= max_cost return Violation.new(limit: :max_duration, allowed: max_duration, used: ledger.duration) if max_duration && ledger.duration >= max_duration nil end |