Class: LittleGhost::Session
- Inherits:
-
Object
- Object
- LittleGhost::Session
- Defined in:
- lib/little_ghost/session.rb
Overview
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.
Instance Attribute Summary collapse
-
#actor_id ⇒ Object
readonly
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
-
#id ⇒ Object
readonly
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
-
#operation_id ⇒ Object
readonly
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
-
#store ⇒ Object
readonly
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
Instance Method Summary collapse
-
#append(messages:, state: self.state, metadata: self.metadata) ⇒ Object
Atomically appends
messageswhen the store still has the expected history length. -
#checkpoint(messages:, state: self.state, metadata: self.metadata, parent_operation_id: @operation_id) ⇒ Object
Persists one conversation checkpoint.
-
#checkpoint_result(result) ⇒ Object
Checkpoints the messages and state from a completed run result.
-
#history(fallback: []) ⇒ Object
Uses persisted conversation messages when present and
fallbackfor a new session. -
#initialize(id:, store:, actor_id: nil, metadata: {}, operation_id: nil) ⇒ Session
constructor
No store access occurs until the session is read or written.
-
#load ⇒ Object
Loads and normalizes the snapshot once.
-
#metadata ⇒ Object
Uses persisted metadata when present and otherwise keeps the metadata from construction.
-
#project_conversation(messages:, metadata: self.metadata) ⇒ Object
Publishes a conversational view without changing the session's stored transcript.
-
#replace(messages:, state: self.state, metadata: self.metadata) ⇒ Object
Replaces the complete persisted snapshot.
-
#state ⇒ Object
Exposes a mutable DataMap copy of the persisted application state.
-
#synchronize(&block) ⇒ Object
Serializes work for this session and actor through the backing store.
Constructor Details
#initialize(id:, store:, actor_id: nil, metadata: {}, operation_id: nil) ⇒ Session
No store access occurs until the session is read or written.
41 42 43 44 45 46 47 48 |
# 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 = DataMap.new().freeze @loaded = false end |
Instance Attribute Details
#actor_id ⇒ Object (readonly)
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
38 39 40 |
# File 'lib/little_ghost/session.rb', line 38 def actor_id @actor_id end |
#id ⇒ Object (readonly)
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
38 39 40 |
# File 'lib/little_ghost/session.rb', line 38 def id @id end |
#operation_id ⇒ Object (readonly)
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
38 39 40 |
# File 'lib/little_ghost/session.rb', line 38 def operation_id @operation_id end |
#store ⇒ Object (readonly)
The store key, explicit actor identity, backing store, and telemetry operation used by this session.
38 39 40 |
# File 'lib/little_ghost/session.rb', line 38 def store @store end |
Instance Method Details
#append(messages:, state: self.state, metadata: self.metadata) ⇒ Object
Atomically appends messages when the store still has the expected
history length. Prefer #checkpoint when replacing earlier messages is
also valid.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/little_ghost/session.rb', line 83 def append(messages:, state: self.state, metadata: self.) current = current_snapshot added = () 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 |
#checkpoint(messages:, state: self.state, metadata: self.metadata, parent_operation_id: @operation_id) ⇒ Object
Persists one conversation checkpoint. History is appended when the stored messages are an unchanged prefix and replaced otherwise.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/little_ghost/session.rb', line 113 def checkpoint(messages:, state: self.state, metadata: self., parent_operation_id: @operation_id) with_store_operation_context(parent_operation_id) do snapshot = build_snapshot(messages:, state:, metadata:) current = current_snapshot if (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 |
#checkpoint_result(result) ⇒ Object
Checkpoints the messages and state from a completed run result.
137 138 139 |
# File 'lib/little_ghost/session.rb', line 137 def checkpoint_result(result) checkpoint(messages: result., state: result.state) end |
#history(fallback: []) ⇒ Object
Uses persisted conversation messages when present and fallback for a new
session.
62 63 64 |
# File 'lib/little_ghost/session.rb', line 62 def history(fallback: []) load&.fetch(:messages) || fallback end |
#load ⇒ Object
Loads and normalizes the snapshot once. A new session has no snapshot.
51 52 53 54 55 56 57 58 |
# 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 |
#metadata ⇒ Object
Uses persisted metadata when present and otherwise keeps the metadata from construction. The returned DataMap is frozen.
75 76 77 78 |
# File 'lib/little_ghost/session.rb', line 75 def loaded = load loaded ? DataMap.new(loaded.fetch(:metadata)).freeze : @metadata end |
#project_conversation(messages:, metadata: self.metadata) ⇒ Object
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.
151 152 153 154 155 |
# File 'lib/little_ghost/session.rb', line 151 def project_conversation(messages:, metadata: self.) with_store_operation_context do store.project_conversation(id, messages:, metadata:, actor_id:) end end |
#replace(messages:, state: self.state, metadata: self.metadata) ⇒ Object
Replaces the complete persisted snapshot.
105 106 107 108 109 |
# File 'lib/little_ghost/session.rb', line 105 def replace(messages:, state: self.state, metadata: self.) snapshot = build_snapshot(messages:, state:, metadata:) with_store_operation_context { store.replace(id, actor_id:, **snapshot) } remember(snapshot) end |
#state ⇒ Object
Exposes a mutable DataMap copy of the persisted application state. String and Symbol keys address the same value; persisted snapshots use Strings.
68 69 70 71 |
# File 'lib/little_ghost/session.rb', line 68 def state snapshot = load DataMap.new(snapshot ? snapshot.fetch(:state) : {}) end |
#synchronize(&block) ⇒ Object
Serializes work for this session and actor through the backing store.
142 143 144 |
# File 'lib/little_ghost/session.rb', line 142 def synchronize(&block) store.synchronize(id, actor_id:, &block) end |