b83546d833
Architecture (Agent 1):
- hermes_memory/tier2/{schema,facts,entities,relations,timeline}.py
- hermes_memory/tier3/{backend,chroma_backend,embedder}.py
- hermes_memory/graph/nx_store.py
- hermes_memory/api/memory_api.py (unified API)
- hermes_memory/cron/{consolidate,embed_queue,graph_refresh,prune}.py
- hermes_memory/config.py + pyproject.toml
Integration Plan (Agent 3):
- INTEGRATION_PLAN.md: Memory Provider Plugin strategy
- Hermes Core needs minimal changes
- sync_turn() + prefetch() hooks
- Skills integration via nextlevel_search/remember
Auto-Extraction (Agent 2):
- ARCHITECTURE.md: Full extraction pipeline docs
- Chunking, Pre-Filter, LLM Prompts, Classification
- Entity-Linking, Temporal Reasoning, Deduplication
All files: Python syntax checked, ECC standards applied.
197 lines
6.4 KiB
Python
197 lines
6.4 KiB
Python
"""SQLite Schema & Migrationen für Tier 2.
|
|
|
|
Enthält alle CREATE TABLE / INDEX / TRIGGER Statements
|
|
mit Schema-Versionierung.
|
|
"""
|
|
|
|
import sqlite3
|
|
import time
|
|
from pathlib import Path
|
|
|
|
SCHEMA_VERSION = 1
|
|
|
|
SCHEMA_SQL = """
|
|
-- Schema-Version
|
|
CREATE TABLE IF NOT EXISTS memory_schema_version (
|
|
version INTEGER PRIMARY KEY,
|
|
applied_at REAL NOT NULL
|
|
);
|
|
|
|
-- Fakten
|
|
CREATE TABLE IF NOT EXISTS facts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uuid TEXT NOT NULL UNIQUE,
|
|
content TEXT NOT NULL,
|
|
content_hash TEXT NOT NULL,
|
|
category TEXT,
|
|
confidence REAL DEFAULT 1.0,
|
|
source_type TEXT NOT NULL,
|
|
source_id TEXT,
|
|
created_at REAL NOT NULL,
|
|
updated_at REAL NOT NULL,
|
|
expires_at REAL,
|
|
access_count INTEGER DEFAULT 0,
|
|
last_accessed REAL,
|
|
is_archived INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_facts_category ON facts(category);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_source ON facts(source_type, source_id);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_created ON facts(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_hash ON facts(content_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_facts_confidence ON facts(confidence DESC);
|
|
|
|
-- Entitäten
|
|
CREATE TABLE IF NOT EXISTS entities (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uuid TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
aliases TEXT,
|
|
entity_type TEXT NOT NULL,
|
|
description TEXT,
|
|
first_seen REAL NOT NULL,
|
|
last_seen REAL NOT NULL,
|
|
occurrence_count INTEGER DEFAULT 1,
|
|
metadata TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(entity_type);
|
|
|
|
-- Relationen
|
|
CREATE TABLE IF NOT EXISTS relations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uuid TEXT NOT NULL UNIQUE,
|
|
from_entity_id TEXT NOT NULL,
|
|
to_entity_id TEXT NOT NULL,
|
|
relation_type TEXT NOT NULL,
|
|
strength REAL DEFAULT 1.0,
|
|
evidence_fact_id TEXT,
|
|
created_at REAL NOT NULL,
|
|
updated_at REAL NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity_id);
|
|
CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity_id);
|
|
CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
|
|
|
|
-- Timeline
|
|
CREATE TABLE IF NOT EXISTS timeline (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
uuid TEXT NOT NULL UNIQUE,
|
|
event_type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
related_entities TEXT,
|
|
related_facts TEXT,
|
|
session_id TEXT,
|
|
timestamp REAL NOT NULL,
|
|
importance REAL DEFAULT 0.5
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_timeline_time ON timeline(timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_timeline_type ON timeline(event_type);
|
|
|
|
-- Audit-Log
|
|
CREATE TABLE IF NOT EXISTS memory_audit_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp REAL NOT NULL,
|
|
action TEXT NOT NULL,
|
|
tier TEXT NOT NULL,
|
|
actor TEXT NOT NULL,
|
|
target_uuid TEXT,
|
|
diff TEXT,
|
|
success INTEGER DEFAULT 1
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON memory_audit_log(timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_actor ON memory_audit_log(actor);
|
|
|
|
-- Embedding-Queue
|
|
CREATE TABLE IF NOT EXISTS embedding_queue (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
fact_id TEXT,
|
|
content TEXT NOT NULL,
|
|
source_type TEXT NOT NULL,
|
|
session_id TEXT,
|
|
message_id INTEGER,
|
|
queued_at REAL NOT NULL,
|
|
processed INTEGER DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_queue_processed ON embedding_queue(processed, queued_at);
|
|
"""
|
|
|
|
FTS_SQL = """
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS facts_fts USING fts5(
|
|
content,
|
|
content_rowid='id',
|
|
tokenize='unicode61'
|
|
);
|
|
|
|
CREATE TRIGGER IF NOT EXISTS facts_fts_insert AFTER INSERT ON facts BEGIN
|
|
INSERT INTO facts_fts(rowid, content) VALUES (new.id, new.content);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS facts_fts_delete AFTER DELETE ON facts BEGIN
|
|
DELETE FROM facts_fts WHERE rowid = old.id;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS facts_fts_update AFTER UPDATE ON facts BEGIN
|
|
DELETE FROM facts_fts WHERE rowid = old.id;
|
|
INSERT INTO facts_fts(rowid, content) VALUES (new.id, new.content);
|
|
END;
|
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
name,
|
|
content_rowid='id',
|
|
tokenize='unicode61'
|
|
);
|
|
|
|
CREATE TRIGGER IF NOT EXISTS entities_fts_insert AFTER INSERT ON entities BEGIN
|
|
INSERT INTO entities_fts(rowid, name) VALUES (new.id, new.name);
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS entities_fts_delete AFTER DELETE ON entities BEGIN
|
|
DELETE FROM entities_fts WHERE rowid = old.id;
|
|
END;
|
|
|
|
CREATE TRIGGER IF NOT EXISTS entities_fts_update AFTER UPDATE ON entities BEGIN
|
|
DELETE FROM entities_fts WHERE rowid = old.id;
|
|
INSERT INTO entities_fts(rowid, name) VALUES (new.id, new.name);
|
|
END;
|
|
"""
|
|
|
|
|
|
def init_schema(conn: sqlite3.Connection) -> None:
|
|
"""Initialisiert alle Tabellen, Indizes und FTS5."""
|
|
conn.executescript(SCHEMA_SQL)
|
|
conn.executescript(FTS_SQL)
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO memory_schema_version (version, applied_at) VALUES (?, ?)",
|
|
(SCHEMA_VERSION, time.time()),
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
def migrate(conn: sqlite3.Connection) -> None:
|
|
"""Führt Migrationen durch (aktuell: nur Schema-Version prüfen)."""
|
|
row = conn.execute("SELECT version FROM memory_schema_version ORDER BY version DESC LIMIT 1").fetchone()
|
|
current = row[0] if row else 0
|
|
if current < SCHEMA_VERSION:
|
|
init_schema(conn)
|
|
|
|
|
|
def connect(db_path: Path, wal_mode: bool = True) -> sqlite3.Connection:
|
|
"""Erstellt Verbindung mit WAL-Mode und Foreign Keys."""
|
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
conn = sqlite3.connect(str(db_path), check_same_thread=False, timeout=10.0)
|
|
conn.row_factory = sqlite3.Row
|
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
if wal_mode:
|
|
try:
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
except sqlite3.OperationalError:
|
|
conn.execute("PRAGMA journal_mode=DELETE")
|
|
return conn
|