Class: Phronomy::Memory::ConversationManager

Inherits:
Object
  • Object
show all
Defined in:
lib/phronomy/memory/conversation_manager.rb

Overview

ConversationManager combines the three independent axes of conversation handling:

  • Storage: where messages are persisted (InMemory, ActiveRecord, ...)
  • Retrieval: which messages to select (Recent, Semantic, ...)
  • Compression: how to reduce message size before storage (Summary, ToolOutputPruner, ...)

This is the primary entry point for context region 4 (Conversation) in Agent::Base.

=== Original preservation policy

All original messages are appended to Storage's raw history with a monotonically increasing seq number (0-based, per thread). Raw messages are never modified or deleted.

When Compression::Summary performs a compaction, a compaction record { start_seq:, end_seq:, summary_text: } is saved in Storage alongside the raw messages. This allows callers to reconstruct the full history or audit which messages were summarised.

On #load, the message list is reconstructed from raw history + compaction records: each compacted range is replaced by a single summary system message, and uncompacted messages are returned verbatim.

Examples:

Simple recency-based in-memory manager

manager = Phronomy::Memory::ConversationManager.new(
  storage:   Phronomy::Memory::Storage::InMemory.new,
  retrieval: Phronomy::Memory::Retrieval::Recent.new(k: 10)
)

With LLM summary compaction

manager = Phronomy::Memory::ConversationManager.new(
  storage:     Phronomy::Memory::Storage::InMemory.new,
  retrieval:   Phronomy::Memory::Retrieval::Recent.new(k: 5),
  compression: Phronomy::Memory::Compression::Summary.new(max_tokens: 4000)
)

Instance Method Summary collapse

Constructor Details

#initialize(storage:, retrieval:, compression: nil, ttl: nil) ⇒ ConversationManager

Returns a new instance of ConversationManager.

Parameters:

  • storage (Memory::Storage::Base)

    persistence backend (required)

  • retrieval (Memory::Retrieval::Base)

    selection strategy (required)

  • compression (Memory::Compression::Base, nil) (defaults to: nil)

    optional compression strategy

  • ttl (Integer, nil) (defaults to: nil)

    message time-to-live in seconds; messages older than this value are removed from storage on each #load call. +nil+ disables TTL (default).



46
47
48
49
50
51
# File 'lib/phronomy/memory/conversation_manager.rb', line 46

def initialize(storage:, retrieval:, compression: nil, ttl: nil)
  @storage = storage
  @retrieval = retrieval
  @compression = compression
  @ttl = ttl
end

Instance Method Details

#clear(thread_id:) ⇒ Object

Delete all messages (raw, compaction records, and legacy store) for a thread.

Parameters:

  • thread_id (String)


93
94
95
96
# File 'lib/phronomy/memory/conversation_manager.rb', line 93

def clear(thread_id:)
  @storage.clear(thread_id: thread_id)
  @retrieval.clear_index(thread_id: thread_id) if @retrieval.respond_to?(:clear_index)
end

#load(thread_id:, query: nil) ⇒ Array

Load conversation messages for a thread, applying retrieval selection.

When a TTL is configured, raw messages older than the TTL are permanently removed from storage before reconstruction.

Reconstructs the message list from raw history + compaction records:

  • Each compacted range [start_seq..end_seq] is replaced by a summary system message.
  • Uncompacted messages are returned in original order.

Parameters:

  • thread_id (String)
  • query (String, nil) (defaults to: nil)

    current user input for query-aware retrieval

Returns:

  • (Array)


66
67
68
69
70
# File 'lib/phronomy/memory/conversation_manager.rb', line 66

def load(thread_id:, query: nil)
  @storage.purge_older_than(thread_id: thread_id, older_than: Time.now - @ttl) if @ttl
  messages = reconstruct(thread_id)
  @retrieval.select(messages, query: query)
end

#purge(thread_id:) ⇒ Object

Permanently erase all stored data for a thread (right-to-erasure / purge). Delegates to the storage backend's Storage::Base#purge and also clears any retrieval index for the thread.

Parameters:

  • thread_id (String)


103
104
105
106
# File 'lib/phronomy/memory/conversation_manager.rb', line 103

def purge(thread_id:)
  @storage.purge(thread_id: thread_id)
  @retrieval.clear_index(thread_id: thread_id) if @retrieval.respond_to?(:clear_index)
end

#save(thread_id:, messages:) ⇒ Object

Persist new messages for a thread and optionally apply compression.

New messages are determined by comparing the incoming array length with the existing raw history length (messages are always append-only). Only truly new messages (beyond raw.length) are appended to raw storage.

When a compression strategy is configured, it is evaluated against the full set of uncompacted raw messages. If compaction fires, the resulting compaction record is saved in storage (originals are preserved).

Parameters:

  • thread_id (String)
  • messages (Array)

    full conversation history up to this point



84
85
86
87
88
# File 'lib/phronomy/memory/conversation_manager.rb', line 84

def save(thread_id:, messages:)
  append_new_messages(thread_id: thread_id, messages: messages)
  compress_and_save(thread_id: thread_id, messages: messages)
  @retrieval.index(thread_id: thread_id, messages: messages) if @retrieval.respond_to?(:index)
end

#save_compaction(thread_id:, start_seq:, end_seq:, summary_text:) ⇒ Object

Record an application-driven compaction for a thread. Called by CompactionContext when the on_compact callback invokes ctx.compact.

Parameters:

  • thread_id (String)
  • start_seq (Integer)

    first seq number in the compacted range

  • end_seq (Integer)

    last seq number in the compacted range

  • summary_text (String)

    replacement text for the compacted messages



115
116
117
118
119
120
121
122
# File 'lib/phronomy/memory/conversation_manager.rb', line 115

def save_compaction(thread_id:, start_seq:, end_seq:, summary_text:)
  @storage.save_compaction(
    thread_id: thread_id,
    start_seq: start_seq,
    end_seq: end_seq,
    summary_text: summary_text
  )
end