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
The run’s authoritative record, once it has been reserved.
Public Class Methods
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
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