Skip to main content
Detailed Spec: spec/database-mixin The DatabaseMixin provides SQLite database access for GAIA agents. It uses Python’s built-in sqlite3 module with zero external dependencies, making it lightweight and fast.

Two Approaches

Use DatabaseAgent when you want the LLM to have direct database access:
Auto-registered tools: db_query, db_insert, db_update, db_delete, db_tables, db_schema db_query is read-only, and db_update/db_delete take structured conditions rather than a WHERE string — see LLM-facing tools vs. the Python API.

LLM-facing tools vs. the Python API

The mixin methods and the auto-registered tools sit at different trust levels, and deliberately have different shapes. update()/delete() interpolate where into the statement, so it must be a literal you wrote. Never build it from LLM output or end-user input — bind untrusted values through :param placeholders inside a literal fragment instead:
The LLM-facing tools don’t accept a fragment at all. Each condition is an object, and GAIA builds the predicate and binds every value:
Conditions are combined with AND. There is no OR and no nesting — an AND chain of allowlisted predicates can only narrow the row set, which is what makes OR 1=1-style payloads unrepresentable. If an agent genuinely needs OR, expose a domain-specific tool that runs the query you intend rather than widening the generic one. An empty condition list is rejected unless you pass all_rows=True, so a full-table update or delete is always deliberate:
Because the tool schema types parameters as strings, the JSON-encoded forms are also accepted: where as a JSON array, data as a JSON object, and all_rows as "true"/"false". Only those exact spellings — an ambiguous value like "yes" raises rather than being guessed at. Malformed input raises ValueError naming the offending value and the rule, which the agent loop surfaces to the model so it can correct itself.
These tools still give the LLM read and write access to every table in the database — there is no per-table authorization. Point db_path at a database holding only what the agent should be able to change, or override _register_db_tools() to expose domain-specific operations.

Composing with Other Mixins

DatabaseAgent can be extended with additional mixins for more capabilities:
Use DatabaseMixin when you want to expose domain-specific tools:

API Reference

Initialization

CRUD Operations

insert, update, and delete interpolate the table name — and insert/update the data keys — into the SQL, so all of them must be bare identifiers matching [A-Za-z_][A-Za-z0-9_]*; anything else raises ValueError. Quoted or non-ASCII identifiers that SQLite would otherwise accept are rejected. Empty data and a blank where raise as well, rather than producing a malformed statement.

Schema & Transactions

Examples

Basic CRUD

Transactions

Use transactions when multiple operations must succeed or fail together:

Schema Initialization

Best Practices

Use Parameterized Queries

Always use :param placeholders to prevent SQL injection and syntax errors:
Placeholders only protect values — SQLite cannot bind identifiers or predicates. Those are covered separately: table and column names are validated as bare identifiers, and LLM-supplied filters go through structured conditions instead of a WHERE string. See LLM-facing tools vs. the Python API. For SQL you didn’t write yourself, use query_readonly().

Initialize Schema Once

Check table_exists() before creating tables to avoid errors on restart:
Ensure data consistency by wrapping related operations in a transaction:

Close on Shutdown

Call close_db() when the agent is done (optional but clean):

Complete Example: Notes Agent

A simple but complete agent that manages notes:

Testing Your Agent

Use the temp_db fixture for isolated tests:

Design Notes