How to install, configure, and integrate smartbotic-database into your C++ project.
Add the Smartbotics repository to your system:
# Download GPG key
curl -fsSL https://repository.smartbotics.ai/smartbotics-repo.gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/smartbotics-repo.gpg
# Configure authentication
sudo tee /etc/apt/auth.conf.d/smartbotics.conf << EOF
machine repository.smartbotics.ai
login callerai
password <repo-password>
EOF
sudo chmod 600 /etc/apt/auth.conf.d/smartbotics.conf
# Add repository
echo "deb [signed-by=/usr/share/keyrings/smartbotics-repo.gpg] https://repository.smartbotics.ai trixie main" | \
sudo tee /etc/apt/sources.list.d/smartbotics.list
# Install
sudo apt update
sudo apt install smartbotic-database
This installs:
/usr/bin/smartbotic-database — the server binary/etc/smartbotic-database/config.json — configuration filesmartbotic-database.service — systemd service (starts automatically)The service listens on localhost:9004 by default.
# Check service status
sudo systemctl status smartbotic-database
# Check health via CLI (if installed)
smartbotic-db-cli health
| Package | What it provides | Install when... |
|---|---|---|
smartbotic-database |
Server + systemd service | You need to run the database on this machine |
libsmartbotic-db-client |
Shared library (.so) |
Your application links the client at runtime |
libsmartbotic-db-client-dev |
Headers + cmake config | You're building a C++ project against the client API |
smartbotic-db-cli |
CLI admin tool | You want to administer the database from the command line |
sudo apt install libsmartbotic-db-client-dev
This pulls in libsmartbotic-db-client (the runtime .so) as a dependency, plus all development headers needed to compile.
sudo apt install libsmartbotic-db-client
Only the shared library — no headers, no cmake config, no dev tooling.
If libsmartbotic-db-client-dev is installed (e.g., in your Docker build image):
# CMakeLists.txt
find_package(smartbotic-db-client REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE smartbotic::db-client)
The smartbotic::db-client target automatically sets up include paths and links the shared library.
git submodule add ssh://git@git.smartbotics.ai:10022/fszontagh/smartbotic-database.git \
external/smartbotic-database
# CMakeLists.txt
add_subdirectory(external/smartbotic-database)
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE smartbotic-db-client)
When built as a submodule, the library is static (.a) and linked directly into your binary.
Use the system package when available, fall back to submodule:
# Try system-installed first (Docker builds, CI)
find_package(smartbotic-db-client QUIET)
if(smartbotic-db-client_FOUND)
message(STATUS "Using system smartbotic-db-client")
set(DB_CLIENT_TARGET smartbotic::db-client)
else()
# Fall back to submodule (local development)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/smartbotic-database/CMakeLists.txt")
add_subdirectory(external/smartbotic-database)
set(DB_CLIENT_TARGET smartbotic-db-client)
else()
message(FATAL_ERROR
"smartbotic-database not found. Either:\n"
" apt install libsmartbotic-db-client-dev\n"
" git submodule update --init external/smartbotic-database")
endif()
endif()
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE ${DB_CLIENT_TARGET})
#include <smartbotic/database/client.hpp>
smartbotic::database::Client::Config config;
config.address = "localhost:9004"; // gRPC endpoint
config.timeoutMs = 5000; // per-RPC timeout
config.maxRetries = 3; // auto-retry on transient failures
smartbotic::database::Client db(config);
if (!db.connect()) {
// handle connection failure
}
// Insert
nlohmann::json doc = {{"name", "Alice"}, {"email", "alice@example.com"}};
std::string id = db.insert("users", doc);
// Or with explicit ID:
std::string id = db.insert("users", doc, "user-123");
// Get
auto result = db.get("users", "user-123");
if (result) {
std::cout << (*result)["name"] << std::endl;
}
// Update (full document replacement, with automatic optimistic locking)
db.update("users", "user-123", {{"name", "Alice Smith"}, {"email", "alice@example.com"}});
// Update with explicit optimistic locking (fails if version changed)
db.updateIfVersion("users", "user-123", {{"name", "Alice"}}, /*expectedVersion=*/2);
// Patch — atomic partial update (only specified fields are modified)
uint64_t newVer = db.patch("users", "user-123", {{"name", "Alice Smith"}});
// Upsert (insert or update)
auto [uid, isNew] = db.upsert("users", doc, "user-123");
// Delete
db.remove("users", "user-123");
// Check existence
bool found = db.exists("users", "user-123");
When multiple clients modify the same document concurrently, choose the right method:
patch() — use for most updates (recommended). Merges only the specified fields on the server, atomically. Two clients can safely modify different fields simultaneously:
// Client A: // Client B:
db.patch("users", "user-123", db.patch("users", "user-123",
{{"name", "Alice Smith"}}); {{"email", "alice@new.com"}});
// Result: both fields updated, nothing lost
update() — full document replacement with automatic retry. Uses optimistic locking internally (reads current version, writes with version check, retries on conflict). Use when you need to replace the entire document:
auto doc = db.get("users", "user-123");
(*doc)["name"] = "Alice Smith";
(*doc)["role"] = "admin";
db.update("users", "user-123", *doc); // replaces entire document
Note: update() is "last writer wins" for complete replacement — if two clients modify different fields via update(), the last one overwrites the other's changes. Use patch() to avoid this.
updateIfVersion() — explicit optimistic locking. Use when you need to detect conflicts and handle them yourself:
auto doc = db.get("users", "user-123");
uint64_t version = (*doc)["_version"];
(*doc)["balance"] = (*doc)["balance"].get<int>() + 100;
if (!db.updateIfVersion("users", "user-123", *doc, version)) {
// Version conflict — someone else modified the document
// Re-read and retry, or report error to user
}
// Find with filters
smartbotic::database::Client::QueryOptions opts;
opts.filters = {{"status", "active"}, {"role", "admin"}};
opts.sortField = "name";
opts.sortDescending = false;
opts.limit = 50;
opts.offset = 0;
auto docs = db.find("users", opts);
for (const auto& doc : docs) {
std::cout << doc["name"] << std::endl;
}
// Find all documents in a collection
auto allDocs = db.find("users");
// Count
uint64_t total = db.count("users");
uint64_t admins = db.count("users", {{"role", "admin"}});
// Create a basic collection
db.createCollection("users");
// Create with options
db.createCollection("sessions",
/*defaultTtlSeconds=*/3600, // auto-expire after 1 hour
/*encrypted=*/false,
/*maxVersions=*/5, // keep last 5 versions
/*vectorDimension=*/0); // no vector support
// Create vector-enabled collection
db.createCollection("memories",
/*defaultTtlSeconds=*/0,
/*encrypted=*/false,
/*maxVersions=*/0,
/*vectorDimension=*/384); // 384-dim embeddings
// List and inspect
auto collections = db.listCollections();
auto info = db.getCollectionInfo("users");
if (info) {
std::cout << "Documents: " << info->documentCount << std::endl;
}
// Drop
db.dropCollection("old_data");
Requires a collection created with vectorDimension > 0.
// Insert documents with embeddings
nlohmann::json doc = {
{"id", "mem-1"},
{"content", "The user prefers dark mode"},
{"_vector", embedding} // std::vector<float>, size must match vectorDimension
};
db.insert("memories", doc, "mem-1");
// Search
std::vector<float> queryVec = getEmbedding("user preferences");
auto results = db.similaritySearch("memories", queryVec, /*topK=*/5, /*minScore=*/0.7f);
for (const auto& r : results) {
std::cout << r.id << " (score=" << r.score << "): "
<< r.data["content"] << std::endl;
}
Notes:
_vector is extracted and stored separately — not returned in get() or find() responses// Get history
auto history = db.getVersionHistory("users", "user-123", /*limit=*/10);
for (const auto& v : history.versions) {
std::cout << "v" << v.version << " by " << v.updatedBy
<< " at " << v.timestamp << std::endl;
}
// Get a specific version
auto oldVersion = db.getDocumentVersion("users", "user-123", /*version=*/2);
// Restore to a previous version
uint64_t newVer = db.restoreVersion("users", "user-123", /*version=*/2);
// Restore to a point in time
auto [fromVer, newVersion] = db.restoreToDate("users", "user-123", timestamp);
Two-level storage with content-addressed blob deduplication. Identical files are stored once.
// Upload
std::vector<uint8_t> data = readFile("photo.jpg");
smartbotic::database::Client::FileUploadMeta meta;
meta.name = "photo.jpg";
meta.mime_type = "image/jpeg";
meta.file_type = "document"; // "plugin", "document", "generated"
meta.related_id = "conversation-1";
meta.is_public = false;
auto result = db.uploadFile(data, meta);
std::cout << "File ID: " << result.id
<< " Deduplicated: " << result.deduplicated << std::endl;
// Download
auto fileData = db.downloadFile(result.id);
// Get file info
auto fileInfo = db.getFileInfo(result.id);
// List files
auto files = db.listFiles(
/*file_type=*/"document",
/*related_id=*/"conversation-1",
/*limit=*/100, /*offset=*/0,
/*checksum=*/"",
/*name=*/"photo.jpg");
// Delete
db.deleteFile(result.id);
auto subscription = db.subscribe({"users", "sessions"}, [](
const std::string& collection,
const std::string& id,
const std::string& eventType,
const std::optional<nlohmann::json>& data) {
std::cout << eventType << " on " << collection << "/" << id << std::endl;
});
// Unsubscribe by releasing the handle
subscription.reset();
Redis-compatible set operations on top of documents.
db.setAdd("tags", "user-123", "admin");
db.setAdd("tags", "user-123", "active");
auto members = db.setMembers("tags", "user-123");
bool isAdmin = db.setIsMember("tags", "user-123", "admin");
db.setRemove("tags", "user-123", "active");
// Quick health check
if (db.healthCheck()) {
std::cout << "Database is healthy" << std::endl;
}
// Detailed health
auto health = db.getHealthInfo();
if (health) {
std::cout << "Uptime: " << health->uptimeMs << "ms"
<< " Docs: " << health->documentCount << std::endl;
}
// Full statistics
auto stats = db.getStats();
if (stats) {
std::cout << "Memory: " << stats->memoryUsedBytes << " bytes"
<< " Insert avg: " << stats->insertAvgMicros() << " us" << std::endl;
}
The server configuration file is at /etc/smartbotic-database/config.json.
{
"log_level": "info",
"storage": {
"bind_address": "localhost",
"rpc_port": 9004,
"node_id": "smartbotic-db",
"data_directory": "/var/lib/smartbotic-database",
"memory": {
"max_memory_mb": 512,
"eviction_threshold_percent": 80,
"eviction_target_percent": 60,
"eviction_check_interval_ms": 5000
},
"persistence": {
"wal_sync_interval_ms": 100,
"snapshot_interval_sec": 3600,
"compression": "lz4"
},
"encryption": {
"enabled": true,
"key_file": "/var/lib/smartbotic-database/storage.key",
"auto_generate_key": true
},
"migrations": {
"enabled": false,
"directory": "/etc/smartbotic-database/migrations",
"auto_apply": false
},
"files": {
"max_file_size_mb": 500,
"cleanup_orphans_interval_sec": 3600
},
"replication": {
"enabled": false
}
}
}
| Setting | Default | Description |
|---|---|---|
bind_address |
localhost |
Change to 0.0.0.0 to accept remote connections |
rpc_port |
9004 |
gRPC listen port |
data_directory |
/var/lib/smartbotic-database |
WAL, snapshots, files stored here |
memory.max_memory_mb |
512 |
Maximum memory for document cache. Evicts LRU when exceeded |
persistence.snapshot_interval_sec |
3600 |
Full snapshot every N seconds (WAL truncated after) |
encryption.enabled |
true |
Field-level AES-256-GCM encryption. Key auto-generated on first start |
migrations.enabled |
false |
Enable to auto-apply JSON migrations on startup |
migrations.directory |
/etc/smartbotic-database/migrations |
Path to migration JSON files |
Consumer projects typically enable migrations by configuring the database via their own postinst script:
# Example: in your project's postinst
DB_CONFIG="/etc/smartbotic-database/config.json"
python3 -c "
import json
with open('$DB_CONFIG') as f:
cfg = json.load(f)
s = cfg['storage']
s['migrations']['enabled'] = True
s['migrations']['directory'] = '/opt/myproject/migrations'
s['migrations']['auto_apply'] = True
with open('$DB_CONFIG', 'w') as f:
json.dump(cfg, f, indent=2)
"
systemctl restart smartbotic-database
Declarative JSON-based schema migrations. Create numbered JSON files in the migrations directory.
{
"version": "001",
"name": "create_users",
"description": "Create users collection with encryption",
"operations": [
{
"type": "create_collection",
"collection": "users",
"options": {
"encrypted": true,
"max_versions": 10
}
}
]
}
| Operation | Fields | Description |
|---|---|---|
create_collection |
collection, options |
Create a new collection. Options: encrypted, max_versions, default_ttl_seconds, vector_dimension |
put |
collection, document |
Insert/upsert a document |
delete |
collection, id |
Delete a document |
delete_where_id_prefix |
collection, prefix |
Bulk-delete documents whose ID starts with prefix |
{
"version": "002",
"name": "create_memories",
"description": "Create vector-enabled memories collection",
"operations": [
{
"type": "create_collection",
"collection": "memories",
"options": {
"vector_dimension": 384
}
}
]
}
Note: vector_dimension is immutable after collection creation.
When building your project in Docker for production, install the dev package in your base image instead of compiling the submodule from source.
FROM debian:trixie
# Add Smartbotics APT repository
COPY smartbotics-repo.gpg /usr/share/keyrings/
RUN echo "deb [signed-by=/usr/share/keyrings/smartbotics-repo.gpg] \
https://repository.smartbotics.ai trixie main" \
> /etc/apt/sources.list.d/smartbotics.list
# Configure authentication
ARG REPO_USER=callerai
ARG REPO_PASS
RUN printf "machine repository.smartbotics.ai\nlogin %s\npassword %s\n" \
"$REPO_USER" "$REPO_PASS" > /etc/apt/auth.conf.d/smartbotics.conf && \
chmod 600 /etc/apt/auth.conf.d/smartbotics.conf
# Install client dev package (pulls shared lib as dependency)
RUN apt-get update && apt-get install -y \
libsmartbotic-db-client-dev \
&& rm -rf /var/lib/apt/lists/*
Then your project's CMake uses find_package(smartbotic-db-client REQUIRED) and links against the pre-built shared library.
Your project's production .deb should declare:
Depends: smartbotic-database (>= 1.2.0), libsmartbotic-db-client (>= 1.2.0), ...
This ensures both the server and runtime library are installed on the target machine.
Install the CLI tool:
sudo apt install smartbotic-db-cli
The CLI connects to the database via gRPC. It does not require the server installed on the same machine — it can administer a remote instance.
# Connect to default (localhost:9004)
smartbotic-db-cli health
# Connect to a remote instance
smartbotic-db-cli --address 192.168.1.50:9004 health
If your machine previously had shadowman-database or callerai-storage installed, the new smartbotic-database package handles the transition automatically:
sudo apt install smartbotic-database
What happens:
dpkg sees Conflicts: shadowman-database / callerai-storageprerm stops the old service)smartbotic-database is installedpostinst script detects old data/config and migrates:
/var/lib/shadowman/data/database/ → /var/lib/smartbotic-database//etc/shadowman/database.json → /etc/smartbotic-database/config.json (paths updated)The old data directory is preserved (copied, not moved) so rollback is safe.
If something goes wrong:
sudo apt remove smartbotic-database
sudo apt install shadowman-database=1.2.0-14 # or callerai-storage=0.5.18-1
Old packages remain in the repository.
The raw .proto file is available at:
/usr/share/smartbotic-database/proto/database.protoproto/database.protoThis can be used to generate client stubs in other languages (Python, Go, etc.) if needed.