Store conversation history in a database
By default, Bub persists conversation Tapes as local JSONL files.
By installing the bub-tapestore-sqlalchemy plugin, you can switch the application to SQLite, PostgreSQL, or any relational database supported by SQLAlchemy, while keeping ,tape.info, ,tape.search, session branching, and Sidecar behavior exactly the same.
Before you start
Section titled “Before you start”- You have installed the Bub application and configured a working model.
- (Optional) Install the
sqlite3command line tool for querying and inspecting tables directly. - Confirm the current Bub home path:
printf '%s\n' "${BUB_HOME:-$HOME/.bub}"
1. Install the plugin
Section titled “1. Install the plugin”Install the store plugin in the environment where Bub runs:
bub install bub-tapestore-sqlalchemy@main
bub hooks
Run bub hooks to check that the extension point is mounted:
provide_tape_store: tapestore-sqlalchemy, builtin
Higher-priority implementations are listed first. Seeing tapestore-sqlalchemy means the plugin is active and will override the built-in file store.
2. Configure the database
Section titled “2. Configure the database”The plugin uses <BUB_HOME>/tapes.db by default. To make demonstration and verification easier, this guide points it at the current working directory:
export BUB_TAPESTORE_SQLALCHEMY_URL="sqlite+pysqlite:///$PWD/bub-tapes.db"
sqlite+pysqliteuses the SQLite driver built into the Python standard library, with no extra dependencies.- The URL must follow standard SQLAlchemy connection string conventions; here
$PWDis used to build an absolute path.
Optional settings
Section titled “Optional settings”| Environment variable | Default | Description |
|---|---|---|
BUB_TAPESTORE_SQLALCHEMY_ECHO |
false |
Set to true to print executed SQL statements for development and debugging. |
BUB_TAPESTORE_SQLALCHEMY_CONNECT_ARGS |
{} |
JSON object passed to create_engine(connect_args=...) (for example, timeout settings). |
To make the settings persist, add the variables to a .env file at the workspace root. The plugin initializes the required table schema automatically on first connection.
3. Verify writes and reads
Section titled “3. Verify writes and reads”Run one session, then use the built-in command to check whether the Tape was written to the database:
bub run "Say hello in one sentence and indicate that this turn of the conversation is stored in the database."
bub run ",tape.info"
The output should include the Tape name, entry count, and anchor status for this session:
name: 86774b31b96845a4__0b871d5e50e7c192
entries: 9
anchors: 1
last_anchor: session/start
entries_since_last_anchor: 8
Use sqlite3 to inspect the physical records in the database:
# Query the Tape session list
sqlite3 "$PWD/bub-tapes.db" \
"SELECT name, last_entry_id FROM tapes ORDER BY name;"
# Query anchor records
sqlite3 "$PWD/bub-tapes.db" \
"SELECT entry_id, anchor_name, entry_date FROM tape_entries WHERE kind = 'anchor';"
The tapes table maintains metadata for all sessions; the tape_entries table records the specific content of each Turn, model answer, and tool call entry by entry.
4. Connect another database
Section titled “4. Connect another database”The plugin is built on SQLAlchemy and supports connecting to relational databases such as PostgreSQL and MySQL:
- Install the driver (install the corresponding database driver in the environment where
bubruns):# For example, PostgreSQL uv add "psycopg[binary]" - Configure the connection string:
export BUB_TAPESTORE_SQLALCHEMY_URL="postgresql+psycopg://user:YOUR_DB_PASSWORD@localhost:5432/bub_db"
In production, test concurrent writes and connection pool timeout settings in advance to keep the service stable.
Implement your own store backend
Section titled “Implement your own store backend”core.Agent and the built-in app depend only on the store protocol, so implementing it swaps the backend entirely. For the difference between “the Tape keeps history” and “TapeContext projects context”, see Tape and model context.
| Protocol | Where it is used |
|---|---|
bub.core.store.TapeStore |
Synchronous implementation passed to Agent(store=...) |
bub.core.store.AsyncTapeStore |
Asynchronous implementation; recommended for databases and remote storage |
provide_tape_store |
Application-level replacement; the built-in app returns FuzzyFileTapeStore |
Full method signatures are in the Python API. At minimum a backend must isolate entries by Tape name, preserve append order, support reset(), and let TapeQuery’s anchor filters work. Forks are built on top of any store by ForkTapeStore, so the backend does not implement them.
Storage in the built-in app
Section titled “Storage in the built-in app”The built-in app supplies FuzzyFileTapeStore through provide_tape_store, with ~/.bub/tapes/ as the default directory. It derives the Tape name from both workspace path and session ID, so same-named sessions in different workspaces do not share history.
BubAgent chooses its store in this order:
- An explicitly supplied
tape_store; - The store returned by
framework.get_tape_store()when the Agent is created; - The in-memory store when neither is available.
Create BubAgent inside framework.running() when it should use the store supplied by that scope. Replace storage with provide_tape_store, and replace the projection strategy with build_tape_context.
The built-in app restores session settings such as model, reasoning effort, and code mode from the Tape. Persist any other application state explicitly.
Troubleshooting
Section titled “Troubleshooting”| Common issue | Possible cause | Solution |
|---|---|---|
tapestore-sqlalchemy is missing from bub hooks |
The plugin was installed into a different Python environment | Check command -v bub and make sure bub install ran in the virtual environment you are using. |
| SQLite reports that the database is locked (Database is locked) | Concurrent writes from multiple processes exceed what SQLite can handle | Configure a busy timeout, or switch to a relational database better suited to high concurrency, such as PostgreSQL. |
Connecting to PostgreSQL raises ModuleNotFoundError |
The corresponding database driver is not installed | Run uv add "psycopg[binary]" in the Bub environment to install the driver. |
| Tables cannot be created, or permission is denied | The target path or remote database user lacks permission to create tables | Confirm that the file directory is writable by the current user, or grant the database user CREATE TABLE permission. |
Cleanup
Section titled “Cleanup”To restore Bub’s default file storage mode, clear the environment variables and delete the test database:
unset BUB_TAPESTORE_SQLALCHEMY_URL BUB_TAPESTORE_SQLALCHEMY_ECHO BUB_TAPESTORE_SQLALCHEMY_CONNECT_ARGS
rm -f "$PWD/bub-tapes.db"