claude-memory-agent 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +11 -1
- package/bin/lib/banner.js +39 -0
- package/bin/lib/environment.js +166 -0
- package/bin/lib/installer.js +291 -0
- package/bin/lib/models.js +95 -0
- package/bin/lib/steps/advanced.js +101 -0
- package/bin/lib/steps/confirm.js +87 -0
- package/bin/lib/steps/model.js +57 -0
- package/bin/lib/steps/provider.js +65 -0
- package/bin/lib/steps/scope.js +59 -0
- package/bin/lib/steps/server.js +74 -0
- package/bin/lib/ui.js +75 -0
- package/bin/onboarding.js +164 -0
- package/bin/postinstall.js +22 -257
- package/config.py +103 -4
- package/dashboard.html +697 -27
- package/hooks/extract_memories.py +439 -0
- package/hooks/pre_compact_hook.py +76 -0
- package/hooks/session_end_hook.py +149 -0
- package/hooks/stop_hook.py +372 -0
- package/install.py +85 -32
- package/main.py +1636 -892
- package/mcp_server.py +451 -0
- package/package.json +14 -3
- package/requirements.txt +12 -8
- package/services/adaptive_ranker.py +272 -0
- package/services/agent_catalog.json +153 -0
- package/services/agent_registry.py +245 -730
- package/services/claude_md_sync.py +320 -4
- package/services/consolidation.py +417 -0
- package/services/database.py +586 -105
- package/services/embedding_pipeline.py +262 -0
- package/services/embeddings.py +493 -85
- package/services/memory_decay.py +408 -0
- package/services/native_memory_paths.py +86 -0
- package/services/native_memory_sync.py +496 -0
- package/services/response_manager.py +183 -0
- package/services/terminal_ui.py +199 -0
- package/services/tier_manager.py +235 -0
- package/services/websocket.py +26 -6
- package/skills/search.py +136 -61
- package/skills/session_review.py +210 -23
- package/skills/store.py +125 -18
- package/terminal_dashboard.py +474 -0
- package/hooks/__pycache__/auto-detect-response.cpython-312.pyc +0 -0
- package/hooks/__pycache__/auto_capture.cpython-312.pyc +0 -0
- package/hooks/__pycache__/grounding-hook.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_end.cpython-312.pyc +0 -0
- package/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/services/__pycache__/agent_registry.cpython-312.pyc +0 -0
- package/services/__pycache__/auth.cpython-312.pyc +0 -0
- package/services/__pycache__/auto_inject.cpython-312.pyc +0 -0
- package/services/__pycache__/claude_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/services/__pycache__/compaction_flush.cpython-312.pyc +0 -0
- package/services/__pycache__/confidence.cpython-312.pyc +0 -0
- package/services/__pycache__/curator.cpython-312.pyc +0 -0
- package/services/__pycache__/daily_log.cpython-312.pyc +0 -0
- package/services/__pycache__/database.cpython-312.pyc +0 -0
- package/services/__pycache__/embeddings.cpython-312.pyc +0 -0
- package/services/__pycache__/insights.cpython-312.pyc +0 -0
- package/services/__pycache__/llm_analyzer.cpython-312.pyc +0 -0
- package/services/__pycache__/memory_md_sync.cpython-312.pyc +0 -0
- package/services/__pycache__/retry_queue.cpython-312.pyc +0 -0
- package/services/__pycache__/timeline.cpython-312.pyc +0 -0
- package/services/__pycache__/vector_index.cpython-312.pyc +0 -0
- package/services/__pycache__/websocket.cpython-312.pyc +0 -0
- package/skills/__pycache__/__init__.cpython-312.pyc +0 -0
- package/skills/__pycache__/admin.cpython-312.pyc +0 -0
- package/skills/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/skills/__pycache__/claude_md.cpython-312.pyc +0 -0
- package/skills/__pycache__/cleanup.cpython-312.pyc +0 -0
- package/skills/__pycache__/confidence_tracker.cpython-312.pyc +0 -0
- package/skills/__pycache__/context.cpython-312.pyc +0 -0
- package/skills/__pycache__/curator.cpython-312.pyc +0 -0
- package/skills/__pycache__/grounding.cpython-312.pyc +0 -0
- package/skills/__pycache__/insights.cpython-312.pyc +0 -0
- package/skills/__pycache__/natural_language.cpython-312.pyc +0 -0
- package/skills/__pycache__/retrieve.cpython-312.pyc +0 -0
- package/skills/__pycache__/search.cpython-312.pyc +0 -0
- package/skills/__pycache__/session_review.cpython-312.pyc +0 -0
- package/skills/__pycache__/state.cpython-312.pyc +0 -0
- package/skills/__pycache__/store.cpython-312.pyc +0 -0
- package/skills/__pycache__/summarize.cpython-312.pyc +0 -0
- package/skills/__pycache__/timeline.cpython-312.pyc +0 -0
- package/skills/__pycache__/verification.cpython-312.pyc +0 -0
- package/test_automation.py +0 -221
- package/test_complete.py +0 -338
- package/test_full.py +0 -322
- package/verify_db.py +0 -134
package/bin/postinstall.js
CHANGED
|
@@ -1,270 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Post-installation script for Claude Memory Agent
|
|
5
|
-
*
|
|
6
|
-
* This runs automatically after npm install to:
|
|
7
|
-
* 1. Check Python availability
|
|
8
|
-
* 2. Install Python dependencies
|
|
9
|
-
* 3. Run the configuration wizard (if interactive)
|
|
6
|
+
* Dispatches to the onboarding wizard (interactive) or installs with defaults (CI/piped).
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
const { execSync, spawn } = require('child_process');
|
|
13
9
|
const path = require('path');
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const readline = require('readline');
|
|
16
10
|
|
|
17
|
-
const AGENT_DIR = path.dirname(__dirname);
|
|
18
|
-
|
|
19
|
-
// Colors for terminal output
|
|
20
|
-
const colors = {
|
|
21
|
-
reset: '\x1b[0m',
|
|
22
|
-
green: '\x1b[32m',
|
|
23
|
-
yellow: '\x1b[33m',
|
|
24
|
-
red: '\x1b[31m',
|
|
25
|
-
cyan: '\x1b[36m',
|
|
26
|
-
bold: '\x1b[1m'
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function log(msg) {
|
|
30
|
-
console.log(msg);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function success(msg) {
|
|
34
|
-
console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function warn(msg) {
|
|
38
|
-
console.log(`${colors.yellow}!${colors.reset} ${msg}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function error(msg) {
|
|
42
|
-
console.log(`${colors.red}✗${colors.reset} ${msg}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function header(msg) {
|
|
46
|
-
console.log(`\n${colors.bold}${colors.cyan}${msg}${colors.reset}\n`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check Python availability
|
|
50
|
-
function getPython() {
|
|
51
|
-
const pythonCommands = ['python3', 'python', 'py'];
|
|
52
|
-
|
|
53
|
-
for (const cmd of pythonCommands) {
|
|
54
|
-
try {
|
|
55
|
-
const result = execSync(`${cmd} --version`, {
|
|
56
|
-
encoding: 'utf8',
|
|
57
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
58
|
-
});
|
|
59
|
-
const match = result.match(/Python (\d+)\.(\d+)/);
|
|
60
|
-
if (match) {
|
|
61
|
-
const major = parseInt(match[1]);
|
|
62
|
-
const minor = parseInt(match[2]);
|
|
63
|
-
if (major >= 3 && minor >= 9) {
|
|
64
|
-
return { cmd, version: result.trim() };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} catch (e) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check if Ollama is running
|
|
75
|
-
function checkOllama() {
|
|
76
|
-
try {
|
|
77
|
-
const http = require('http');
|
|
78
|
-
return new Promise((resolve) => {
|
|
79
|
-
const req = http.get('http://localhost:11434/api/tags', { timeout: 2000 }, (res) => {
|
|
80
|
-
resolve(res.statusCode === 200);
|
|
81
|
-
});
|
|
82
|
-
req.on('error', () => resolve(false));
|
|
83
|
-
req.on('timeout', () => {
|
|
84
|
-
req.destroy();
|
|
85
|
-
resolve(false);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
} catch (e) {
|
|
89
|
-
return Promise.resolve(false);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Install Python dependencies
|
|
94
|
-
function installPythonDeps(python) {
|
|
95
|
-
const requirementsPath = path.join(AGENT_DIR, 'requirements.txt');
|
|
96
|
-
|
|
97
|
-
if (!fs.existsSync(requirementsPath)) {
|
|
98
|
-
warn('requirements.txt not found, skipping Python dependencies');
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
log('Installing Python dependencies...');
|
|
103
|
-
try {
|
|
104
|
-
execSync(`${python} -m pip install -r requirements.txt -q --disable-pip-version-check`, {
|
|
105
|
-
cwd: AGENT_DIR,
|
|
106
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
107
|
-
});
|
|
108
|
-
success('Python dependencies installed');
|
|
109
|
-
return true;
|
|
110
|
-
} catch (e) {
|
|
111
|
-
error('Failed to install Python dependencies');
|
|
112
|
-
console.log(' Run manually: pip install -r requirements.txt');
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Create .env file if it doesn't exist
|
|
118
|
-
function createEnvFile() {
|
|
119
|
-
const envPath = path.join(AGENT_DIR, '.env');
|
|
120
|
-
const examplePath = path.join(AGENT_DIR, '.env.example');
|
|
121
|
-
|
|
122
|
-
if (fs.existsSync(envPath)) {
|
|
123
|
-
success('.env file already exists');
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Create basic .env
|
|
128
|
-
const envContent = `# Claude Memory Agent Configuration
|
|
129
|
-
# Generated during npm install
|
|
130
|
-
|
|
131
|
-
# Server
|
|
132
|
-
PORT=8102
|
|
133
|
-
HOST=0.0.0.0
|
|
134
|
-
MEMORY_AGENT_URL=http://localhost:8102
|
|
135
|
-
|
|
136
|
-
# Ollama
|
|
137
|
-
OLLAMA_HOST=http://localhost:11434
|
|
138
|
-
EMBEDDING_MODEL=nomic-embed-text
|
|
139
|
-
|
|
140
|
-
# Database (relative to agent directory)
|
|
141
|
-
DATABASE_PATH=${path.join(AGENT_DIR, 'memories.db').replace(/\\/g, '/')}
|
|
142
|
-
|
|
143
|
-
# Logging
|
|
144
|
-
LOG_LEVEL=INFO
|
|
145
|
-
`;
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
fs.writeFileSync(envPath, envContent);
|
|
149
|
-
success('Created .env configuration file');
|
|
150
|
-
return true;
|
|
151
|
-
} catch (e) {
|
|
152
|
-
warn('Could not create .env file');
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Main installation
|
|
158
11
|
async function main() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Install Python dependencies
|
|
173
|
-
installPythonDeps(python.cmd);
|
|
174
|
-
|
|
175
|
-
// Create .env file
|
|
176
|
-
createEnvFile();
|
|
177
|
-
|
|
178
|
-
// Check Ollama
|
|
179
|
-
log('Checking Ollama...');
|
|
180
|
-
const ollamaRunning = await checkOllama();
|
|
181
|
-
|
|
182
|
-
if (ollamaRunning) {
|
|
183
|
-
success('Ollama is running');
|
|
184
|
-
// Check if model is installed
|
|
185
|
-
const modelInstalled = await checkOllamaModel();
|
|
186
|
-
if (modelInstalled) {
|
|
187
|
-
success('Embedding model is ready');
|
|
188
|
-
} else {
|
|
189
|
-
warn('Embedding model not found');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Print next steps based on what's missing
|
|
194
|
-
header('Installation Complete!');
|
|
195
|
-
|
|
196
|
-
if (!ollamaRunning) {
|
|
197
|
-
// Ollama not installed - show big warning
|
|
198
|
-
console.log(`${colors.red}${colors.bold}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
|
|
199
|
-
console.log(`${colors.red}${colors.bold}║ REQUIRED: Install Ollama for semantic search to work ║${colors.reset}`);
|
|
200
|
-
console.log(`${colors.red}${colors.bold}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
201
|
-
console.log('');
|
|
202
|
-
console.log(' Ollama is required for the memory agent to work properly.');
|
|
203
|
-
console.log(' Without it, semantic search will be disabled.');
|
|
204
|
-
console.log('');
|
|
205
|
-
console.log(` ${colors.cyan}Step 1:${colors.reset} Download and install Ollama:`);
|
|
206
|
-
console.log(` ${colors.bold}https://ollama.ai/download${colors.reset}`);
|
|
207
|
-
console.log('');
|
|
208
|
-
console.log(` ${colors.cyan}Step 2:${colors.reset} After installing, open a terminal and run:`);
|
|
209
|
-
console.log(` ${colors.bold}ollama pull nomic-embed-text${colors.reset}`);
|
|
210
|
-
console.log('');
|
|
211
|
-
console.log(` ${colors.cyan}Step 3:${colors.reset} Start Ollama (it runs in background):`);
|
|
212
|
-
console.log(` ${colors.bold}ollama serve${colors.reset}`);
|
|
213
|
-
console.log('');
|
|
214
|
-
console.log(` ${colors.cyan}Step 4:${colors.reset} Then run the setup:`);
|
|
215
|
-
console.log(` ${colors.bold}claude-memory-agent install${colors.reset}`);
|
|
216
|
-
console.log('');
|
|
217
|
-
} else {
|
|
218
|
-
// Ollama running - show normal next steps
|
|
219
|
-
console.log('Next steps:');
|
|
220
|
-
console.log('');
|
|
221
|
-
console.log(' 1. Run the setup wizard:');
|
|
222
|
-
console.log(` ${colors.bold}claude-memory-agent install${colors.reset}`);
|
|
223
|
-
console.log('');
|
|
224
|
-
console.log(' 2. Start the agent:');
|
|
225
|
-
console.log(` ${colors.bold}claude-memory-agent start${colors.reset}`);
|
|
226
|
-
console.log('');
|
|
227
|
-
console.log(' 3. Open the dashboard:');
|
|
228
|
-
console.log(` ${colors.bold}claude-memory-agent dashboard${colors.reset}`);
|
|
229
|
-
console.log('');
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Check if embedding model is installed in Ollama
|
|
234
|
-
async function checkOllamaModel() {
|
|
12
|
+
// Skip in CI
|
|
13
|
+
if (process.env.CI) {
|
|
14
|
+
console.log('Skipping post-install in CI environment');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const { main: runOnboarding } = require('./onboarding');
|
|
20
|
+
await runOnboarding();
|
|
21
|
+
} catch (err) {
|
|
22
|
+
// Fallback: if onboarding deps missing, do basic setup
|
|
23
|
+
console.log('Running basic setup (onboarding dependencies not available)...');
|
|
235
24
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
res.on('data', chunk => data += chunk);
|
|
241
|
-
res.on('end', () => {
|
|
242
|
-
try {
|
|
243
|
-
const json = JSON.parse(data);
|
|
244
|
-
const models = json.models || [];
|
|
245
|
-
const hasModel = models.some(m =>
|
|
246
|
-
m.name && m.name.includes('nomic-embed-text')
|
|
247
|
-
);
|
|
248
|
-
resolve(hasModel);
|
|
249
|
-
} catch (e) {
|
|
250
|
-
resolve(false);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
req.on('error', () => resolve(false));
|
|
255
|
-
req.on('timeout', () => {
|
|
256
|
-
req.destroy();
|
|
257
|
-
resolve(false);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
} catch (e) {
|
|
261
|
-
return false;
|
|
25
|
+
const { basicSetup } = require('./lib/installer');
|
|
26
|
+
await basicSetup(path.dirname(__dirname));
|
|
27
|
+
} catch (fallbackErr) {
|
|
28
|
+
console.log('Post-install: run "claude-memory-agent install" to complete setup.');
|
|
262
29
|
}
|
|
30
|
+
}
|
|
263
31
|
}
|
|
264
32
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
} else {
|
|
269
|
-
console.log('Skipping post-install in CI environment');
|
|
270
|
-
}
|
|
33
|
+
main().catch(() => {
|
|
34
|
+
console.log('Post-install: run "claude-memory-agent install" to complete setup.');
|
|
35
|
+
});
|
package/config.py
CHANGED
|
@@ -10,10 +10,13 @@ Usage:
|
|
|
10
10
|
print(config.MEMORY_AGENT_URL)
|
|
11
11
|
"""
|
|
12
12
|
import os
|
|
13
|
+
import logging
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Optional
|
|
15
16
|
from dotenv import load_dotenv
|
|
16
17
|
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
17
20
|
# Load .env from agent directory
|
|
18
21
|
AGENT_DIR = Path(__file__).parent.resolve()
|
|
19
22
|
load_dotenv(AGENT_DIR / ".env")
|
|
@@ -38,17 +41,18 @@ class Config:
|
|
|
38
41
|
self.PID_FILE = AGENT_DIR / "memory-agent.pid"
|
|
39
42
|
|
|
40
43
|
# Server configuration
|
|
41
|
-
self.HOST = os.getenv("HOST", "
|
|
44
|
+
self.HOST = os.getenv("HOST", "127.0.0.1")
|
|
42
45
|
self.PORT = int(os.getenv("PORT", "8102"))
|
|
43
46
|
self.MEMORY_AGENT_URL = os.getenv(
|
|
44
47
|
"MEMORY_AGENT_URL",
|
|
45
48
|
f"http://localhost:{self.PORT}"
|
|
46
49
|
)
|
|
47
50
|
|
|
48
|
-
#
|
|
51
|
+
# Embeddings
|
|
52
|
+
self.EMBEDDING_PROVIDER = os.getenv("EMBEDDING_PROVIDER", "sentence-transformers")
|
|
49
53
|
self.OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
|
|
50
|
-
self.EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "
|
|
51
|
-
self.EMBEDDING_DIM = int(os.getenv("EMBEDDING_DIM", "
|
|
54
|
+
self.EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "Alibaba-NLP/gte-large-en-v1.5")
|
|
55
|
+
self.EMBEDDING_DIM = int(os.getenv("EMBEDDING_DIM", "1024"))
|
|
52
56
|
self.OLLAMA_HEALTH_TIMEOUT = float(os.getenv("OLLAMA_HEALTH_TIMEOUT", "2.0"))
|
|
53
57
|
self.OLLAMA_HEALTH_CACHE_TTL = float(os.getenv("OLLAMA_HEALTH_CACHE_TTL", "30.0"))
|
|
54
58
|
|
|
@@ -68,12 +72,106 @@ class Config:
|
|
|
68
72
|
self.AUTH_RATE_LIMIT = int(os.getenv("AUTH_RATE_LIMIT", "100"))
|
|
69
73
|
self.AUTH_RATE_WINDOW = int(os.getenv("AUTH_RATE_WINDOW", "60"))
|
|
70
74
|
|
|
75
|
+
# Response size limits
|
|
76
|
+
self.MAX_RESPONSE_CHARS = int(os.getenv("MAX_RESPONSE_CHARS", "80000"))
|
|
77
|
+
self.CONTENT_TRUNCATE_LENGTH = int(os.getenv("CONTENT_TRUNCATE_LENGTH", "300"))
|
|
78
|
+
self.MIN_RESULT_COUNT = int(os.getenv("MIN_RESULT_COUNT", "3"))
|
|
79
|
+
|
|
71
80
|
# Logging
|
|
72
81
|
self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
73
82
|
|
|
83
|
+
# Memory decay
|
|
84
|
+
self.DECAY_ARCHIVE_THRESHOLD = float(os.getenv("DECAY_ARCHIVE_THRESHOLD", "0.1"))
|
|
85
|
+
self.DECAY_CHECK_INTERVAL_HOURS = int(os.getenv("DECAY_CHECK_INTERVAL_HOURS", "24"))
|
|
86
|
+
|
|
74
87
|
# Hook timeouts
|
|
75
88
|
self.API_TIMEOUT = int(os.getenv("API_TIMEOUT", "30"))
|
|
76
89
|
|
|
90
|
+
# CLaRa-inspired memory tiers
|
|
91
|
+
self.TIER_HOT_MAX_AGE_DAYS = int(os.getenv("TIER_HOT_MAX_AGE_DAYS", "14"))
|
|
92
|
+
self.TIER_HOT_MIN_IMPORTANCE = int(os.getenv("TIER_HOT_MIN_IMPORTANCE", "7"))
|
|
93
|
+
self.TIER_WARM_MAX_AGE_DAYS = int(os.getenv("TIER_WARM_MAX_AGE_DAYS", "90"))
|
|
94
|
+
|
|
95
|
+
# Memory consolidation
|
|
96
|
+
self.CONSOLIDATION_THRESHOLD = float(os.getenv("CONSOLIDATION_THRESHOLD", "0.85"))
|
|
97
|
+
self.CONSOLIDATION_MIN_GROUP = int(os.getenv("CONSOLIDATION_MIN_GROUP", "3"))
|
|
98
|
+
self.CONSOLIDATION_MAX_GROUP = int(os.getenv("CONSOLIDATION_MAX_GROUP", "20"))
|
|
99
|
+
self.CONSOLIDATION_MAX_PER_RUN = int(os.getenv("CONSOLIDATION_MAX_PER_RUN", "5"))
|
|
100
|
+
self.CONSOLIDATION_INTERVAL_HOURS = int(os.getenv("CONSOLIDATION_INTERVAL_HOURS", "12"))
|
|
101
|
+
|
|
102
|
+
# Embedding pipeline
|
|
103
|
+
self.EMBEDDING_CACHE_SIZE = int(os.getenv("EMBEDDING_CACHE_SIZE", "500"))
|
|
104
|
+
self.EMBEDDING_BATCH_SIZE = int(os.getenv("EMBEDDING_BATCH_SIZE", "10"))
|
|
105
|
+
self.EMBEDDING_PRECOMPUTE_INTERVAL = int(os.getenv("EMBEDDING_PRECOMPUTE_INTERVAL", "60"))
|
|
106
|
+
|
|
107
|
+
# Adaptive search ranking
|
|
108
|
+
self.DEFAULT_SEARCH_TEMPERATURE = float(os.getenv("DEFAULT_SEARCH_TEMPERATURE", "1.0"))
|
|
109
|
+
|
|
110
|
+
# Validate configuration
|
|
111
|
+
self._validate()
|
|
112
|
+
|
|
113
|
+
def _validate(self):
|
|
114
|
+
"""Validate configuration values and warn on invalid settings."""
|
|
115
|
+
warnings = []
|
|
116
|
+
|
|
117
|
+
# Critical: port must be valid
|
|
118
|
+
if not (1 <= self.PORT <= 65535):
|
|
119
|
+
raise ValueError(f"PORT must be 1-65535, got {self.PORT}")
|
|
120
|
+
|
|
121
|
+
# Critical: pool size must be positive
|
|
122
|
+
if self.DB_POOL_SIZE < 1:
|
|
123
|
+
raise ValueError(f"DB_POOL_SIZE must be >= 1, got {self.DB_POOL_SIZE}")
|
|
124
|
+
|
|
125
|
+
# Critical: embedding provider must be valid
|
|
126
|
+
valid_providers = ("sentence-transformers", "ollama")
|
|
127
|
+
if self.EMBEDDING_PROVIDER not in valid_providers:
|
|
128
|
+
raise ValueError(f"EMBEDDING_PROVIDER must be one of {valid_providers}, got '{self.EMBEDDING_PROVIDER}'")
|
|
129
|
+
|
|
130
|
+
# Warnings for non-critical misconfigurations
|
|
131
|
+
if self.DB_TIMEOUT < 1.0:
|
|
132
|
+
warnings.append(f"DB_TIMEOUT={self.DB_TIMEOUT}s is very low, may cause spurious timeouts")
|
|
133
|
+
|
|
134
|
+
if self.DB_MAX_RETRIES < 0:
|
|
135
|
+
warnings.append(f"DB_MAX_RETRIES={self.DB_MAX_RETRIES} is negative, setting to 0")
|
|
136
|
+
self.DB_MAX_RETRIES = 0
|
|
137
|
+
|
|
138
|
+
if self.EMBEDDING_DIM not in (384, 768, 1024):
|
|
139
|
+
warnings.append(f"EMBEDDING_DIM={self.EMBEDDING_DIM} is unusual (expected 384/768/1024)")
|
|
140
|
+
|
|
141
|
+
if not (0.0 <= self.CONSOLIDATION_THRESHOLD <= 1.0):
|
|
142
|
+
warnings.append(f"CONSOLIDATION_THRESHOLD={self.CONSOLIDATION_THRESHOLD} out of range [0,1], clamping")
|
|
143
|
+
self.CONSOLIDATION_THRESHOLD = max(0.0, min(1.0, self.CONSOLIDATION_THRESHOLD))
|
|
144
|
+
|
|
145
|
+
if not (0.0 <= self.DECAY_ARCHIVE_THRESHOLD <= 1.0):
|
|
146
|
+
warnings.append(f"DECAY_ARCHIVE_THRESHOLD={self.DECAY_ARCHIVE_THRESHOLD} out of range [0,1], clamping")
|
|
147
|
+
self.DECAY_ARCHIVE_THRESHOLD = max(0.0, min(1.0, self.DECAY_ARCHIVE_THRESHOLD))
|
|
148
|
+
|
|
149
|
+
if not (0.0 <= self.DEFAULT_SEARCH_TEMPERATURE <= 2.0):
|
|
150
|
+
warnings.append(f"DEFAULT_SEARCH_TEMPERATURE={self.DEFAULT_SEARCH_TEMPERATURE} out of range [0,2], clamping")
|
|
151
|
+
self.DEFAULT_SEARCH_TEMPERATURE = max(0.0, min(2.0, self.DEFAULT_SEARCH_TEMPERATURE))
|
|
152
|
+
|
|
153
|
+
if self.MAX_RESPONSE_CHARS < 1000:
|
|
154
|
+
warnings.append(f"MAX_RESPONSE_CHARS={self.MAX_RESPONSE_CHARS} is very low, responses may be cut off")
|
|
155
|
+
|
|
156
|
+
if self.EMBEDDING_CACHE_SIZE < 0:
|
|
157
|
+
warnings.append(f"EMBEDDING_CACHE_SIZE={self.EMBEDDING_CACHE_SIZE} is negative, setting to 0")
|
|
158
|
+
self.EMBEDDING_CACHE_SIZE = 0
|
|
159
|
+
|
|
160
|
+
valid_log_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
|
161
|
+
if self.LOG_LEVEL.upper() not in valid_log_levels:
|
|
162
|
+
warnings.append(f"LOG_LEVEL='{self.LOG_LEVEL}' is invalid, using INFO")
|
|
163
|
+
self.LOG_LEVEL = "INFO"
|
|
164
|
+
|
|
165
|
+
if self.TIER_HOT_MAX_AGE_DAYS >= self.TIER_WARM_MAX_AGE_DAYS:
|
|
166
|
+
warnings.append(
|
|
167
|
+
f"TIER_HOT_MAX_AGE_DAYS ({self.TIER_HOT_MAX_AGE_DAYS}) >= "
|
|
168
|
+
f"TIER_WARM_MAX_AGE_DAYS ({self.TIER_WARM_MAX_AGE_DAYS}), "
|
|
169
|
+
f"hot tier would overlap warm tier"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for w in warnings:
|
|
173
|
+
logger.warning(f"Config: {w}")
|
|
174
|
+
|
|
77
175
|
def get_health_url(self) -> str:
|
|
78
176
|
"""Get the health check URL."""
|
|
79
177
|
return f"{self.MEMORY_AGENT_URL}/health"
|
|
@@ -100,5 +198,6 @@ PORT = config.PORT
|
|
|
100
198
|
HOST = config.HOST
|
|
101
199
|
MEMORY_AGENT_URL = config.MEMORY_AGENT_URL
|
|
102
200
|
OLLAMA_HOST = config.OLLAMA_HOST
|
|
201
|
+
EMBEDDING_PROVIDER = config.EMBEDDING_PROVIDER
|
|
103
202
|
EMBEDDING_MODEL = config.EMBEDDING_MODEL
|
|
104
203
|
DATABASE_PATH = config.DATABASE_PATH
|