Class: Rubino::Agent::IterationBudget
- Inherits:
-
Object
- Object
- Rubino::Agent::IterationBudget
- Defined in:
- lib/rubino/agent/iteration_budget.rb
Overview
Manages turn and iteration budgets to prevent runaway loops.
Constant Summary collapse
- MAX_CAP =
Upper bound on a turn/iteration cap (F2). A 0/negative cap is rejected as “would never run”; a value this large is just as nonsensical the other way — ‘–max-turns 99999999999999` was silently accepted, defeating the whole point of a runaway guard. Reject anything above this sane ceiling with the same clear message rather than letting an effectively-unbounded cap through. 10_000 is far past any legitimate agentic turn yet small enough to keep the comparison meaningful.
10_000
Instance Method Summary collapse
-
#can_continue?(iteration) ⇒ Boolean
Returns true if the agent can continue iterating.
-
#extend!(by) ⇒ Object
Grants ‘by` more tool iterations so a turn that hit the cap can resume the SAME turn with full context (#399, the Cline/Roo “reset the counter, keep context” pattern).
-
#extendable?(iteration) ⇒ Boolean
True ONLY when offering the interactive Continue extension would actually help: the SOFT iteration ceiling (@max_tool_iterations) is what’s exhausted, and neither non-extendable rail is the blocker (#403).
-
#initialize(config: nil, max_tool_iterations: nil) ⇒ IterationBudget
constructor
A new instance of IterationBudget.
Constructor Details
#initialize(config: nil, max_tool_iterations: nil) ⇒ IterationBudget
Returns a new instance of IterationBudget.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/rubino/agent/iteration_budget.rb', line 16 def initialize(config: nil, max_tool_iterations: nil) @config = config || Rubino.configuration # A 0/negative cap is nonsense (the turn could never run a single # iteration), so REJECT it with a clear message at both entry points — # the configured `agent.max_turns` and the CLI `--max-turns N` override — # rather than silently coercing it to "unbounded" / the default and # surprising the user. nil/absent stays meaningful (unbounded rail / # config default). @max_turns = require_positive_cap!(@config.agent_max_turns, "agent.max_turns") # An explicit override (the CLI `--max-turns N` flag, threaded through # Runner → Lifecycle) wins over the config default so the documented # control knob actually caps tool iterations (#141). A nil/blank # override falls back to the configured budget, unchanged. override = require_positive_cap!(max_tool_iterations, "--max-turns") @max_tool_iterations = override || @config.agent_max_tool_iterations @max_turn_seconds = @config.agent_max_turn_seconds @turn_started_at = Time.now end |
Instance Method Details
#can_continue?(iteration) ⇒ Boolean
Returns true if the agent can continue iterating
36 37 38 |
# File 'lib/rubino/agent/iteration_budget.rb', line 36 def can_continue?(iteration) within_iteration_limit?(iteration) && within_time_limit? end |
#extend!(by) ⇒ Object
Grants ‘by` more tool iterations so a turn that hit the cap can resume the SAME turn with full context (#399, the Cline/Roo “reset the counter, keep context” pattern). Only the soft iteration ceiling moves — the max_turns OUTER rail and the max_turn_seconds safety-net are untouched, so repeated extensions can never bypass the max_turns/clock ceiling (a runaway still stops at max_turns). No-op on an unbounded (nil) cap. Returns the new ceiling.
61 62 63 64 65 66 |
# File 'lib/rubino/agent/iteration_budget.rb', line 61 def extend!(by) amount = positive_int(by) return @max_tool_iterations if amount.nil? || @max_tool_iterations.nil? @max_tool_iterations += amount end |
#extendable?(iteration) ⇒ Boolean
True ONLY when offering the interactive Continue extension would actually help: the SOFT iteration ceiling (@max_tool_iterations) is what’s exhausted, and neither non-extendable rail is the blocker (#403). extend! raises only the soft ceiling, so it is impotent against the TIME limit AND the max_turns OUTER rail. When either of those is what’s spent, extending is a no-op and re-prompting would loop forever — callers must force-summarize instead. Hence extendable? is FALSE when the time limit OR the max_turns outer rail is the blocker, and only TRUE when the soft iteration ceiling is what’s exhausted. Also false on an unbounded soft cap (nothing to extend).
50 51 52 |
# File 'lib/rubino/agent/iteration_budget.rb', line 50 def extendable?(iteration) within_time_limit? && within_turns_rail?(iteration) && !within_soft_iteration_limit?(iteration) end |