diff --git a/Plugin/unraid/Tools/ai_chat_worker.php b/Plugin/unraid/Tools/ai_chat_worker.php index 238bd09..1e21791 100644 --- a/Plugin/unraid/Tools/ai_chat_worker.php +++ b/Plugin/unraid/Tools/ai_chat_worker.php @@ -774,8 +774,11 @@ if (vv_ai_mem_learn_enabled()) { . "MEMORY: \n" . "Rules: one line, under 200 characters, stated as fact with no hedging. Not a " . "summary of your answer, not a restatement of the question, not anything already " - . "written above in what you know. Most exchanges warrant nothing — when in doubt, " - . "leave the line out entirely.\n\n"; + . "written above in what you know.\n" + . "Never where a setting lives or how to reach it in the web UI — all of that is " + . "already documented and retrievable, so remembering it gains nothing and costs " + . "budget that a fact the documents cannot supply would have used.\n" + . "Most exchanges warrant nothing — when in doubt, leave the line out entirely.\n\n"; } if (trim($mem['learned']) !== '') { diff --git a/Plugin/unraid/include/ai_memory_learn.php b/Plugin/unraid/include/ai_memory_learn.php index c4eb905..e15ba9f 100644 --- a/Plugin/unraid/include/ai_memory_learn.php +++ b/Plugin/unraid/include/ai_memory_learn.php @@ -111,6 +111,28 @@ function vv_ai_mem_is_dup(string $text, ?array $known = null): bool { // rather than stating a fact, and it would eat the budget it is competing for. const VV_AI_MEM_MAX_LINE = 220; +// Where a setting lives in the UI is documented, and documentation does not belong in memory — +// memory is for what the documents cannot tell you. The UI map made this a real problem rather +// than a theoretical one: it describes 447 settings, so without this the assistant will propose +// "X is on the Settings tab" over and over, each one costing a dismissal. +// +// Gated on WHICH document the candidate is nearest to, not on how near. A plain score threshold +// was tried first and does not separate these: a genuine fact about rsync scores 0.78 against +// rsync.sh because it is about rsync, higher than the junk proposal that started this at 0.75. +// Similarity measures topic, not novelty. But the junk lands on the UI map and nothing worth +// remembering ever has — that is a difference in kind, and kind is what the index records. +const VV_AI_MEM_UI_NEAR = 0.62; + +function vv_ai_mem_is_ui_fact(string $text): bool { + // Retrieval needs the index and the embedder. If either is unavailable the answer is "not + // provably documented", which files the proposal — the operator can still dismiss it, and + // failing closed here would silently stop learning whenever Ollama was restarting. + $r = @vv_ai_retrieve($text, '', '', 1); + if (empty($r['ok']) || empty($r['results'])) return false; + $top = $r['results'][0]; + return ($top['kind'] ?? '') === 'ui' && (float) ($top['score'] ?? 0) >= VV_AI_MEM_UI_NEAR; +} + function vv_ai_mem_propose(string $text, array $meta = []): array { if (!vv_ai_mem_learn_enabled()) return ['ok' => false, 'error' => 'learning disabled']; @@ -118,6 +140,7 @@ function vv_ai_mem_propose(string $text, array $meta = []): array { if ($text === '') return ['ok' => false, 'error' => 'empty']; if (mb_strlen($text) > VV_AI_MEM_MAX_LINE) return ['ok' => false, 'error' => 'too long']; if (vv_ai_mem_is_dup($text)) return ['ok' => false, 'error' => 'duplicate']; + if (vv_ai_mem_is_ui_fact($text)) return ['ok' => false, 'error' => 'already in the UI map']; $id = substr(hash('sha256', $text . microtime(true)), 0, 12); $row = [