Class: Pikuri::Agent::Control::StepLimit
- Inherits:
-
Object
- Object
- Pikuri::Agent::Control::StepLimit
- Defined in:
- lib/pikuri/agent/control/step_limit.rb
Overview
Caps the number of tool calls per Pikuri::Agent#run_loop. ruby_llm has no
step budget; the Agent pokes #tick! on every before_tool_call and
#reset! at turn start. Once the count exceeds the cap, #tick! raises
Exceeded and the Agent applies #on_exhausted: re-raise to the host
(default), or run the synthesizer to salvage a partial answer.
Why the policy lives here, not on Agent
Synthesis can only fire off a tripped step limit, so an
Agent.new(synthesize: ...) kwarg would be meaningless when
step_limit: is nil — an invalid combination. Attaching the policy to
the budget makes "what happens when it runs out" travel with the budget,
and that nonsense state unrepresentable. Hosts pick per wiring: a Q&A
REPL wants :synthesize (salvage from evidence so far); a coding agent
wants :raise (a tools-free pass can't finish code — stop, let the user
say "continue"; #reset! refreshes the budget next turn).
Defined Under Namespace
Classes: Exceeded
Constant Summary collapse
- ON_EXHAUSTED =
Valid #on_exhausted policies.
%i[raise synthesize].freeze
Instance Attribute Summary collapse
-
#max ⇒ Integer
readonly
The configured cap.
-
#on_exhausted ⇒ Symbol
readonly
What Pikuri::Agent#run_loop does when this budget trips:
:raiselets Exceeded propagate to the host;:synthesizeruns the tools-free synthesizer rescue. -
#step ⇒ Integer
readonly
Current step count.
Instance Method Summary collapse
-
#initialize(max:, on_exhausted: :raise) ⇒ StepLimit
constructor
A new instance of StepLimit.
-
#reset! ⇒ void
Reset the counter to zero.
- #tick! ⇒ void
-
#to_s ⇒ String
Short config dump for Pikuri::Agent#to_s; the policy renders only when the non-default
:synthesize.
Constructor Details
#initialize(max:, on_exhausted: :raise) ⇒ StepLimit
Returns a new instance of StepLimit.
53 54 55 56 57 58 59 60 61 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 53 def initialize(max:, on_exhausted: :raise) raise ArgumentError, "max must be positive, got #{max}" if max <= 0 raise ArgumentError, "on_exhausted must be one of #{ON_EXHAUSTED.inspect}, got #{on_exhausted.inspect}" \ unless ON_EXHAUSTED.include?(on_exhausted) @max = max @on_exhausted = on_exhausted @step = 0 end |
Instance Attribute Details
#max ⇒ Integer (readonly)
Returns the configured cap.
39 40 41 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 39 def max @max end |
#on_exhausted ⇒ Symbol (readonly)
Returns what Pikuri::Agent#run_loop does when this
budget trips: :raise lets Exceeded propagate to the
host; :synthesize runs the tools-free synthesizer
rescue. See the class header for how to pick.
45 46 47 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 45 def on_exhausted @on_exhausted end |
#step ⇒ Integer (readonly)
Returns current step count.
84 85 86 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 84 def step @step end |
Instance Method Details
#reset! ⇒ void
This method returns an undefined value.
Reset the counter to zero. Called by Pikuri::Agent at each turn start so one instance governs many turns. Mid-loop Interloper injections deliberately do not reset — they're context for the same turn, and a chatty user could otherwise refresh the budget forever by injecting.
79 80 81 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 79 def reset! @step = 0 end |
#tick! ⇒ void
This method returns an undefined value.
Increment the counter; raise Exceeded once it crosses #max. Called
from Pikuri::Agent's before_tool_call wiring.
68 69 70 71 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 68 def tick! @step += 1 raise Exceeded, @max if @step > @max end |
#to_s ⇒ String
Returns short config dump for Pikuri::Agent#to_s; the policy
renders only when the non-default :synthesize.
88 89 90 91 |
# File 'lib/pikuri/agent/control/step_limit.rb', line 88 def to_s policy = @on_exhausted == :raise ? '' : ", on_exhausted=#{@on_exhausted}" "StepLimit(max=#{@max}#{policy})" end |