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

Extended by:
Repair
Included in:
Riffer, 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].

Signature:

  • (Array[Riffer::Messages::Base]) -> [Array[Riffer::Messages::Base], Array[String]]



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.

Signature:

  • (Array[Riffer::Messages::Base]) -> Array[Riffer::Messages::Base]



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