Module: Riffer::Agent::Session::Repair

Extended by:
Repair
Included in:
Repair
Defined in:
lib/riffer/agent/session/repair.rb

Overview

Pure, stateless transformations keeping the tool_usetool_result invariant on a message array. Each entry point no-ops when Riffer.config.experimental_history_healing is off.

Constant Summary collapse

ORPHAN_PLACEHOLDER =

Placeholder response filled in for an orphaned tool_use on interrupt.

->(_tool_call) {
  Riffer::Tools::Response.error("Tool call interrupted before completion.", type: :interrupted)
}

Instance Method Summary collapse

Instance Method Details

#fill_orphans(messages) ⇒ Object

Fills each orphaned tool_use in messages with an ORPHAN_PLACEHOLDER result inserted after its parent. Returns [new_messages, filled_call_ids]. – : (Array) -> [Array, Array]



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/riffer/agent/session/repair.rb', line 19

def fill_orphans(messages)
  return [messages, []] unless Riffer.config.experimental_history_healing

  result_ids = messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) }
  filled = [] #: Array[String]
  new_messages = [] #: Array[Riffer::Messages::Base]

  messages.each do |m|
    new_messages << 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)
      new_messages << Riffer::Messages::Tool.new(
        response.content,
        tool_call_id: tc.call_id,
        name: tc.name,
        error: response.error_message,
        error_type: response.error_type
      )
      filled << tc.call_id
    end
  end

  [new_messages, filled]
end

#prune_orphans(messages) ⇒ Object

Prunes a seeded message array to the invariant — dropping orphaned tool exchanges and parentless Tool messages, but preserving the pending tool_calls on the resume boundary (the last assistant) for execute_pending_tool_calls. Returns a new array. – : (Array) -> Array



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
# File 'lib/riffer/agent/session/repair.rb', line 54

def prune_orphans(messages)
  return messages unless Riffer.config.experimental_history_healing

  resume_boundary = (messages.length - 1).downto(0).find { |idx|
    m = messages[idx]
    m.is_a?(Riffer::Messages::Assistant) &&
      (messages[(idx + 1)..] || []).all? { |later| later.is_a?(Riffer::Messages::Tool) }
  }

  result_ids = messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) }
  parent_ids = messages.flat_map { |m|
    m.is_a?(Riffer::Messages::Assistant) ? m.tool_calls.map(&:call_id) : []
  }

  strip_offenders = messages.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)
  }

  messages.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