Class: Rubino::Output::TurnRecorder

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/output/turn_recorder.rb

Overview

Collects the per-turn telemetry the machine-readable headless output needs (‘rubino prompt –output-format json|stream-json`) by subscribing to the existing process event bus — the SAME MODEL_CALL_FINISHED event the CLI status row, the SSE server and metrics already consume. Nothing new is threaded through the agent loop or the Runner’s return value; the loop simply emits a richer MODEL_CALL_FINISHED payload (input/output tokens + stop_reason) and this recorder aggregates it off the bus.

The per-step content blocks for stream-json are NOT reconstructed from (lossy, truncated) tool events — they’re read back from the persisted session messages by ResultSerializer, which already hold the full assistant(tool_use) / tool(result) structure with correct call-id pairing.

Captured here:

num_turns   — count of completed model calls (MODEL_CALL_FINISHED), the
              same notion Claude Code reports.
usage       — summed input/output tokens across every model call in the
              turn. cache_* tokens are surfaced when (and only when) the
              provider reports them; MiniMax does not, so they stay 0.
stop_reason — the LAST model call's normalized finish reason, mapped to
              the Claude-Code exit_reason vocabulary by the serializer.

Attach before the run, detach in an ensure. A recorder is single-run.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(event_bus: Rubino.event_bus) ⇒ TurnRecorder

Returns a new instance of TurnRecorder.



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubino/output/turn_recorder.rb', line 33

def initialize(event_bus: Rubino.event_bus)
  @event_bus    = event_bus
  @num_turns    = 0
  @input_tokens = 0
  @output_tokens = 0
  @cache_creation_input_tokens = 0
  @cache_read_input_tokens     = 0
  @stop_reason = nil
  @model_id    = nil
  @attached    = false
end

Instance Attribute Details

#cache_creation_input_tokensObject (readonly)

Returns the value of attribute cache_creation_input_tokens.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def cache_creation_input_tokens
  @cache_creation_input_tokens
end

#cache_read_input_tokensObject (readonly)

Returns the value of attribute cache_read_input_tokens.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def cache_read_input_tokens
  @cache_read_input_tokens
end

#input_tokensObject (readonly)

Returns the value of attribute input_tokens.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def input_tokens
  @input_tokens
end

#model_idObject (readonly)

Returns the value of attribute model_id.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def model_id
  @model_id
end

#num_turnsObject (readonly)

Returns the value of attribute num_turns.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def num_turns
  @num_turns
end

#output_tokensObject (readonly)

Returns the value of attribute output_tokens.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def output_tokens
  @output_tokens
end

#stop_reasonObject (readonly)

Returns the value of attribute stop_reason.



29
30
31
# File 'lib/rubino/output/turn_recorder.rb', line 29

def stop_reason
  @stop_reason
end

Instance Method Details

#attach!Object

Subscribes to the bus. Idempotent.



46
47
48
49
50
51
52
# File 'lib/rubino/output/turn_recorder.rb', line 46

def attach!
  return self if @attached

  @attached = true
  @event_bus.on(Interaction::Events::MODEL_CALL_FINISHED) { |p| record_model_call(p) }
  self
end

#detach!Object

Drops THIS recorder’s contribution. The bus has no per-listener removal, so we flip the flag and the closure becomes an inert no-op — it only ever mutates this object, never anything global.



57
58
59
60
# File 'lib/rubino/output/turn_recorder.rb', line 57

def detach!
  @attached = false
  self
end