engram 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +123 -3
- data/lib/engram/adapters/in_memory_store.rb +5 -1
- data/lib/engram/adapters/pgvector_store.rb +15 -0
- data/lib/engram/extraction.rb +30 -0
- data/lib/engram/internal/candidate_integrity.rb +510 -0
- data/lib/engram/internal/core_hash.rb +36 -0
- data/lib/engram/internal/scope.rb +31 -0
- data/lib/engram/memory.rb +15 -0
- data/lib/engram/persistence.rb +79 -9
- data/lib/engram/persistence_policy.rb +11 -1
- data/lib/engram/ports/consolidator.rb +7 -2
- data/lib/engram/ports/extractor.rb +1 -1
- data/lib/engram/ports/memory_store.rb +11 -0
- data/lib/engram/provenance.rb +349 -18
- data/lib/engram/record.rb +12 -5
- data/lib/engram/use_cases/grounding_report.rb +44 -0
- data/lib/engram/use_cases/observe.rb +266 -13
- data/lib/engram/use_cases/source_impact.rb +42 -0
- data/lib/engram/version.rb +1 -1
- data/lib/engram.rb +6 -0
- metadata +8 -2
|
@@ -49,8 +49,9 @@ module Engram
|
|
|
49
49
|
next []
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
decisions = consolidate(candidates: candidates, scope: scope)
|
|
53
|
-
|
|
52
|
+
decisions, candidate_budget, detached_by_candidate = consolidate(candidates: candidates, scope: scope)
|
|
53
|
+
operations = preflight(decisions, candidate_budget, detached_by_candidate, scope)
|
|
54
|
+
applied_decisions = operations.filter_map { |operation| apply(operation, scope) }
|
|
54
55
|
payload[:decision_count] = applied_decisions.size
|
|
55
56
|
payload[:decision_actions] = applied_decisions.map { |decision| decision.action.to_s }
|
|
56
57
|
complete_claim(scope, idempotency_key, claim)
|
|
@@ -79,36 +80,288 @@ module Engram
|
|
|
79
80
|
def extract(messages:, scope:)
|
|
80
81
|
payload = Engram::Instrumentation.payload(scope: scope, store: @store, message_count: messages.size)
|
|
81
82
|
Engram::Instrumentation.instrument("extract", payload) do
|
|
82
|
-
candidates = @extractor.extract(messages: messages, scope: scope)
|
|
83
|
+
candidates = normalize_extractions(@extractor.extract(messages: messages, scope: scope))
|
|
83
84
|
payload[:candidate_count] = candidates.size
|
|
84
85
|
candidates
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
89
|
+
def normalize_extractions(results)
|
|
90
|
+
unless core_array?(results)
|
|
91
|
+
raise Engram::Error, "extractor must return an Array containing only Engram::Record or Engram::Extraction values"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
core_array_map(results) do |result|
|
|
95
|
+
candidate = case result
|
|
96
|
+
when Engram::Record then normalize_record(result)
|
|
97
|
+
when Engram::Extraction then normalize_record(result.to_record)
|
|
98
|
+
else
|
|
99
|
+
raise Engram::Error, "extractor must return an Array containing only Engram::Record or Engram::Extraction values"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raise Engram::Error, "observation candidates must not have an id" unless candidate.id.nil?
|
|
103
|
+
|
|
104
|
+
candidate
|
|
105
|
+
end
|
|
106
|
+
rescue Engram::Internal::CandidateIntegrity::Error => error
|
|
107
|
+
raise Engram::Error, error.message
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def normalize_record(record)
|
|
111
|
+
if Object.instance_method(:instance_of?).bind_call(record, Engram::Record)
|
|
112
|
+
candidate_integrity.validate!(record)
|
|
113
|
+
return record
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
candidate_integrity.detach(record)
|
|
117
|
+
rescue Engram::Internal::CandidateIntegrity::Error => error
|
|
118
|
+
raise Engram::Error, error.message
|
|
119
|
+
end
|
|
120
|
+
|
|
88
121
|
def consolidate(candidates:, scope:)
|
|
89
122
|
payload = Engram::Instrumentation.payload(scope: scope, store: @store, candidate_count: candidates.size)
|
|
90
123
|
Engram::Instrumentation.instrument("consolidate", payload) do
|
|
91
|
-
|
|
124
|
+
candidate_snapshot = snapshot_candidates(candidates)
|
|
125
|
+
detached_candidates = detach_candidates(candidates)
|
|
126
|
+
candidate_budget, detached_by_candidate = candidate_context(candidates, detached_candidates)
|
|
127
|
+
raw_decisions = @consolidator.reconcile_all(candidates: candidates, scope: scope)
|
|
128
|
+
decisions = canonicalize_decisions(raw_decisions)
|
|
129
|
+
verify_candidates!(candidates, candidate_snapshot)
|
|
130
|
+
|
|
92
131
|
payload[:decision_count] = decisions.size
|
|
93
132
|
payload[:decision_actions] = decisions.map { |decision| decision.action.to_s }
|
|
94
|
-
decisions
|
|
133
|
+
[decisions, candidate_budget, detached_by_candidate]
|
|
95
134
|
end
|
|
96
135
|
end
|
|
97
136
|
|
|
98
|
-
def
|
|
99
|
-
|
|
137
|
+
def snapshot_candidates(candidates)
|
|
138
|
+
candidate_integrity.snapshot(candidates)
|
|
139
|
+
rescue Engram::Internal::CandidateIntegrity::Error => error
|
|
140
|
+
raise Engram::Error, error.message
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def verify_candidates!(candidates, snapshot)
|
|
144
|
+
candidate_integrity.verify!(candidates, snapshot)
|
|
145
|
+
rescue Engram::Internal::CandidateIntegrity::Error => error
|
|
146
|
+
raise Engram::Error, error.message
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def detach_candidates(candidates)
|
|
150
|
+
candidates.map { |candidate| candidate_integrity.detach(candidate) }
|
|
151
|
+
rescue Engram::Internal::CandidateIntegrity::Error => error
|
|
152
|
+
raise Engram::Error, error.message
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def candidate_context(candidates, detached_candidates)
|
|
156
|
+
candidate_budget = Hash.new(0).compare_by_identity
|
|
157
|
+
detached_by_candidate = {}.compare_by_identity
|
|
158
|
+
candidates.zip(detached_candidates) do |candidate, detached|
|
|
159
|
+
candidate_budget[candidate] += 1
|
|
160
|
+
(detached_by_candidate[candidate] ||= []) << detached
|
|
161
|
+
end
|
|
162
|
+
[candidate_budget, detached_by_candidate]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def candidate_integrity
|
|
166
|
+
@candidate_integrity ||= Engram::Internal::CandidateIntegrity.new
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def canonicalize_decisions(raw_decisions)
|
|
170
|
+
unless core_array?(raw_decisions)
|
|
171
|
+
raise Engram::Error, "consolidator must return an Array of Engram::Decision values"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
core_array_map(raw_decisions) do |raw_decision|
|
|
175
|
+
unless Object.instance_method(:is_a?).bind_call(raw_decision, Engram::Decision)
|
|
176
|
+
raise Engram::Error, "consolidator must return an Array of Engram::Decision values"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
action = raw_decision.action
|
|
180
|
+
candidate = raw_decision.candidate
|
|
181
|
+
target_id = canonical_target_id(raw_decision.target_id)
|
|
182
|
+
reason = raw_decision.reason
|
|
183
|
+
unless Engram::Decision::ACTIONS.include?(action)
|
|
184
|
+
raise Engram::Error, "unsupported decision action #{action.inspect}"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
Engram::Decision.new(action: action, candidate: candidate, target_id: target_id, reason: reason)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Array subclasses are supported, but checks and traversal at an untrusted
|
|
192
|
+
# return boundary must not dispatch through singleton or subclass behavior.
|
|
193
|
+
def core_array?(value)
|
|
194
|
+
Object.instance_method(:is_a?).bind_call(value, Array)
|
|
195
|
+
rescue TypeError
|
|
196
|
+
false
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def core_array_map(values)
|
|
200
|
+
mapped = []
|
|
201
|
+
Array.instance_method(:each).bind_call(values) { |value| mapped << yield(value) }
|
|
202
|
+
mapped
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def canonical_target_id(target_id)
|
|
206
|
+
return if BasicObject.instance_method(:equal?).bind_call(target_id, nil)
|
|
207
|
+
|
|
208
|
+
target_class = Object.instance_method(:class).bind_call(target_id)
|
|
209
|
+
valid_class = target_class.equal?(Integer) || target_class.equal?(String)
|
|
210
|
+
valid_state = Object.instance_method(:instance_variables).bind_call(target_id).empty?
|
|
211
|
+
unless valid_class && valid_state
|
|
212
|
+
raise Engram::Error, "decision target_id must be a plain String or Integer"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
return target_id if target_class.equal?(Integer)
|
|
216
|
+
|
|
217
|
+
if custom_behavior?(target_id, target_class)
|
|
218
|
+
raise Engram::Error, "decision target_id must be a plain String or Integer"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
String.instance_method(:dup).bind_call(target_id)
|
|
222
|
+
rescue TypeError
|
|
223
|
+
raise Engram::Error, "decision target_id must be a plain String or Integer"
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def custom_behavior?(value, value_class)
|
|
227
|
+
singleton_class = Object.instance_method(:singleton_class).bind_call(value)
|
|
228
|
+
return false if singleton_class.equal?(value_class)
|
|
229
|
+
|
|
230
|
+
method_visibilities = %i[
|
|
231
|
+
public_instance_methods protected_instance_methods private_instance_methods
|
|
232
|
+
]
|
|
233
|
+
return true if method_visibilities.any? do |visibility|
|
|
234
|
+
Module.instance_method(visibility).bind_call(singleton_class, false).any?
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
singleton_ancestors = Module.instance_method(:ancestors).bind_call(singleton_class)
|
|
238
|
+
class_ancestors = Module.instance_method(:ancestors).bind_call(value_class)
|
|
239
|
+
return true unless singleton_ancestors.length == class_ancestors.length + 1 &&
|
|
240
|
+
class_ancestors.each_with_index.all? { |ancestor, index| singleton_ancestors[index + 1].equal?(ancestor) }
|
|
241
|
+
|
|
242
|
+
method_visibilities.any? do |visibility|
|
|
243
|
+
singleton_methods = Module.instance_method(visibility).bind_call(singleton_class, true)
|
|
244
|
+
class_methods = Module.instance_method(visibility).bind_call(value_class, true)
|
|
245
|
+
singleton_methods.length != class_methods.length ||
|
|
246
|
+
singleton_methods.any? { |method_name| !class_methods.include?(method_name) }
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def preflight(decisions, candidate_budget, detached_by_candidate, scope)
|
|
251
|
+
decision_counts = Hash.new(0).compare_by_identity
|
|
252
|
+
safe_decisions = []
|
|
253
|
+
|
|
254
|
+
decisions.each do |decision|
|
|
255
|
+
unless decision.is_a?(Engram::Decision)
|
|
256
|
+
raise Engram::Error, "consolidator must return an Array of Engram::Decision values"
|
|
257
|
+
end
|
|
258
|
+
unless Engram::Decision::ACTIONS.include?(decision.action)
|
|
259
|
+
raise Engram::Error, "unsupported decision action #{decision.action.inspect}"
|
|
260
|
+
end
|
|
261
|
+
unless Object.instance_method(:is_a?).bind_call(decision.candidate, Engram::Record)
|
|
262
|
+
message = if decision.action == :forget
|
|
263
|
+
"forget decision candidate must be an Engram::Record"
|
|
264
|
+
else
|
|
265
|
+
"decision candidate must be an Engram::Record"
|
|
266
|
+
end
|
|
267
|
+
raise Engram::Error, message
|
|
268
|
+
end
|
|
269
|
+
unless candidate_budget.key?(decision.candidate)
|
|
270
|
+
raise Engram::Error, "decision must reference the actual candidate supplied to the consolidator"
|
|
271
|
+
end
|
|
272
|
+
decision_counts[decision.candidate] += 1
|
|
273
|
+
if decision_counts[decision.candidate] > candidate_budget.fetch(decision.candidate)
|
|
274
|
+
raise Engram::Error,
|
|
275
|
+
"consolidator must return no more than one decision per candidate occurrence; " \
|
|
276
|
+
"multiple decisions reference the same candidate"
|
|
277
|
+
end
|
|
278
|
+
detached_candidate = detached_by_candidate.fetch(decision.candidate).fetch(
|
|
279
|
+
decision_counts.fetch(decision.candidate) - 1
|
|
280
|
+
)
|
|
281
|
+
unless Engram::Internal::Scope.record_matches?(detached_candidate, scope)
|
|
282
|
+
raise Engram::Error, "cannot move memory across scopes"
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
if %i[update forget].include?(decision.action) && !decision.target_id
|
|
286
|
+
raise Engram::Error, "#{decision.action} decision requires a target_id"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if %i[add update forget].include?(decision.action)
|
|
290
|
+
Engram::Provenance.extract_for_persistence(detached_candidate.metadata)
|
|
291
|
+
end
|
|
292
|
+
safe_decisions << Engram::Decision.new(action: decision.action, candidate: detached_candidate,
|
|
293
|
+
target_id: decision.target_id, reason: decision.reason)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
preflight_destructive_targets!(safe_decisions, scope)
|
|
297
|
+
decisions.zip(safe_decisions).map do |result_decision, safe_decision|
|
|
298
|
+
prepare_operation(safe_decision, scope, result_decision)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def preflight_destructive_targets!(decisions, scope)
|
|
303
|
+
target_ids = decisions.filter_map do |decision|
|
|
304
|
+
decision.target_id if %i[update forget].include?(decision.action)
|
|
305
|
+
end
|
|
306
|
+
return if target_ids.empty?
|
|
307
|
+
|
|
308
|
+
duplicate_id = target_ids.tally.find { |_, count| count > 1 }&.first
|
|
309
|
+
if duplicate_id
|
|
310
|
+
raise Engram::Error, "multiple decisions target memory #{duplicate_id.inspect}"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
existing_ids = scoped_existing_ids(scope, target_ids)
|
|
314
|
+
existing_id_lookup = existing_ids.each_with_object({}) { |id, lookup| lookup[id] = true }
|
|
315
|
+
missing_id = target_ids.find { |target_id| !existing_id_lookup[target_id] }
|
|
316
|
+
if missing_id
|
|
317
|
+
raise Engram::Error, "no memory with id #{missing_id.inspect} in scope #{scope.inspect}"
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def scoped_existing_ids(scope, target_ids)
|
|
322
|
+
if @store.respond_to?(:existing_ids)
|
|
323
|
+
begin
|
|
324
|
+
return Array(@store.existing_ids(scope: scope, ids: target_ids))
|
|
325
|
+
rescue NotImplementedError
|
|
326
|
+
# Optional capability: legacy adapters may inherit the default stub.
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
@store.all(scope: scope).map(&:id)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def prepare_operation(decision, scope, result_decision)
|
|
334
|
+
prepared = case decision.action
|
|
100
335
|
when :add
|
|
101
|
-
|
|
336
|
+
persistence.prepare(decision.candidate)
|
|
337
|
+
when :update
|
|
338
|
+
persistence.prepare(decision.candidate) if decision.target_id
|
|
339
|
+
when :forget
|
|
340
|
+
persistence.allowed?(decision.candidate) if decision.target_id
|
|
341
|
+
end
|
|
102
342
|
|
|
103
|
-
|
|
343
|
+
if %i[add update].include?(decision.action) && prepared &&
|
|
344
|
+
!Engram::Internal::Scope.record_matches?(prepared, scope)
|
|
345
|
+
raise Engram::Error, "cannot move memory across scopes"
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
[result_decision, decision, prepared]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def apply(operation, scope)
|
|
352
|
+
result_decision, decision, prepared = operation
|
|
353
|
+
case decision.action
|
|
354
|
+
when :add
|
|
355
|
+
result_decision if prepared && persistence.add_prepared(prepared, scope: scope)
|
|
104
356
|
when :update
|
|
105
|
-
if decision.target_id &&
|
|
106
|
-
|
|
357
|
+
if decision.target_id && prepared &&
|
|
358
|
+
persistence.update_prepared(scope: scope, id: decision.target_id, record: prepared)
|
|
359
|
+
result_decision
|
|
107
360
|
end
|
|
108
361
|
when :forget
|
|
109
|
-
if decision.target_id
|
|
362
|
+
if decision.target_id && prepared
|
|
110
363
|
deleted = @store.delete(scope: scope, id: decision.target_id)
|
|
111
|
-
|
|
364
|
+
result_decision if deleted && deleted != 0
|
|
112
365
|
end
|
|
113
366
|
when :noop
|
|
114
367
|
nil
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Engram
|
|
4
|
+
module UseCases
|
|
5
|
+
# Look up which memories in a scope reference a given host source.
|
|
6
|
+
#
|
|
7
|
+
# Returns the records whose understood provenance lists a source matching both
|
|
8
|
+
# `source_id` and `source_type` exactly, with no trimming or normalization.
|
|
9
|
+
# Reads stay tolerant: legacy, malformed, and future-schema
|
|
10
|
+
# provenance simply do not match. Source IDs are references, not authorization
|
|
11
|
+
# boundaries, so callers still bound the lookup to a `scope`.
|
|
12
|
+
class SourceImpact
|
|
13
|
+
def initialize(store:)
|
|
14
|
+
@store = store
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns the Array<Record> in `scope` referencing the exact source. Result
|
|
18
|
+
# order follows the store's #all enumeration and is not otherwise guaranteed.
|
|
19
|
+
def call(scope:, source_id:, source_type:)
|
|
20
|
+
source_id = require_string!("source_id", source_id)
|
|
21
|
+
source_type = require_string!("source_type", source_type)
|
|
22
|
+
|
|
23
|
+
@store.all(scope: scope).select do |record|
|
|
24
|
+
provenance = record.provenance
|
|
25
|
+
provenance&.sources&.any? do |source|
|
|
26
|
+
source.source_id == source_id && source.source_type == source_type
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def require_string!(name, value)
|
|
34
|
+
unless value.is_a?(String) && !value.strip.empty?
|
|
35
|
+
raise ArgumentError, "#{name} must be a non-empty String"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/engram/version.rb
CHANGED
data/lib/engram.rb
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
require_relative "engram/version"
|
|
4
4
|
require_relative "engram/configuration"
|
|
5
5
|
require_relative "engram/math"
|
|
6
|
+
require_relative "engram/internal/core_hash"
|
|
6
7
|
require_relative "engram/reserved_metadata"
|
|
7
8
|
require_relative "engram/embedding_metadata"
|
|
8
9
|
require_relative "engram/provenance"
|
|
9
10
|
require_relative "engram/memory_kind"
|
|
10
11
|
require_relative "engram/record"
|
|
12
|
+
require_relative "engram/internal/scope"
|
|
13
|
+
require_relative "engram/internal/candidate_integrity"
|
|
14
|
+
require_relative "engram/extraction"
|
|
11
15
|
require_relative "engram/decision"
|
|
12
16
|
require_relative "engram/turn_digest"
|
|
13
17
|
require_relative "engram/persistence_policy"
|
|
@@ -28,6 +32,8 @@ require_relative "engram/use_cases/inject"
|
|
|
28
32
|
require_relative "engram/use_cases/observe"
|
|
29
33
|
require_relative "engram/use_cases/forget"
|
|
30
34
|
require_relative "engram/use_cases/rebuild_embeddings"
|
|
35
|
+
require_relative "engram/use_cases/source_impact"
|
|
36
|
+
require_relative "engram/use_cases/grounding_report"
|
|
31
37
|
|
|
32
38
|
# Built-in adapters (pure Ruby, no external deps)
|
|
33
39
|
require_relative "engram/adapters/in_memory_store"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: engram
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexandr Kholodniak
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Engram gives AI agents durable, long-term memory. It recalls relevant facts about a
|
|
@@ -37,9 +37,13 @@ files:
|
|
|
37
37
|
- lib/engram/consolidators/llm_consolidator.rb
|
|
38
38
|
- lib/engram/decision.rb
|
|
39
39
|
- lib/engram/embedding_metadata.rb
|
|
40
|
+
- lib/engram/extraction.rb
|
|
40
41
|
- lib/engram/extractors/llm_extractor.rb
|
|
41
42
|
- lib/engram/instrumentation.rb
|
|
42
43
|
- lib/engram/integrations/ruby_llm.rb
|
|
44
|
+
- lib/engram/internal/candidate_integrity.rb
|
|
45
|
+
- lib/engram/internal/core_hash.rb
|
|
46
|
+
- lib/engram/internal/scope.rb
|
|
43
47
|
- lib/engram/math.rb
|
|
44
48
|
- lib/engram/memory.rb
|
|
45
49
|
- lib/engram/memory_kind.rb
|
|
@@ -61,10 +65,12 @@ files:
|
|
|
61
65
|
- lib/engram/reserved_metadata.rb
|
|
62
66
|
- lib/engram/turn_digest.rb
|
|
63
67
|
- lib/engram/use_cases/forget.rb
|
|
68
|
+
- lib/engram/use_cases/grounding_report.rb
|
|
64
69
|
- lib/engram/use_cases/inject.rb
|
|
65
70
|
- lib/engram/use_cases/observe.rb
|
|
66
71
|
- lib/engram/use_cases/rebuild_embeddings.rb
|
|
67
72
|
- lib/engram/use_cases/recall.rb
|
|
73
|
+
- lib/engram/use_cases/source_impact.rb
|
|
68
74
|
- lib/engram/version.rb
|
|
69
75
|
- lib/generators/engram/install_generator.rb
|
|
70
76
|
- lib/generators/engram/templates/create_engram_memories.rb.tt
|