Give the assistant a standing memory file
A small operator-written file handed to the model at the start of every conversation — who you are, how this install is set up, what has already been decided. Injected ahead of the retrieved passages and marked operator-authored so it outranks anything they contradict, and never cited as a source. Deliberately not indexed and deliberately under DATA_DIR: it changes constantly, vector similarity is the wrong way to retrieve things you were told to remember, and gitignoring it keeps personal notes out of a pushed repository. The character cap is a context budget — this text costs its share of 16k on every single turn.
This commit is contained in:
@@ -67,6 +67,7 @@
|
||||
// Status vv_ai_stats(), vv_ai_index_stats(), vv_ai_runtime_stats(), vv_ai_index_meta()
|
||||
// Models vv_ai_models_available(), vv_ai_models_loaded()
|
||||
// Diagnosis vv_ai_health(), vv_ai_recent_logs()
|
||||
// Memory vv_ai_memory_path(), vv_ai_memory_read(), vv_ai_memory_write()
|
||||
// Retrieval vv_ai_retrieve()
|
||||
// Jobs vv_ai_job_dir(), vv_ai_job_path(), vv_ai_job_read()
|
||||
//
|
||||
@@ -452,6 +453,78 @@ function vv_ai_retrieve(string $query, string $kind = '', string $section = '',
|
||||
'scanned' => $d['scanned'] ?? 0];
|
||||
}
|
||||
|
||||
// ── Memory ───────────────────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// A small operator-maintained file handed to the model at the start of every conversation.
|
||||
// Injected, never indexed: it changes constantly, and vector similarity is the wrong retrieval
|
||||
// mechanism for "things I was told to remember". Living under DATA_DIR keeps it out of the
|
||||
// repository and therefore out of the index by construction.
|
||||
|
||||
function vv_ai_memory_path(): string {
|
||||
$cfg = vv_ai_config();
|
||||
$vars = vv_conf_vars();
|
||||
$p = trim($vars['AI_MEMORY_FILE'] ?? '');
|
||||
$p = str_replace(['$DATA_DIR', '${DATA_DIR}'], DATA_DIR, $p);
|
||||
return $p !== '' ? $p : DATA_DIR . '/ai_memory.md';
|
||||
}
|
||||
|
||||
function vv_ai_memory_max(): int {
|
||||
$vars = vv_conf_vars();
|
||||
$n = (int)($vars['AI_MEMORY_MAX_CHARS'] ?? 4000);
|
||||
return max(200, min($n, 20000));
|
||||
}
|
||||
|
||||
// Returns ['text'=>string,'chars'=>int,'truncated'=>bool,'exists'=>bool].
|
||||
// Truncates rather than refusing: an over-long memory file should cost its own tail, not the
|
||||
// whole conversation, and the notice tells the model its knowledge is incomplete rather than
|
||||
// letting it assume it saw everything.
|
||||
function vv_ai_memory_read(): array {
|
||||
$p = vv_ai_memory_path();
|
||||
$max = vv_ai_memory_max();
|
||||
|
||||
if (!file_exists($p)) return ['text' => '', 'chars' => 0, 'truncated' => false, 'exists' => false];
|
||||
|
||||
$raw = (string)@file_get_contents($p);
|
||||
$len = mb_strlen($raw);
|
||||
if ($len <= $max) {
|
||||
return ['text' => $raw, 'chars' => $len, 'truncated' => false, 'exists' => true];
|
||||
}
|
||||
return [
|
||||
'text' => mb_substr($raw, 0, $max) . "\n\n[memory truncated at {$max} characters]",
|
||||
'chars' => $len,
|
||||
'truncated' => true,
|
||||
'exists' => true,
|
||||
];
|
||||
}
|
||||
|
||||
// Atomic, and refuses to exceed the cap. The cap is a context budget shared with retrieval and
|
||||
// reasoning on every turn, so it is enforced on write rather than silently trimmed on read.
|
||||
function vv_ai_memory_write(string $text): array {
|
||||
$p = vv_ai_memory_path();
|
||||
$max = vv_ai_memory_max();
|
||||
$len = mb_strlen($text);
|
||||
|
||||
if ($len > $max) {
|
||||
return ['ok' => false, 'error' => "Memory is {$len} characters; the limit is {$max}. "
|
||||
. "It is included in every prompt, so it competes with retrieval for context."];
|
||||
}
|
||||
|
||||
$dir = dirname($p);
|
||||
if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
|
||||
return ['ok' => false, 'error' => 'Cannot create ' . $dir];
|
||||
}
|
||||
|
||||
$tmp = $p . '.vv.tmp';
|
||||
if (@file_put_contents($tmp, $text) === false) {
|
||||
return ['ok' => false, 'error' => 'Cannot write ' . $tmp];
|
||||
}
|
||||
if (!@rename($tmp, $p)) {
|
||||
@unlink($tmp);
|
||||
return ['ok' => false, 'error' => 'Cannot install ' . $p];
|
||||
}
|
||||
return ['ok' => true, 'chars' => $len];
|
||||
}
|
||||
|
||||
function vv_ai_job_dir(): string {
|
||||
if (!is_dir(VV_AI_JOB_DIR)) @mkdir(VV_AI_JOB_DIR, 0700, true);
|
||||
return VV_AI_JOB_DIR;
|
||||
|
||||
Reference in New Issue
Block a user