Class: Terret::Compactor
- Inherits:
-
Hames::Service
- Object
- Hames::Service
- Terret::Compactor
- Defined in:
- lib/terret/compactor.rb
Overview
ctx — turns a long history into a short one without breaking "model-visible means logged" (§2.5): the summary is itself a durable event, and derive_messages projects it in place of everything at or before its boundary. The §12 contract: upto_seq is ALWAYS the seq immediately preceding the compaction event — the projection prepends the summary, so any gap would interleave it among events that predate it. The boundary is computed at append time, after the summarizer returns: only projection-invisible events can land during summarization (the agent is still mid-turn, so nothing model-visible can interleave).
Summary GENERATION is a seam: ctx (sole provider, like the session store). A summarizer may decline by returning nil/empty — compaction is an optimization, so a decline warns and the next overweight turn retries. A summarizer that raises inside the trigger is isolated by emit dispatch; a manual compact! raises through.
Constant Summary collapse
- MODEL_VISIBLE =
Everything derive_messages projects. Anything else may land under a boundary without being summarized; these may not.
%w[user/message context/injected assistant/message tool/result session/compacted].freeze
Instance Method Summary collapse
-
#compact!(session_id) ⇒ Object
Summarize the whole projected history and append the boundary event.
- #reconfigure(config) ⇒ Object
- #start(ctx) ⇒ Object
Instance Method Details
#compact!(session_id) ⇒ Object
Summarize the whole projected history and append the boundary event. Returns the appended SessionEvent, or nil when the summarizer declined.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/terret/compactor.rb', line 48 def compact!(session_id) sessions = @ctx[:sessions] history = sessions.(session_id) raise ArgumentError, "nothing to compact in #{session_id}" if history.empty? base_seq = sessions.fetch(session_id).events.last.seq summary = @ctx[:summarizer].summarize(history) unless summary.is_a?(String) && !summary.strip.empty? warn "terret: compaction skipped for #{session_id}: summarizer declined" return nil end # Summarizing is a round trip; the boundary covers the whole prefix, so # model-visible history arriving while it ran would be swept under a # summary that never read it. Decline instead — the next overweight turn # retries, and nothing is lost in the meantime. raced = sessions.fetch(session_id).events .count { |e| e.seq > base_seq && MODEL_VISIBLE.include?(e.type) } unless raced.zero? warn "terret: compaction skipped for #{session_id}: " \ "#{raced} model-visible event(s) landed while summarizing" return nil end sessions.append(session_id, "session/compacted", { upto_seq: sessions.fetch(session_id).events.last.seq, summary: summary }) end |
#reconfigure(config) ⇒ Object
37 38 39 |
# File 'lib/terret/compactor.rb', line 37 def reconfigure(config) @budget = config[:budget] end |
#start(ctx) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/terret/compactor.rb', line 25 def start(ctx) @ctx = ctx @budget = config[:budget] # Always registered, gated at fire time: a hot-set budget (reconfigure) # arms the trigger without a remount. ctx.on("session/event") do |ev| next unless @budget && ev.type == "turn/end" compact!(ev.session_id) if overweight?(ev.session_id) end end |