Class: Rubino::Interaction::Lifecycle

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/interaction/lifecycle.rb

Overview

Orchestrates the full lifecycle of a single user interaction. Coordinates all phases from input to final response and post-turn jobs.

Instance Method Summary collapse

Constructor Details

#initialize(session:, event_bus:, ui:, config:, ignore_rules: false, agent_definition: nil, cancel_token: nil, model_override: nil, provider_override: nil, max_tool_iterations: nil) ⇒ Lifecycle

Returns a new instance of Lifecycle.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/rubino/interaction/lifecycle.rb', line 8

def initialize(session:, event_bus:, ui:, config:, ignore_rules: false,
               agent_definition: nil, cancel_token: nil,
               model_override: nil, provider_override: nil,
               max_tool_iterations: nil)
  @session = session
  @event_bus = event_bus
  @ui = ui
  @config = config
  @ignore_rules = ignore_rules
  @agent_definition = agent_definition
  @cancel_token = cancel_token
  @model_override = model_override
  @provider_override = provider_override
  # Explicit per-run cap from `--max-turns` (Runner → here → IterationBudget).
  # nil ⇒ use the configured agent_max_tool_iterations (#141).
  @max_tool_iterations = max_tool_iterations
  @state = State.new
  @session_repo = Session::Repository.new
  @message_store = Session::Store.new
end

Instance Method Details

#execute(input, image_paths: [], input_queue: nil, paste_expansions: []) ⇒ Object

Executes the full interaction lifecycle for a user input. image_paths are vision-capable attachments routed natively to the primary model (ruby_llm ‘with:` slot); only consumed on the first iteration of the inner agent loop. Subsequent iterations carry tool results, not user input, and don’t re-attach the images. input_queue is the optional steering hand-off (Interaction::InputQueue) for mid-turn injection: when given, the inner agent loop drains any text the user typed while it was working and folds it into the turn at a safe iteration boundary. Nil for the API/server path and for nested SUBAGENT runs, which stay isolated — no user injection, exactly as before.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/rubino/interaction/lifecycle.rb', line 39

def execute(input, image_paths: [], input_queue: nil, paste_expansions: [])
  @event_bus.emit(Events::INTERACTION_STARTED, input: input)
  @state.transition_to!(:receiving_input, event_bus: @event_bus)

  # 1. Persist user message
  @state.transition_to!(:loading_session, event_bus: @event_bus)
  persist_user_message(input, paste_expansions: paste_expansions)

  # 2. Load memory (if enabled)
  @state.transition_to!(:loading_memory, event_bus: @event_bus)
  memory_context = load_memory(input)

  # 3. Build prompt/context
  @state.transition_to!(:building_context, event_bus: @event_bus)
  messages = build_messages(input, memory_context)
  tools = load_tools

  # 4. Check token budget
  @state.transition_to!(:checking_budget, event_bus: @event_bus)
  messages = check_and_compact(messages)

  # 5. Run agent loop
  @state.transition_to!(:calling_model, event_bus: @event_bus)
  response = run_agent_loop(messages, tools, image_paths: image_paths,
                                             input_queue: input_queue)

  # 6. Persist session state
  @state.transition_to!(:persisting_session, event_bus: @event_bus)
  update_session_state

  # 7. Enqueue post-turn jobs
  @state.transition_to!(:enqueueing_jobs, event_bus: @event_bus)
  enqueue_post_turn_jobs

  # 8. Finish
  # Carry the final assistant text as the terminal event's authoritative
  # output, regardless of streaming mode. Streaming consumers also receive
  # it incrementally via MODEL_STREAM (message.delta), but the
  # non-streaming path emits no deltas — so without this, a completed run
  # would terminate with no final text for clients to display. This makes
  # run.completed the single source of truth for the answer.
  @state.transition_to!(:finished, event_bus: @event_bus)
  @event_bus.emit(Events::INTERACTION_FINISHED, output: response.to_s)

  response
rescue StandardError => e
  @state.transition_to!(:failed, event_bus: @event_bus)
  @event_bus.emit(Events::INTERACTION_FAILED, error: e.message)
  raise
end