Add a troubleshooting profile with the scoped log, and a deterministic conf-key lookup

This commit is contained in:
Gmer4Lfe
2026-08-05 20:18:05 -04:00
parent 185abdb442
commit 5a813eb4c8
4 changed files with 151 additions and 10 deletions
+63 -4
View File
@@ -88,7 +88,7 @@ require_once dirname(__DIR__) . '/include/ai.php';
if ($jobFile === '' || $question === '') exit(1);
if (!preg_match('#/[0-9a-f]{32}\.json$#', $jobFile)) exit(1);
$profile = in_array($profile, ['varaverk', 'chat', 'code'], true) ? $profile : 'varaverk';
$profile = in_array($profile, ['varaverk', 'chat', 'code', 'troubleshoot'], true) ? $profile : 'varaverk';
function jw(string $f, array $d): void {
file_put_contents($f, json_encode($d));
@@ -104,7 +104,7 @@ $sources = [];
$context = '';
$tRetrieve = 0.0;
if ($profile === 'varaverk') {
if ($profile === 'varaverk' || $profile === 'troubleshoot') {
jw($jobFile, ['status' => 'retrieving']);
// A definitional question with no explicit filter goes to the narrative docs. Left alone,
@@ -143,11 +143,14 @@ if ($profile === 'varaverk') {
// supposed to do; only the logs and the current config say what it actually did. Attached only
// when the question is asking why something failed — otherwise it is a few thousand tokens of
// noise competing with the retrieved passages for a context budget that is already tight.
$diagnostic = $profile === 'varaverk' && (bool)preg_match(
// The troubleshooting profile is diagnostic by definition — the operator opened a log and asked
// about it, which is a clearer signal than any phrasing test. For the assistant it stays a
// keyword gate, since most of its questions are not about failures.
$diagnostic = $profile === 'troubleshoot' || ($profile === 'varaverk' && (bool)preg_match(
'/\b(why|fail(ed|ing|ure)?|error|broken?|not work|isn.t work|wrong|stuck|hang|'
. 'never runs?|didn.t|won.t|debug|troubleshoot|diagnos)/i',
$question
);
));
$diagBlock = '';
if ($diagnostic) {
@@ -171,6 +174,42 @@ if ($diagnostic) {
}
}
// The troubleshooting profile gets the actual tail of the one log the operator is looking at,
// warnings and ordinary lines alike. The fleet-wide WARN/ERROR sweep above cannot answer "why
// did this one stop" — the last line a script printed before dying is usually not labelled.
$scopedLog = null;
if ($profile === 'troubleshoot' && $scope !== '') {
$scopedLog = vv_ai_scoped_log($scope, 120);
if ($scopedLog['ok']) {
$diagBlock .= 'LOG: ' . $scopedLog['path']
. ' (' . $scopedLog['total'] . " lines total, newest last)\n"
. implode("\n", $scopedLog['tail']) . "\n\n";
} else {
$diagBlock .= "LOG: none found for " . $scope . " — it may never have run.\n\n";
}
}
// Where a named conf key really lives, resolved before the model sees the question. Deterministic
// so the answer cannot be a guess: the operator may be certain a setting is in master.conf when
// it is in the host conf, and the useful reply names the file and line rather than not finding it.
if ($profile === 'varaverk' || $profile === 'troubleshoot') {
$seen = [];
if (preg_match_all('/\b([A-Z][A-Z0-9_]{4,})\b/', $question, $km)) {
foreach (array_slice(array_unique($km[1]), 0, 4) as $k) {
$hit = vv_ai_find_conf_key($k);
if (!$hit['ok']) continue;
$seen[] = '- ' . $k . ' is set in ' . $hit['file'] . ' at line ' . $hit['line']
. ($hit['commented'] ? ' (commented out, so it is NOT active)' : '')
. ': ' . $hit['text'];
}
}
if ($seen) {
$diagBlock .= "WHERE THESE SETTINGS ACTUALLY LIVE (looked up just now, authoritative)\n"
. implode("\n", $seen) . "\n"
. "If this is a different file from the one they have open, say so plainly.\n\n";
}
}
// One prompt per profile. Explicit profiles rather than an automatic router: misclassifying a
// Varaverk question as chat produces a confident invention about the user's system, which is
// precisely what retrieval exists to prevent. The user always knows which contract is in force,
@@ -196,6 +235,26 @@ if ($profile === 'chat') {
. "knowledge freely — Linux, scripting, hardware, whatever comes up. The restriction "
. "is only about the specifics of THIS installation.\n\n";
} elseif ($profile === 'troubleshoot') {
// Different inputs and a different refusal rule from the assistant, which is what earns it a
// profile of its own. The assistant's contract is "answer only from the passages, refuse if
// absent" — exactly wrong here, where the evidence is a log that is not in the index and
// never should be. This one reasons from the log first and the documentation second.
$system = "You are helping the operator work out why something on their Varaverk server did "
. "not do what they expected. You have the tail of the relevant log, live system "
. "state measured just now, and documentation passages about the scripts involved.\n\n"
. "HOW TO ANSWER\n"
. "Lead with what the log actually shows. Quote the line that matters. Then say what "
. "it means, using the documentation to explain what the script was trying to do.\n\n"
. "Say plainly when the log does not explain it. \"The log ends at X with no error, "
. "so it was killed or is still running\" is a real and useful answer. Do not "
. "manufacture a cause to have one — a wrong cause sends someone to fix the wrong "
. "thing, which is worse than no answer.\n\n"
. "Distinguish what you observed from what you inferred. The log is evidence; "
. "anything beyond it is a hypothesis and should be labelled as one.\n\n"
. "Do not suggest editing conf files by hand. Settings on this page have controls, "
. "and the operator is reading this inside the WebGUI. Name the control.\n\n";
} elseif ($profile === 'code') {
$system = "You are drafting a short shell script for the operator of an Unraid server, to be "
. "saved as a Varaverk Custom Script. Assume bash on Unraid: GNU coreutils, paths "