Class: Protege::ThreadHistoryResolver

Inherits:
Resolver
  • Object
show all
Defined in:
app/resolvers/protege/thread_history_resolver.rb

Overview

Built-in resolver that replays a thread's prior messages so the agent has conversational memory across exchanges. For each persisted message in the current message's thread it contributes an ordered ModelMessage — inbound mapped to :user, outbound to :assistant — with the current inbound message excluded (it arrives as the final :user message through the normal harness flow).

Each turn is the message's Protege::Message#to_llm_text: a standard header block (From / To / Cc / Date / Subject, always rendered) followed by the body — plain text, or the tag-stripped HTML fallback — and, when the message carries attachments, the note the app builds listing each (filename, content type, size, and the attachment id the read_attachment / send_email tools accept). So the agent knows who wrote each turn and when, not just what was said, and an attachment-only message still contributes. A message with neither a body nor attachments is skipped.

It also replays the agent's tool use between turns: the ToolUse rows recorded during each prior run are woven back in right after the inbound message that triggered them, as the same structured messages a live run builds — one :assistant message carrying the round's tool_calls, followed by one :tool message per result. Without this the agent would forget, on the next turn, what it searched, fetched, or read earlier. Each replayed result is truncated to REPLAY_RESULT_LIMIT so past tool output can't dominate the context. Pass +include_tool_calls: false+ to replay only the plain message turns.

Returns nil when the thread has no prior content.

By default it replays only the most recent DEFAULT_LIMIT prior messages (still in chronological order) — a bounded verbatim window that pairs well with a running summary recapping everything older, and a guard against unbounded context on long threads. Pass limit: to change the window, or limit: nil to replay the entire thread.

resolvers { |chain| chain.use Protege::ThreadHistoryResolver }            # most recent DEFAULT_LIMIT
resolvers { |chain| chain.use Protege::ThreadHistoryResolver, limit: 10 } # most recent 10
resolvers { |chain| chain.use Protege::ThreadHistoryResolver, limit: nil } # the entire thread

Constant Summary collapse

DEFAULT_LIMIT =

Most-recent prior messages replayed when no limit: is given.

50
REPLAY_RESULT_LIMIT =

Maximum characters of each prior tool result replayed into history.

4_000

Instance Method Summary collapse

Constructor Details

#initialize(limit: DEFAULT_LIMIT, include_tool_calls: true) ⇒ ThreadHistoryResolver

Returns a new instance of ThreadHistoryResolver.

Parameters:

  • limit (Integer, nil) (defaults to: DEFAULT_LIMIT)

    keep only the most recent N prior messages; nil keeps all

  • include_tool_calls (Boolean) (defaults to: true)

    weave prior tool calls/results back into the history



44
45
46
47
48
# File 'app/resolvers/protege/thread_history_resolver.rb', line 44

def initialize(limit: DEFAULT_LIMIT, include_tool_calls: true)
  super()
  @limit              = limit
  @include_tool_calls = include_tool_calls
end

Instance Method Details

#resolve(context:) ⇒ Array<Protege::ModelMessage>?

Build the thread's prior messages — and the tool use between them — as ordered provider messages.

Parameters:

Returns:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/resolvers/protege/thread_history_resolver.rb', line 54

def resolve(context:)
  thread = context.message.email_thread
  return nil unless thread

  prior = thread.messages
                .with_attached_attachments
                .where.not(message_id: context.message.message_id)
                .order(:sent_at)
  prior = prior.includes(:tool_uses) if @include_tool_calls # eager-load each turn's tool history
  prior = prior.last(@limit) if @limit # most recent N, still ascending by sent_at

  prior.flat_map { |message| [history_message(message), *tool_turns(message)] }
       .compact.presence
end