Class: Kernai::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/kernai/recorder.rb

Overview

Append-only log of every kernel event. Entries are always stamped with their execution scope (‘depth` + `task_id`) so consumers can rebuild the parent/sub-agent tree from the flat stream.

Persistence is handled by a pluggable Sink. The default MemorySink keeps entries in RAM (matching the historical behavior), but host applications can plug their own sink (DB, file, SSE fanout, …) without subclassing the Recorder. Use CompositeSink for fan-out.

Defined Under Namespace

Modules: Sink

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(sink: Sink::MemorySink.new) ⇒ Recorder

Returns a new instance of Recorder.



89
90
91
# File 'lib/kernai/recorder.rb', line 89

def initialize(sink: Sink::MemorySink.new)
  @sink = sink
end

Instance Attribute Details

#sinkObject (readonly)

Returns the value of attribute sink.



87
88
89
# File 'lib/kernai/recorder.rb', line 87

def sink
  @sink
end

Instance Method Details

#clear!Object



119
120
121
# File 'lib/kernai/recorder.rb', line 119

def clear!
  @sink.clear!
end

#entriesObject



107
108
109
# File 'lib/kernai/recorder.rb', line 107

def entries
  @sink.entries
end

#for_event(event) ⇒ Object



131
132
133
# File 'lib/kernai/recorder.rb', line 131

def for_event(event)
  entries.select { |e| e[:event] == event.to_sym }
end

#for_step(step) ⇒ Object



127
128
129
# File 'lib/kernai/recorder.rb', line 127

def for_step(step)
  entries.select { |e| e[:step] == step }
end

#record(step:, event:, data:, scope: nil) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/kernai/recorder.rb', line 93

def record(step:, event:, data:, scope: nil)
  scope ||= {}
  entry = {
    step: step,
    depth: scope[:depth] || 0,
    task_id: scope[:task_id],
    event: event.to_sym,
    data: data,
    timestamp: Time.now.iso8601(3)
  }
  @sink.record(entry)
  entry
end

#stepsObject



123
124
125
# File 'lib/kernai/recorder.rb', line 123

def steps
  entries.map { |e| e[:step] }.uniq.sort
end

#to_aObject



111
112
113
# File 'lib/kernai/recorder.rb', line 111

def to_a
  entries.dup
end

#to_json(*_args) ⇒ Object



115
116
117
# File 'lib/kernai/recorder.rb', line 115

def to_json(*_args)
  JSON.pretty_generate(entries)
end

#token_usageHash{Symbol => Integer, nil}

Returns aggregate usage across every ‘:llm_response` entry in the recorder.

Returns:

  • (Hash{Symbol => Integer, nil})

    aggregate usage across every ‘:llm_response` entry in the recorder.



148
149
150
# File 'lib/kernai/recorder.rb', line 148

def token_usage
  build_usage(for_event(:llm_response).map { |e| e[:data] })
end

#token_usage_per_stepHash{Integer => Hash}

Returns usage grouped by step.

Returns:

  • (Hash{Integer => Hash})

    usage grouped by step.



153
154
155
156
157
# File 'lib/kernai/recorder.rb', line 153

def token_usage_per_step
  for_event(:llm_response).group_by { |e| e[:step] }.transform_values do |entries|
    build_usage(entries.map { |e| e[:data] })
  end
end

#token_usage_per_taskHash{String,Symbol => Hash}

Returns usage grouped by ‘task_id`. Root-agent turns are keyed under `:root`.

Returns:

  • (Hash{String,Symbol => Hash})

    usage grouped by ‘task_id`. Root-agent turns are keyed under `:root`.



161
162
163
164
165
# File 'lib/kernai/recorder.rb', line 161

def token_usage_per_task
  for_event(:llm_response).group_by { |e| e[:task_id] || :root }.transform_values do |entries|
    build_usage(entries.map { |e| e[:data] })
  end
end