1. Project Overview & Quickstart (tursodatabase/turso)
File: bindings/rust/README.md
turso
The next evolution of SQLite: A high-performance, SQLite-compatible database library for Rust
Features
- SQLite Compatible: Similar interface to rusqlite with familiar API apart from using async Rust
- High Performance: Built with Rust for maximum speed and efficiency
- Async/Await Support: Native async operations with tokio support
- In-Process: No network overhead, runs directly in your application
- Cross-Platform: Supports Linux, macOS, and Windows
- Transaction Support: Full ACID transactions with rollback support
- Prepared Statements: Optimized query execution with parameter binding
- Cloud Sync: Sync with Turso Cloud using
Builder::new_remote()(optionalsyncfeature)
Installation
Add this to your Cargo.toml:
[dependencies]
turso = "0.4.3"
tokio = { version = "1.0", features = ["full"] }For cloud sync capabilities, enable the sync feature:
[dependencies]
turso = { version = "0.4.3", features = ["sync"] }
tokio = { version = "1.0", features = ["full"] }Quick Start
In-Memory Database
use turso::Builder;
#[tokio::main]
async fn main() -> turso::Result<()> {
// Create an in-memory database
let db = Builder::new_local(":memory:").build().await?;
let conn = db.connect()?;
// Create a table
conn.execute(
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)",
()
).await?;
// Insert data
conn.execute(
"INSERT INTO users (name, email) VALUES (?1, ?2)",
["Alice", "[email protected]"]
).await?;
conn.execute(
"INSERT INTO users (name, email) VALUES (?1, ?2)",
["Bob", "[email protected]"]
).await?;
// Query data
let mut rows = conn.query("SELECT * FROM users", ()).await?;
while let Some(row) = rows.next().await? {
let id = row.get_value(0)?;
let name = row.get_value(1)?;
let email = row.get_value(2)?;
println!("User: {} - {} ({})",
id.as_integer().unwrap_or(&0),
name.as_text().unwrap_or(&"".to_string()),
email.as_text().unwrap_or(&"".to_string())
);
}
Ok(())
}File-Based Database
use turso::Builder;
#[tokio::main]
async fn main() -> turso::Result<()> {
// Create or open a database file
let db = Builder::new_local("my-database.db").build().await?;
let conn = db.connect()?;
// Create a table
conn.execute(
r#"CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)"#,
()
).await?;
// Insert a post
let rows_affected = conn.execute(
"INSERT INTO posts (title, content) VALUES (?1, ?2)",
["Hello World", "This is my first blog post!"]
).await?;
println!("Inserted {} rows", rows_affected);
Ok(())
}Synced Database
Sync your local database with Turso Cloud:
use turso::sync::Builder;
#[tokio::main]
async fn main() -> turso::Result<()> {
// Create a synced database
let db = Builder::new_remote("local.db")
.with_remote_url("libsql://your-database.turso.io")
.with_auth_token("your-token")
.build()
.await?;
let conn = db.connect().await?;
// Create a table and insert data
conn.execute(
"CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, content TEXT)",
()
).await?;
conn.execute(
"INSERT INTO notes (content) VALUES (?1)",
["My first synced note"]
).await?;
// Push local changes to remote
db.push().await?;
// Pull remote changes to local
db.pull().await?;
Ok(())
}API Reference
Builder
Create a new database:
let db = Builder::new_local(":memory:").build().await?;
let db = Builder::new_local("data.db").build().await?;Connection
Execute queries and statements:
// Execute SQL directly
let rows_affected = conn.execute("INSERT INTO users (name) VALUES (?1)", ["Alice"]).await?;
// Query for multiple rows
let mut rows = conn.query("SELECT * FROM users WHERE age > ?1", [18]).await?;
// Prepare statements for reuse
let mut stmt = conn.prepare("SELECT * FROM users WHERE id = ?1").await?;
let mut rows = stmt.query([42]).await?;
// Execute prepared statements
let rows_affected = stmt.execute(["Alice"]).await?;Working with Results
use futures_util::TryStreamExt;
let mut rows = conn.query("SELECT name, email FROM users", ()).await?;
while let Some(row) = rows.try_next().await? {
let name = row.get_value(0)?.as_text().unwrap_or(&"".to_string());
let email = row.get_value(1)?.as_text().unwrap_or(&"".to_string());
println!("{}: {}", name, email);
}Sync API Reference
sync::Builder
Create a synced database that synchronizes with Turso Cloud:
use turso::sync::Builder;
let db = Builder::new_remote("local.db") // Local database path (or ":memory:")
.with_remote_url("libsql://db.turso.io") // Remote URL (https://, http://, or libsql://)
.with_auth_token("your-token") // Authorization token
.bootstrap_if_empty(true) // Download schema on first sync (default: true)
.with_remote_encryption("base64-encoded-key", RemoteEncryptionCipher::Aes256Gcm) // Optional remote encryption
.build()
.await?;sync::Database
Operations for synced databases:
// Push local changes to remote
db.push().await?;
// Pull remote changes (returns true if changes were applied)
let had_changes = db.pull().await?;
// Force WAL checkpoint
db.checkpoint().await?;
// Get sync statistics
let stats = db.stats().await?;
println!("Received: {} bytes", stats.network_received_bytes);
println!("Sent: {} bytes", stats.network_sent_bytes);
println!("WAL size: {} bytes", stats.main_wal_size);License
MIT
Support
File: bindings/react-native/README.md
Turso Sync React Native SDK
React Native bindings for Turso embedded replicas - sync your local SQLite database with Turso cloud.
Installation
npm install @tursodatabase/sync-react-nativeiOS
cd ios && pod installAndroid
Requires minSdkVersion 21+ in android/build.gradle.
Quick Start
import { Database, getDbPath } from '@tursodatabase/sync-react-native';
// Get platform-specific writable path
const dbPath = getDbPath('myapp.db');
// Create database with sync
const db = new Database({
path: dbPath,
url: 'libsql://your-db.turso.io',
authToken: 'your-auth-token',
});
// Connect (bootstraps from remote if empty)
await db.connect();
// Query local replica (fast)
const users = await db.all('SELECT * FROM users');
// Make local changes
await db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);
// Sync with remote
await db.push(); // Push local changes
await db.pull(); // Pull remote changes
// Close when done
await db.close();Local-Only Database
const db = new Database({ path: getDbPath('local.db') });
await db.connect();
await db.exec('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
await db.run('INSERT INTO users (name) VALUES (?)', ['Bob']);
const user = await db.get('SELECT * FROM users WHERE id = ?', [1]);
await db.close();Encrypted Remote Database
const db = new Database({
path: getDbPath('encrypted.db'),
url: 'libsql://your-db.turso.io',
authToken: 'your-auth-token',
remoteEncryption: {
cipher: 'aes256gcm',
key: 'base64-encoded-key',
},
});API
Database Methods
| Method | Description |
|---|---|
connect() |
Open/bootstrap the database |
exec(sql) |
Execute SQL (no results) |
run(sql, params?) |
Execute SQL, return { changes, lastInsertRowid } |
get(sql, params?) |
Query single row |
all(sql, params?) |
Query all rows |
prepare(sql) |
Create prepared statement |
close() |
Close database |
Sync Methods (when `url` is provided)
| Method | Description |
|---|---|
push() |
Push local changes to remote |
pull() |
Pull remote changes to local |
sync() |
Push then pull |
stats() |
Get sync statistics |
Transactions
await db.transaction(async () => {
await db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);
await db.run('INSERT INTO users (name) VALUES (?)', ['Bob']);
// Commits on success, rolls back on error
});License
This project is licensed under the MIT license.
Links
File: bindings/python/README.md
About
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Features
- SQLite compatible: SQLite query language and file format support (status).
- In-process: No network overhead, runs directly in your Python process
- Cross-platform: Supports Linux, macOS, Windows
- Remote partial sync: Bootstrap from a remote database, pull remote changes, and push local changes when online — all while enjoying a fully operational database offline.
- Asyncio support: Built-in integration with asyncio to ensure queries won’t block your event loop
Installation
uv pip install pytursoDatabase driver
A minimal DB‑API 2.0 example using an in‑memory database:
import turso
# Standard DB-API usage
conn = turso.connect(":memory:")
cur = conn.cursor()
cur.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT)")
cur.execute("INSERT INTO users VALUES (1, 'alice'), (2, 'bob')")
cur.execute("SELECT * FROM users ORDER BY id")
rows = cur.fetchall()
print(rows) # [(1, 'alice'), (2, 'bob')]
conn.close()Database driver (asyncio)
Non-blocking access with asyncio:
import asyncio
import turso.aio
async def main():
# Connect and use as an async context manager
async with turso.aio.connect(":memory:") as conn:
# Executes multiple statements
await conn.executescript("""
CREATE TABLE t (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO t(name) VALUES ('alice'), ('bob');
""")
# Use a cursor for parameterized queries
cur = conn.cursor()
await cur.execute("SELECT COUNT(*) FROM t WHERE name LIKE ?", ("a%",))
count = (await cur.fetchone())[0]
print(count) # 1
# JSON and generate_series also available
cur = conn.cursor()
await cur.execute("SELECT SUM(value) FROM generate_series(1, 10)")
print((await cur.fetchone())[0]) # 55
asyncio.run(main())Synchronization driver
Use a remote Turso database while working locally. You can bootstrap local state from the remote, pull remote changes, and push local commits.
Note: You need a Turso remote URL. See the Turso docs for provisioning and authentication.
import turso.sync
# Connect a local database to a remote Turso database
conn = turso.sync.connect(
":memory:", # local db path (or a file path)
remote_url="https://<db>.<region>.turso.io" # your remote URL
)
# Read data (fetched from remote if not present locally yet)
rows = conn.execute("SELECT * FROM t").fetchall()
print(rows)
# Pull new changes from remote into local
changed = conn.pull()
print("Pulled:", changed) # True if there were new remote changes
# Make local changes
conn.execute("INSERT INTO t VALUES ('push works')")
conn.commit()
# Push local commits to remote
conn.push()
# Optional: inspect and manage sync state
stats = conn.stats()
print("Network received (bytes):", stats.network_received_bytes)
conn.checkpoint() # compact local WAL after many writes
conn.close()Partial bootstrap to reduce initial network cost:
import turso.sync
conn = turso.sync.connect(
"local.db",
remote_url="https://<db>.<region>.turso.io",
# fetch first 128 KiB upfront
partial_sync_experimental=turso.sync.PartialSyncOpts(
bootstrap_strategy=turso.sync.PartialSyncPrefixBootstrap(length=128 * 1024),
),
)Synchronization driver (asyncio)
The same sync primitives, but fully async:
import asyncio
async def main():
conn = await turso.aio.sync.connect(":memory:", remote_url="https://<db>.<region>.turso.io")
# Read data
rows = await (await conn.execute("SELECT * FROM t")).fetchall()
print(rows)
# Pull and push
await conn.pull()
await conn.execute("INSERT INTO t VALUES ('hello from asyncio')")
await conn.commit()
await conn.push()
# Stats and maintenance
stats = await conn.stats()
print("Main WAL size:", stats.main_wal_size)
await conn.checkpoint()
await conn.close()
asyncio.run(main())License
This project is licensed under the MIT license.
Support
File: bindings/javascript/docs/API.md
class Database
The Database class represents a connection that can prepare and execute SQL statements.
Methods
new Database(path, [options]) ⇒ Database
Creates a new database connection.
| Param | Type | Description |
|---|---|---|
| path | string |
Path to the database file |
| options | object |
Options. |
The path parameter points to the SQLite database file to open. If the file pointed to by path does not exists, it will be created.
To open an in-memory database, please pass :memory: as the path parameter.
Supported options fields include:
timeout: busy timeout in millisecondsdefaultQueryTimeout: default maximum query execution time in milliseconds before interruption
Per-query timeout override is available via queryOptions, for example:
db.exec("SELECT 1", { queryTimeout: 100 })stmt.get(undefined, { queryTimeout: 100 })
The function returns a Database object.
prepare(sql) ⇒ Statement
Prepares a SQL statement for execution.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string to prepare. |
The function returns a Statement object.
run(sql[, ...bindParameters][, queryOptions]) ⇒ object
Convenience wrapper that prepares sql and executes Statement.run. Returns the same info object as Statement.run (changes and lastInsertRowid). The internally prepared statement is finalized before the call returns.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string. |
| bindParameters | any |
Optional positional or named bind parameters. |
| queryOptions | object |
Optional per-query overrides (for example, { queryTimeout: 100 }). |
Note: This is an extension in libSQL and not available in better-sqlite3.
get(sql[, ...bindParameters][, queryOptions]) ⇒ row
Convenience wrapper that prepares sql and executes Statement.get. Returns the first row, or undefined if no row matched. The internally prepared statement is finalized before the call returns.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string. |
| bindParameters | any |
Optional positional or named bind parameters. |
| queryOptions | object |
Optional per-query overrides (for example, { queryTimeout: 100 }). |
Note: This is an extension in libSQL and not available in better-sqlite3.
all(sql[, ...bindParameters][, queryOptions]) ⇒ array of rows
Convenience wrapper that prepares sql and executes Statement.all. Returns all matching rows as an array. The internally prepared statement is finalized before the call returns.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string. |
| bindParameters | any |
Optional positional or named bind parameters. |
| queryOptions | object |
Optional per-query overrides (for example, { queryTimeout: 100 }). |
Note: This is an extension in libSQL and not available in better-sqlite3.
iterate(sql[, ...bindParameters][, queryOptions]) ⇒ async iterator
Convenience wrapper that prepares sql and executes Statement.iterate. Returns an async iterator over the resulting rows. The internally prepared statement is finalized when the iterator is exhausted or closed.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string. |
| bindParameters | any |
Optional positional or named bind parameters. |
| queryOptions | object |
Optional per-query overrides (for example, { queryTimeout: 100 }). |
Note: This is an extension in libSQL and not available in better-sqlite3.
transaction(function) ⇒ function
Returns a function that runs the given function in a transaction.
| Param | Type | Description |
|---|---|---|
| function | function |
The function to run in a transaction. |
pragma(string, [options]) ⇒ results
Executes the given PRAGMA and returns its results.
| Param | Type | Description |
|---|---|---|
| source | string |
Pragma to be executed |
| options | object |
Options. |
Most PRAGMA return a single value, the simple: boolean option is provided to return the first column of the first row.
db.pragma('cache_size = 32000');
console.log(db.pragma('cache_size', { simple: true })); // => 32000backup(destination, [options]) ⇒ promise
This function is currently not supported.
serialize([options]) ⇒ Buffer
This function is currently not supported.
function(name, [options], function) ⇒ this
This function is currently not supported.
aggregate(name, options) ⇒ this
This function is currently not supported.
table(name, definition) ⇒ this
This function is currently not supported.
loadExtension(path, [entryPoint]) ⇒ this
Loads a SQLite3 extension.
| Param | Type | Description |
|---|---|---|
| path | string |
The path to the extension to be loaded. |
exec(sql) ⇒ this
Executes a SQL statement.
| Param | Type | Description |
|---|---|---|
| sql | string |
The SQL statement string to execute. |
This can execute strings that contain multiple SQL statements.
interrupt() ⇒ this
Cancel ongoing operations and make them return at earliest opportunity.
Note: This is an extension in libSQL and not available in better-sqlite3.
This function is currently not supported.
close() ⇒ this
Closes the database connection.
class Statement
Methods
run([...bindParameters]) ⇒ object
Executes the SQL statement and (currently) returns an array with results.
Note: It should return an info object.
| Param | Type | Description |
|---|---|---|
| bindParameters | array of objects |
The bind parameters for executing the statement. |
The returned info object contains two properties: changes that describes the number of modified rows and info.lastInsertRowid that represents the rowid of the last inserted row.
This function is currently not supported.
get([...bindParameters]) ⇒ row
Executes the SQL statement and returns the first row.
| Param | Type | Description |
|---|---|---|
| bindParameters | array of objects |
The bind parameters for executing the statement. |
all([...bindParameters]) ⇒ array of rows
Executes the SQL statement and returns an array of the resulting rows.
| Param | Type | Description |
|---|---|---|
| bindParameters | array of objects |
The bind parameters for executing the statement. |
iterate([...bindParameters]) ⇒ iterator
Executes the SQL statement and returns an iterator to the resulting rows.
| Param | Type | Description |
|---|---|---|
| bindParameters | array of objects |
The bind parameters for executing the statement. |
pluck([toggleState]) ⇒ this
Makes the prepared statement only return the value of the first column of any rows that it retrieves.
| Param | Type | Description |
|---|---|---|
| pluckMode | boolean |
Enable of disable pluck mode. If you don't pass the paramenter, pluck mode is enabled. |
stmt.pluck(); // plucking ON
stmt.pluck(true); // plucking ON
stmt.pluck(false); // plucking OFFNOTE: When plucking is turned on, raw mode is turned off (they are mutually exclusive options).
expand([toggleState]) ⇒ this
This function is currently not supported.
raw([rawMode]) ⇒ this
Makes the prepared statement return rows as arrays instead of objects.
| Param | Type | Description |
|---|---|---|
| rawMode | boolean |
Enable or disable raw mode. If you don't pass the parameter, raw mode is enabled. |
This function enables or disables raw mode. Prepared statements return objects by default, but if raw mode is enabled, the functions return arrays instead.
stmt.raw(); // raw mode ON
stmt.raw(true); // raw mode ON
stmt.raw(false); // raw mode OFFNOTE: When raw mode is turned on, plucking is turned off (they are mutually exclusive options).
columns() ⇒ array of objects
Returns the columns in the result set returned by this prepared statement.
This function is currently not supported.
bind([...bindParameters]) ⇒ this
| Param | Type | Description |
|---|---|---|
| bindParameters | array of objects |
The bind parameters for executing the statement. |
Binds permanently the given parameters to the statement. After a statement's parameters are bound this way, you may no longer provide it with execution-specific (temporary) bound parameters.
File: bindings/javascript/packages/wasm-common/README.md
About
This package is the Turso embedded database common JS library which is shared between final builds for Node and Browser.
Do not use this package directly - instead you must use @tursodatabase/database or @tursodatabase/database-wasm.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
File: bindings/javascript/packages/wasm/README.md
About
This package is the Turso embedded database library for JavaScript in Browser.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Features
- SQLite compatible: SQLite query language and file format support (status).
- In-process: No network overhead, runs directly in your Node.js process
- TypeScript support: Full TypeScript definitions included
Installation
npm install @tursodatabase/database-wasmGetting Started
In-Memory Database
import { connect } from '@tursodatabase/database-wasm';
// Create an in-memory database
const db = await connect(':memory:');
// Create a table
await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
// Insert data
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
await insert.run('Alice', '[email protected]');
await insert.run('Bob', '[email protected]');
// Query data
const users = await db.prepare('SELECT * FROM users').all();
console.log(users);
// Output: [
// { id: 1, name: 'Alice', email: '[email protected]' },
// { id: 2, name: 'Bob', email: '[email protected]' }
// ]File-Based Database
import { connect } from '@tursodatabase/database-wasm';
// Create or open a database file
const db = await connect('my-database.db');
// Create a table
await db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Insert a post
const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');
const result = await insertPost.run('Hello World', 'This is my first blog post!');
console.log(`Inserted post with ID: ${result.lastInsertRowid}`);Transactions
import { connect } from '@tursodatabase/database-wasm';
const db = await connect('transactions.db');
// Using transactions for atomic operations
const transaction = db.transactionAsync(async (txn, users) => {
const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
for (const user of users) {
await insert.run(user.name, user.email);
}
});
// Execute transaction
await transaction([
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
]);API Reference
For complete API documentation, see JavaScript API Reference.
Related Packages
- The @tursodatabase/serverless package provides a serverless driver with the same API.
- The @tursodatabase/sync package provides bidirectional sync between a local Turso database and Turso Cloud.
License
This project is licensed under the MIT license.
Support
File: bindings/javascript/packages/native/README.md
About
This package is the Turso embedded database library for JavaScript in Node.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Features
- SQLite compatible: SQLite query language and file format support (status).
- In-process: No network overhead, runs directly in your Node.js process
- TypeScript support: Full TypeScript definitions included
- Cross-platform: Supports Linux (x86 and arm64), macOS, Windows (browser is supported in the separate package
@tursodatabase/database-wasmpackage)
Installation
npm install @tursodatabase/databaseGetting Started
In-Memory Database
import { connect } from '@tursodatabase/database';
// Create an in-memory database
const db = await connect(':memory:');
// Create a table
await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
// Insert data
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
await insert.run('Alice', '[email protected]');
await insert.run('Bob', '[email protected]');
// Query data
const users = await db.prepare('SELECT * FROM users').all();
console.log(users);
// Output: [
// { id: 1, name: 'Alice', email: '[email protected]' },
// { id: 2, name: 'Bob', email: '[email protected]' }
// ]File-Based Database
import { connect } from '@tursodatabase/database';
// Create or open a database file
const db = await connect('my-database.db');
// Create a table
await db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Insert a post
const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');
const result = await insertPost.run('Hello World', 'This is my first blog post!');
console.log(`Inserted post with ID: ${result.lastInsertRowid}`);Transactions
import { connect } from '@tursodatabase/database';
const db = await connect('transactions.db');
// Using transactions for atomic operations
const transaction = db.transactionAsync(async (txn, users) => {
const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
for (const user of users) {
await insert.run(user.name, user.email);
}
});
// Execute transaction
await transaction([
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
]);API Reference
For complete API documentation, see JavaScript API Reference.
Related Packages
- The @tursodatabase/serverless package provides a serverless driver with the same API.
- The @tursodatabase/sync package provides bidirectional sync between a local Turso database and Turso Cloud.
License
This project is licensed under the MIT license
Support
File: bindings/javascript/packages/common/README.md
About
This package is the Turso embedded database common JS library which is shared between final builds for Node and Browser.
Do not use this package directly - instead you must use @tursodatabase/database or @tursodatabase/database-wasm.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
File: bindings/javascript/sync/packages/wasm/README.md
About
This package is the Turso embedded database library for JavaScript in Browser.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Features
- SQLite compatible: SQLite query language and file format support (status).
- In-process: No network overhead, runs directly in your Node.js process
- TypeScript support: Full TypeScript definitions included
Installation
npm install @tursodatabase/database-wasmGetting Started
In-Memory Database
import { connect } from '@tursodatabase/database-wasm';
// Create an in-memory database
const db = await connect(':memory:');
// Create a table
await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
// Insert data
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
await insert.run('Alice', '[email protected]');
await insert.run('Bob', '[email protected]');
// Query data
const users = await db.prepare('SELECT * FROM users').all();
console.log(users);
// Output: [
// { id: 1, name: 'Alice', email: '[email protected]' },
// { id: 2, name: 'Bob', email: '[email protected]' }
// ]File-Based Database
import { connect } from '@tursodatabase/database-wasm';
// Create or open a database file
const db = await connect('my-database.db');
// Create a table
await db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Insert a post
const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');
const result = await insertPost.run('Hello World', 'This is my first blog post!');
console.log(`Inserted post with ID: ${result.lastInsertRowid}`);Transactions
import { connect } from '@tursodatabase/database-wasm';
const db = await connect('transactions.db');
// Using transactions for atomic operations
const transaction = db.transactionAsync(async (txn, users) => {
const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
for (const user of users) {
await insert.run(user.name, user.email);
}
});
// Execute transaction
await transaction([
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
]);API Reference
For complete API documentation, see JavaScript API Reference.
Related Packages
- The @tursodatabase/serverless package provides a serverless driver with the same API.
- The @tursodatabase/sync package provides bidirectional sync between a local Turso database and Turso Cloud.
License
This project is licensed under the MIT license.
Support
File: bindings/javascript/sync/packages/native/README.md
About
This package is the Turso embedded database library for JavaScript in Node.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Features
- SQLite compatible: SQLite query language and file format support (status).
- In-process: No network overhead, runs directly in your Node.js process
- TypeScript support: Full TypeScript definitions included
- Cross-platform: Supports Linux (x86 and arm64), macOS, Windows (browser is supported in the separate package
@tursodatabase/database-wasmpackage)
Installation
npm install @tursodatabase/databaseGetting Started
In-Memory Database
import { connect } from '@tursodatabase/database';
// Create an in-memory database
const db = await connect(':memory:');
// Create a table
await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)');
// Insert data
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
await insert.run('Alice', '[email protected]');
await insert.run('Bob', '[email protected]');
// Query data
const users = await db.prepare('SELECT * FROM users').all();
console.log(users);
// Output: [
// { id: 1, name: 'Alice', email: '[email protected]' },
// { id: 2, name: 'Bob', email: '[email protected]' }
// ]File-Based Database
import { connect } from '@tursodatabase/database';
// Create or open a database file
const db = await connect('my-database.db');
// Create a table
await db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Insert a post
const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)');
const result = await insertPost.run('Hello World', 'This is my first blog post!');
console.log(`Inserted post with ID: ${result.lastInsertRowid}`);Transactions
import { connect } from '@tursodatabase/database';
const db = await connect('transactions.db');
// Using transactions for atomic operations
const transaction = db.transactionAsync(async (txn, users) => {
const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
for (const user of users) {
await insert.run(user.name, user.email);
}
});
// Execute transaction
await transaction([
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' }
]);API Reference
For complete API documentation, see JavaScript API Reference.
Related Packages
- The @tursodatabase/serverless package provides a serverless driver with the same API.
- The @tursodatabase/sync package provides bidirectional sync between a local Turso database and Turso Cloud.
License
This project is licensed under the MIT license.
Support
File: bindings/javascript/sync/packages/common/README.md
About
This package is the Turso Sync common JS library which is shared between final builds for Node and Browser.
Do not use this package directly - instead you must use @tursodatabase/sync or @tursodatabase/sync-wasm.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
File: bindings/javascript/sync/README.md
About
This package is for syncing local Turso databases to the Turso Cloud and back.
Turso Database runs in production today at multiple organizations. It has not yet reached 1.0, so as with any database we recommend keeping backups — see the FAQ for where the project stands.
Installation
npm install @tursodatabase/syncGetting Started
To sync a database hosted at Turso Cloud:
import { connect } from '@tursodatabase/sync';
const db = await connect({
path: 'local.db', // path used as a prefix for local files created by sync-engine
url: 'https://<db>.turso.io', // URL of the remote database: turso db show <db>
authToken: '...', // auth token issued from the Turso Cloud: turso db tokens create <db>
clientName: 'turso-sync-example' // arbitrary client name
});
// db has same functions as Database class from @tursodatabase/database package but adds few more methods for sync:
await db.pull(); // pull changes from the remote
await db.push(); // push changes to the remote
await db.sync(); // pull & push changesRelated Packages
- The @tursodatabase/database package provides the Turso in-memory database, compatible with SQLite.
- The @tursodatabase/serverless package provides a serverless driver with the same API.
License
This project is licensed under the MIT license.
Support
2. Official Technical Reference & Guides (tursodatabase/turso-docs)
File: agentfs/guides/auditing.mdx
title: Audit Filesystem Changes sidebarTitle: Audit Filesystem Changes
AgentFS records every file operation and tool call, giving you complete visibility into agent behavior. This is essential for debugging, compliance, and understanding how agents work.
Viewing the Timeline
See a chronological list of tool calls:
agentfs timeline my-sessionOutput:
ID TOOL STATUS DURATION STARTED
4 execute_code pending -- 2024-01-05 09:44:20
3 api_call error 300ms 2024-01-05 09:44:15
2 read_file success 50ms 2024-01-05 09:44:10
1 web_search success 1200ms 2024-01-05 09:43:45Filtering Results
Show only specific tool types:
agentfs timeline my-session --filter web_searchShow only errors:
agentfs timeline my-session --status errorLimit number of entries:
agentfs timeline my-session --limit 20JSON Output
For programmatic analysis:
agentfs timeline my-session --format json[
{
"id": 1,
"name": "web_search",
"status": "success",
"started_at": 1704447825,
"completed_at": 1704447826,
"duration_ms": 1200,
"parameters": {"query": "AI agents"},
"result": {"results": ["result1", "result2"]}
}
]Inspecting Files
List all files in a session:
agentfs fs ls my-sessionOutput:
d artifacts
f config.json
f output.txtList a subdirectory:
agentfs fs ls my-session /artifactsRead file contents:
agentfs fs cat my-session /output.txtViewing Changes (Diff)
See what changed compared to the original filesystem:
agentfs diff my-sessionThis shows:
- New files created
- Modified files
- Deleted files
Querying with SQL
Since AgentFS uses SQLite, you can run arbitrary queries:
Example queries:
-- Find all tool calls that took longer than 1 second
SELECT name, duration_ms
FROM toolcalls
WHERE duration_ms > 1000;
-- Count tool usage by type
SELECT name, COUNT(*) as count
FROM toolcalls
GROUP BY name
ORDER BY count DESC;
-- Find files modified in the last hour
SELECT path, mtime
FROM fs_inode
WHERE mtime > strftime('%s', 'now', '-1 hour');
-- Get total bytes written
SELECT SUM(size) as total_bytes
FROM fs_inode
WHERE mode & 0170000 = 0100000; -- regular files onlyUse Cases
Debugging Agent Failures
When an agent fails, use the timeline to understand what happened:
# See recent activity
agentfs timeline my-session --limit 20
# Focus on errors
agentfs timeline my-session --status error
# Check what files were created/modified
agentfs fs ls my-sessionPerformance Analysis
Find slow operations:
SELECT name, AVG(duration_ms) as avg_ms, COUNT(*) as count
FROM toolcalls
GROUP BY name
ORDER BY avg_ms DESC;Compliance Auditing
Export a complete record of agent activity:
agentfs timeline my-session --format json > audit-log.jsonNext Steps
File: agentfs/guides/mcp.mdx
title: MCP Server Integration sidebarTitle: MCP Server Integration
AgentFS can run as an MCP (Model Context Protocol) server, exposing filesystem and key-value operations to AI assistants like Claude Desktop.
Starting the MCP Server
agentfs serve mcp my-agentThis starts an MCP server that exposes AgentFS tools over stdio.
Available Tools
Filesystem Tools
| Tool | Description |
|---|---|
read_file |
Read file contents |
write_file |
Write file contents |
readdir |
List directory contents |
mkdir |
Create directory |
remove |
Remove file or directory |
rename |
Rename file or directory |
stat |
Get file metadata |
access |
Check file access permissions |
Key-Value Tools
| Tool | Description |
|---|---|
kv_get |
Get value by key |
kv_set |
Set key-value pair |
kv_delete |
Delete key |
kv_list |
List all keys |
Limiting Exposed Tools
For security, you can limit which tools are available:
# Read-only mode
agentfs serve mcp my-agent --tools read_file,readdir,stat,kv_get,kv_list
# Filesystem only
agentfs serve mcp my-agent --tools read_file,write_file,readdir,mkdir,remove
# Key-value only
agentfs serve mcp my-agent --tools kv_get,kv_set,kv_delete,kv_listSecurity Considerations
- Limit tools - Only expose tools the assistant needs
- Use overlays - Create overlay filesystems instead of giving access to real directories
- Review the agent database - Check
.agentfs/*.dbperiodically
Debugging
Check if the MCP server is working:
# Run manually to see output
agentfs serve mcp my-agent
# Check if the database exists
ls -la .agentfs/my-agent.dbNext Steps
File: agentfs/guides/nfs.mdx
title: NFS Server Access sidebarTitle: NFS Server Access
AgentFS can expose filesystems over NFS, enabling remote access from other machines, containers, or virtual machines.
Starting the NFS Server
agentfs serve nfs my-agentBy default, this listens on 127.0.0.1:11111.
Binding to All Interfaces
To allow remote access:
agentfs serve nfs my-agent --bind 0.0.0.0 --port 2049Mounting the Filesystem
From the Same Machine
mkdir /mnt/agentfs
mount -t nfs -o vers=3,tcp,port=11111,mountport=11111,nolock 127.0.0.1:/ /mnt/agentfsFrom a Remote Machine
mount -t nfs -o vers=3,tcp,port=2049,mountport=2049,nolock server-ip:/ /mnt/agentfsMount Options Explained
| Option | Description |
|---|---|
vers=3 |
Use NFSv3 protocol |
tcp |
Use TCP transport |
port=11111 |
NFS server port |
mountport=11111 |
Mount protocol port |
nolock |
Disable file locking (single-user) |
Unmounting
umount /mnt/agentfsIf the mount is busy:
umount -f /mnt/agentfs # Force unmount
# or
umount -l /mnt/agentfs # Lazy unmountNext Steps
File: agentfs/guides/overlay.mdx
title: Copy-on-Write Overlays sidebarTitle: Copy-on-Write Overlays
AgentFS uses an overlay filesystem to provide copy-on-write isolation. This lets agents freely modify files while keeping your original data safe.
How Overlay Works
An overlay filesystem combines two layers:
┌────────────────────────────────────┐
│ Merged View (what you see) │
├────────────────────────────────────┤
│ Delta Layer (AgentFS database) │ ← Writes go here
├────────────────────────────────────┤
│ Base Layer (original directory) │ ← Read-only
└────────────────────────────────────┘Reading a file:
- Check delta layer first
- If not found, read from base layer
Writing a file:
- Copy file to delta layer (if from base)
- Write changes to delta layer
- Base layer is never modified
Deleting a file:
- Create a "whiteout" marker in delta layer
- File appears deleted in merged view
- Original file in base layer is untouched
Creating an Overlay Filesystem
With `agentfs run`
The simplest way - automatically creates an overlay over your current directory:
cd /path/to/project
agentfs run /bin/bashWith `agentfs init --base`
Create an overlay explicitly:
agentfs init my-overlay --base /path/to/projectThen mount it:
agentfs mount my-overlay ./workspaceOr run a command in it:
agentfs run --session my-overlay /bin/bashViewing Changes
See what's different from the base:
agentfs diff my-overlayPractical Examples
Safe Refactoring
Let an agent refactor your code without risk:
cd my-project
agentfs run --session refactor python3 refactor_agent.py
# Review what changed
agentfs diff refactor
# Happy? Apply changes manually or via script
# Not happy? Just delete the session
rm .agentfs/refactor.dbTesting Destructive Operations
Try something dangerous safely:
agentfs run --session test /bin/bash
$ rm -rf src/ # Yikes!
$ exit
# Original files are fine
ls src/ # Still there!
# Only the overlay was affected
agentfs fs ls test # Shows the deletionParallel Experiments
Run multiple experiments on the same codebase:
# Experiment A
agentfs run --session exp-a python3 approach_a.py
# Experiment B (same base, different changes)
agentfs run --session exp-b python3 approach_b.py
# Compare results
agentfs diff exp-a
agentfs diff exp-bStorage Efficiency
The overlay only stores:
- Modified files - Full copy after first write
- New files - Stored entirely in delta
- Deleted files - Small whiteout marker
Original files are never duplicated. A 10GB project with 1MB of changes only uses ~1MB in the delta layer.
Technical Details
File Operations
| Operation | Behavior |
|---|---|
| Read existing file | Pass through to base |
| Write existing file | Copy to delta, then write |
| Create new file | Write directly to delta |
| Delete file | Create whiteout in delta |
| Rename file | Delete old + create new |
Whiteouts
When you delete a base file, AgentFS creates a "whiteout" - a marker that hides the file:
-- Whiteouts in the database
SELECT * FROM fs_inode WHERE whiteout = 1;Database Schema
The delta layer is stored in SQLite:
-- Files and directories
CREATE TABLE fs_inode (
ino INTEGER PRIMARY KEY,
parent_ino INTEGER,
name TEXT,
mode INTEGER,
size INTEGER,
...
whiteout INTEGER DEFAULT 0
);
-- File contents
CREATE TABLE fs_block (
ino INTEGER,
block_num INTEGER,
data BLOB
);See the AgentFS Specification for the complete schema.
Next Steps
File: agentfs/guides/sandbox.mdx
title: Agentic Coding sidebarTitle: Agentic Coding
AgentFS lets you run AI coding agents like Claude Code, Codex, and OpenCode in a sandboxed environment where file changes are isolated from your original source tree.
Supported Agents
| Agent | Command |
|---|---|
| Claude Code | agentfs run claude |
| Codex | agentfs run codex |
| OpenCode | agentfs run opencode |
| Any CLI tool | agentfs run <command> |
Workflow
Here's a typical agentic coding workflow. This example uses Claude Code, but the workflow is the same with OpenCode, Codex, and others.
Step 1: Clone the Repository
Clone the git repository to a host directory. Keep this source tree clean from build artifacts. When there's a merge to main, pull to this repository to keep it up-to-date.
git clone https://github.com/tursodatabase/agentfs.git
cd agentfsStep 2: Start an AgentFS Session
Use agentfs run with the --session flag to start a session. Pick a name that can eventually become your git branch name.
The agent now runs in a copy-on-write environment. Your original source tree remains untouched while the agent works.
Step 3 (Optional): Manual Investigation
If the agent does something unusual, open another terminal and join the same session to investigate:
agentfs run --session fix-bug-123 bashThe filesystem is shared between the shell and the agent. You can browse files, run tests, or check what's happening while the agent works.
Step 4: Push to Branch
Once the agent has completed the work, ask it to create a branch, commit the code, and push:
"Create a branch called fix-bug-123, commit these changes, and push to origin"
You can then create a PR manually from the pushed branch.
Why This Workflow?
- Clean source tree - Your original clone stays pristine and easy to update from upstream
- Isolated changes - Each task gets its own session, preventing cross-contamination
- Easy investigation - Join any session to see what's happening in real-time
- Standard git flow - Push branches and create PRs as usual
Next Steps
File: agentfs/guides/sessions.mdx
title: Shared Agent Sessions sidebarTitle: Shared Agent Sessions
Named sessions let you share a filesystem between multiple agents, terminals, or runs. All participants see the same copy-on-write view of your project.
Creating a Session
Use --session to create a named session:
agentfs run --session my-project /bin/bashThis creates a session called my-project. All changes are stored in .agentfs/my-project.db.
Resuming a Session
Run the same command to resume where you left off:
# First run - make some changes
agentfs run --session my-project /bin/bash
$ echo "hello" > test.txt
$ exit
# Second run - changes are still there
agentfs run --session my-project /bin/bash
$ cat test.txt
helloMulti-Terminal Collaboration
Multiple terminals can share the same session simultaneously:
# Terminal 1
agentfs run --session shared-work /bin/bash
# Terminal 2 - joins the same filesystem
agentfs run --session shared-work python3 agent.py
# Terminal 3 - also sees the same files
agentfs run --session shared-work vimAll terminals share the same copy-on-write filesystem. Changes made in one terminal are immediately visible in others.
Use Cases
Iterative Development
Work on a task across multiple sessions:
# Morning session
agentfs run --session feature-x /bin/bash
# ... work on the feature, then exit for lunch
# Afternoon session - continue where you left off
agentfs run --session feature-x /bin/bashSafe Experimentation
Create a session, try something risky, then decide whether to keep it:
agentfs run --session experiment /bin/bash
# ... try some changes
# Option A: Happy with changes
agentfs diff experiment # Review
# Apply changes to real filesystem (manually or via script)
# Option B: Discard everything
rm .agentfs/experiment.dbAgent Supervision
Run an agent and monitor its work from another terminal:
# Terminal 1: Run the agent
agentfs run --session agent-task python3 agent.py
# Terminal 2: Watch what it's doing
agentfs run --session agent-task /bin/bash
$ watch ls -la # See files being created
$ tail -f agent.log # Monitor logsManaging Sessions
List files in a session:
agentfs fs ls my-sessionView session changes:
agentfs diff my-sessionDelete a session:
rm .agentfs/my-session.dbSession Storage
Sessions are stored as SQLite databases in .agentfs/:
.agentfs/
├── my-project.db
├── experiment.db
└── shared-work.dbEach database contains:
- All modified files
- Deleted file markers (whiteouts)
- Tool call audit log
- Key-value store data
Next Steps
File: agentfs/guides/sync.mdx
title: Sync with Turso Cloud sidebarTitle: Sync with Turso Cloud
AgentFS can sync with Turso Cloud, enabling backup, collaboration, and distributed agent deployments.
Setting Up Sync
1. Create a Turso Database
If you don't have a Turso account, sign up first.
Create a database for your agent:
turso db create my-agent-dbGet the database URL:
turso db show my-agent-db --url
# Output: libsql://my-agent-db-username.turso.io2. Initialize with Sync
Create a new agent filesystem with sync enabled:
agentfs init my-agent --sync-remote-url libsql://my-agent-db-username.turso.ioOr add sync to an existing agent:
# Edit the database to add sync configuration
# (Feature coming soon)Sync Commands
Pull Remote Changes
Download changes from Turso Cloud:
agentfs sync my-agent pullPush Local Changes
Upload local changes to Turso Cloud:
agentfs sync my-agent pushView Sync Status
Check sync statistics:
agentfs sync my-agent statsCreate Checkpoint
Force a sync checkpoint:
agentfs sync my-agent checkpointUse Cases
Backup and Recovery
Automatically back up agent state:
# After important work
agentfs sync my-agent push
# On a new machine
agentfs init my-agent --sync-remote-url libsql://...
agentfs sync my-agent pullMulti-Machine Agents
Run agents on multiple machines with shared state:
# Machine A
agentfs run --session shared-agent python3 agent.py
agentfs sync shared-agent push
# Machine B
agentfs sync shared-agent pull
agentfs run --session shared-agent python3 agent.pyPartial Sync
For large filesystems, use partial sync to only fetch what's needed:
agentfs init my-agent \
--sync-remote-url libsql://... \
--sync-partial-prefetch \
--sync-partial-segment-size 1000Partial Sync Options
| Option | Description |
|---|---|
--sync-partial-prefetch |
Enable prefetching |
--sync-partial-segment-size |
Segment size for partial sync |
--sync-partial-bootstrap-query |
Custom bootstrap query |
--sync-partial-bootstrap-length |
Bootstrap prefix length |
Authentication
Sync requires a Turso auth token. Set it via environment variable:
export TURSO_AUTH_TOKEN=$(turso db tokens create my-agent-db)Or create a long-lived token:
turso db tokens create my-agent-db --expiration noneConflict Resolution
AgentFS uses last-write-wins for conflicts. If the same file is modified on multiple machines:
- Push from Machine A succeeds
- Push from Machine B fails (conflict)
- Machine B must pull first, then push
Next Steps
File: guides/agent-databases.mdx
title: Agent Databases
Looking for a complete agent state management solution? Check out AgentFS - our specialized SDK designed specifically for AI agents, offering filesystem operations, key-value storage, and automatic tool call tracking in a single database file.
AI agents need databases to store context, track state, and maintain memory across executions. Turso Database offers two powerful approaches:
Embedded Databases for local-first agent processing, and
Turso Sync for distributed agent coordination with cloud persistence.
Embedded Databases (Local-First)
Use embedded databases for agents that process data locally within a single workflow, need offline-capable access, or work with temporary data without sharing state with other agents.
Local data pipelines, on-device processing of sensitive data, development/testing workflows, and single-agent analysis tasks.
import { connect } from "@tursodatabase/database";
// Create a local embedded database for this agent
const db = await connect("agent-workflow.db");
// Agent can now store and query data locally
const stmt = db.prepare(`
CREATE TABLE IF NOT EXISTS steps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT NOT NULL,
result TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
stmt.run();
// Track agent workflow steps
db.prepare("INSERT INTO steps (action, result) VALUES (?, ?)").run(
"fetch_data",
"success",
);Benefit from zero network latency, full offline capability, complete data privacy, and simple single-file deployment.
Data is isolated to this agent instance with no built-in multi-agent collaboration or cross-session persistence (unless you manage the file yourself).
Turso Sync (Cloud-Connected)
Use Turso Sync for agents that need persistent memory across sessions, coordinate with other agents, or require cloud-backed durability for long-running workflows.
💡 Conversational agents with session memory, multi-agent coordination systems, long-running workflows with recovery needs, and agents reporting to central systems.
import { connect } from "@tursodatabase/database";
// Connect to a synced database (embedded locally, synced to Turso Cloud)
const db = await connect({
path: "agent-memory.db",
url: "https://your-database.turso.io",
authToken: "your-auth-token",
sync: "full", // Bidirectional sync
});
// Agent can access data from previous sessions or other agents
const previousContext = db
.prepare("SELECT * FROM conversation_history WHERE session_id = ?")
.all("session-123");
// New data is automatically synced to cloud and other agent instances
db.prepare("INSERT INTO agent_actions (agent_id, action) VALUES (?, ?)").run(
"agent-1",
"processed_document",
);
// Manually trigger sync if needed
await db.sync();💡 Persistent memory across restarts, multi-agent coordination via shared state, cloud backup and durability, with fast local reads and background sync.
Sync modes: sync (bidirectional), pull (cloud → agent), push (agent → cloud).
Multi-Agent Architectures
Pattern 1: Isolated Agent Databases
Each agent gets its own embedded database. Best for independent agents with no shared state.
// Agent 1
const agent1DB = await connect("agent-1.db");
// Agent 2
const agent2DB = await connect("agent-2.db");Independent agents performing separate tasks with no coordination needs and maximum isolation.
Pattern 2: Shared Database with Sync
Multiple agents connect to the same synced database. Best for coordinated agent systems.
// Agent 1 connects and writes tasks
const agent1DB = await connect({
path: "agent-1-local.db",
url: "https://shared-tasks.turso.io",
authToken: process.env.TURSO_TOKEN,
sync: "full",
});
agent1DB.prepare("INSERT INTO tasks (status) VALUES (?)").run("pending");
// Agent 2 connects to same database and reads tasks
const agent2DB = await connect({
path: "agent-2-local.db",
url: "https://shared-tasks.turso.io", // Same database
authToken: process.env.TURSO_TOKEN,
sync: "full",
});
const pendingTasks = agent2DB
.prepare("SELECT * FROM tasks WHERE status = ?")
.all("pending");💡 Agents coordinating work through shared task queues or a common knowledge base.
Pattern 3: Hub-and-Spoke
A central synced database with agents pushing results and pulling new tasks.
// Worker agents push results only
const workerDB = await connect({
path: "worker-local.db",
url: "https://hub.turso.io",
authToken: process.env.WORKER_TOKEN,
sync: "push", // Only push results to cloud
});
// Coordinator agent pulls from all workers
const coordinatorDB = await connect({
path: "coordinator-local.db",
url: "https://hub.turso.io",
authToken: process.env.COORDINATOR_TOKEN,
sync: "pull", // Only pull data from cloud
});💡 Many worker agents feeding results to a central coordinator for monitoring or aggregation with unidirectional data flow.
Database-Per-Agent Pattern
For systems managing many agents, you can create individual databases for each agent using the Platform API:
import { createClient } from "@tursodatabase/api";
const turso = createClient({
token: process.env.TURSO_PLATFORM_API_TOKEN,
org: process.env.TURSO_ORG_NAME,
});
// Create a dedicated database for each agent
async function provisionAgentDatabase(agentId) {
const database = await turso.databases.create(`agent-${agentId}`, {
group: "default",
});
console.log(`Database created for agent ${agentId}: ${database.Name}`);
return database;
}Complete isolation between agents, independent scaling per agent, improved security (compromised agent only affects one database), and easy cleanup when agents are retired.
Examples
Explore these examples to see agent database patterns in action:
- sync-node — Node.js with bidirectional sync to Turso Cloud
- sync-wasm-vite — Browser (WASM) with bidirectional sync to Turso Cloud
- database-node — Node.js, local file database (no sync)
- database-wasm-vite — Browser (WASM), local database in the browser
File: guides/ai-memory.mdx
title: AI Memory
AI agents start every session from scratch. A persistent memory system lets agents store lessons and retrieve relevant context via vector search. Turso's embedded database with built-in vector functions makes this possible in a single local file.
This guide covers the schema and queries for building a memory system with vector similarity search.
Schema
At its simplest, a memory system needs just two tables: one for memories and one for tracking which memories were used in which tasks.
CREATE TABLE IF NOT EXISTS memories (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
embedding F8_BLOB(384),
category TEXT NOT NULL,
created_at INTEGER NOT NULL,
last_retrieved INTEGER,
retrieval_count INTEGER DEFAULT 0,
source_task TEXT
);
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
description TEXT,
embedding F8_BLOB(384),
started_at INTEGER,
finished_at INTEGER
);memoriesstores each lesson with its vector embedding and a category (e.g.correction,insight,user,discovery). The embedding is a 384-dimensional int8-quantized vector (F8_BLOB(384)).tasksrecords each task the agent worked on, linking task descriptions to their embeddings for retrieval.
Connecting
import { connect } from "@tursodatabase/database";
const db = await connect(".memory/agent.db");
await db.exec(SCHEMA);Storing a memory
When the agent learns something — a correction, a user preference, an insight — store it with an embedding. Use vector8() to convert a JSON float array into an int8-quantized vector:
INSERT INTO memories (id, content, embedding, category, created_at, source_task)
VALUES (?, ?, vector8(?), ?, ?, ?);The parameter for vector8() is a JSON-stringified float array (e.g. 384 dimensions from a model like all-MiniLM-L6-v2). Turso handles the quantization internally:
db.prepare(`
INSERT INTO memories (id, content, embedding, category, created_at)
VALUES (?, ?, vector8(?), ?, ?)
`).run(id, content, JSON.stringify(Array.from(embedding)), "correction", Date.now());Retrieving relevant memories
When a new task starts, find the most relevant memories using Turso's vector_distance_cos() function:
SELECT id, content, category, created_at, retrieval_count,
vector_distance_cos(embedding, vector8(?)) AS distance
FROM memories
WHERE embedding IS NOT NULL
ORDER BY distance ASC
LIMIT ?;vector_distance_cos() returns cosine distance (0 = identical, 2 = opposite). Lower is better. To convert to similarity: 1.0 - distance.
After retrieval, update the memory's metadata:
UPDATE memories SET last_retrieved = ?, retrieval_count = retrieval_count + 1
WHERE id = ?;Correcting wrong memories
When a memory turns out to be wrong, delete it and store a corrected version:
DELETE FROM memories WHERE id = ?;
INSERT INTO memories (id, content, embedding, category, created_at)
VALUES (?, ?, vector8(?), 'correction', ?);Task tracking
Record tasks to understand what the agent has been working on:
-- Start a task
INSERT INTO tasks (id, description, embedding, started_at)
VALUES (?, ?, vector8(?), ?);
-- Complete a task
UPDATE tasks SET finished_at = ? WHERE id = ?;Purging old memories
Remove memories that have been around a long time but are never retrieved:
DELETE FROM memories
WHERE retrieval_count = 0 AND created_at < ?;Key design points
- Vector search uses Turso's
vector8()andvector_distance_cos()for cosine similarity directly in SQL. Int8 quantization reduces storage by 75% compared to float32 with minimal impact on search quality. - For small per-project datasets, a full table scan with
ORDER BY distance LIMIT kis fast enough without a vector index. - Everything runs in a single embedded database file with no external services.
Going further
The schema above is deliberately minimal. In practice you will want to experiment with strategies for keeping the memory store useful as it grows. Some ideas:
- Weighted memories — add a
weight REALcolumn and boost or penalize memories based on whether the agent found them useful after retrieval. Use an exponential moving average to update weights over time. - Time decay — multiply a recency factor into the retrieval ranking so that stale memories rank lower. For example:
(1.0 - distance) * POWER(0.95, days_since_last_retrieved). - Garbage collection — periodically remove memories with low weight and high retrieval count (retrieved often, never useful), or memories that have never been retrieved after a threshold period.
- Task scoring — track outcome metrics (tokens used, errors, corrections) per task and use them to compute a credit signal for the memories that were retrieved during that task.
- Retrieval attribution — add a join table
(memory_id, task_id, similarity, credit)to record exactly which memories were used in each task and how they were rated.
There is no single best approach — the right strategy depends on your agent's workload, how often memories are created, and how aggressively you want to prune. Start simple and iterate.
Example
memelord is an MCP server and hooks system that implements this pattern with reinforcement learning and weight decay as a persistent memory layer for AI coding agents.
File: guides/code-indexing.mdx
title: Code Indexing
AI coding agents work better when they can search a codebase semantically ("find the authentication logic") rather than just by filename or grep. A code index backed by Turso can combine full-text search over identifiers with vector similarity search over embeddings, all in a single embedded database.
This guide covers the schema and queries for building a code indexer with both FTS and vector search.
Schema
CREATE TABLE IF NOT EXISTS codebases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
root_path TEXT NOT NULL UNIQUE,
name TEXT NOT NULL DEFAULT '',
indexed_at INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS chunks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codebase_id INTEGER NOT NULL REFERENCES codebases(id),
file_path TEXT NOT NULL,
chunk_key TEXT NOT NULL UNIQUE,
language TEXT NOT NULL,
kind TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
signature TEXT NOT NULL DEFAULT '',
snippet TEXT NOT NULL,
start_line INTEGER NOT NULL,
end_line INTEGER NOT NULL,
file_hash TEXT NOT NULL,
indexed_at INTEGER NOT NULL,
embedding F8_BLOB(384),
embedding_model TEXT DEFAULT ''
);
CREATE TABLE IF NOT EXISTS indexed_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codebase_id INTEGER NOT NULL REFERENCES codebases(id),
file_path TEXT NOT NULL,
file_hash TEXT NOT NULL,
chunk_count INTEGER NOT NULL DEFAULT 0,
indexed_at INTEGER NOT NULL,
UNIQUE(codebase_id, file_path)
);How it fits together
codebasesregisters each project root. A single database can index multiple codebases.chunksstores individual semantic units extracted from source files — functions, structs, classes, impl blocks, etc. Each chunk has aname,signature, codesnippet, line range, and optionally a vectorembedding. Thechunk_keyis a unique identifier (e.g.file_path::kind::name) for upsert operations.indexed_filestracks which files have been indexed and their content hashes, enabling incremental re-indexing — only changed files are re-processed.
Connecting
The FTS index features require an experimental flag at connection time:
import { connect } from "@tursodatabase/database";
const db = await connect(".index/code.db", {
experimental: ["index_method"],
});
await db.exec(SCHEMA);The experimental: ["index_method"] flag enables Turso's USING fts index syntax and the fts_match() / fts_score() functions.
Indexing files
Upserting chunks
When a file is parsed, upsert its chunks. Setting embedding = NULL on conflict forces re-embedding when content changes:
INSERT INTO chunks (codebase_id, file_path, chunk_key, language, kind, name,
signature, snippet, start_line, end_line, file_hash, indexed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(chunk_key) DO UPDATE SET
language = excluded.language,
kind = excluded.kind,
name = excluded.name,
signature = excluded.signature,
snippet = excluded.snippet,
start_line = excluded.start_line,
end_line = excluded.end_line,
file_hash = excluded.file_hash,
indexed_at = excluded.indexed_at,
embedding = NULL,
embedding_model = '';Tracking indexed files
INSERT INTO indexed_files (codebase_id, file_path, file_hash, chunk_count, indexed_at)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(codebase_id, file_path) DO UPDATE SET
file_hash = excluded.file_hash,
chunk_count = excluded.chunk_count,
indexed_at = excluded.indexed_at;Skipping unchanged files
Before parsing a file, check if it has changed:
SELECT file_hash FROM indexed_files WHERE codebase_id = ? AND file_path = ?;If the hash matches, skip it entirely.
Storing embeddings
Embeddings can be stored as int8-quantized vectors using Turso's vector8() function, which reduces storage from 1,536 bytes (float32, 384 dims) to 395 bytes:
UPDATE chunks SET embedding = vector8(?), embedding_model = ? WHERE chunk_key = ?;The parameter for vector8() is a JSON-stringified float array. Turso handles the quantization internally.
Find chunks that need embedding:
SELECT chunk_key, name, signature, file_path, kind, snippet
FROM chunks
WHERE codebase_id = ? AND (embedding IS NULL OR embedding_model != ?)
LIMIT ?;Full-text search
Turso provides an FTS index with weighted BM25 scoring that is separate from SQLite's fts5 virtual tables.
Creating the FTS index
Create an FTS table per codebase and populate it from the chunks:
-- Create the FTS table
CREATE TABLE IF NOT EXISTS fts_1 (
chunk_id INTEGER NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT '',
signature TEXT NOT NULL DEFAULT ''
);
-- Populate from chunks
INSERT INTO fts_1 (chunk_id, name, signature)
SELECT id, name, signature FROM chunks WHERE codebase_id = 1;
-- Create the FTS index with weighted columns
CREATE INDEX IF NOT EXISTS idx_fts_1 ON fts_1
USING fts (name, signature)
WITH (
tokenizer = 'default',
weights = 'name=5.0,signature=3.0'
);The weights parameter controls BM25 scoring — here, matches in the function/type name are weighted 5x and signature matches 3x.
Searching with FTS
SELECT chunk_id, fts_score(name, signature, ?1) AS score
FROM fts_1
WHERE fts_match(name, signature, ?1)
ORDER BY score DESC
LIMIT ?;Then fetch the full chunk data:
SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line
FROM chunks WHERE id = ?;Vector search
For semantic/natural language queries, use vector cosine distance:
SELECT chunk_key, file_path, name, kind, signature, snippet, start_line, end_line,
vector_distance_cos(embedding, vector8(?)) AS distance
FROM chunks
WHERE embedding IS NOT NULL
ORDER BY distance ASC
LIMIT ?;The score is 1 - distance (cosine similarity from cosine distance).
Hybrid search
For the best results, combine both approaches using Reciprocal Rank Fusion (RRF). Run the FTS and vector queries separately, then merge results in application code:
// Run both searches
const ftsResults = await ftsSearch(query, limit);
const vecResults = await vectorSearch(queryEmbedding, limit);
// Reciprocal Rank Fusion
const k = 60; // RRF constant
const scores = new Map();
ftsResults.forEach((r, i) => {
const key = r.chunk_key;
scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1));
});
vecResults.forEach((r, i) => {
const key = r.chunk_key;
scores.set(key, (scores.get(key) || 0) + 1 / (k + i + 1));
});
// Sort by combined score
const merged = [...scores.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, limit);Removing stale files
When files are deleted from the codebase, clean up their chunks and records:
DELETE FROM chunks WHERE codebase_id = ? AND file_path = ?;
DELETE FROM indexed_files WHERE codebase_id = ? AND file_path = ?;After removing stale data, rebuild the FTS table to keep the index consistent:
DROP TABLE IF EXISTS fts_1;
-- Then recreate and repopulate as shown aboveKey design points
- Incremental indexing via file hashes means only changed files are re-parsed and re-embedded, making updates fast even on large codebases.
- Two search modes — FTS for identifier/keyword lookup and vector search for semantic/natural language queries — cover different use cases. Hybrid search combines both.
- Weighted FTS (
name=5.0, signature=3.0) biases results toward function and type names, which is what developers usually search for. - Int8 quantized vectors via
vector8()reduce storage by 75% compared to float32 with minimal impact on search quality. - Everything runs in a single embedded database file with no external services.
Example
codemogger is an MCP server that implements this pattern as a code indexer for AI coding agents.
File: guides/content-caching.mdx
title: Content Caching
Tools and agents frequently re-read files they have already seen, wasting tokens and time. A content-addressed cache backed by an embedded Turso database can detect unchanged files and return diffs instead of full content, dramatically reducing redundant I/O.
This guide walks through the schema and queries needed to build a file content cache with per-session tracking.
Schema
The cache uses four tables: one for file versions, one for per-session read pointers, and two for statistics.
CREATE TABLE IF NOT EXISTS file_versions (
path TEXT NOT NULL,
hash TEXT NOT NULL,
content TEXT NOT NULL,
lines INTEGER NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (path, hash)
);
CREATE TABLE IF NOT EXISTS session_reads (
session_id TEXT NOT NULL,
path TEXT NOT NULL,
hash TEXT NOT NULL,
read_at INTEGER NOT NULL,
PRIMARY KEY (session_id, path)
);
CREATE TABLE IF NOT EXISTS stats (
key TEXT PRIMARY KEY,
value INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS session_stats (
session_id TEXT NOT NULL,
key TEXT NOT NULL,
value INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (session_id, key)
);
INSERT OR IGNORE INTO stats (key, value) VALUES ('tokens_saved', 0);How it fits together
file_versionsis a content-addressed store. The composite primary key(path, hash)means the same file can have multiple versions stored simultaneously. When a file changes and then changes back (e.g. a branch switch), the old version is already cached.session_readstracks which version of each file a given session last saw. This is keyed by(session_id, path)so each session independently tracks its own read state.statsandsession_statstrack cumulative token savings globally and per session.
Connecting
import { connect } from "@tursodatabase/database";
const db = await connect(".cache/content.db");
await db.exec(SCHEMA); // The CREATE TABLE statements aboveStoring a file version
When a file is read for the first time (or has changed), store its content keyed by a hash. INSERT OR IGNORE makes this a no-op if the same version already exists:
INSERT OR IGNORE INTO file_versions (path, hash, content, lines, created_at)
VALUES (?, ?, ?, ?, ?);The hash should be computed from the file content (e.g. a truncated SHA-256). This makes storage content-addressed — identical content is never stored twice for the same path.
Tracking reads per session
Each session needs to know what it last saw for a given file. On first read, record the session's read pointer:
INSERT OR REPLACE INTO session_reads (session_id, path, hash, read_at)
VALUES (?, ?, ?, ?);On subsequent reads, check what the session last saw:
SELECT hash FROM session_reads WHERE session_id = ? AND path = ?;If the stored hash matches the current file hash, the file is unchanged — return a short confirmation instead of the full content. If the hash differs, fetch the old content to compute a diff:
SELECT content FROM file_versions WHERE path = ? AND hash = ?;Then update the session's read pointer to the new hash:
UPDATE session_reads SET hash = ?, read_at = ? WHERE session_id = ? AND path = ?;Tracking token savings
Use an atomic counter for global statistics and an upsert for per-session tracking:
-- Global counter
UPDATE stats SET value = value + ? WHERE key = 'tokens_saved';
-- Per-session counter (upsert)
INSERT INTO session_stats (session_id, key, value)
VALUES (?, 'tokens_saved', ?)
ON CONFLICT(session_id, key) DO UPDATE SET value = value + ?;Querying statistics
-- Number of distinct files cached
SELECT COUNT(DISTINCT path) as file_count FROM file_versions;
-- Global tokens saved
SELECT value FROM stats WHERE key = 'tokens_saved';
-- Tokens saved in the current session
SELECT value FROM session_stats WHERE session_id = ? AND key = 'tokens_saved';Cleanup
When a file is deleted from disk, remove all its versions and read pointers:
DELETE FROM file_versions WHERE path = ?;
DELETE FROM session_reads WHERE path = ?;To clear the entire cache:
DELETE FROM file_versions;
DELETE FROM session_reads;
DELETE FROM session_stats;
UPDATE stats SET value = 0;Key design points
- Content-addressed storage means branch switches are handled naturally. If you switch from
mainto a feature branch and back, the original file versions are still in the cache. - Session isolation ensures that each consumer independently tracks what it has seen. Session B gets full file content even if Session A already cached it.
- Hash-based change detection at read time means there is no polling or file watching required for correctness.
- An embedded Turso database keeps everything in a single file with zero network overhead.
Example
cachebro is an MCP server that implements this pattern as a drop-in file reading cache for AI coding agents.
File: guides/secrets-vault.mdx
title: Secrets Vault
AI coding agents regularly need real credentials — an API token to call GitHub as you, a key to hit your billing provider, a token to deploy. The path of least resistance is to paste the secret into the chat, but then the value lives in the model's context, the transcript, and your provider's logs forever. A secrets vault backed by an encrypted Turso database lets an agent use a secret without ever seeing its value: the secret is injected into a child process, the command runs, and the output is scrubbed before the agent sees it.
This guide covers the schema, the encrypted connection, and the queries for a local secrets vault that an agent can use but never read.
Schema
The vault uses two tables: one for the secrets themselves, one for an append-only audit log.
CREATE TABLE IF NOT EXISTS secrets (
name TEXT PRIMARY KEY,
value TEXT NOT NULL,
provider TEXT,
account TEXT,
environment TEXT,
access TEXT,
tags TEXT NOT NULL DEFAULT '',
description TEXT,
created_at TEXT NOT NULL,
last_used_at TEXT
);
CREATE TABLE IF NOT EXISTS audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
secrets TEXT NOT NULL,
command TEXT NOT NULL,
cwd TEXT NOT NULL,
exit_code INTEGER,
outcome TEXT NOT NULL
);How it fits together
secretsstores each credential keyed byname. Thevaluecolumn holds the secret itself and is only ever read to inject into a child process, never returned to the agent. The metadata columns (provider,account,environment,access,tags) describe a secret without revealing it, so an agent can reason about "the production database token" by its attributes.audit_logis an append-only record of every use: which secrets were injected, the command, the working directory, the exit code, and the outcome (ranordenied).
Connecting
The vault is a single encrypted database file. Turso encrypts every page at rest when you pass a key at connection time, and multiprocess_wal lets several processes (a couple of agent sessions plus a CLI) open the same file safely:
import { connect } from "@tursodatabase/database";
const db = await connect("vault.db", {
encryption: { cipher: "aes256gcm", hexkey },
experimental: ["multiprocess_wal"],
});
await db.exec(SCHEMA); // The CREATE TABLE statements abovehexkey is a 64-character hex string. Where it comes from is up to you. You can derive it from a passphrase with scrypt (and never store it), unwrap it from the OS keychain, or fetch it from a cloud KMS. Turso only needs the key at connect() time; the storage layer is identical regardless of the source.
Storing a secret
Upsert by name so rotating a value keeps the existing metadata:
INSERT INTO secrets (name, value, provider, account, environment, access, tags, description, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
value = excluded.value,
provider = COALESCE(excluded.provider, secrets.provider),
account = COALESCE(excluded.account, secrets.account),
environment = COALESCE(excluded.environment, secrets.environment),
access = COALESCE(excluded.access, secrets.access),
description = COALESCE(excluded.description, secrets.description);Listing without exposing values
The query an agent is allowed to run simply never selects the value column:
SELECT name, provider, account, environment, access, tags, description, created_at, last_used_at
FROM secrets ORDER BY name;This returns secret names and attributes — enough for an agent to pick the right credential — while the value never leaves the database.
Using a secret
To actually use a secret, read its value, inject it into a child process as an environment variable, run the command, and scrub the value out of the output before returning it:
SELECT value FROM secrets WHERE name = ?;The injection and scrubbing happen in application code, not the database:
const { value } = await db.prepare(
"SELECT value FROM secrets WHERE name = ?"
).get([name]);
const result = await runCommand(command, {
env: { ...process.env, [name]: value },
});
// Replace every occurrence of the value before the agent sees the output
const safe = result.stdout.replaceAll(value, "***");Scrubbing captured output is not a sandbox. A command that can read a secret can always leak it on purpose (write it to a file, send it over the network). What the vault guarantees is narrower and still useful: the model never receives the value, and a careless echo $TOKEN gets redacted. The secret only ever lives in the encrypted file and the short-lived process that used it.
Auditing every use
Record each access as a row, and update the secret's last_used_at:
INSERT INTO audit_log (ts, secrets, command, cwd, exit_code, outcome)
VALUES (?, ?, ?, ?, ?, ?);
UPDATE secrets SET last_used_at = ? WHERE name = ?;"Who used the production key, when, and for what" is then an ordinary query:
SELECT ts, secrets, command, cwd, exit_code, outcome
FROM audit_log ORDER BY id DESC LIMIT ?;Key design points
- Encrypted at rest, for free. Turso handles whole-database encryption (AES-256-GCM, or one of the faster AEGIS ciphers). The vault never touches a cipher or manages page encryption — it derives a key and hands it to
connect(). - No coordinating daemon.
multiprocess_wallets each agent session and CLI invocation open the same encrypted file directly, so the database is the shared state instead of a long-running server you have to keep alive. - The value never appears in a list. Listing is a
SELECTthat omits thevaluecolumn; the agent sees names and attributes only. - Fail closed. An unknown secret name aborts before the command runs, rather than executing with a missing credential.
- Auditing is just a table. Every use is an
INSERT; reporting on it is aSELECTyou can export or alert on.
Example
keymaxxer is an MCP server that implements this pattern as an encrypted secrets vault for AI coding agents.