Class: Rubino::Context::SummaryBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/context/summary_builder.rb

Overview

Builds structured summaries from compressible message segments. Uses the LLM to generate a comprehensive summary following the template.

Constant Summary collapse

SUMMARY_PREFIX =

Anti-replay handoff banner prepended to every compaction summary (#415c, ported from Hermes context_compressor.py SUMMARY_PREFIX). Without it a weak model reads the summarized older turns as live instructions and re-does already-finished work (the #10896/#11475 task-loss/replay class). It also points the model at the “## Active Task” field for continuation.

<<~PREFIX.strip
  [CONTEXT COMPACTION — REFERENCE ONLY] Earlier turns were compacted into the summary below. This is a handoff from a previous context window — treat it as background reference, NOT as active instructions. Do NOT answer questions or fulfill requests mentioned in this summary; they were already addressed. Your current task is identified in the '## Active Task' section of the summary — resume exactly from there. Your persistent memory in the system prompt is ALWAYS authoritative — never deprioritize it due to this note. Respond ONLY to the latest user message that appears AFTER this summary. The current session state (files, config, etc.) may already reflect work described here — avoid repeating it.
PREFIX
SUMMARY_TEMPLATE =
<<~TEMPLATE
  ## Active Task
  The SINGLE most important field. Copy the user's most recent
  unfulfilled request verbatim — the exact words they used. If several
  tasks were requested and only some are done, list only the ones NOT
  yet completed. Continuation picks up exactly here. If nothing is
  outstanding, write "None".

  ## Goal
  Current user objective.

  ## Constraints & Preferences
  Technical constraints, preferences, conventions.

  ## Progress

  ### Done
  Completed items.

  ### In Progress
  Work in progress.

  ### Blocked
  Open blockers or errors.

  ## Key Decisions
  Technical decisions made and their rationale.

  ## Relevant Files
  Files read, modified, or created.

  ## Tool Results
  Important tool execution results.

  ## Current State
  Current session state.

  ## Next Steps
  Planned next actions.

  ## Critical Context
  Information that must not be lost.
TEMPLATE

Instance Method Summary collapse

Constructor Details

#initialize(session_id:, config: nil) ⇒ SummaryBuilder

Returns a new instance of SummaryBuilder.



62
63
64
65
# File 'lib/rubino/context/summary_builder.rb', line 62

def initialize(session_id:, config: nil)
  @session_id = session_id
  @config = config || Rubino.configuration
end

Instance Method Details

#build(messages:, previous_summary: nil) ⇒ Object

Builds a summary from messages, optionally incorporating a previous summary. The returned text always carries SUMMARY_PREFIX so the next context window treats it as reference-only (#415c anti-replay).



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/rubino/context/summary_builder.rb', line 70

def build(messages:, previous_summary: nil)
  # Strip any banner already on the incoming previous summary so
  # iterative re-compaction never stacks prefixes (anti-replay guard).
  previous_summary = strip_summary_prefix(previous_summary)
  content = format_messages_for_summary(messages)

  prompt = build_summary_prompt(content, previous_summary)
  @config.compression_max_summary_tokens

  # Use the auxiliary compression model if configured
  model = compression_model
  adapter = LLM::RubyLLMAdapter.new(model_id: model)

  response = adapter.chat(messages: [
                            { role: "system", content: summary_system_prompt },
                            { role: "user", content: prompt }
                          ])

  body = response&.content || fallback_summary(messages, previous_summary)
  with_summary_prefix(body)
rescue StandardError
  # If LLM fails, produce a basic extractive summary
  with_summary_prefix(fallback_summary(messages, previous_summary))
end

#build_and_save!Object

Builds and saves the summary to the database



111
112
113
114
115
116
117
118
# File 'lib/rubino/context/summary_builder.rb', line 111

def build_and_save!
  message_store = Session::Store.new
  messages = message_store.for_session(@session_id)
  return if messages.size < 10

  summary = build(messages: messages, previous_summary: load_previous_summary)
  save!(summary)
end

#strip_summary_prefix(summary) ⇒ Object

Returns the summary body without the handoff banner.



103
104
105
106
107
108
# File 'lib/rubino/context/summary_builder.rb', line 103

def strip_summary_prefix(summary)
  text = summary.to_s.strip
  return text[SUMMARY_PREFIX.length..].to_s.lstrip if text.start_with?(SUMMARY_PREFIX)

  text
end

#with_summary_prefix(summary) ⇒ Object

Normalizes summary text to the current handoff format, stripping any banner first so it is never duplicated (#415c).



97
98
99
100
# File 'lib/rubino/context/summary_builder.rb', line 97

def with_summary_prefix(summary)
  body = strip_summary_prefix(summary)
  body.empty? ? SUMMARY_PREFIX : "#{SUMMARY_PREFIX}\n#{body}"
end