class LittleGhost::Session
Sessions let an agent continue a conversation without tying it to one Ruby process. Each session keeps messages, application state, and metadata together behind a SessionStore.
session = LittleGhost::Session.new( id: "conversation-42", actor_id: "user-7", store: LittleGhost::SessionStores::Memory.new ) session.append( messages: [LittleGhost::Message.new(role: :user, content: "Hello")], state: {language: "en"} ) reopened = LittleGhost::Session.new( id: "conversation-42", actor_id: "user-7", store: session.store ) reopened.history.last.text # => "Hello" reopened.state[:language] # => "en"
Persistence and trust
System messages, transient messages, and private model reasoning are removed before persistence. Store failures reach the caller. A successful write becomes the checkpoint used by later updates.
Multi-tenant applications must derive actor_id from stable, authenticated identity. A nil actor provides no tenant isolation and is appropriate only for a store that serves one actor.
Attributes
The trusted application identity that owns this Session, when supplied.
The telemetry operation associated with this Session, when supplied.
The SessionStore that loads and saves snapshots.
Public Class Methods
# File lib/little_ghost/session.rb, line 46 def initialize(id:, store:, actor_id: nil, metadata: {}, operation_id: nil) @id = String(id) @actor_id = actor_id&.to_s @store = store @operation_id = operation_id @metadata = DataMap.new(metadata).freeze @loaded = false end
No store access occurs until the session is read or written.
Public Instance Methods
# File lib/little_ghost/session.rb, line 88 def append(messages:, state: self.state, metadata: self.metadata) current = current_snapshot added = persistable_messages(messages) snapshot = build_snapshot( messages: [*current.fetch(:messages), *added], state:, metadata: ) with_store_operation_context do store.append( id, messages: added, state: snapshot.fetch(:state), metadata: snapshot.fetch(:metadata), expected_count: current.fetch(:messages).length, actor_id: ) end remember(snapshot) end
Atomically appends messages when the store still has the expected history length. Prefer checkpoint when replacing earlier messages is also valid.
# File lib/little_ghost/session.rb, line 118 def checkpoint(messages:, state: self.state, metadata: self.metadata, parent_operation_id: @operation_id) with_store_operation_context(parent_operation_id) do snapshot = build_snapshot(messages:, state:, metadata:) current = current_snapshot if message_prefix?(current.fetch(:messages), snapshot.fetch(:messages)) added = snapshot.fetch(:messages).drop(current.fetch(:messages).length) unless added.empty? && same_session_data?(current, snapshot) store.append( id, messages: added, state: snapshot.fetch(:state), metadata: snapshot.fetch(:metadata), expected_count: current.fetch(:messages).length, actor_id: ) end else store.replace(id, actor_id:, **snapshot) end remember(snapshot) end end
Persists one conversation checkpoint. History is appended when the stored messages are an unchanged prefix and replaced otherwise.
Source
# File lib/little_ghost/session.rb, line 142 def checkpoint_result(result) checkpoint(messages: result.messages, state: result.state) end
Checkpoints the messages and state from a completed run result.
Source
# File lib/little_ghost/session.rb, line 67 def history(fallback: []) load&.fetch(:messages) || fallback end
Uses persisted conversation messages when present and fallback for a new session.
Source
# File lib/little_ghost/session.rb, line 56 def load return @snapshot if @loaded value = with_store_operation_context { store.load(id, actor_id:) } @snapshot = normalize(value) @loaded = true @snapshot end
Loads and normalizes the snapshot once. A new session has no snapshot.
Source
# File lib/little_ghost/session.rb, line 80 def metadata loaded = load loaded ? DataMap.new(loaded.fetch(:metadata)).freeze : @metadata end
Uses persisted metadata when present and otherwise keeps the metadata from construction. The returned DataMap is frozen.
# File lib/little_ghost/session.rb, line 156 def project_conversation(messages:, metadata: self.metadata) with_store_operation_context do store.project_conversation(id, messages:, metadata:, actor_id:) end end
Publishes a conversational view without changing the session’s stored transcript. Unlike session persistence, projection does not automatically remove system or transient messages; callers must omit any message whose visible text should stay local. Stores that do not support projections return nil.
# File lib/little_ghost/session.rb, line 110 def replace(messages:, state: self.state, metadata: self.metadata) snapshot = build_snapshot(messages:, state:, metadata:) with_store_operation_context { store.replace(id, actor_id:, **snapshot) } remember(snapshot) end
Replaces the complete persisted snapshot.
Source
# File lib/little_ghost/session.rb, line 73 def state snapshot = load DataMap.new(snapshot ? snapshot.fetch(:state) : {}) end
Exposes a mutable DataMap copy of the persisted application state. String and Symbol keys address the same value; persisted snapshots use Strings.
Source
# File lib/little_ghost/session.rb, line 147 def synchronize(&block) store.synchronize(id, actor_id:, &block) end
Serializes work for this session and actor through the backing store.