Databases
Keep structured data in a SQLite file that your apps and agents share.
A Database is a SQLite file on your computer. It lives apart from any Thing, so your data stays when an app is rebuilt or removed. An app lists the Databases it uses, and agents you connect with Full access can query them.
Use a Database when:
- An app saves records, such as meals, books, or expenses.
- Several apps or agents share the same data.
- You want to keep data from somewhere else and query it later from any agent.
Some Databases to keep:
- Expenses, budget goals, and investments, entered by hand or pulled from another connector.
- Books you have read, with your ratings.
- Workouts, sleep, or weight, from a fitness app's export.
- People you know and when you last talked to them.
- Recipes you like, searchable by ingredient.
- Links and articles you saved, with your notes.
To keep a file such as a PDF or a photo, put it on your computer as a Thing instead. CLI commands for Databases start with empty dataset.
$ empty dataset create nutrition --name "Nutrition log"
Created Database nutrition at /home/exedev/data/nutrition
$ empty dataset exec nutrition "CREATE TABLE meals (date TEXT, food TEXT, calories INTEGER)"
Executed on nutrition (0 rows changed)
$ empty dataset exec nutrition "INSERT INTO meals (date, food, calories) VALUES ('2026-09-11', 'oats', 320)"
Executed on nutrition (1 row changed)
$ empty dataset query nutrition "SELECT date, food FROM meals ORDER BY date DESC"
DATE FOOD
2026-09-11 oats
A Database lives at ~/data/<id>/ with owner-managed dataset.yaml and imports/, plus a guarded SQLite payload created by the protected runtime. The id matches [a-z0-9_-]+ and the directory name. The only type is sqlite. Database writes are not Changes. Undo never touches ~/data. Keep durable user data under ~/data, not in a Thing repository.
Create a Database
Run empty dataset create <id> to create that directory. Pass --name for a display name. The default is the id.
To list Databases, run empty datasets. To inspect one, including the service variable EMPTYOS_DATASET_<ID> (the id uppercased, - as _), run empty dataset describe <id>.
Read and write with SQL
empty dataset query runs SQLite as a bounded non-owner tool with no network and access to only the selected Database. It uses read-only safe mode, so writes, attaches, and file access fail. --json prints { id, rows, rowCount }.
empty dataset exec runs under the computer's mutation lock, stops at the first error, and reports { id, changes, lastInsertRowid } in JSON. When several statements must land together, wrap them in BEGIN/COMMIT.
Both commands have a 120-second default deadline. Pass --timeout <seconds> after the SQL to choose an integer from 1 to 600 seconds. The deadline covers the full protected tool run; an unfinished command is stopped when it expires.
SQL is one quoted argument, at most 16 KB over the workstation transport. For bulk imports, package the source file in a Thing repository under /app and run a service Thing that reads it and writes to a Database declared in thing.yaml. For smaller operations, split SQL into commands below the limit. The owner-managed imports/ directory is staging outside the protected payload; it is not mounted into a service or Database tool and is not a direct import input. Do not open the guarded payload with owner sqlite3, and never copy SQLite files between the workstation and the computer. Flag-level detail is on CLI.
Use a Database from a Thing
List Database ids in the Thing's thing.yaml:
data:
- nutrition
A service receives each sandboxed database path as EMPTYOS_DATASET_<ID> and only its declared mounts under EMPTYOS_DATA_DIR. Read those variables. Do not hardcode a home path. A missing or malformed declared Database prevents service preparation. IDs such as sales-log and sales_log can coexist, but a service cannot declare both because they produce the same environment variable. empty thing remove does not delete Databases.
Delete a Database
To see what would be deleted, run empty dataset remove <id>. To delete it, pass --confirm <id> repeating the id. There is no Undo. Removal is refused while a registered service still declares the Database; remove its declaration and update the service registration first.