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
- #bulk_insert_otel_events(rows) ⇒ Object
-
#bulk_insert_otel_metrics(rows) ⇒ Object
Bulk insert OTel metric rows in a single SQL statement.
- #bulk_insert_otel_traces(rows) ⇒ Object
-
#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_otel_event(event_name:, occurred_at:, session_id: nil, prompt_id: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel log-style event row.
-
#insert_otel_metric(name:, value_type:, recorded_at:, value_int: nil, value_float: nil, unit: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel metric data point.
-
#insert_otel_trace_span(trace_id:, span_id:, name:, recorded_at:, parent_span_id: nil, session_id: nil, prompt_id: nil, start_unix_nano: nil, end_unix_nano: nil, duration_ms: nil, status_code: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel trace span row.
-
#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
- #otel_events ⇒ Sequel::Dataset
- #otel_metrics ⇒ Sequel::Dataset
- #otel_traces ⇒ 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] |
#bulk_insert_otel_events(rows) ⇒ Object
231 232 233 234 235 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 231 def bulk_insert_otel_events(rows) return 0 if rows.empty? otel_events.multi_insert(rows.map { |r| otel_event_row(**r) }) rows.size end |
#bulk_insert_otel_metrics(rows) ⇒ Object
Bulk insert OTel metric rows in a single SQL statement. Hot-path callers (the OTLP receiver) batch dozens of points per request; multi_insert avoids the per-row prepare/bind overhead.
207 208 209 210 211 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 207 def bulk_insert_otel_metrics(rows) return 0 if rows.empty? otel_metrics.multi_insert(rows.map { |r| otel_metric_row(**r) }) rows.size end |
#bulk_insert_otel_traces(rows) ⇒ Object
267 268 269 270 271 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 267 def bulk_insert_otel_traces(rows) return 0 if rows.empty? otel_traces.multi_insert(rows.map { |r| otel_trace_row(**r) }) rows.size end |
#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.
156 157 158 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 156 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.
368 369 370 371 372 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 368 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.
591 592 593 594 595 596 597 598 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 591 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.
576 577 578 579 580 581 582 583 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 576 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.
494 495 496 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 494 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.
442 443 444 445 446 447 448 449 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 442 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.
360 361 362 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 360 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.
412 413 414 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 412 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.
696 697 698 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 696 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.
640 641 642 643 644 645 646 647 648 649 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 640 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.
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 468 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).
662 663 664 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 662 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.
171 172 173 174 175 176 177 178 179 180 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 171 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_otel_event(event_name:, occurred_at:, session_id: nil, prompt_id: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel log-style event row.
222 223 224 225 226 227 228 229 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 222 def insert_otel_event(event_name:, occurred_at:, session_id: nil, prompt_id: nil, attributes: nil, resource: nil) otel_events.insert(otel_event_row( event_name: event_name, occurred_at: occurred_at, session_id: session_id, prompt_id: prompt_id, attributes: attributes, resource: resource )) end |
#insert_otel_metric(name:, value_type:, recorded_at:, value_int: nil, value_float: nil, unit: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel metric data point. Two value columns let us preserve int64 precision for counters (token counts) without losing fidelity in Float — see migration 018.
195 196 197 198 199 200 201 202 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 195 def insert_otel_metric(name:, value_type:, recorded_at:, value_int: nil, value_float: nil, unit: nil, attributes: nil, resource: nil) otel_metrics.insert(otel_metric_row( name: name, value_type: value_type, recorded_at: recorded_at, value_int: value_int, value_float: value_float, unit: unit, attributes: attributes, resource: resource )) end |
#insert_otel_trace_span(trace_id:, span_id:, name:, recorded_at:, parent_span_id: nil, session_id: nil, prompt_id: nil, start_unix_nano: nil, end_unix_nano: nil, duration_ms: nil, status_code: nil, attributes: nil, resource: nil) ⇒ Integer
Insert one OTel trace span row. Only used when traces are explicitly opted in via Configuration#otel_traces_enabled?.
254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 254 def insert_otel_trace_span(trace_id:, span_id:, name:, recorded_at:, parent_span_id: nil, session_id: nil, prompt_id: nil, start_unix_nano: nil, end_unix_nano: nil, duration_ms: nil, status_code: nil, attributes: nil, resource: nil) otel_traces.insert(otel_trace_row( trace_id: trace_id, span_id: span_id, name: name, recorded_at: recorded_at, parent_span_id: parent_span_id, session_id: session_id, prompt_id: prompt_id, start_unix_nano: start_unix_nano, end_unix_nano: end_unix_nano, duration_ms: duration_ms, status_code: status_code, attributes: attributes, resource: resource )) 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.
612 613 614 615 616 617 618 619 620 621 622 623 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 612 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.
382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 382 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.
653 654 655 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 653 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] |
#otel_events ⇒ Sequel::Dataset
118 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 118 def otel_events = @db[:otel_events] |
#otel_metrics ⇒ Sequel::Dataset
115 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 115 def otel_metrics = @db[:otel_metrics] |
#otel_traces ⇒ Sequel::Dataset
121 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 121 def otel_traces = @db[:otel_traces] |
#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.
628 629 630 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 628 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.
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 546 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).
689 690 691 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 689 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.
399 400 401 402 403 404 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 399 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.
672 673 674 675 676 677 678 679 680 681 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 672 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.
421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 421 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.
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 509 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.
533 534 535 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 533 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.
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 329 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.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/claude_memory/store/sqlite_store.rb', line 132 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 |