Class: ActiveHarness::Memory
- Inherits:
-
Object
- Object
- ActiveHarness::Memory
- Defined in:
- lib/active_harness/memory.rb,
lib/active_harness/memory/adapter/base.rb,
lib/active_harness/memory/adapter/file.rb
Overview
Conversational memory for agents.
Memory only records the history of request/response turns. It does NOT automatically inject history into LLM messages. Injection is always manual — you control when and how context is used.
— Recording (automatic) —
When a Memory object is passed to an agent, the agent automatically saves each successful turn (request + response) after the call.
memory = ActiveHarness::Memory.new(session_id: "u42", depth: 8)
SupportAgent.call(input: "Hello", memory: memory)
# => turn is saved to storage/ai/memory/u42.json
— Manual injection patterns —
Option A: prepend history to input in a before_call hook
on :before_call do
history = @memory&.
if history&.any?
lines = history.map { |m| "#{m[:role]}: #{m[:content]}" }.join("\n")
@input = "Previous conversation:\n#{lines}\n\nUser: #{@input}"
end
end
Option B: inject history into the system prompt
on :after_system_prompt do |prompt|
history = @memory&.
if history&.any?
lines = history.map { |m| "#{m[:role]}: #{m[:content]}" }.join("\n")
@system_prompt = "#{prompt}\n\nConversation so far:\n#{lines}"
end
end
Option C: use a prompt class that reads @memory directly
class SupportPrompt
def call
base = "You are a helpful assistant."
return base unless @memory&.size&.positive?
history = @memory.
.map { |m| "#{m[:role]}: #{m[:content]}" }
.join("\n")
"#{base}\n\nConversation so far:\n#{history}"
end
end
Direct Known Subclasses
Defined Under Namespace
Modules: Adapter
Constant Summary collapse
Instance Attribute Summary collapse
-
#session_id ⇒ Object
readonly
Returns the value of attribute session_id.
Instance Method Summary collapse
-
#clear ⇒ Object
Clear in-RAM turns (does not touch the storage file/key).
-
#close ⇒ Object
Flush and close the adapter.
-
#delete ⇒ Object
Delete the session from storage backend entirely.
-
#initialize(session_id:, depth: nil, adapter: :file, enabled: true, read_only: false, namespace: nil, on_trim: nil, async: false, **adapter_opts) ⇒ Memory
constructor
————————————————————————- Constructor ————————————————————————- session_id — required; uniquely identifies this conversation depth — how many past turns to inject into messages (nil = all) adapter — :file (default), or an adapter instance enabled — false disables all reads and writes (no-op mode) read_only — true: load history but never write new turns namespace — isolates history per-agent within a session on_trim — Proc called with trimmed turns on storage trim async — write to adapter in a background thread **adapter_opts — forwarded to the adapter (path, storage_size, etc.).
-
#load ⇒ Object
Load history from storage into RAM.
-
#record(request:, response:, **meta) ⇒ Object
Record a turn after a successful agent call.
-
#size ⇒ Object
Number of turns currently in memory.
-
#to_messages(filter: nil, since: nil) ⇒ Object
Returns messages array for LLM consumption, respecting depth.
-
#turns ⇒ Object
All stored turns without depth/filter trimming.
Constructor Details
#initialize(session_id:, depth: nil, adapter: :file, enabled: true, read_only: false, namespace: nil, on_trim: nil, async: false, **adapter_opts) ⇒ Memory
Constructor
session_id — required; uniquely identifies this conversation
depth — how many past turns to inject into messages (nil = all)
adapter — :file (default), or an adapter instance
enabled — false disables all reads and writes (no-op mode)
read_only — true: load history but never write new turns
namespace — isolates history per-agent within a session
on_trim — Proc called with trimmed turns on storage trim
async — write to adapter in a background thread
**adapter_opts — forwarded to the adapter (path, storage_size, etc.)
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/active_harness/memory.rb', line 74 def initialize( session_id:, depth: nil, adapter: :file, enabled: true, read_only: false, namespace: nil, on_trim: nil, async: false, **adapter_opts ) @session_id = session_id @depth = depth @enabled = enabled @read_only = read_only @namespace = namespace @async = async @turns = [] @loaded = false adapter_opts[:namespace] = namespace if namespace adapter_opts[:on_trim] = on_trim if on_trim @adapter = resolve_adapter(adapter, adapter_opts) end |
Instance Attribute Details
#session_id ⇒ Object (readonly)
Returns the value of attribute session_id.
60 61 62 |
# File 'lib/active_harness/memory.rb', line 60 def session_id @session_id end |
Instance Method Details
#clear ⇒ Object
Clear in-RAM turns (does not touch the storage file/key).
166 167 168 169 |
# File 'lib/active_harness/memory.rb', line 166 def clear @turns = [] @loaded = false end |
#close ⇒ Object
Flush and close the adapter.
181 182 183 |
# File 'lib/active_harness/memory.rb', line 181 def close @adapter.close end |
#delete ⇒ Object
Delete the session from storage backend entirely.
172 173 174 175 176 177 178 |
# File 'lib/active_harness/memory.rb', line 172 def delete return unless @enabled @adapter.open(@session_id) unless @loaded @adapter.delete clear end |
#load ⇒ Object
Load history from storage into RAM. Called automatically by the agent at the start of #call. After loading, history is available via #turns and #to_messages for manual injection in hooks or prompt classes.
108 109 110 111 112 113 114 115 |
# File 'lib/active_harness/memory.rb', line 108 def load return unless @enabled return if @loaded @adapter.open(@session_id) @turns = @adapter.read @loaded = true end |
#record(request:, response:, **meta) ⇒ Object
Record a turn after a successful agent call.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/active_harness/memory.rb', line 118 def record(request:, response:, **) return unless @enabled return if @read_only turn = { request: request.to_s, response: response.to_s } turn.merge!() unless .empty? turn[:at] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") @turns << turn if @async Thread.new { safe_write(turn) } else safe_write(turn) # keep RAM in sync with what adapter stored (adapter may have trimmed) @turns = @adapter.read end end |
#size ⇒ Object
Number of turns currently in memory.
161 162 163 |
# File 'lib/active_harness/memory.rb', line 161 def size @turns.size end |
#to_messages(filter: nil, since: nil) ⇒ Object
Returns messages array for LLM consumption, respecting depth. Optional filters:
filter: ->(turn) { turn[:agent] == "SupportAgent" }
since: Time.now - 3600
141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/active_harness/memory.rb', line 141 def (filter: nil, since: nil) turns = @turns.dup turns.select! { |t| filter.call(t) } if filter turns.select! { |t| after?(t, since) } if since turns = turns.last(@depth) if @depth turns.flat_map do |t| [ { role: "user", content: t[:request] }, { role: "assistant", content: t[:response] } ] end end |
#turns ⇒ Object
All stored turns without depth/filter trimming.
156 157 158 |
# File 'lib/active_harness/memory.rb', line 156 def turns @turns.dup end |