Skip to main content
Component: DatabaseMixin Module: gaia.database.mixin Import: from gaia.database.mixin import DatabaseMixin

Overview

DatabaseMixin is a zero-dependency SQLite mixin for agents that need persistent, queryable state (user intake records, chat sessions, job queues, etc.). It wraps Python’s stdlib sqlite3 with a small ergonomic API and an optional transaction context manager. Design goals:
  • No extra dependencies. Uses only sqlite3 from the standard library.
  • Dict-oriented API. Every query returns dicts (via sqlite3.Row under the hood) so tools can serialize results without transformation.
  • LLM-friendly SQL. Explicit parameterised SQL with :name placeholders and a consistent (table, data, where, params) signature for CRUD operations.
  • Safe concurrency for agents. Single connection with check_same_thread=False; use transaction() for atomic writes.

API Reference

All methods are instance methods on any class that mixes in DatabaseMixin.

Connection lifecycle

  • init_db(path) — open (or re-open) a SQLite connection. Accepts :memory: or a filesystem path; parent directories are auto-created. Enables foreign keys (PRAGMA foreign_keys = ON) and uses sqlite3.Row for dict-style access.
  • close_db() — close the connection. Safe to call multiple times; safe to call before init_db.
  • db_readyTrue if a connection is open.
All query/mutation methods raise RuntimeError("Database not initialized. Call init_db() first.") if the connection isn’t ready.

Reads

Execute a SELECT. Uses :name placeholders. When one=True, returns either a single row dict or None; otherwise returns a list of row dicts (possibly empty).

Writes

  • insert(table, data) — builds INSERT INTO table (...) VALUES (...) from the data dict and returns the new row’s lastrowid.
  • update(table, data, where, params) — the implementation prefixes the data keys with __set_ internally so they can’t collide with the params used in the WHERE clause. Returns the affected row count.
  • delete(table, where, params) — returns the deleted row count.
Every mutation auto-commits unless the call is inside a transaction() block.

Transactions

Atomic block: commits on success, rolls back on exception, never nested.
execute() (raw DDL below) cannot be called inside a transaction — it will raise RuntimeError.

Raw SQL / schema

  • execute(sql) — runs arbitrary SQL via executescript(). Use for CREATE TABLE/CREATE INDEX bootstrap. Auto-commits any pending work.
  • table_exists(name) — returns True if a table of that name exists.

Example agent


Caveats

  • SQLite only — there is no PostgreSQL/MySQL support. If you need a different backend, you’ll need a different mixin.
  • Single connection per agent instance; the mixin assumes single-process ownership. For multi-process scenarios, either use a shared DB file with WAL-mode via execute("PRAGMA journal_mode=WAL") or route writes through a single coordinator agent.
  • There is no ORM layer. You work directly with SQL and dict results, which is intentional — it keeps the surface small and LLM-friendly.
  • EMR Agent uses this mixin as its persistence layer.
  • FileWatcher is commonly paired with DatabaseMixin to record ingestion events.