Class: Pikuri::Agent::Control::StepLimit

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/agent/control/step_limit.rb

Overview

Caps the number of tool calls per Pikuri::Agent#run_loop invocation. ruby_llm has no built-in step budget; the Agent pokes #tick! on every before_tool_call callback and #reset! at the start of each turn. Once the counter exceeds the configured cap, #tick! raises Exceeded and the Agent applies the #on_exhausted policy: re-raise to the host (the default), or run the step-exhaustion synthesizer to salvage a partial answer.

Why the policy lives here, not on Agent

Synthesis can only ever fire off a tripped step limit, so an Agent.new(synthesize: …) kwarg would be meaningless whenever step_limit: is nil — an invalid combination the API would have to document away. Attaching the policy to the budget makes “what happens when the budget runs out” travel with the budget, and the nonsense state is unrepresentable. The host picks per wiring: a Q&A REPL wants :synthesize (salvage an answer from the evidence gathered so far); a coding agent wants the default :raise (a tools-free pass can’t finish writing code —stop, let the user say “continue”; #reset! at the next turn boundary refreshes the budget).

Defined Under Namespace

Classes: Exceeded

Constant Summary collapse

ON_EXHAUSTED =

Valid #on_exhausted policies.

%i[raise synthesize].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max:, on_exhausted: :raise) ⇒ StepLimit

Returns a new instance of StepLimit.

Parameters:

  • max (Integer)

    hard cap on tool-call rounds; must be positive

  • on_exhausted (Symbol) (defaults to: :raise)

    :raise (default) or :synthesize — see #on_exhausted

Raises:

  • (ArgumentError)

    if max is zero or negative, or on_exhausted is not one of ON_EXHAUSTED



61
62
63
64
65
66
67
68
69
# File 'lib/pikuri/agent/control/step_limit.rb', line 61

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

#maxInteger (readonly)

Returns the configured cap.

Returns:

  • (Integer)

    the configured cap



47
48
49
# File 'lib/pikuri/agent/control/step_limit.rb', line 47

def max
  @max
end

#on_exhaustedSymbol (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.

Returns:

  • (Symbol)

    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.



53
54
55
# File 'lib/pikuri/agent/control/step_limit.rb', line 53

def on_exhausted
  @on_exhausted
end

#stepInteger (readonly)

Returns current step count; exposed so callers can introspect it (and so tests can assert it).

Returns:

  • (Integer)

    current step count; exposed so callers can introspect it (and so tests can assert it)



99
100
101
# File 'lib/pikuri/agent/control/step_limit.rb', line 99

def step
  @step
end

Instance Method Details

#reset!void

This method returns an undefined value.

Reset the counter back to zero. Called by Pikuri::Agent at the start of each turn (in Pikuri::Agent#run_loop before forwarding the user message to the chat) so the same instance can govern many turns across a long-running REPL. Mid-loop Interloper injections deliberately do not trigger a reset — those are additional context for the same turn, not a fresh one, and a chatty user could otherwise refresh the budget forever by injecting.



93
94
95
# File 'lib/pikuri/agent/control/step_limit.rb', line 93

def reset!
  @step = 0
end

#tick!void

This method returns an undefined value.

Increment the tool-call counter; raise Exceeded once it crosses #max. Called by Pikuri::Agent from its before_tool_call wiring.

Raises:



78
79
80
81
# File 'lib/pikuri/agent/control/step_limit.rb', line 78

def tick!
  @step += 1
  raise Exceeded, @max if @step > @max
end

#to_sString

Returns short config dump for Pikuri::Agent#to_s. The policy only renders when it’s the non-default :synthesize, so existing banner output is unchanged.

Returns:

  • (String)

    short config dump for Pikuri::Agent#to_s. The policy only renders when it’s the non-default :synthesize, so existing banner output is unchanged.



104
105
106
107
# File 'lib/pikuri/agent/control/step_limit.rb', line 104

def to_s
  policy = @on_exhausted == :raise ? '' : ", on_exhausted=#{@on_exhausted}"
  "StepLimit(max=#{@max}#{policy})"
end