diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index dd97a51..6c800d4 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -23,7 +23,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Postgres owns jobs, audit events, interaction memory, and rate-limit state. - Qdrant owns history and source vectors. - `.voidbot/` owns archives, artifacts, logs, status files, and backups. -- `owner_codex` and `local_llm` are the active reply lanes. +- `owner_codex` is the active reply lane. The Raven-backed `local_llm` lane is disabled because the host was only generating stack/watchdog noise. - The ordinary bot/worker/provider/RAG/Postgres/Qdrant foundation still deserves to survive. - The moderation/mood/private-self-state foundation now runs on the typed store boundary. Treat it as live but still under watch until ordinary unattended rumination and sleep cycles have shown they do not produce duplicate speech, stale short-term residue, or ungrounded memory writes. - CultCache-backed self-state uses the `.cc` extension. The active store path is `.voidbot/private/void-self-state.cc`. @@ -65,7 +65,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou - Ephemeral room-reading now comes from a quick `think=false` local Ollama sidecar in `packages/core/src/situational-social-read.ts` instead of hand-built phrase heuristics, and the inferred reads are recorded as audit events so they can be aggregated into richer profiles later without immediately mutating durable identity state. - State storage is no longer one persistence omnibus; `packages/core/src/state-storage.ts` is now a thin factory over domain-specific Postgres/file store modules plus bootstrap/migration helpers. - Source archive storage is no longer one giant JSON monolith; `packages/rag/src/source-document-archive.ts` now keeps a small manifest at `.voidbot/rag/source-documents.json` and per-repo shards under `.voidbot/rag/source-documents.repos/`. -- Ops health, backup verification, offsite sync, and dashboard surfaces already exist. +- Ops health, local backup verification, and dashboard surfaces already exist. Raven-backed offsite sync is disabled by config and should not be treated as a live health requirement. - The stack has a dedicated logon startup task path now, so a reboot does not have to mean manual necromancy for bot and worker. - The operations watchdog now supports ignored local extension checks, so adjacent-service fire signals can ride the same owner DM alert path without hardcoding private project gossip into the public repo. - Source repo discovery now reconciles automatically during `npm run stack:start` and through the watchdog path, so new repos under `SOURCE_REPO_ROOT` stop waiting for manual hook folklore before they get indexed. diff --git a/scripts/check-voidbot-operations.ps1 b/scripts/check-voidbot-operations.ps1 index d09ee73..f3c8f76 100644 --- a/scripts/check-voidbot-operations.ps1 +++ b/scripts/check-voidbot-operations.ps1 @@ -560,6 +560,32 @@ function Get-OptionalPropertyValue { return $property.Value } +function Test-ConfigEnabled { + param( + [Parameter(Mandatory = $true)] + [hashtable] $Config, + [Parameter(Mandatory = $true)] + [string] $Key, + [bool] $Default = $true + ) + + if (-not $Config.ContainsKey($Key)) { + return $Default + } + + $value = [string]$Config[$Key] + if ([string]::IsNullOrWhiteSpace($value)) { + return $false + } + + $normalized = $value.Trim().ToLowerInvariant() + if ($normalized -in @("0", "false", "no", "off", "disabled")) { + return $false + } + + return $true +} + function Invoke-RemotePowerShellJson { param( [Parameter(Mandatory = $true)] @@ -764,6 +790,7 @@ $offsiteStatusPath = Join-Path $storageRoot "status\offsite-backup.json" $backupRoot = Join-Path $storageRoot "backups" $backupMaxAgeHours = if ($config.ContainsKey("VOIDBOT_BACKUP_MAX_AGE_HOURS")) { [double]$config["VOIDBOT_BACKUP_MAX_AGE_HOURS"] } else { 30.0 } $offsiteMaxAgeHours = if ($config.ContainsKey("VOIDBOT_OFFSITE_BACKUP_MAX_AGE_HOURS")) { [double]$config["VOIDBOT_OFFSITE_BACKUP_MAX_AGE_HOURS"] } else { 36.0 } +$offsiteBackupEnabled = Test-ConfigEnabled -Config $config -Key "OFFSITE_BACKUP_ENABLED" -Default $true $notifyRepeatHours = if ($config.ContainsKey("VOIDBOT_HEALTHCHECK_NOTIFY_REPEAT_HOURS")) { [double]$config["VOIDBOT_HEALTHCHECK_NOTIFY_REPEAT_HOURS"] } else { 12.0 } $stackStartupTaskName = if ($config.ContainsKey("VOIDBOT_STACK_STARTUP_TASK_NAME") -and -not [string]::IsNullOrWhiteSpace($config["VOIDBOT_STACK_STARTUP_TASK_NAME"])) { $config["VOIDBOT_STACK_STARTUP_TASK_NAME"] } else { "VoidBot Stack Startup" } $watchdogTaskName = if ($config.ContainsKey("VOIDBOT_HEALTHCHECK_TASK_NAME") -and -not [string]::IsNullOrWhiteSpace($config["VOIDBOT_HEALTHCHECK_TASK_NAME"])) { $config["VOIDBOT_HEALTHCHECK_TASK_NAME"] } else { "VoidBot Operations Watchdog" } @@ -1252,14 +1279,27 @@ try { try { $offsiteTask = Get-ScheduledTask -TaskName $offsiteTaskName -ErrorAction Stop $offsiteEnabled = [bool]$offsiteTask.Settings.Enabled - $offsiteTaskStatus = if ($offsiteEnabled) { "passed" } else { "failed" } - $offsiteTaskDetail = if ($offsiteEnabled) { "Offsite backup task is installed and enabled." } else { "Offsite backup task is installed but disabled." } + $offsiteTaskStatus = if ($offsiteBackupEnabled) { + if ($offsiteEnabled) { "passed" } else { "failed" } + } else { + "passed" + } + $offsiteTaskDetail = if ($offsiteBackupEnabled) { + if ($offsiteEnabled) { "Offsite backup task is installed and enabled." } else { "Offsite backup task is installed but disabled." } + } else { + "Offsite backup is disabled by OFFSITE_BACKUP_ENABLED=false; scheduled task state is ignored." + } Add-Check -Name "scheduled_task.offsite_backup" -Status $offsiteTaskStatus -Detail $offsiteTaskDetail -Data @{ taskName = $offsiteTaskName state = [string]$offsiteTask.State + offsiteBackupEnabled = $offsiteBackupEnabled } } catch { - Add-Check -Name "scheduled_task.offsite_backup" -Status "failed" -Detail "Offsite backup task $offsiteTaskName is not installed." + if ($offsiteBackupEnabled) { + Add-Check -Name "scheduled_task.offsite_backup" -Status "failed" -Detail "Offsite backup task $offsiteTaskName is not installed." + } else { + Add-Check -Name "scheduled_task.offsite_backup" -Status "passed" -Detail "Offsite backup is disabled by OFFSITE_BACKUP_ENABLED=false; no scheduled task is required." + } } try { @@ -1285,7 +1325,11 @@ try { Add-Check -Name "scheduled_task.watchdog" -Status "warning" -Detail "Operations watchdog task $watchdogTaskName is not installed yet." } - if (Test-Path -LiteralPath $offsiteStatusPath) { + if (-not $offsiteBackupEnabled) { + Add-Check -Name "offsite.status_file" -Status "passed" -Detail "Offsite backup status check skipped because OFFSITE_BACKUP_ENABLED=false." -Data @{ + path = $offsiteStatusPath + } + } elseif (Test-Path -LiteralPath $offsiteStatusPath) { try { $offsiteStatus = Get-Content -LiteralPath $offsiteStatusPath -Raw | ConvertFrom-Json @@ -1307,7 +1351,10 @@ try { } Set-WatchdogStep -CurrentStep "offsite_remote_checks" -Detail "Checking remote offsite backup freshness and retention." - if ([string]::IsNullOrWhiteSpace($offsiteTarget)) { + if (-not $offsiteBackupEnabled) { + Add-Check -Name "offsite.remote" -Status "passed" -Detail "Remote offsite backup check skipped because OFFSITE_BACKUP_ENABLED=false." + Add-Check -Name "offsite.retention" -Status "passed" -Detail "Remote offsite retention check skipped because OFFSITE_BACKUP_ENABLED=false." + } elseif ([string]::IsNullOrWhiteSpace($offsiteTarget)) { Add-Check -Name "offsite.remote" -Status "warning" -Detail "Remote offsite backup check skipped because OFFSITE_BACKUP_SSH_TARGET is blank." } else { try { diff --git a/scripts/sync-voidbot-backup-offsite.ps1 b/scripts/sync-voidbot-backup-offsite.ps1 index 0ddee0a..93faf30 100644 --- a/scripts/sync-voidbot-backup-offsite.ps1 +++ b/scripts/sync-voidbot-backup-offsite.ps1 @@ -118,6 +118,32 @@ function Stop-LogCapture { } } +function Test-ConfigEnabled { + param( + [Parameter(Mandatory = $true)] + [hashtable] $Config, + [Parameter(Mandatory = $true)] + [string] $Key, + [bool] $Default = $true + ) + + if (-not $Config.ContainsKey($Key)) { + return $Default + } + + $value = [string]$Config[$Key] + if ([string]::IsNullOrWhiteSpace($value)) { + return $false + } + + $normalized = $value.Trim().ToLowerInvariant() + if ($normalized -in @("0", "false", "no", "off", "disabled")) { + return $false + } + + return $true +} + function Convert-WindowsPathToSftpPath { param( [Parameter(Mandatory = $true)] @@ -389,6 +415,23 @@ $backupLabelValue = if (-not [string]::IsNullOrWhiteSpace($BackupLabel)) { "offsite-auto" } +$offsiteBackupEnabled = Test-ConfigEnabled -Config $config -Key "OFFSITE_BACKUP_ENABLED" -Default $true + +if (-not $offsiteBackupEnabled) { + $status = @{ + startedAt = (Get-Date).ToString("o") + completedAt = (Get-Date).ToString("o") + repoRoot = $repoRoot + status = "skipped" + statusPath = $statusPath + logPath = $logPath + reason = "OFFSITE_BACKUP_ENABLED=false" + } + Write-StatusFile -Path $statusPath -Status $status + Write-Host "Offsite backup skipped because OFFSITE_BACKUP_ENABLED=false." + exit 0 +} + if ([string]::IsNullOrWhiteSpace($sshTargetValue)) { throw "OFFSITE_BACKUP_SSH_TARGET is required for offsite backup sync." } diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 0a7cae3..cda454b 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -383,3 +383,4 @@ {"ts":"2026-06-06T08:58:51Z","type":"implementation","status":"ok","note":"Generalized Persona stress response seeding across the live swarm. scripts/seed-persona-stress-responses.mjs now writes one character-specific stress profile for every registered identity (Libby avoidant dissociation/submission, Aqua fawn-noise, Mimir severe withdrawal, Epiphany purity panic, etc.) while preserving Nibu's richer existing trigger map and Meita's Metacrat stress profiles. scripts/smoke-persona-stress-coverage.mjs verifies 17/17 identities have stress responses; Libby prompt assembly projects her avoidant collapse as character-facing pressure."} {"ts":"2026-06-06T09:12:38Z","type":"implementation","status":"ok","note":"Dispatched six Persona stress-profile research shards across 17 identities, imported shard-*.json outputs through reusable agents:apply-stress-shards tooling, replaced swarm-generalization sketch stress responses in live Persona state, and verified stress coverage plus Libby projector prompt assembly."} {"ts":"2026-06-06T11:50:49Z","type":"implementation","status":"ok","note":"Renamed native Persona identity from the obsolete Meita label to stable id metame with public native name メタメ; moved private .cc state to .voidbot/private/personas/metame/metame.cc, repaired .cc selfProfile agentId/publicName, updated live registry and heartbeat heat override, and changed future stress seeding to metame."} +{"ts":"2026-06-06T12:38:42Z","type":"implementation","status":"ok","note":"Cut Raven out of VoidBot's active stack: disabled Raven-backed local_llm provider and offsite backup in private config, taught operations checks/offsite sync to treat OFFSITE_BACKUP_ENABLED=false as an intentional skip, disabled the scheduled offsite task, restarted the stack, and verified operations health healthy with Raven checks skipped."} diff --git a/state/map.yaml b/state/map.yaml index f8ca660..80c78a1 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -75,7 +75,7 @@ accepted_design: - Agents and scheduled runners should cross the private-state boundary with small typed mutation operations such as cursor updates, open-case upserts, receipt records, short-term memory records, sleep-owned distillations, incubation merges, agency-pressure changes, and candidate-intervention changes. - JSON may be used for operation payloads or debug exports, but not as an editable working projection of canonical self-state. - Deterministic cleanup code should not become a permanent organ just because recent manual cleanup requests accidentally produced compensators. The desired architecture prevents bad state from entering, then uses narrow migrations for historical debris. - - `owner_codex` and `local_llm` remain distinct lanes with different tool-execution and safety posture. + - `owner_codex` is the active reply lane; the old Raven-backed `local_llm` lane is disabled until a live local inference host is deliberately reinstated. - Ephemeral room-reading should come from an explicit local-LLM sidecar inference pass plus raw recent-message evidence, not from a rules bucket pretending to understand natural language. - The VoidBot MCP server is the preferred search plane for archived Discord, indexed GameCult repos, Aetheria lore, and owner notifications. - Bot-directed prompts should remain in the raw Discord archive for forensics, but default history indexing and retrieval should treat them as toxic self-reference rather than ordinary community memory. @@ -196,7 +196,7 @@ landed_baseline: - Discord bot, worker, provider registry, and retrieval stack are live. - Postgres-backed jobs, audit events, interaction memory, and rate-limit state are live. - Qdrant-backed history and source retrieval are live. - - Local and offsite backups, watchdogs, and the operations dashboard are live. + - Local backups, watchdogs, and the operations dashboard are live; Raven-backed offsite backup is explicitly disabled because the host is not a reliable stack dependency. - The stack now has a dedicated logon startup task path so bot and worker can come back after a reboot without waiting for manual resurrection. - Interaction memory and behavioral profiling are live. - Tool-driven retrieval is live, and source-grounding hints now bias the reply lanes instead of hard-gating them with a lexical pre-classifier.