Class: ClaudeMemory::Store::SQLiteStore
- Inherits:
-
Object
- Object
- ClaudeMemory::Store::SQLiteStore
- Includes:
- LLMCache, MetricsAggregator, RetryHandler, SchemaManager
- Defined in:
- lib/claude_memory/store/sqlite_store.rb
Overview
SQLite-backed fact store for ClaudeMemory. Manages all database tables (content_items, entities, facts, provenance, conflicts, fact_links, etc.) via Sequel with Extralite adapter. Includes RetryHandler for transient lock recovery and SchemaManager for automatic migrations on open.
Constant Summary
Constants included from SchemaManager
ClaudeMemory::Store::SchemaManager::SCHEMA_VERSION
Constants included from RetryHandler
RetryHandler::MAX_RETRIES, RetryHandler::RETRY_BASE_DELAY
Instance Attribute Summary collapse
-
#db ⇒ Sequel::Database
readonly
The underlying Sequel database connection.
Instance Method Summary collapse
- #activity_events ⇒ Sequel::Dataset
-
#checkpoint_wal ⇒ void
Checkpoint the WAL file to prevent unlimited growth.
-
#clear_moment_feedback(event_id) ⇒ Integer
Remove the verdict for a moment, if any.
-
#close ⇒ void
Disconnect from the database.
- #conflicts ⇒ Sequel::Dataset
-
#content_item_by_transcript_and_mtime(transcript_path, mtime_iso8601) ⇒ Hash?
Find a content item by transcript path and source modification time.
- #content_items ⇒ Sequel::Dataset
- #delta_cursors ⇒ Sequel::Dataset
- #entities ⇒ Sequel::Dataset
- #entity_aliases ⇒ Sequel::Dataset
- #fact_links ⇒ Sequel::Dataset
- #facts ⇒ Sequel::Dataset
-
#facts_for_slot(subject_entity_id, predicate, status: "active") ⇒ Array<Hash>
Find all facts for a given subject + predicate combination (a “slot”).
-
#facts_with_embeddings(limit: 1000) ⇒ Array<Hash>
Retrieve active facts that have stored embeddings.
-
#find_fact_by_docid(docid) ⇒ Hash?
Look up a fact by its short document identifier.
-
#find_or_create_entity(type:, name:) ⇒ Integer
Find an entity by its slug or create a new one.
-
#get_content_item(id) ⇒ Hash?
Fetch a single content item by primary key.
-
#get_delta_cursor(session_id, transcript_path) ⇒ Integer?
Get the last-read byte offset for a session/transcript pair.
-
#get_meta(key) ⇒ String?
Retrieve a value from the meta table.
- #ingestion_metrics ⇒ Sequel::Dataset
-
#initialize(db_path) ⇒ SQLiteStore
constructor
Open (or create) a SQLite database and migrate to the current schema.
-
#insert_conflict(fact_a_id:, fact_b_id:, status: "open", notes: nil) ⇒ Integer
Record a conflict between two facts.
-
#insert_fact(subject_entity_id:, predicate:, object_entity_id: nil, object_literal: nil, datatype: nil, polarity: "positive", valid_from: nil, status: "active", confidence: 1.0, created_from: nil, scope: "project", project_path: nil) ⇒ Integer
Insert a new fact (subject-predicate-object triple) with an auto-generated docid.
-
#insert_fact_link(from_fact_id:, to_fact_id:, link_type:) ⇒ Integer
Create a directional link between two facts (e.g. supersession).
-
#insert_mcp_tool_call(tool_name:, duration_ms:, result_count: nil, scope: nil, error_class: nil, called_at: nil) ⇒ Integer
Record a single MCP tool invocation for telemetry.
-
#insert_provenance(fact_id:, content_item_id: nil, quote: nil, attribution_entity_id: nil, strength: "stated", line_start: nil, line_end: nil) ⇒ Integer
Record a provenance link between a fact and its source evidence.
-
#insert_tool_calls(content_item_id, tool_calls_data) ⇒ void
Bulk-insert tool call records for a content item.
- #llm_cache ⇒ Sequel::Dataset
- #mcp_tool_calls ⇒ Sequel::Dataset
- #moment_feedback ⇒ Sequel::Dataset
-
#open_conflicts ⇒ Array<Hash>
Retrieve all unresolved conflicts.
- #operation_progress ⇒ Sequel::Dataset
- #provenance ⇒ Sequel::Dataset
-
#provenance_for_fact(fact_id) ⇒ Array<Hash>
Retrieve all provenance records for a given fact.
-
#reject_fact(fact_id, reason: nil) ⇒ Hash?
Reject a fact as incorrect (e.g. a distiller hallucination).
- #schema_health ⇒ Sequel::Dataset
-
#schema_version ⇒ Integer?
Current schema version stored in the meta table.
-
#set_meta(key, value) ⇒ void
Set a key-value pair in the meta table (upsert).
- #tool_calls ⇒ Sequel::Dataset
-
#tool_calls_for_content_item(content_item_id) ⇒ Array<Hash>
Retrieve tool calls for a content item, ordered by timestamp.
-
#undistilled_content_items(limit: 3, min_length: 200) ⇒ Array<Hash>
Fetch content items that have not yet been distilled, ordered newest first.
-
#update_delta_cursor(session_id, transcript_path, offset) ⇒ void
Create or update the byte-offset cursor for a session/transcript pair.
-
#update_fact(fact_id, status: nil, valid_to: nil, scope: nil, project_path: nil, embedding: nil) ⇒ Boolean
Selectively update one or more fields on a fact.
-
#update_fact_embedding(fact_id, embedding_vector) ⇒ void
Overwrite the embedding vector for a fact.
-
#upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil, project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil, git_branch: nil, cwd: nil, claude_version: nil, thinking_level: nil, source_mtime: nil) ⇒ Integer
Insert a content item or return the existing id if a duplicate (same text_hash + session_id) already exists.
-
#upsert_moment_feedback(event_id:, verdict:, note: nil, recorded_at: nil) ⇒ Hash
Upsert a thumbs-up/down verdict for a moment.
-
#vector_index ⇒ Index::VectorIndex
Lazily-initialized vector index for semantic search.
Methods included from MetricsAggregator
#aggregate_ingestion_metrics, #backfill_distillation_metrics!, #count_undistilled, #record_ingestion_metrics
Methods included from LLMCache
#llm_cache_key, #llm_cache_lookup, #llm_cache_prune, #llm_cache_store
Methods included from RetryHandler
#transaction_with_retry, #with_retry
Constructor Details
#initialize(db_path) ⇒ SQLiteStore
Open (or create) a SQLite database and migrate to the current schema.
32 33 34 35 36 37 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 32 def initialize(db_path) @db_path = db_path @db = connect_database(db_path) ensure_schema! end |
Instance Attribute Details
#db ⇒ Sequel::Database (readonly)
Returns the underlying Sequel database connection.
28 29 30 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 28 def db @db end |
Instance Method Details
#activity_events ⇒ Sequel::Dataset
109 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 109 def activity_events = @db[:activity_events] |
#checkpoint_wal ⇒ void
This method returns an undefined value.
Checkpoint the WAL file to prevent unlimited growth.
53 54 55 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 53 def checkpoint_wal @db.run("PRAGMA wal_checkpoint(TRUNCATE)") end |
#clear_moment_feedback(event_id) ⇒ Integer
Remove the verdict for a moment, if any.
147 148 149 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 147 def clear_moment_feedback(event_id) with_retry { moment_feedback.where(event_id: event_id).delete } end |
#close ⇒ void
This method returns an undefined value.
Disconnect from the database.
41 42 43 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 41 def close @db.disconnect end |
#conflicts ⇒ Sequel::Dataset
88 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 88 def conflicts = @db[:conflicts] |
#content_item_by_transcript_and_mtime(transcript_path, mtime_iso8601) ⇒ Hash?
Find a content item by transcript path and source modification time.
232 233 234 235 236 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 232 def content_item_by_transcript_and_mtime(transcript_path, mtime_iso8601) content_items .where(transcript_path: transcript_path, source_mtime: mtime_iso8601) .first end |
#content_items ⇒ Sequel::Dataset
67 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 67 def content_items = @db[:content_items] |
#delta_cursors ⇒ Sequel::Dataset
70 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 70 def delta_cursors = @db[:delta_cursors] |
#entities ⇒ Sequel::Dataset
73 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 73 def entities = @db[:entities] |
#entity_aliases ⇒ Sequel::Dataset
76 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 76 def entity_aliases = @db[:entity_aliases] |
#fact_links ⇒ Sequel::Dataset
85 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 85 def fact_links = @db[:fact_links] |
#facts ⇒ Sequel::Dataset
79 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 79 def facts = @db[:facts] |
#facts_for_slot(subject_entity_id, predicate, status: "active") ⇒ Array<Hash>
Find all facts for a given subject + predicate combination (a “slot”). Used by the resolver to detect supersession and conflicts.
455 456 457 458 459 460 461 462 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 455 def facts_for_slot(subject_entity_id, predicate, status: "active") facts .where(subject_entity_id: subject_entity_id, predicate: predicate, status: status) .select(:id, :subject_entity_id, :predicate, :object_entity_id, :object_literal, :datatype, :polarity, :valid_from, :valid_to, :status, :confidence, :created_from, :created_at) .all end |
#facts_with_embeddings(limit: 1000) ⇒ Array<Hash>
Retrieve active facts that have stored embeddings.
440 441 442 443 444 445 446 447 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 440 def (limit: 1000) facts .where(Sequel.~(embedding_json: nil)) .where(status: "active") .select(:id, :subject_entity_id, :predicate, :object_literal, :embedding_json, :scope) .limit(limit) .all end |
#find_fact_by_docid(docid) ⇒ Hash?
Look up a fact by its short document identifier.
358 359 360 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 358 def find_fact_by_docid(docid) facts.where(docid: docid).first end |
#find_or_create_entity(type:, name:) ⇒ Integer
Find an entity by its slug or create a new one.
306 307 308 309 310 311 312 313 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 306 def find_or_create_entity(type:, name:) slug = slugify(type, name) existing = entities.where(slug: slug).get(:id) return existing if existing now = Time.now.utc.iso8601 entities.insert(type: type, canonical_name: name, slug: slug, created_at: now) end |
#get_content_item(id) ⇒ Hash?
Fetch a single content item by primary key.
224 225 226 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 224 def get_content_item(id) content_items.where(id: id).first end |
#get_delta_cursor(session_id, transcript_path) ⇒ Integer?
Get the last-read byte offset for a session/transcript pair.
276 277 278 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 276 def get_delta_cursor(session_id, transcript_path) delta_cursors.where(session_id: session_id, transcript_path: transcript_path).get(:last_byte_offset) end |
#get_meta(key) ⇒ String?
Retrieve a value from the meta table.
560 561 562 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 560 def (key) @db[:meta].where(key: key).get(:value) end |
#ingestion_metrics ⇒ Sequel::Dataset
100 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 100 def ingestion_metrics = @db[:ingestion_metrics] |
#insert_conflict(fact_a_id:, fact_b_id:, status: "open", notes: nil) ⇒ Integer
Record a conflict between two facts.
504 505 506 507 508 509 510 511 512 513 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 504 def insert_conflict(fact_a_id:, fact_b_id:, status: "open", notes: nil) now = Time.now.utc.iso8601 conflicts.insert( fact_a_id: fact_a_id, fact_b_id: fact_b_id, status: status, detected_at: now, notes: notes ) end |
#insert_fact(subject_entity_id:, predicate:, object_entity_id: nil, object_literal: nil, datatype: nil, polarity: "positive", valid_from: nil, status: "active", confidence: 1.0, created_from: nil, scope: "project", project_path: nil) ⇒ Integer
Insert a new fact (subject-predicate-object triple) with an auto-generated docid.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 332 def insert_fact(subject_entity_id:, predicate:, object_entity_id: nil, object_literal: nil, datatype: nil, polarity: "positive", valid_from: nil, status: "active", confidence: 1.0, created_from: nil, scope: "project", project_path: nil) now = Time.now.utc.iso8601 docid = generate_docid(subject_entity_id, predicate, object_literal, now) facts.insert( subject_entity_id: subject_entity_id, predicate: predicate, object_entity_id: object_entity_id, object_literal: object_literal, datatype: datatype, polarity: polarity, valid_from: valid_from || now, status: status, confidence: confidence, created_from: created_from, created_at: now, scope: scope, project_path: project_path, docid: docid ) end |
#insert_fact_link(from_fact_id:, to_fact_id:, link_type:) ⇒ Integer
Create a directional link between two facts (e.g. supersession).
526 527 528 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 526 def insert_fact_link(from_fact_id:, to_fact_id:, link_type:) fact_links.insert(from_fact_id: from_fact_id, to_fact_id: to_fact_id, link_type: link_type) end |
#insert_mcp_tool_call(tool_name:, duration_ms:, result_count: nil, scope: nil, error_class: nil, called_at: nil) ⇒ Integer
Record a single MCP tool invocation for telemetry. Inserts synchronously; callers wrap in with_retry at the call site if needed.
162 163 164 165 166 167 168 169 170 171 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 162 def insert_mcp_tool_call(tool_name:, duration_ms:, result_count: nil, scope: nil, error_class: nil, called_at: nil) mcp_tool_calls.insert( tool_name: tool_name, called_at: called_at || Time.now.utc.iso8601, duration_ms: duration_ms, result_count: result_count, scope: scope, error_class: error_class ) end |
#insert_provenance(fact_id:, content_item_id: nil, quote: nil, attribution_entity_id: nil, strength: "stated", line_start: nil, line_end: nil) ⇒ Integer
Record a provenance link between a fact and its source evidence.
476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 476 def insert_provenance(fact_id:, content_item_id: nil, quote: nil, attribution_entity_id: nil, strength: "stated", line_start: nil, line_end: nil) provenance.insert( fact_id: fact_id, content_item_id: content_item_id, quote: quote, attribution_entity_id: attribution_entity_id, strength: strength, line_start: line_start, line_end: line_end ) end |
#insert_tool_calls(content_item_id, tool_calls_data) ⇒ void
This method returns an undefined value.
Bulk-insert tool call records for a content item.
246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 246 def insert_tool_calls(content_item_id, tool_calls_data) tool_calls_data.each do |tc| tool_calls.insert( content_item_id: content_item_id, tool_name: tc[:tool_name], tool_input: tc[:tool_input], tool_result: tc[:tool_result], compressed_summary: tc[:compressed_summary], is_error: tc[:is_error] || false, timestamp: tc[:timestamp] ) end end |
#llm_cache ⇒ Sequel::Dataset
103 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 103 def llm_cache = @db[:llm_cache] |
#mcp_tool_calls ⇒ Sequel::Dataset
106 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 106 def mcp_tool_calls = @db[:mcp_tool_calls] |
#moment_feedback ⇒ Sequel::Dataset
112 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 112 def moment_feedback = @db[:moment_feedback] |
#open_conflicts ⇒ Array<Hash>
Retrieve all unresolved conflicts.
517 518 519 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 517 def open_conflicts conflicts.where(status: "open").all end |
#operation_progress ⇒ Sequel::Dataset
94 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 94 def operation_progress = @db[:operation_progress] |
#provenance ⇒ Sequel::Dataset
82 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 82 def provenance = @db[:provenance] |
#provenance_for_fact(fact_id) ⇒ Array<Hash>
Retrieve all provenance records for a given fact.
492 493 494 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 492 def provenance_for_fact(fact_id) provenance.where(fact_id: fact_id).all end |
#reject_fact(fact_id, reason: nil) ⇒ Hash?
Reject a fact as incorrect (e.g. a distiller hallucination). Sets status to “rejected”, closes any open conflicts involving the fact, and records the reason in conflict notes when provided. All updates run in a single transaction.
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 410 def reject_fact(fact_id, reason: nil) row = facts.where(id: fact_id).first return nil unless row now = Time.now.utc.iso8601 resolved = 0 @db.transaction do facts.where(id: fact_id).update(status: "rejected", valid_to: now) open_conflict_rows = conflicts .where(status: "open") .where { (fact_a_id =~ fact_id) | (fact_b_id =~ fact_id) } .all open_conflict_rows.each do |conflict| suffix = reason ? " | resolved: rejected fact #{fact_id} (#{reason})" : " | resolved: rejected fact #{fact_id}" notes = "#{conflict[:notes]}#{suffix}" conflicts.where(id: conflict[:id]).update(status: "resolved", notes: notes) end resolved = open_conflict_rows.size end {rejected: true, conflicts_resolved: resolved} end |
#schema_health ⇒ Sequel::Dataset
97 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 97 def schema_health = @db[:schema_health] |
#schema_version ⇒ Integer?
Current schema version stored in the meta table.
59 60 61 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 59 def schema_version @db[:meta].where(key: "schema_version").get(:value)&.to_i end |
#set_meta(key, value) ⇒ void
This method returns an undefined value.
Set a key-value pair in the meta table (upsert).
553 554 555 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 553 def (key, value) @db[:meta].insert_conflict(target: :key, update: {value: value}).insert(key: key, value: value) end |
#tool_calls ⇒ Sequel::Dataset
91 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 91 def tool_calls = @db[:tool_calls] |
#tool_calls_for_content_item(content_item_id) ⇒ Array<Hash>
Retrieve tool calls for a content item, ordered by timestamp.
263 264 265 266 267 268 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 263 def tool_calls_for_content_item(content_item_id) tool_calls .where(content_item_id: content_item_id) .order(:timestamp) .all end |
#undistilled_content_items(limit: 3, min_length: 200) ⇒ Array<Hash>
Fetch content items that have not yet been distilled, ordered newest first.
536 537 538 539 540 541 542 543 544 545 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 536 def undistilled_content_items(limit: 3, min_length: 200) content_items .left_join(:ingestion_metrics, content_item_id: :id) .where(Sequel[:ingestion_metrics][:id] => nil) .where { byte_len >= min_length } .order(Sequel.desc(:occurred_at)) .limit(limit) .select_all(:content_items) .all end |
#update_delta_cursor(session_id, transcript_path, offset) ⇒ void
This method returns an undefined value.
Create or update the byte-offset cursor for a session/transcript pair.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 285 def update_delta_cursor(session_id, transcript_path, offset) now = Time.now.utc.iso8601 delta_cursors .insert_conflict( target: [:session_id, :transcript_path], update: {last_byte_offset: offset, updated_at: now} ) .insert( session_id: session_id, transcript_path: transcript_path, last_byte_offset: offset, updated_at: now ) end |
#update_fact(fact_id, status: nil, valid_to: nil, scope: nil, project_path: nil, embedding: nil) ⇒ Boolean
Selectively update one or more fields on a fact. Only provided (non-nil) keyword arguments are written. Setting scope to “global” automatically clears project_path.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 373 def update_fact(fact_id, status: nil, valid_to: nil, scope: nil, project_path: nil, embedding: nil) updates = {} updates[:status] = status if status updates[:valid_to] = valid_to if valid_to if scope updates[:scope] = scope updates[:project_path] = (scope == "global") ? nil : project_path end if updates[:embedding_json] = .to_json end return false if updates.empty? facts.where(id: fact_id).update(updates) true end |
#update_fact_embedding(fact_id, embedding_vector) ⇒ void
This method returns an undefined value.
Overwrite the embedding vector for a fact.
397 398 399 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 397 def (fact_id, ) facts.where(id: fact_id).update(embedding_json: .to_json) end |
#upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil, project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil, git_branch: nil, cwd: nil, claude_version: nil, thinking_level: nil, source_mtime: nil) ⇒ Integer
Insert a content item or return the existing id if a duplicate (same text_hash + session_id) already exists. Wrapped in retry logic.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 193 def upsert_content_item(source:, text_hash:, byte_len:, session_id: nil, transcript_path: nil, project_path: nil, occurred_at: nil, raw_text: nil, metadata: nil, git_branch: nil, cwd: nil, claude_version: nil, thinking_level: nil, source_mtime: nil) with_retry("upsert_content_item") do existing = content_items.where(text_hash: text_hash, session_id: session_id).get(:id) return existing if existing now = Time.now.utc.iso8601 content_items.insert( source: source, session_id: session_id, transcript_path: transcript_path, project_path: project_path, occurred_at: occurred_at || now, ingested_at: now, text_hash: text_hash, byte_len: byte_len, raw_text: raw_text, metadata_json: &.to_json, git_branch: git_branch, cwd: cwd, claude_version: claude_version, thinking_level: thinking_level, source_mtime: source_mtime ) end end |
#upsert_moment_feedback(event_id:, verdict:, note: nil, recorded_at: nil) ⇒ Hash
Upsert a thumbs-up/down verdict for a moment. One row per event_id (unique constraint on the column) — repeat clicks overwrite. Returns the persisted row.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 123 def upsert_moment_feedback(event_id:, verdict:, note: nil, recorded_at: nil) raise ArgumentError, "verdict must be 'up' or 'down'" unless %w[up down].include?(verdict) ts = recorded_at || Time.now.utc.iso8601 with_retry do @db.transaction do existing = moment_feedback.where(event_id: event_id).first if existing moment_feedback.where(id: existing[:id]).update( verdict: verdict, note: note, recorded_at: ts ) moment_feedback.where(id: existing[:id]).first else id = moment_feedback.insert( event_id: event_id, verdict: verdict, note: note, recorded_at: ts ) moment_feedback.where(id: id).first end end end end |
#vector_index ⇒ Index::VectorIndex
Lazily-initialized vector index for semantic search.
47 48 49 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 47 def vector_index @vector_index ||= Index::VectorIndex.new(self) end |