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.

feat: Default memory_path setup from cli

While loading the model we create the necessary files
which will track the memory_path and then the memory_path
to the py server in /start api, instead of py server
handling the creation of memory_path. This allows us to
configure the memory_path later via cli.

madclaws 01ea71e9 bf6bfeb3

+57 -15
+8 -7
server/api.py
··· 21 21 # SOFTWARE. 22 22 23 23 from fastapi import FastAPI, HTTPException 24 - from .config import SYSTEM_PROMPT,MEMORY_PATH 24 + from .config import SYSTEM_PROMPT 25 25 26 26 import json 27 27 import time ··· 33 33 from pydantic import BaseModel, Field 34 34 35 35 from .cache_utils import ( 36 - detect_framework, 37 36 get_model_path 38 37 ) 39 38 from .mlx_runner import MLXRunner ··· 46 45 _default_max_tokens: Optional[int] = None # Use dynamic model-aware limits by default 47 46 _runner: MLXRunner = {} 48 47 _max_tool_turns = 5 48 + _memory_path = "" 49 49 50 50 class CompletionRequest(BaseModel): 51 51 model: str ··· 102 102 103 103 class StartRequest(BaseModel): 104 104 model: str 105 + memory_path: str 105 106 106 107 class Agent: 107 108 def __init__( ··· 194 195 @app.post("/start") 195 196 async def start_model(request: StartRequest): 196 197 """Load the model and start the agent""" 197 - global _messages, _runner 198 + global _messages, _runner,_memory_path 198 199 print(str(request)) 199 200 _messages = [ChatMessage(role="system", content=SYSTEM_PROMPT)] 200 - 201 + _memory_path = request.memory_path 201 202 try: 202 203 _runner = get_or_load_model(request.model) 203 204 return {"message": "Model loaded"} ··· 207 208 @app.post("/v1/chat/completions") 208 209 async def create_chat_completion(request: ChatCompletionRequest): 209 210 """Create a chat completion.""" 210 - global _messages, _max_tool_turns 211 + global _messages, _max_tool_turns, _memory_path 211 212 try: 212 213 runner = get_or_load_model(request.model) 213 214 ··· 253 254 create_memory_if_not_exists() 254 255 result = execute_sandboxed_code( 255 256 code=python_code, 256 - allowed_path=MEMORY_PATH, 257 + allowed_path=_memory_path, 257 258 import_module="server.mem_agent.tools", 258 259 ) 259 260 ··· 280 281 create_memory_if_not_exists() 281 282 result = execute_sandboxed_code( 282 283 code=python_code, 283 - allowed_path=MEMORY_PATH, 284 + allowed_path=_memory_path, 284 285 import_module="server.mem_agent.tools", 285 286 ) 286 287 else:
+49 -8
src/runner/mlx.rs
··· 1 + use anyhow::{Context, Result}; 1 2 use reqwest::Client; 2 3 use serde_json::{Value, json}; 3 - use std::ffi::NulError; 4 4 use std::io::Write; 5 + use std::path::PathBuf; 6 + use std::{env, fs}; 5 7 use std::{io, process::Command}; 6 8 7 9 use crate::core::modelfile::Modelfile; ··· 71 73 } 72 74 73 75 async fn run_model_with_server(modelfile: Modelfile) -> reqwest::Result<()> { 74 - // println!("gonna ping"); 75 - // let _ = ping().await; 76 76 let stdin = io::stdin(); 77 77 let mut stdout = io::stdout(); 78 - // loading the model from mem-agent via daeomn server 78 + // loading the model from mem-agent via daemon server 79 + let memory_path = get_memory_path() 80 + .context("Retrieving memory_path failed") 81 + .unwrap(); 79 82 let modelname = modelfile.from.as_ref().unwrap(); 80 - load_model(&modelname).await.unwrap(); 83 + load_model(&modelname, &memory_path).await.unwrap(); 81 84 println!("Running in interactive mode"); 82 85 loop { 83 86 print!(">> "); ··· 109 112 Ok(()) 110 113 } 111 114 112 - async fn load_model(model_name: &str) -> Result<(), String> { 115 + async fn load_model(model_name: &str, memory_path: &str) -> Result<(), String> { 113 116 let client = Client::new(); 114 117 let body = json!({ 115 - "model": model_name 118 + "model": model_name, 119 + "memory_path": memory_path 116 120 }); 117 121 let res = client 118 122 .post("http://127.0.0.1:6969/start") ··· 120 124 .send() 121 125 .await 122 126 .unwrap(); 123 - // println!("{:?}", res); 124 127 if res.status() == 200 { 125 128 Ok(()) 126 129 } else { ··· 152 155 Err(String::from("request failed")) 153 156 } 154 157 } 158 + 159 + fn get_memory_path() -> Result<String> { 160 + let home_dir = env::home_dir().context("Failed to fetch $HOME")?; 161 + let config_dir = match env::var("XDG_CONFIG_HOME") { 162 + Ok(val) => PathBuf::from(val), 163 + Err(_err) => home_dir.join(".config"), 164 + }; 165 + 166 + let data_dir = match env::var("XDG_DATA_HOME") { 167 + Ok(val) => PathBuf::from(val), 168 + Err(_err) => home_dir.join(".local/share"), 169 + }; 170 + 171 + let tiles_config_dir = config_dir.join("tiles"); 172 + let tiles_data_dir = data_dir.join("tiles"); 173 + let mut is_memory_path_found: bool = false; 174 + let mut memory_path: String = String::from(""); 175 + if tiles_config_dir.is_dir() { 176 + if let Ok(content) = fs::read_to_string(tiles_config_dir.join(".memory_path")) { 177 + memory_path = content; 178 + is_memory_path_found = true; 179 + } 180 + } 181 + 182 + if is_memory_path_found { 183 + Ok(memory_path) 184 + } else { 185 + let memory_path = tiles_data_dir.join("memory"); 186 + fs::create_dir_all(&memory_path).context("Failed to create tiles memory directory")?; 187 + fs::create_dir_all(&tiles_config_dir).context("Failed to create tiles config directory")?; 188 + fs::write( 189 + tiles_config_dir.join(".memory_path"), 190 + memory_path.to_str().unwrap(), 191 + ) 192 + .context("Failed to write the default path to .memory_path")?; 193 + Ok(memory_path.to_string_lossy().to_string()) 194 + } 195 + }