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 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 = .to_h.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.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/little_ghost/session.rb', line 81 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.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/little_ghost/session.rb', line 111 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.
135 136 137 |
# File 'lib/little_ghost/session.rb', line 135 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.
74 75 76 |
# File 'lib/little_ghost/session.rb', line 74 def load&.fetch(:metadata) || @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.
149 150 151 152 153 |
# File 'lib/little_ghost/session.rb', line 149 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.
103 104 105 106 107 |
# File 'lib/little_ghost/session.rb', line 103 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 copy of the persisted application state.
67 68 69 70 |
# File 'lib/little_ghost/session.rb', line 67 def state snapshot = load snapshot ? mutable_copy(snapshot.fetch(:state)) : {} end |
#synchronize(&block) ⇒ Object
Serializes work for this session and actor through the backing store.
140 141 142 |
# File 'lib/little_ghost/session.rb', line 140 def synchronize(&block) store.synchronize(id, actor_id:, &block) end |