class RubyLLM::AgUI::Run

Runs one RubyLLM 2.x chat turn as one AG-UI 1.0 run.

input = RubyLLM::AgUI::Input.parse(request_body)
chat  = host_chat_for(actor, input.thread_id)   # host-owned RubyLLM::Chat
RubyLLM::AgUI::Run.new(chat:, input:, scope: "tenant:#{actor.id}").stream(to: response.stream)

The host owns authentication, authorization, the chat, the tools a run may use and the transport. This class owns the run’s identity (reserved before any provider work), the ordering of the events, and the outcome: a cancelled run is never reported as success, a provider failure is a sanitized RUN_ERROR, a paused run is an interrupt rather than a success, and an identity that is already reserved is never executed a second time.

scope is the host’s tenant/actor boundary, built from authenticated identity. The gem takes it from the host rather than deriving it from the input, because a scope derived from anything the client sent would let one client attach to another’s run.

Attributes

record[R]

The run’s authoritative record, once it has been reserved.

Public Class Methods

new(chat:, input:, scope:, store: nil, logger: nil, redact: nil, clock: nil, expires_in: nil, state: nil) click to toggle source

rubocop:disable-next Metrics/ParameterLists – one keyword per host-supplied or overridable value

# File lib/ruby_llm/ag_ui/run.rb, line 27
def initialize(chat:, input:, scope:, store: nil, logger: nil, redact: nil, clock: nil, expires_in: nil,
               state: nil)
  @chat = chat
  @input = input
  @scope = scope
  @store = store || RubyLLM::AgUI.config.run_store!
  @logger = logger || RubyLLM::AgUI.config.logger
  @redact = redact
  @clock = clock || RubyLLM::AgUI.config.clock
  @expires_in = expires_in || RubyLLM::AgUI.config.interrupt_ttl
  @mutex = Mutex.new
  validate!
  @state = state.nil? ? @input.state : state
end

Public Instance Methods

stream(to:) click to toggle source

Reserves the run and streams it to to, an IO-like stream. Returns the run’s record, which carries the status the run ended with.

The reservation happens before any provider work: a request whose input digest matches an identity already reserved attaches instead of executing, and a changed input under the same key is refused by the store. A reserved run that already ended is served as a reconnect — its outcome and state snapshot — never as a second execution.

# File lib/ruby_llm/ag_ui/run.rb, line 50
def stream(to:)
  resume.plan(now: @clock.call) if resume.needed?
  reservation = reserve
  return execute(reservation.record, to) if reservation.created?

  reconnect(reservation.record, to)
end