Module: Silas::Budget
- Defined in:
- lib/silas/budget.rb
Overview
Per-turn budget caps beyond max_steps: cumulative input tokens, cost, and wall-clock. Checked between steps in the framework-owned loop (never inside a continuation step). A breach PARKS the turn at zero compute (like an approval); a human resumes it with Turn#raise_budget!, which records a per-turn override consulted here ahead of the agent's limits.
Token/cost checks are deterministic (persisted step data). The timeout check reads the wall clock — benign non-determinism: a cap firing later on resume is correct (the turn genuinely ran too long across the crash). The clock RESTARTS when an approval resumes the turn (resume_turn! resets started_at): timeout bounds active stretches, never the hours a human spends deciding. Crash-rescue resumes keep the original clock — the turn was live the whole time.
Constant Summary collapse
- REASONS =
%w[max_input_tokens max_cost timeout].freeze
Class Method Summary collapse
-
.exceeded_reason(turn, agent: Silas.agent) ⇒ Object
Returns a limit-reason string if a cap is exceeded, else nil.
-
.limit_for(turn, agent, key) ⇒ Object
A human top-up (turn.budget_overrides) beats the agent's configured limit.
- .over_cost?(turn, agent) ⇒ Boolean
- .over_time?(turn, agent) ⇒ Boolean
- .over_tokens?(turn, agent) ⇒ Boolean
Class Method Details
.exceeded_reason(turn, agent: Silas.agent) ⇒ Object
Returns a limit-reason string if a cap is exceeded, else nil.
21 22 23 24 25 26 27 |
# File 'lib/silas/budget.rb', line 21 def exceeded_reason(turn, agent: Silas.agent) return "max_input_tokens" if over_tokens?(turn, agent) return "max_cost" if over_cost?(turn, agent) return "timeout" if over_time?(turn, agent) nil end |
.limit_for(turn, agent, key) ⇒ Object
A human top-up (turn.budget_overrides) beats the agent's configured limit.
30 31 32 33 |
# File 'lib/silas/budget.rb', line 30 def limit_for(turn, agent, key) override = turn.budget_overrides&.dig(key.to_s) override.nil? ? agent.public_send(key) : override end |
.over_cost?(turn, agent) ⇒ Boolean
41 42 43 44 45 46 47 |
# File 'lib/silas/budget.rb', line 41 def over_cost?(turn, agent) limit = limit_for(turn, agent, :max_cost) or return false spent = Silas::Inbox::Cost.for_turn(turn) # Only enforce on priced tokens; unpriced models can't be cost-capped. spent[:microcents] > (limit.to_f * 1_000_000) end |
.over_time?(turn, agent) ⇒ Boolean
49 50 51 52 53 54 |
# File 'lib/silas/budget.rb', line 49 def over_time?(turn, agent) limit = limit_for(turn, agent, :timeout) or return false return false unless turn.started_at (Time.current - turn.started_at) > limit end |