mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
Phase 10: Memory Core — auto-extraction knowledge graph
- reactor.py v9.0.0+: memory_extract + memory_store handlers (21 handlers) - handle_memory_extract: Haiku-powered fact extraction from conversations - handle_memory_store: explicit memory insertion from voice commands - FastAPI: /memory/facts CRUD, /memory/context (relevance retrieval), /memory/stats - chat.php: Tier 0.9k voice commands (remember/forget/recall/memory status) - Memory context injected into Groq + Claude system prompts - Auto-trigger memory_extract after every LLM response (async, non-blocking) - memory.php: new API endpoint proxying Arc Reactor memory routes - api.php: added memory route - admin/index.php: MEMORY CORE nav + tab (browse by category, search, add/delete facts) - index.html: MEMORY count in bottom bar (polls every 60s)
This commit is contained in:
+127
-2
@@ -1081,6 +1081,52 @@ if (!$reply && preg_match('/\b(news|headlines|latest|what.?s happening|current e
|
||||
// ── Tier 0.9: Arc Protocols — research, triage, remote_exec, screenshot, sysinfo ─
|
||||
$arcJobId = null;
|
||||
|
||||
// ── Memory Core helpers ───────────────────────────────────────────────────────
|
||||
|
||||
function getMemoryContext(string $message, int $limit = 12): string {
|
||||
try {
|
||||
$ch = curl_init('http://127.0.0.1:7474/memory/context?limit=' . $limit .
|
||||
'&message=' . urlencode(substr($message, 0, 200)));
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 2,
|
||||
CURLOPT_CONNECTTIMEOUT => 1,
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if (!$raw) return '';
|
||||
$data = json_decode($raw, true);
|
||||
return $data['context'] ?? '';
|
||||
} catch (Throwable $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function memoryExtractAsync(string $userMsg, string $assistantMsg, string $sessionId): void {
|
||||
// Fire-and-forget background memory extraction — does not block the response
|
||||
$body = json_encode([
|
||||
'type' => 'memory_extract',
|
||||
'payload' => [
|
||||
'user_message' => substr($userMsg, 0, 600),
|
||||
'assistant_message' => substr($assistantMsg, 0, 600),
|
||||
'conversation_id' => null,
|
||||
],
|
||||
'priority' => 2,
|
||||
'created_by' => 'chat:' . $sessionId,
|
||||
]);
|
||||
$ch = curl_init('http://127.0.0.1:7474/job');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $body,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
CURLOPT_TIMEOUT => 1,
|
||||
CURLOPT_CONNECTTIMEOUT => 1,
|
||||
]);
|
||||
@curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
// Helper: submit job to Arc Reactor
|
||||
function arcPost(string $path, array $body): ?array {
|
||||
$ch = curl_init('http://127.0.0.1:7474' . $path);
|
||||
@@ -1481,6 +1527,71 @@ if (!$reply) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 0.9k: Memory Core — remember/forget/recall voice commands ───────────
|
||||
if (!$reply) {
|
||||
// "remember that I prefer X", "remember X", "note that X"
|
||||
if (preg_match('/^(?:jarvis[,\s]+)?(?:remember|note down|make a note|remember that|note that)\s+(?:that\s+)?(.+)/i', $message, $m)) {
|
||||
$fact = trim($m[1]);
|
||||
// Use LLM to parse the fact into subject/predicate/object — but for speed, use heuristic
|
||||
$arcRes = arcPost('/job', [
|
||||
'type' => 'memory_store',
|
||||
'payload' => ['subject' => 'user', 'predicate' => 'note', 'object' => $fact, 'category' => 'instruction'],
|
||||
'priority' => 8,
|
||||
'created_by' => 'chat:' . $sessionId,
|
||||
]);
|
||||
$reply = "Noted, {$userAddr}. I'll remember that: {$fact}";
|
||||
$source = 'memory:store';
|
||||
}
|
||||
}
|
||||
if (!$reply) {
|
||||
// "forget X", "forget that X", "remove memory X"
|
||||
if (preg_match('/^(?:jarvis[,\s]+)?(?:forget|discard|remove|delete)\s+(?:that\s+|the\s+memory\s+(?:about\s+)?)?(.+)/i', $message, $m)) {
|
||||
$keyword = trim($m[1]);
|
||||
// Mark matching facts inactive
|
||||
$affected = JarvisDB::execute(
|
||||
"UPDATE memory_facts SET active=0 WHERE active=1 AND (subject LIKE ? OR object LIKE ? OR predicate LIKE ?)",
|
||||
["%{$keyword}%", "%{$keyword}%", "%{$keyword}%"]
|
||||
);
|
||||
$reply = $affected
|
||||
? "Memory cleared, {$userAddr}. Removed facts related to: {$keyword}."
|
||||
: "Nothing in memory matched \"{$keyword}\", {$userAddr}.";
|
||||
$source = 'memory:forget';
|
||||
}
|
||||
}
|
||||
if (!$reply) {
|
||||
// "what do you know about X", "what do you remember about X"
|
||||
if (preg_match('/^(?:jarvis[,\s]+)?(?:what do you know about|what do you remember about|recall|memory about)\s+(.+)/i', $message, $m)) {
|
||||
$keyword = trim($m[1]);
|
||||
$facts = JarvisDB::query(
|
||||
"SELECT * FROM memory_facts WHERE active=1 AND (subject LIKE ? OR object LIKE ?) ORDER BY confirmed_count DESC LIMIT 8",
|
||||
["%{$keyword}%", "%{$keyword}%"]
|
||||
) ?: [];
|
||||
if (empty($facts)) {
|
||||
$reply = "I don't have any stored memories about \"{$keyword}\", {$userAddr}.";
|
||||
} else {
|
||||
$lines = array_map(fn($f) => "{$f['subject']} {$f['predicate']}: {$f['object']}", $facts);
|
||||
$reply = "◈ I know the following about \"{$keyword}\", {$userAddr}:\n" . implode("\n", $lines);
|
||||
}
|
||||
$source = 'memory:recall';
|
||||
}
|
||||
}
|
||||
if (!$reply) {
|
||||
// "how many memories", "memory status", "what do you know about me"
|
||||
if (preg_match('/^(?:jarvis[,\s]+)?(?:(?:how many|show)\s+memories|memory\s+(?:status|count|summary)|what do you know about me)/i', $message)) {
|
||||
$stats = JarvisDB::query(
|
||||
"SELECT category, COUNT(*) cnt FROM memory_facts WHERE active=1 GROUP BY category ORDER BY cnt DESC"
|
||||
) ?: [];
|
||||
$total = array_sum(array_column($stats, 'cnt'));
|
||||
if (!$total) {
|
||||
$reply = "My memory core is empty, {$userAddr}. I'll start learning from our conversations.";
|
||||
} else {
|
||||
$breakdown = implode(', ', array_map(fn($s) => "{$s['cnt']} {$s['category']}", $stats));
|
||||
$reply = "◈ Memory Core: {$total} facts stored — {$breakdown}. I use these to personalize responses.";
|
||||
}
|
||||
$source = 'memory:status';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 1: Intent Engine (instant, no LLM) ───────────────────────────────
|
||||
if (!$reply) {
|
||||
$matched = KBEngine::match($message);
|
||||
@@ -1612,6 +1723,12 @@ if (!$reply) {
|
||||
}
|
||||
|
||||
|
||||
// ── Memory injection — fetch relevant facts before LLM tiers ─────────────
|
||||
$memoryContext = '';
|
||||
if (!$reply) {
|
||||
$memoryContext = getMemoryContext($message, 12);
|
||||
}
|
||||
|
||||
// ── Tier 2: Ollama local LLM (fast local fallback) ───────────────────────
|
||||
if (!$reply && defined('OLLAMA_HOST') && OLLAMA_HOST) {
|
||||
$ollamaHost = OLLAMA_HOST;
|
||||
@@ -1672,10 +1789,12 @@ if (!$reply && defined('GROQ_API_KEY') && GROQ_API_KEY) {
|
||||
);
|
||||
$groqModel = $needsSearch ? GROQ_MODEL_SEARCH : GROQ_MODEL_GENERAL;
|
||||
|
||||
$memSuffix = $memoryContext ? "\n\n{$memoryContext}" : '';
|
||||
$groqMessages = [['role' => 'system', 'content' =>
|
||||
"You are JARVIS — Just A Rather Very Intelligent System — the AI of {$userName} " .
|
||||
"(address him as \"{$userAddr}\"). Formal, efficient, British butler tone. " .
|
||||
'Be concise — 2-4 sentences unless detail is explicitly requested. Today: ' . date('D M j Y g:i A T') . '.'],
|
||||
'Be concise — 2-4 sentences unless detail is explicitly requested. Today: ' . date('D M j Y g:i A T') .
|
||||
$memSuffix . '.'],
|
||||
];
|
||||
foreach (array_slice($history, -6) as $h) {
|
||||
$groqMessages[] = ['role' => $h['role'], 'content' => $h['content']];
|
||||
@@ -1757,7 +1876,8 @@ Infrastructure:
|
||||
- Network: 10.48.200.0/24, FortiGate firewall
|
||||
|
||||
Live data:
|
||||
{$systemContext}" . ($kbContext ? "\nKnowledge base:\n{$kbContext}" : '') . "
|
||||
{$systemContext}" . ($kbContext ? "\nKnowledge base:\n{$kbContext}" : '') .
|
||||
($memoryContext ? "\n\n{$memoryContext}" : '') . "
|
||||
Today: " . date('l, F j Y, g:i A T') . "
|
||||
|
||||
Respond as JARVIS. Voice readout: under 3 sentences unless detail is requested. For system status, interpret the data and give an assessment — don't just recite numbers.";
|
||||
@@ -1827,6 +1947,11 @@ JarvisDB::insert(
|
||||
);
|
||||
KBEngine::learnFromConversation($message, $reply);
|
||||
|
||||
// Memory Core — async extraction for LLM responses (don't extract from intent/KB/fallback)
|
||||
if ($reply && !in_array(explode(':', $source)[0], ['intent', 'kb', 'fallback', 'memory', 'arc'])) {
|
||||
memoryExtractAsync($message, $reply, $sessionId);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'reply' => $reply,
|
||||
'source' => $source,
|
||||
|
||||
Reference in New Issue
Block a user