A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #112 from tilesprivacy/fix/streamline-db-conn

streamline db conn references

authored by

Anandu Pavanan and committed by
GitHub
f3554127 fdc886c4

+75 -42
+12
CHANGELOG.md
··· 5 5 6 6 ## [Unreleased] 7 7 8 + ## [0.4.6] - 2026-03-30 9 + 10 + ### Added 11 + - Added P2P chat sync in [#109](https://github.com/tilesprivacy/tiles/pull/109) 12 + - Commands for chat syncing 13 + - `tiles sync` - Starts listening for a sync request from the linked peers. 14 + - `tiles sync <DID>` - Initiates the syncing with the peer using the peer's linked DID (which one can get from `tiles link list-peers`). 15 + - Added at rest encryption for local databases in [#110](https://github.com/tilesprivacy/tiles/pull/110) 16 + 17 + ### Fixed 18 + - Fixed the loading issue of qwen 3.5 series in [#111](https://github.com/tilesprivacy/tiles/pull/111) 19 + 8 20 ## [0.4.5] - 2026-03-23 9 21 10 22 ### Added
+7 -12
tiles/src/commands/mod.rs
··· 4 4 5 5 use anyhow::{Result, anyhow}; 6 6 use owo_colors::OwoColorize; 7 - use tiles::core; 8 7 use tiles::core::accounts::{ 9 8 RootUser, create_root_account, get_peer_list, get_root_user_details, save_root_account, 10 9 set_nickname, unlink, 11 10 }; 12 - use tiles::core::storage::db::get_db_conn; 11 + use tiles::core::storage::db::Dbconn; 13 12 use tiles::runtime::Runtime; 14 13 use tiles::utils::config::{ 15 14 ConfigProvider, DefaultProvider, get_or_create_config, set_user_data_path, ··· 254 253 Ok(()) 255 254 } 256 255 257 - pub async fn run(runtime: &Runtime, run_args: RunArgs) -> Result<()> { 258 - runtime.run(run_args).await 256 + pub async fn run(runtime: &Runtime, run_args: RunArgs, db_conn: &Dbconn) -> Result<()> { 257 + runtime.run(run_args, db_conn).await 259 258 } 260 259 261 260 pub fn set_data(path: &str) { ··· 338 337 ) 339 338 } 340 339 341 - pub fn show_peers() -> Result<()> { 342 - let db_conn = get_db_conn(&core::storage::db::DBTYPE::COMMON)?; 343 - 344 - let peers = get_peer_list(&db_conn)?; 340 + pub fn show_peers(db_conn: &Dbconn) -> Result<()> { 341 + let peers = get_peer_list(&db_conn.common)?; 345 342 346 343 println!("DID\tNickname\n"); 347 344 for peer in peers { ··· 350 347 Ok(()) 351 348 } 352 349 353 - pub fn unlink_peer(user_id: &str) -> Result<()> { 354 - let db_conn = get_db_conn(&core::storage::db::DBTYPE::COMMON)?; 355 - 356 - if let Err(err) = unlink(&db_conn, user_id) { 350 + pub fn unlink_peer(db_conn: &Dbconn, user_id: &str) -> Result<()> { 351 + if let Err(err) = unlink(&db_conn.common, user_id) { 357 352 println!("{:?}", err) 358 353 } else { 359 354 println!("Succesfully disabled the peer")
+6 -5
tiles/src/core/accounts.rs
··· 12 12 use uuid::Uuid; 13 13 14 14 use crate::{ 15 - core::storage::db::{DBTYPE, get_db_conn}, 15 + core::storage::db::Dbconn, 16 16 utils::config::{get_app_name, get_or_create_config, save_config}, 17 17 }; 18 18 const ROOT_USER_CONFIG_KEY: &str = "root-user"; ··· 249 249 .map_err(<rusqlite::Error as Into<anyhow::Error>>::into) 250 250 } 251 251 252 - pub fn save_root_account_db() -> Result<()> { 253 - let conn = get_db_conn(&DBTYPE::COMMON)?; 252 + pub fn save_root_account_db(db_conn: &Dbconn) -> Result<()> { 254 253 let config = get_or_create_config()?; 255 254 let root_user = get_root_user_details(&config)?; 256 255 let user = User { ··· 270 269 .as_secs(), 271 270 }; 272 271 273 - let mut fetch_root_user = conn.prepare("select id from users where root = true")?; 272 + let mut fetch_root_user = db_conn 273 + .common 274 + .prepare("select id from users where root = true")?; 274 275 275 276 match fetch_root_user.query_one([], |_row| Ok(())) { 276 277 Err(rusqlite::Error::QueryReturnedNoRows) => { 277 - conn.execute("insert into users (id, user_id, username, active_profile, account_type, root) values 278 + db_conn.common.execute("insert into users (id, user_id, username, active_profile, account_type, root) values 278 279 (?1, ?2, ?3,?4, ?5, ?6)", (&user.id.to_string(), &user.user_id, &user.username, &user.active_profile, 279 280 user.account_type.to_string(), &user.root))?; 280 281 Ok(())
+12 -1
tiles/src/core/chats.rs
··· 180 180 // TODO: Handle primary key conflict, for now reject it (in a way its impossible to have this scenario, and if its occuring then that means 181 181 // some issue in syncing, so ignore it, by rejecting it), later 182 182 // do LWW based on issuer of UCAN 183 + // 184 + 183 185 let txn = chat_conn.transaction()?; 184 186 { 185 187 let mut stmt = txn.prepare("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at, row_counter) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)")?; ··· 204 206 &chat.id 205 207 ); 206 208 } 207 - Err(err) => log::error!("err in writing row due to {:?}", err), 209 + // NOTE: If any other error occurs and write failed we abort the sync, so the the row_counter doesn't get skipped. 210 + // use RUST_LOG=error tiles to debug the issue 211 + Err(err) => { 212 + log::error!( 213 + "err in writing row due to {:?}, Aborting the sync ....", 214 + err 215 + ); 216 + break; 217 + } 218 + 208 219 Ok(_) => (), 209 220 } 210 221 }
+3 -4
tiles/src/core/mod.rs
··· 5 5 6 6 use anyhow::Result; 7 7 8 - use crate::core::{accounts::save_root_account_db, storage::db::init_db}; 8 + use crate::core::{accounts::save_root_account_db, storage::db::Dbconn}; 9 9 10 10 pub mod accounts; 11 11 pub mod chats; ··· 14 14 pub mod storage; 15 15 16 16 // Entrypoint of the core 17 - pub fn init() -> Result<()> { 18 - init_db()?; 19 - save_root_account_db() 17 + pub fn init(db_conn: &Dbconn) -> Result<()> { 18 + save_root_account_db(db_conn) 20 19 }
+12 -2
tiles/src/core/storage/db.rs
··· 19 19 CHAT, 20 20 } 21 21 22 + pub struct Dbconn { 23 + pub chat: Connection, 24 + pub common: Connection, 25 + } 26 + 22 27 // DEFINE MIGRATIONS 23 28 24 29 // TODO: add the schema doc ··· 71 76 72 77 const CHATS_MIGRATIONS: Migrations = Migrations::from_slice(CHATS_MIGRATION_ARRAY); 73 78 74 - pub fn init_db() -> Result<()> { 79 + pub fn init_db() -> Result<Dbconn> { 75 80 let mut chat_conn = get_db_conn(&DBTYPE::CHAT)?; 76 81 let mut common_conn = get_db_conn(&DBTYPE::COMMON)?; 77 82 78 - apply_migrations(&mut common_conn, &mut chat_conn) 83 + apply_migrations(&mut common_conn, &mut chat_conn)?; 84 + 85 + Ok(Dbconn { 86 + chat: chat_conn, 87 + common: common_conn, 88 + }) 79 89 } 80 90 81 91 pub fn get_db_conn(db_type: &DBTYPE) -> Result<Connection> {
+11 -6
tiles/src/main.rs
··· 7 7 core::{ 8 8 self, 9 9 network::{link, sync}, 10 + storage::db::init_db, 10 11 }, 11 12 daemon::{start_cmd, start_server, stop_cmd}, 12 13 runtime::{RunArgs, build_runtime}, ··· 188 189 build_logger(); 189 190 let cli = Cli::parse(); 190 191 let runtime = build_runtime(); 192 + let db_conn = init_db()?; 193 + 191 194 match cli.command { 192 195 None => { 193 196 // Running tiles without subcommand - launch default model with flags ··· 207 210 let _ = start_cmd(None).await; 208 211 }); 209 212 } 210 - core::init().inspect_err(|e| eprintln!("Tiles core init failed due to {:?}", e))?; 213 + core::init(&db_conn) 214 + .inspect_err(|e| eprintln!("Tiles core init failed due to {:?}", e))?; 211 215 if !cli.flags.no_repl { 212 - commands::run(&runtime, run_args) 216 + commands::run(&runtime, run_args, &db_conn) 213 217 .await 214 218 .inspect_err(|e| eprintln!("Tiles failed to run due to {:?}", e))?; 215 219 } ··· 223 227 relay_count: flags.relay_count, 224 228 memory: flags.memory, 225 229 }; 226 - core::init().inspect_err(|e| eprintln!("Tiles core init failed due to {:?}", e))?; 227 - commands::run(&runtime, run_args) 230 + core::init(&db_conn) 231 + .inspect_err(|e| eprintln!("Tiles core init failed due to {:?}", e))?; 232 + commands::run(&runtime, run_args, &db_conn) 228 233 .await 229 234 .inspect_err(|e| eprintln!("Tiles failed to run due to {:?}", e))?; 230 235 } ··· 270 275 }, 271 276 Some(Commands::Link(link_args)) => match link_args.command { 272 277 LinkCommands::Enable { ticket } => link(ticket).await?, 273 - LinkCommands::Disable { did } => unlink_peer(&did)?, 278 + LinkCommands::Disable { did } => unlink_peer(&db_conn, &did)?, 274 279 LinkCommands::ListPeers => { 275 - show_peers()?; 280 + show_peers(&db_conn)?; 276 281 } 277 282 }, 278 283 Some(Commands::Sync { did }) => sync(did).await?,
+9 -9
tiles/src/runtime/mlx.rs
··· 1 1 use crate::core::accounts::{User, get_current_user}; 2 2 use crate::core::chats::{Message, save_chat}; 3 - use crate::core::storage::db::get_db_conn; 3 + use crate::core::storage::db::Dbconn; 4 4 use crate::runtime::RunArgs; 5 5 use crate::utils::config::{ConfigProvider, DefaultProvider, get_memory_path, get_model_cache}; 6 6 use crate::utils::hf_model_downloader::*; ··· 70 70 MLXRuntime {} 71 71 } 72 72 73 - pub async fn run(&self, run_args: super::RunArgs) -> Result<()> { 73 + pub async fn run(&self, run_args: super::RunArgs, db_conn: &Dbconn) -> Result<()> { 74 74 let default_modelfile_path = get_default_modelfile(run_args.memory)?; 75 75 let default_modelfile = 76 76 tilekit::modelfile::parse_from_file(default_modelfile_path.to_str().unwrap()).unwrap(); ··· 89 89 } 90 90 }; 91 91 92 - run_model_with_server(self, modelfile, default_modelfile, &run_args).await 92 + run_model_with_server(self, modelfile, default_modelfile, &run_args, db_conn).await 93 93 } 94 94 95 95 #[allow(clippy::zombie_processes)] ··· 228 228 modelfile: Modelfile, 229 229 default_modelfile: Modelfile, 230 230 run_args: &RunArgs, 231 + db_conn: &Dbconn, 231 232 ) -> Result<()> { 232 233 if !cfg!(debug_assertions) { 233 234 let _ = mlx_runtime.start_server_daemon().await.inspect_err(|e| { ··· 238 239 // loading the model from mem-agent via daemon server 239 240 let memory_path = get_memory_path().context("Setting/Retrieving memory_path failed")?; 240 241 match load_model(&modelfile, &default_modelfile, &memory_path).await { 241 - Ok(_) => start_repl(mlx_runtime, &modelfile, run_args).await?, 242 + Ok(_) => start_repl(mlx_runtime, &modelfile, run_args, db_conn).await?, 242 243 Err(err) => return Err(anyhow::anyhow!(err)), 243 244 } 244 245 Ok(()) ··· 248 249 mlx_runtime: &MLXRuntime, 249 250 modelfile: &Modelfile, 250 251 run_args: &RunArgs, 252 + db_conn: &Dbconn, 251 253 ) -> Result<()> { 252 254 let modelname = modelfile 253 255 .from ··· 255 257 .ok_or_else(|| anyhow!("Error getting FROM from modelfile due to"))?; 256 258 257 259 println!("Running {} in interactive mode", modelname); 258 - let common_db_conn = get_db_conn(&crate::core::storage::db::DBTYPE::COMMON)?; 259 - let chat_db_conn = get_db_conn(&crate::core::storage::db::DBTYPE::CHAT)?; 260 - let current_user = get_current_user(&common_db_conn)?; 260 + let current_user = get_current_user(&db_conn.common)?; 261 261 262 262 let config = Config::builder().auto_add_history(true).build(); 263 263 let mut editor = Editor::<TilesHinter, DefaultHistory>::with_config(config).unwrap(); ··· 315 315 &g_reply, 316 316 run_args, 317 317 &prev_response_id, 318 - &chat_db_conn, 318 + &db_conn.chat, 319 319 &current_user, 320 320 &conversations, 321 321 ) ··· 349 349 content: g_reply.clone(), 350 350 }); 351 351 352 - save_chat(&chat_db_conn, &current_user, &g_reply, Some(&response))?; 352 + save_chat(&db_conn.chat, &current_user, &g_reply, Some(&response))?; 353 353 // Display benchmark metrics if available 354 354 if let Some(metrics) = response.metrics { 355 355 bench_metrics.update(metrics);
+3 -3
tiles/src/runtime/mod.rs
··· 1 1 #[allow(unused_imports)] 2 2 use crate::runtime::cpu::CPURuntime; 3 - use crate::runtime::mlx::MLXRuntime; 3 + use crate::{core::storage::db::Dbconn, runtime::mlx::MLXRuntime}; 4 4 use anyhow::Result; 5 5 pub mod cpu; 6 6 pub mod mlx; ··· 17 17 } 18 18 19 19 impl Runtime { 20 - pub async fn run(&self, run_args: RunArgs) -> Result<()> { 20 + pub async fn run(&self, run_args: RunArgs, db_conn: &Dbconn) -> Result<()> { 21 21 match self { 22 - Runtime::Mlx(runtime) => runtime.run(run_args).await, 22 + Runtime::Mlx(runtime) => runtime.run(run_args, db_conn).await, 23 23 Runtime::Cpu(runtime) => runtime.run(run_args).await, 24 24 } 25 25 }