Class: Snapshot
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Snapshot
- Defined in:
- app/models/snapshot.rb
Overview
A persisted summary of conversation context created by Mneme before messages evict from the viewport. Snapshots capture the “gist” of what happened so the agent retains awareness of past context.
Level 1 snapshots are created from raw messages (conversation + thinks). Level 2 snapshots compress multiple Level 1 snapshots (days/weeks scale). Both levels use the same message ID range tracking — an L2 snapshot’s range is the union of its constituent L1 snapshots.
Constant Summary collapse
- MAX_TEXT_BYTES =
32KB — generous upper bound (~8K tokens). The LLM tool description advises a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage.
32_768
Instance Attribute Summary collapse
-
#from_message_id ⇒ Integer
First message ID covered by this snapshot.
-
#level ⇒ Integer
Compression level (1 = from raw messages, 2 = from L1 snapshots).
-
#text ⇒ String
The summary text generated by Mneme.
-
#to_message_id ⇒ Integer
Last message ID covered by this snapshot.
-
#token_count ⇒ Integer
Cached token count of the summary text.
Instance Method Summary collapse
-
#token_cost ⇒ Integer
Token cost, using cached count or heuristic estimate.
Instance Attribute Details
#from_message_id ⇒ Integer
Returns first message ID covered by this snapshot.
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 47 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 76 |
# File 'app/models/snapshot.rb', line 22 class Snapshot < ApplicationRecord belongs_to :session # 32KB — generous upper bound (~8K tokens). The LLM tool description advises # a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage. MAX_TEXT_BYTES = 32_768 validates :text, presence: true, length: {maximum: MAX_TEXT_BYTES} validates :from_message_id, presence: true validates :to_message_id, presence: true validates :level, presence: true, numericality: {greater_than: 0} validates :token_count, numericality: {greater_than_or_equal_to: 0}, allow_nil: true validate :from_message_id_not_after_to_message_id scope :for_level, ->(level) { where(level: level) } scope :chronological, -> { order(:from_message_id) } # L1 snapshots whose message range is NOT fully contained within any L2 snapshot. # Used to determine which L1 snapshots are still "live" in the viewport. scope :not_covered_by_l2, -> { where.not( "EXISTS (SELECT 1 FROM snapshots l2 " \ "WHERE l2.session_id = snapshots.session_id " \ "AND l2.level = 2 " \ "AND l2.from_message_id <= snapshots.from_message_id " \ "AND l2.to_message_id >= snapshots.to_message_id)" ) } # Snapshots whose source messages have fully evicted from the sliding window. # A snapshot is visible when its entire message range precedes the first # message currently in the viewport. # # @param first_message_id [Integer] the first message ID in the sliding window scope :source_messages_evicted, ->() { where("to_message_id < ?", ) } # @return [Integer] token cost, using cached count or heuristic estimate def token_cost token_count.positive? ? token_count : estimate_tokens end private def return unless && errors.add(:from_message_id, "must be <= to_message_id") if > end # @return [Integer] estimated token count (at least 1) def estimate_tokens [(text.bytesize / Message::BYTES_PER_TOKEN.to_f).ceil, 1].max end end |
#level ⇒ Integer
Returns compression level (1 = from raw messages, 2 = from L1 snapshots).
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 47 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 76 |
# File 'app/models/snapshot.rb', line 22 class Snapshot < ApplicationRecord belongs_to :session # 32KB — generous upper bound (~8K tokens). The LLM tool description advises # a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage. MAX_TEXT_BYTES = 32_768 validates :text, presence: true, length: {maximum: MAX_TEXT_BYTES} validates :from_message_id, presence: true validates :to_message_id, presence: true validates :level, presence: true, numericality: {greater_than: 0} validates :token_count, numericality: {greater_than_or_equal_to: 0}, allow_nil: true validate :from_message_id_not_after_to_message_id scope :for_level, ->(level) { where(level: level) } scope :chronological, -> { order(:from_message_id) } # L1 snapshots whose message range is NOT fully contained within any L2 snapshot. # Used to determine which L1 snapshots are still "live" in the viewport. scope :not_covered_by_l2, -> { where.not( "EXISTS (SELECT 1 FROM snapshots l2 " \ "WHERE l2.session_id = snapshots.session_id " \ "AND l2.level = 2 " \ "AND l2.from_message_id <= snapshots.from_message_id " \ "AND l2.to_message_id >= snapshots.to_message_id)" ) } # Snapshots whose source messages have fully evicted from the sliding window. # A snapshot is visible when its entire message range precedes the first # message currently in the viewport. # # @param first_message_id [Integer] the first message ID in the sliding window scope :source_messages_evicted, ->() { where("to_message_id < ?", ) } # @return [Integer] token cost, using cached count or heuristic estimate def token_cost token_count.positive? ? token_count : estimate_tokens end private def return unless && errors.add(:from_message_id, "must be <= to_message_id") if > end # @return [Integer] estimated token count (at least 1) def estimate_tokens [(text.bytesize / Message::BYTES_PER_TOKEN.to_f).ceil, 1].max end end |
#text ⇒ String
Returns the summary text generated by Mneme.
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 47 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 76 |
# File 'app/models/snapshot.rb', line 22 class Snapshot < ApplicationRecord belongs_to :session # 32KB — generous upper bound (~8K tokens). The LLM tool description advises # a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage. MAX_TEXT_BYTES = 32_768 validates :text, presence: true, length: {maximum: MAX_TEXT_BYTES} validates :from_message_id, presence: true validates :to_message_id, presence: true validates :level, presence: true, numericality: {greater_than: 0} validates :token_count, numericality: {greater_than_or_equal_to: 0}, allow_nil: true validate :from_message_id_not_after_to_message_id scope :for_level, ->(level) { where(level: level) } scope :chronological, -> { order(:from_message_id) } # L1 snapshots whose message range is NOT fully contained within any L2 snapshot. # Used to determine which L1 snapshots are still "live" in the viewport. scope :not_covered_by_l2, -> { where.not( "EXISTS (SELECT 1 FROM snapshots l2 " \ "WHERE l2.session_id = snapshots.session_id " \ "AND l2.level = 2 " \ "AND l2.from_message_id <= snapshots.from_message_id " \ "AND l2.to_message_id >= snapshots.to_message_id)" ) } # Snapshots whose source messages have fully evicted from the sliding window. # A snapshot is visible when its entire message range precedes the first # message currently in the viewport. # # @param first_message_id [Integer] the first message ID in the sliding window scope :source_messages_evicted, ->() { where("to_message_id < ?", ) } # @return [Integer] token cost, using cached count or heuristic estimate def token_cost token_count.positive? ? token_count : estimate_tokens end private def return unless && errors.add(:from_message_id, "must be <= to_message_id") if > end # @return [Integer] estimated token count (at least 1) def estimate_tokens [(text.bytesize / Message::BYTES_PER_TOKEN.to_f).ceil, 1].max end end |
#to_message_id ⇒ Integer
Returns last message ID covered by this snapshot.
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 47 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 76 |
# File 'app/models/snapshot.rb', line 22 class Snapshot < ApplicationRecord belongs_to :session # 32KB — generous upper bound (~8K tokens). The LLM tool description advises # a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage. MAX_TEXT_BYTES = 32_768 validates :text, presence: true, length: {maximum: MAX_TEXT_BYTES} validates :from_message_id, presence: true validates :to_message_id, presence: true validates :level, presence: true, numericality: {greater_than: 0} validates :token_count, numericality: {greater_than_or_equal_to: 0}, allow_nil: true validate :from_message_id_not_after_to_message_id scope :for_level, ->(level) { where(level: level) } scope :chronological, -> { order(:from_message_id) } # L1 snapshots whose message range is NOT fully contained within any L2 snapshot. # Used to determine which L1 snapshots are still "live" in the viewport. scope :not_covered_by_l2, -> { where.not( "EXISTS (SELECT 1 FROM snapshots l2 " \ "WHERE l2.session_id = snapshots.session_id " \ "AND l2.level = 2 " \ "AND l2.from_message_id <= snapshots.from_message_id " \ "AND l2.to_message_id >= snapshots.to_message_id)" ) } # Snapshots whose source messages have fully evicted from the sliding window. # A snapshot is visible when its entire message range precedes the first # message currently in the viewport. # # @param first_message_id [Integer] the first message ID in the sliding window scope :source_messages_evicted, ->() { where("to_message_id < ?", ) } # @return [Integer] token cost, using cached count or heuristic estimate def token_cost token_count.positive? ? token_count : estimate_tokens end private def return unless && errors.add(:from_message_id, "must be <= to_message_id") if > end # @return [Integer] estimated token count (at least 1) def estimate_tokens [(text.bytesize / Message::BYTES_PER_TOKEN.to_f).ceil, 1].max end end |
#token_count ⇒ Integer
Returns cached token count of the summary text.
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 47 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 76 |
# File 'app/models/snapshot.rb', line 22 class Snapshot < ApplicationRecord belongs_to :session # 32KB — generous upper bound (~8K tokens). The LLM tool description advises # a tighter limit (mneme_max_tokens), but this hard cap prevents unbounded storage. MAX_TEXT_BYTES = 32_768 validates :text, presence: true, length: {maximum: MAX_TEXT_BYTES} validates :from_message_id, presence: true validates :to_message_id, presence: true validates :level, presence: true, numericality: {greater_than: 0} validates :token_count, numericality: {greater_than_or_equal_to: 0}, allow_nil: true validate :from_message_id_not_after_to_message_id scope :for_level, ->(level) { where(level: level) } scope :chronological, -> { order(:from_message_id) } # L1 snapshots whose message range is NOT fully contained within any L2 snapshot. # Used to determine which L1 snapshots are still "live" in the viewport. scope :not_covered_by_l2, -> { where.not( "EXISTS (SELECT 1 FROM snapshots l2 " \ "WHERE l2.session_id = snapshots.session_id " \ "AND l2.level = 2 " \ "AND l2.from_message_id <= snapshots.from_message_id " \ "AND l2.to_message_id >= snapshots.to_message_id)" ) } # Snapshots whose source messages have fully evicted from the sliding window. # A snapshot is visible when its entire message range precedes the first # message currently in the viewport. # # @param first_message_id [Integer] the first message ID in the sliding window scope :source_messages_evicted, ->() { where("to_message_id < ?", ) } # @return [Integer] token cost, using cached count or heuristic estimate def token_cost token_count.positive? ? token_count : estimate_tokens end private def return unless && errors.add(:from_message_id, "must be <= to_message_id") if > end # @return [Integer] estimated token count (at least 1) def estimate_tokens [(text.bytesize / Message::BYTES_PER_TOKEN.to_f).ceil, 1].max end end |
Instance Method Details
#token_cost ⇒ Integer
Returns token cost, using cached count or heuristic estimate.
61 62 63 |
# File 'app/models/snapshot.rb', line 61 def token_cost token_count.positive? ? token_count : estimate_tokens end |