diff --git a/Plugin/unraid/api/ai.php b/Plugin/unraid/api/ai.php
index a42d44e..1167db0 100644
--- a/Plugin/unraid/api/ai.php
+++ b/Plugin/unraid/api/ai.php
@@ -256,7 +256,13 @@ if ($action === 'chat_save') {
$cap = vv_ai_profiles_max_turns() * 2;
if (count($clean) > $cap) $clean = array_slice($clean, -$cap);
- $r = vv_ai_chat_save(trim($_POST['id'] ?? ''), $profile, $clean);
+ // Whitelisted exactly as ask's is, and for the same reason: a scope is only ever a name from
+ // a page's own view state, it is stored and later replayed into a prompt, and anything
+ // richer than a file name is an instruction-injection surface for no benefit.
+ $scope = trim($_POST['scope'] ?? '');
+ if ($scope !== '' && !vv_ai_scope_ok($scope)) $scope = '';
+
+ $r = vv_ai_chat_save(trim($_POST['id'] ?? ''), $profile, $clean, $scope);
vv_ai_log('chat_save ' . ($r['ok'] ? 'ok id=' . substr($r['id'], 0, 12)
: 'FAILED: ' . $r['error']));
echo json_encode($r);
diff --git a/Plugin/unraid/include/ai.php b/Plugin/unraid/include/ai.php
index 9b8c10d..753faaa 100644
--- a/Plugin/unraid/include/ai.php
+++ b/Plugin/unraid/include/ai.php
@@ -1207,6 +1207,7 @@ function vv_ai_chats_list(): array {
'id' => $d['id'],
'ts' => (int)($d['ts'] ?? 0),
'profile' => (string)($d['profile'] ?? 'chat'),
+ 'scope' => (string)($d['scope'] ?? ''),
'title' => (string)($d['title'] ?? 'Untitled conversation'),
'turns' => (int)($d['turns'] ?? count($d['messages'] ?? [])),
];
@@ -1244,7 +1245,7 @@ function vv_ai_chats_prune(?int $max = null): int {
// Writes a whole conversation. An empty id mints one; a known id overwrites in place, which is
// what makes a continued conversation stay one row in the list instead of breeding a new one per
// turn. Written to a temp file and renamed, so a reader never sees half a transcript.
-function vv_ai_chat_save(string $id, string $profile, array $messages): array {
+function vv_ai_chat_save(string $id, string $profile, array $messages, string $scope = ''): array {
if (!$messages) return ['ok' => false, 'error' => 'Nothing to save'];
if ($id === '') $id = bin2hex(random_bytes(16));
@@ -1257,12 +1258,19 @@ function vv_ai_chat_save(string $id, string $profile, array $messages): array {
$prev = vv_ai_chat_read($id);
$created = (int)($prev['created'] ?? time());
+ // Scope travels with the conversation. A Scheduler dock thread is bound to what the operator
+ // had open — a script, a log, a conf key — and its turns carry log excerpts chosen for that
+ // thing. Storing the scope means reopening the thread anywhere restores the context it was
+ // reasoned in, rather than silently continuing a troubleshooting conversation against
+ // whatever happens to be on screen. The isolation the dock enforces in memory becomes a
+ // property of the record instead of something lost the moment it is saved.
$rec = [
'id' => $id,
'created' => $created,
'ts' => $created,
'updated' => time(),
'profile' => $profile,
+ 'scope' => $scope,
'title' => vv_ai_chat_title($messages),
'turns' => (int)floor(count($messages) / 2),
'messages' => $messages,
diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php
index 788893d..7d5f44d 100644
--- a/Plugin/unraid/include/ai_chat.php
+++ b/Plugin/unraid/include/ai_chat.php
@@ -70,10 +70,51 @@ require_once __DIR__ . '/ai_profiles.php';
// Emitted once even if two instances are rendered. A second copy of the script would re-register
// the factories harmlessly but would also install a second Escape handler and a second copy of
// every keyframe, so the guard is cheaper than reasoning about whether it matters.
+// The conversation store, on its own, with no styling and no chat widget attached.
+//
+// Emitted separately because the surface that most needs it is the one that does not want the
+// rest: the Scheduler dock draws its own one-line bar, with its own scope chip and fix flow, and
+// pulling in the full transcript stylesheet to reach a save function would restyle a component
+// that was deliberately built to look different.
+//
+// Where a conversation lives is not a presentation decision, so it gets one answer for every
+// surface. Anything added to the store — a new field, a new cap, a changed prune rule — arrives
+// everywhere at once because there is only one writer.
+function vv_ai_chat_store_script(): void {
+ static $done = false;
+ if ($done) return;
+ $done = true;
+ ?>
+
+