Skip to content

Latest commit

 

History

History
123 lines (102 loc) · 6.38 KB

File metadata and controls

123 lines (102 loc) · 6.38 KB

Storage schema

One store = one SQLite file. WAL journal mode, synchronous=NORMAL, foreign keys ON. All timestamps are ISO-8601 UTC strings. Schema is versioned via meta.schema_version; migrations run in db.open_store().

Tables

meta

key TEXT PK, value TEXT — schema version, install markers, checkpoints.

sessions

column notes
id PK
started_at, ended_at ended_at NULL while live; orphans (crash) are closed on next open
title first user message, ≤ 80 chars — the no-LLM summary floor
summary LLM 1–2 sentence episodic summary (nullable)
rolling_summary last working-memory digest, used by resume_session
message_count maintained by record_turn
host_tag free-form host label (model id, app version…)

messages — the permanent verbatim record

column notes
session_id → sessions ON DELETE CASCADE
turn user-turn index within the session
role user | assistant | system
content text; media as [image: <ref>] placeholders
token_estimate chars/4 at write time; used by resume budgeting

Index: (session_id, turn).

chunks — episodic retrieval units

One per exchange (User: …\nAssistant: …), sentence-split beyond chunk_target_tokens (default 400).

column notes
first_msg_id, last_msg_id span of source messages
text the condensed exchange
embedding little-endian float32 BLOB (array('f')), NULL until embedded
embedding_model label; lets a re-index detect stale vectors. "elastimem:bge-small-en-v1.5" for vectors from the built-in default embedder, "host" for any host-supplied embedder= (the label doesn't distinguish between different host embedders — a host that swaps its own embedding model should re-embed or track that distinction itself)
importance default 0.5; consolidation may raise/lower later

Indexes: (session_id); partial WHERE embedding IS NULL (the embed queue). FTS5: chunks_fts (content-linked, porter unicode61, trigger-maintained).

facts — temporally versioned semantic memory

column notes
key normalized snake_case
value short string
category profile (always injected) | note (competes by score)
source explicit(1.0) | rule(0.7) | auto(0.5) | import(0.8)importance
valid_from, invalidated_at NULL invalidated_at = current version
invalidated_by → facts the superseding row (audit chain)
archived decay-forgotten; excluded from prompts, never deleted
last_accessed_at, access_count bumped when injected; feeds decay

Indexes: unique partial (key) WHERE invalidated_at IS NULL (one live version per key — updates must invalidate before insert); (key, valid_from) for history. FTS5: facts_fts over (key, value).

lessons

text UNIQUE, tag, use_count, archived — procedural memory; capped by archiving the oldest beyond max_lessons.

quarantine

Rejected automatic extractions: ts, key, value, reason, source. Capped at quarantine_cap (200). Never injected into prompts; exists so extractor misbehavior is inspectable.

graph_nodes — embedded semantic knowledge graph (entities)

Extracted alongside facts by the same LLM completion (extraction.py), gated by MemoryProfile.graph_hops (LITE=1, STANDARD=1, FULL=2 — see governor.py). One more retrieval signal inside the existing hybrid pipeline, not a separate store.

column notes
type person | place | org | thing | entity
canonical_name normalized (lowercased, whitespace-collapsed, leading article stripped) identity — see graph._canonicalize
aliases JSON array of raw surface forms seen, capped at 8
importance, confidence confidence is a running average across re-extractions, used to weight the graph retrieval nudge
mention_count bumped on every re-extraction of the same entity
cluster_id nullable; the root node id of this entity's connected component (see graph.compute_clusters), NULL for a singleton with no edges
cluster_label nullable; a short LLM-generated topic name for the cluster (e.g. "Local AI"), set separately from clustering itself — see graph.label_clusters

Unique index (type, canonical_name) — write-time dedup; repeated mentions update the existing row (ON CONFLICT DO UPDATE) instead of inserting a new one. Rows beyond graph_node_cap (default 2000) are trimmed by lowest (importance, mention_count, updated_at). Beyond the cap, a background consolidation sweep (graph.apply_decay, graph.merge_duplicates) hard-deletes confidence-decayed rows and merges LLM-confirmed duplicate entities — see governor.md.

graph_edges — embedded semantic knowledge graph (relationships)

column notes
source_node, target_node → graph_nodes ON DELETE CASCADE; directed, but traversed bidirectionally at retrieval time (graph.expand)
relationship short snake_case label, e.g. works_at, builds, runs_on
confidence, importance, weight confidence is a running average, same pattern as nodes
seen_count, last_seen bumped on repeated extraction of the same relationship
source_chunk_id → chunks nullable; the chunk that produced this edge (not currently surfaced by explain(), which computes its traversal fresh rather than reading provenance off individual edges — kept for a future direct-provenance lookup)

Unique index (source_node, target_node, relationship) — write-time dedup, same pattern as nodes. Rows beyond graph_edge_cap (default 5000) are trimmed by lowest (importance, seen_count, last_seen).

Traversal is a WITH RECURSIVE CTE bounded by the governor's hop count — no graph library, no separate index. Query-time entity detection (graph.detect_seed_nodes) is a plain substring scan over canonical_name/aliases, no NER call.

Size expectations

384-dim float32 vector = 1.5 KB/chunk → 10k chunks ≈ 15 MB of vectors. Benchmarked recall at 10k chunks (pure Python): FTS5 ~2 ms, hybrid ~43 ms. The elastimem[vec] extra is reserved for a future sqlite-vec-backed index over the same BLOBs, for much larger stores — it is declared but not yet wired into the codebase; brute-force cosine runs unconditionally today.