Skip to content

db

Single-file SQLite store: relational tables + sqlite-vec vectors.

backup_db(conn, dest)

Write a consistent single-file copy of the database to dest.

cp hermes.db backup.db is the obvious thing to type and it silently loses data: get_db sets journal_mode=WAL, so recent commits live in hermes.db-wal until a checkpoint. The copy opens, looks intact, and is missing the newest clicks and items -- worse than having no backup, because it looks trustworthy. Five such copies were sitting in the real data directory when this was written.

VACUUM INTO reads through the WAL and writes one consistent, compacted file without stopping writers, which is exactly the operation an operator thought they were getting.

Refuses an existing destination: a backup that silently replaces the previous one is one keystroke from being no backup at all.

embed_dims()

Stored embedding dimensionality (EMBED_DIMS, default 256).

Read at call time so tests and .env loading are respected. Changing it invalidates every stored vector — get_db() refuses mismatched databases.

get_db(path)

Open (creating if absent) the SQLite store at path.

Never seeds personas. A new database is EMPTY, so the web UI's first screen is the onboarding form and an agent's first feed.list creates the reader it names. Demo personas (SEED_USERS) exist only when asked for: attest bootstrap-persona <demo name> creates the one it is given, and seed_demo_users() plants all three. Seeding on creation put the author's own persona in every stranger's database; seeding on every open resurrected personas the reader had deleted.

resolve_db_path(explicit)

Resolve the hermes.db path with this precedence:

  1. explicit (the --db flag, when the caller actually passed one)
  2. ATTEST_DB env var, when set -- or RSS_DB, its pre-rename name, which stays honoured so no existing cron line or MCP entry breaks
  3. the co-located skill data dir (~/.hermes/skills/science-recommendations/data/hermes.db), but only if that file already exists
  4. ./hermes.db (cwd-relative default)

seed_demo_users(conn)

Insert the three hardcoded demo personas (researcher, bench-chemist, ml-engineer).

INSERT OR IGNORE, so calling this against a database that already has these rows (or rows a researcher has since deleted and doesn't want back) is safe -- but it is still a write, and callers should call it only when they actually want demo data seeded, not on every connection open. A persona deleted via delete_persona must stay deleted; re-running this would silently resurrect it.