Class: Riffer::Agent::Session
- Inherits:
-
Object
- Object
- Riffer::Agent::Session
- Includes:
- Enumerable
- Defined in:
- lib/riffer/agent/session.rb
Overview
Owns the conversation handle for an agent: the message array, the on_message callbacks, and the tool_use ↔ tool_result invariant that keeps tool calls and their results consistent.
agent.session.add(msg) # append + fire callbacks
agent.session.set([msg1, msg2]) # bulk replace (silent)
agent.session.unset # clear (silent)
agent.session.remove(id: "a_1")
agent.session.update(id: "a_1", content: "...")
agent.session.find { |m| m.id == "a_1" }
Defined Under Namespace
Modules: Repair
Instance Attribute Summary collapse
-
#messages ⇒ Object
readonly
The message history.
Instance Method Summary collapse
-
#add(message, silent: false) ⇒ Object
Appends
messageand fires every registered callback once with it. -
#each(&block) ⇒ Object
Yields each message in order, or returns an Enumerator without a block.
-
#final_assistant_message ⇒ Object
The most recent
Riffer::Messages::Assistantin the session, ornilwhen none exists. -
#initialize(messages: []) ⇒ Session
constructor
– : (?messages: Array) -> void.
-
#on_message(&block) ⇒ Object
Registers a callback invoked once per message appended via
#add. -
#orphaned_tool_call_ids ⇒ Object
Returns the call_ids of every
tool_callwith no matchingRiffer::Messages::Toolresult anywhere in history — a hook for checking thetool_use↔tool_resultinvariant before mutating or persisting. -
#pending_tool_calls ⇒ Object
Returns [last_assistant, pending_tool_calls]; the second element is empty when there’s no assistant message or no pending calls.
-
#remove(id:) ⇒ Object
Removes a message by id, cascading to drop the
Toolresults of a removed assistant’stool_callsso thetool_use↔tool_resultinvariant holds. -
#set(messages) ⇒ Object
Replaces the message history wholesale – : (Array) -> self.
-
#steps ⇒ Object
The number of LLM steps completed, used by the agent loop to enforce
max_stepson resume. -
#unset ⇒ Object
Clears the session.
-
#update(id: nil, tool_call_id: nil, **attrs) ⇒ Object
Partial in-place update: looks up a message by
id:ortool_call_id:(exactly one), overlaysattrsonto a same-type replacement, and swaps it in.
Constructor Details
Instance Attribute Details
#messages ⇒ Object (readonly)
The message history.
21 22 23 |
# File 'lib/riffer/agent/session.rb', line 21 def @messages end |
Instance Method Details
#add(message, silent: false) ⇒ Object
Appends message and fires every registered callback once with it. Pass silent: true to skip callbacks — used for non-inference inputs like user messages that subscribers don’t expect on the callback channel. – : (Riffer::Messages::Base, ?silent: bool) -> Riffer::Messages::Base
44 45 46 47 48 |
# File 'lib/riffer/agent/session.rb', line 44 def add(, silent: false) @messages << @callbacks.each { |callback| callback.call() } unless silent end |
#each(&block) ⇒ Object
Yields each message in order, or returns an Enumerator without a block. – : () -> Enumerator[Riffer::Messages::Base, self] : () { (Riffer::Messages::Base) -> void } -> untyped
155 156 157 158 |
# File 'lib/riffer/agent/session.rb', line 155 def each(&block) return @messages.each unless block @messages.each(&block) end |
#final_assistant_message ⇒ Object
The most recent Riffer::Messages::Assistant in the session, or nil when none exists.
– : () -> Riffer::Messages::Assistant?
173 174 175 176 177 178 |
# File 'lib/riffer/agent/session.rb', line 173 def # TODO: Replace with rfind when minimum Ruby is 4.0+ # rubocop:disable Style/ReverseFind @messages.reverse_each.find { |m| m.is_a?(Riffer::Messages::Assistant) } #: Riffer::Messages::Assistant? # rubocop:enable Style/ReverseFind end |
#on_message(&block) ⇒ Object
Registers a callback invoked once per message appended via #add. – : () { (Riffer::Messages::Base) -> void } -> self
33 34 35 36 37 |
# File 'lib/riffer/agent/session.rb', line 33 def (&block) raise Riffer::ArgumentError, "on_message requires a block" unless block_given? @callbacks << block self end |
#orphaned_tool_call_ids ⇒ Object
Returns the call_ids of every tool_call with no matching Riffer::Messages::Tool result anywhere in history — a hook for checking the tool_use ↔ tool_result invariant before mutating or persisting. – : () -> Array
125 126 127 128 129 130 131 |
# File 'lib/riffer/agent/session.rb', line 125 def orphaned_tool_call_ids result_ids = @messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) } @messages.flat_map { |m| next [] unless m.is_a?(Riffer::Messages::Assistant) m.tool_calls.reject { |tc| result_ids.include?(tc.call_id) }.map(&:call_id) } end |
#pending_tool_calls ⇒ Object
Returns [last_assistant, pending_tool_calls]; the second element is empty when there’s no assistant message or no pending calls. – : () -> [Riffer::Messages::Assistant?, Array]
137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/riffer/agent/session.rb', line 137 def pending_tool_calls last_assistant_idx = @messages.rindex { |m| m.is_a?(Riffer::Messages::Assistant) } return [nil, []] unless last_assistant_idx assistant = @messages[last_assistant_idx] #: Riffer::Messages::Assistant return [assistant, []] if assistant.tool_calls.empty? executed_ids = (@messages[(last_assistant_idx + 1)..] || []).filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) } [assistant, assistant.tool_calls.reject { |tc| executed_ids.include?(tc.call_id) }] end |
#remove(id:) ⇒ Object
Removes a message by id, cascading to drop the Tool results of a removed assistant’s tool_calls so the tool_use ↔ tool_result invariant holds. Raises on a Tool message — that would orphan its parent; use #update instead. Returns nil if no message matches. – : (id: String) -> Riffer::Messages::Base?
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/riffer/agent/session.rb', line 72 def remove(id:) idx = @messages.index { |m| m.id == id } return nil unless idx target = @messages[idx] if target.is_a?(Riffer::Messages::Tool) raise Riffer::ArgumentError, "remove cannot drop a Tool message (would orphan the parent's tool_use); use #update instead" end if target.is_a?(Riffer::Messages::Assistant) && !target.tool_calls.empty? child_ids = target.tool_calls.map(&:call_id) @messages.reject! { |m| m.is_a?(Riffer::Messages::Tool) && child_ids.include?(m.tool_call_id) } @messages.delete(target) else @messages.delete_at(idx) end target end |
#set(messages) ⇒ Object
Replaces the message history wholesale – : (Array) -> self
53 54 55 56 |
# File 'lib/riffer/agent/session.rb', line 53 def set() @messages = self end |
#steps ⇒ Object
The number of LLM steps completed, used by the agent loop to enforce max_steps on resume. – : () -> Integer
164 165 166 |
# File 'lib/riffer/agent/session.rb', line 164 def steps @messages.count { |m| m.is_a?(Riffer::Messages::Assistant) } end |
#unset ⇒ Object
Clears the session. – : () -> self
61 62 63 64 |
# File 'lib/riffer/agent/session.rb', line 61 def unset @messages = [] self end |
#update(id: nil, tool_call_id: nil, **attrs) ⇒ Object
Partial in-place update: looks up a message by id: or tool_call_id: (exactly one), overlays attrs onto a same-type replacement, and swaps it in. Dropping tool_calls from an assistant cascades to remove their Tool results, preserving the invariant. Raises on neither/both keys or no match. – : (?id: String?, ?tool_call_id: String?, **untyped) -> Riffer::Messages::Base
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/riffer/agent/session.rb', line 98 def update(id: nil, tool_call_id: nil, **attrs) raise Riffer::ArgumentError, "update requires either id: or tool_call_id:" if id.nil? && tool_call_id.nil? raise Riffer::ArgumentError, "update accepts id: or tool_call_id:, not both" if id && tool_call_id idx = if id @messages.index { |m| m.id == id } else @messages.index { |m| m.is_a?(Riffer::Messages::Tool) && m.tool_call_id == tool_call_id } end unless idx key = id ? "id #{id.inspect}" : "tool_call_id #{tool_call_id.inspect}" raise Riffer::ArgumentError, "no message found for #{key}" end old = @messages[idx] #: Riffer::Messages::Base replacement = (old, attrs) @messages[idx] = replacement cascade_dropped_tool_calls(old, replacement) replacement end |