Module: Riffer::Agent::Session::Repair
- Defined in:
- lib/riffer/agent/session/repair.rb
Overview
Riffer::Agent::Session::Repair holds the pure transformations that keep the tool_use ↔ tool_result invariant on a message array. No state, no instance — module-level functions only. Each entry point is gated by Riffer.config.experimental_history_healing: when the flag is off the function returns its input unchanged.
Two seams:
-
fill_orphans— fills orphantool_useblocks with placeholder results. Used on interrupt (caller-issued ormax_steps). -
prune_orphans— drops orphantool_useblocks and parentless Tool messages from a caller-provided seed so it is well-formed before the next inference call. Used at construction time when Riffer::Agent.new(session:) receives a session.
Constant Summary collapse
- ORPHAN_PLACEHOLDER =
Placeholder used to fill orphan
tool_useblocks. Emitted as theRiffer::Tools::Responsebody for each filled call_id. ->(_tool_call) { Riffer::Tools::Response.error("Tool call interrupted before completion.", type: :interrupted) }
Class Method Summary collapse
-
.fill_orphans(messages) ⇒ Object
Fills any orphaned
tool_useinmessageswith theORPHAN_PLACEHOLDERresponse. -
.prune_orphans(messages) ⇒ Object
Prunes a seeded message array so the
tool_use↔tool_resultinvariant holds.
Class Method Details
.fill_orphans(messages) ⇒ Object
Fills any orphaned tool_use in messages with the ORPHAN_PLACEHOLDER response. Each placeholder Tool message is inserted immediately after its parent assistant message. Returns [new_messages, filled_call_ids]; filled_call_ids is empty when there are no orphans.
No-op when Riffer.config.experimental_history_healing is off: returns [messages, []] with the same array reference.
36 37 38 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 |
# File 'lib/riffer/agent/session/repair.rb', line 36 def self.fill_orphans() return [, []] unless Riffer.config.experimental_history_healing result_ids = .filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) } filled = [] #: Array[String] = [] #: Array[Riffer::Messages::Base] .each do |m| << m next unless m.is_a?(Riffer::Messages::Assistant) && !m.tool_calls.empty? m.tool_calls.each do |tc| next if result_ids.include?(tc.call_id) response = ORPHAN_PLACEHOLDER.call(tc) << Riffer::Messages::Tool.new( response.content, tool_call_id: tc.call_id, name: tc.name, error: response., error_type: response.error_type ) filled << tc.call_id end end [, filled] end |
.prune_orphans(messages) ⇒ Object
Prunes a seeded message array so the tool_use ↔ tool_result invariant holds. Drops orphaned tool exchanges (assistant tool_call with no matching Tool result) and parentless Tool messages. Returns a new array; the input is not mutated.
Pending tool_calls on the resume boundary — the last assistant whose tail is purely Tool results (or empty) — are preserved. They get swept up by execute_pending_tool_calls at the start of the next generate/stream call.
No-op when Riffer.config.experimental_history_healing is off: returns messages unchanged.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/riffer/agent/session/repair.rb', line 80 def self.prune_orphans() return unless Riffer.config.experimental_history_healing resume_boundary = (.length - 1).downto(0).find { |idx| m = [idx] m.is_a?(Riffer::Messages::Assistant) && ([(idx + 1)..] || []).all? { |later| later.is_a?(Riffer::Messages::Tool) } } result_ids = .filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) } parent_ids = .flat_map { |m| m.is_a?(Riffer::Messages::Assistant) ? m.tool_calls.map(&:call_id) : [] } strip_offenders = .each_with_index.flat_map { |m, idx| next [] unless m.is_a?(Riffer::Messages::Assistant) && !m.tool_calls.empty? next [] if idx == resume_boundary # preserve pending exchange next [] if m.tool_calls.all? { |tc| result_ids.include?(tc.call_id) } m.tool_calls.map(&:call_id) } .reject { |m| case m when Riffer::Messages::Assistant !m.tool_calls.empty? && m.tool_calls.any? { |tc| strip_offenders.include?(tc.call_id) } when Riffer::Messages::Tool strip_offenders.include?(m.tool_call_id) || !parent_ids.include?(m.tool_call_id) else false end } end |