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 is the checkpoint boundary.
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 store key, explicit actor identity, backing store, and telemetry operation used by this session.
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
Public Class Methods
# File lib/little_ghost/session.rb, line 41 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 = metadata.to_h.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 81 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 111 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 135 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 62 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 51 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 74 def metadata load&.fetch(:metadata) || @metadata end
Uses persisted metadata when present and otherwise keeps the metadata from construction.
# File lib/little_ghost/session.rb, line 149 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 103 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 67 def state snapshot = load snapshot ? mutable_copy(snapshot.fetch(:state)) : {} end
Exposes a mutable copy of the persisted application state.
Source
# File lib/little_ghost/session.rb, line 140 def synchronize(&block) store.synchronize(id, actor_id:, &block) end
Serializes work for this session and actor through the backing store.