Add startup task for stack resurrection

This commit is contained in:
Meta Vens 2026-04-30 16:41:44 +01:00
commit fe05427159
10 changed files with 109 additions and 2 deletions

View file

@ -68,5 +68,6 @@ OFFSITE_BACKUP_TASK_TIME=04:30
VOIDBOT_BACKUP_MAX_AGE_HOURS=30
VOIDBOT_OFFSITE_BACKUP_MAX_AGE_HOURS=36
VOIDBOT_HEALTHCHECK_NOTIFY_REPEAT_HOURS=12
VOIDBOT_STACK_STARTUP_TASK_NAME=VoidBot Stack Startup
VOIDBOT_HEALTHCHECK_TASK_NAME=VoidBot Operations Watchdog
VOIDBOT_HEALTHCHECK_INTERVAL_MINUTES=60

View file

@ -193,6 +193,14 @@ That script:
- writes runtime status to `.voidbot/status/runtime-stack.json`
- writes logs to `.voidbot/logs/bot.log` and `.voidbot/logs/worker.log`
If you do not want a reboot or fresh logon to leave the bot dead on the floor, install the startup task once:
```bash
npm run state:install-startup-task
```
That creates the `VoidBot Stack Startup` scheduled task, which runs `scripts/start-voidbot-stack.ps1` at logon through the same hidden launcher shim the other ops tasks use.
If you want the raw split-terminal path for debugging, those still exist:
```bash

View file

@ -26,6 +26,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
- Interaction-memory logic is no longer one thousand-line slab; event/tone analysis, shared constants, and profile synthesis are split across `packages/core/src/interaction-memory-*.ts`.
- 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.
- Ops health, backup verification, offsite sync, and dashboard surfaces already exist.
- 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.
- The new state/notes surfaces are now the continuity spine for future nontrivial work.

View file

@ -85,6 +85,8 @@ This note is the source-grounded description of how the live VoidBot stack is sh
- `scripts/start-voidbot-stack.ps1`
- stack bootstrap, health checks, fresh build, stale-process cleanup, bot/worker restart, runtime status emission.
- `scripts/install-stack-startup-task.ps1`
- installs the hidden logon task that runs `start-voidbot-stack.ps1` automatically after reboot or sign-in.
- `scripts/check-voidbot-operations.ps1`
- watchdog for process liveness, Qdrant, Postgres, Ollama, Discord auth, backup freshness, offsite sync freshness, and optional ignored local extension checks.
- `scripts/backup-voidbot-state.ps1`

View file

@ -18,6 +18,7 @@ Use the persistent project-state surfaces to keep future work source-grounded an
- add a real restore-drill cadence instead of stopping at backup verification
- reduce dependence on interactive workstation logon where practical
- keep the stack startup task installed and visible so reboots do not quietly leave runtime dead
- keep watchdog signals and dashboard diagnostics honest when dependencies are degraded
- keep adjacent-service checks behind private local extensions so VoidBot does not quietly mutate into a generic infra panic funnel

View file

@ -22,6 +22,7 @@
"state:dashboard": "powershell -ExecutionPolicy Bypass -File scripts/render-voidbot-operations-dashboard.ps1 -Open",
"state:render-dashboard": "powershell -ExecutionPolicy Bypass -File scripts/render-voidbot-operations-dashboard.ps1",
"state:sync-offsite": "powershell -ExecutionPolicy Bypass -File scripts/sync-voidbot-backup-offsite.ps1",
"state:install-startup-task": "powershell -ExecutionPolicy Bypass -File scripts/install-stack-startup-task.ps1",
"state:install-offsite-task": "powershell -ExecutionPolicy Bypass -File scripts/install-offsite-backup-task.ps1",
"state:install-watchdog-task": "powershell -ExecutionPolicy Bypass -File scripts/install-operations-watchdog-task.ps1",
"postgres:up": "docker compose -f infra/postgres/docker-compose.yml up -d",

View file

@ -546,6 +546,7 @@ $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 }
$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" }
$offsiteTaskName = if ($config.ContainsKey("OFFSITE_BACKUP_TASK_NAME") -and -not [string]::IsNullOrWhiteSpace($config["OFFSITE_BACKUP_TASK_NAME"])) { $config["OFFSITE_BACKUP_TASK_NAME"] } else { "VoidBot Offsite Backup" }
$offsiteRemoteWindowsDir = if ($config.ContainsKey("OFFSITE_BACKUP_REMOTE_WINDOWS_DIR") -and -not [string]::IsNullOrWhiteSpace($config["OFFSITE_BACKUP_REMOTE_WINDOWS_DIR"])) { $config["OFFSITE_BACKUP_REMOTE_WINDOWS_DIR"] } else { "C:\Meta\voidbot-backups" }
@ -865,6 +866,19 @@ try {
}
Set-WatchdogStep -CurrentStep "scheduled_task_checks" -Detail "Inspecting scheduled task installation and status files."
try {
$startupTask = Get-ScheduledTask -TaskName $stackStartupTaskName -ErrorAction Stop
$startupEnabled = [bool]$startupTask.Settings.Enabled
$startupTaskStatus = if ($startupEnabled) { "passed" } else { "failed" }
$startupTaskDetail = if ($startupEnabled) { "Stack startup task is installed and enabled." } else { "Stack startup task is installed but disabled." }
Add-Check -Name "scheduled_task.stack_startup" -Status $startupTaskStatus -Detail $startupTaskDetail -Data @{
taskName = $stackStartupTaskName
state = [string]$startupTask.State
}
} catch {
Add-Check -Name "scheduled_task.stack_startup" -Status "warning" -Detail "Stack startup task $stackStartupTaskName is not installed yet."
}
try {
$offsiteTask = Get-ScheduledTask -TaskName $offsiteTaskName -ErrorAction Stop
$offsiteEnabled = [bool]$offsiteTask.Settings.Enabled

View file

@ -0,0 +1,77 @@
param(
[string] $TaskName,
[switch] $RunNow
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
$envFile = Join-Path $repoRoot ".env"
$startScript = Join-Path $PSScriptRoot "start-voidbot-stack.ps1"
$hiddenLauncher = Join-Path $PSScriptRoot "run-hidden-powershell.vbs"
function Read-DotEnv {
param(
[Parameter(Mandatory = $true)]
[string] $Path
)
if (-not (Test-Path $Path)) {
throw "Missing .env at $Path"
}
$values = @{}
foreach ($line in Get-Content -Path $Path) {
if ([string]::IsNullOrWhiteSpace($line)) {
continue
}
if ($line.TrimStart().StartsWith("#")) {
continue
}
$separatorIndex = $line.IndexOf("=")
if ($separatorIndex -lt 1) {
continue
}
$key = $line.Substring(0, $separatorIndex).Trim()
$value = $line.Substring($separatorIndex + 1)
$values[$key] = $value
}
return $values
}
$config = Read-DotEnv -Path $envFile
$taskNameValue = if (-not [string]::IsNullOrWhiteSpace($TaskName)) {
$TaskName
} elseif ($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"
}
$action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "//B //nologo `"$hiddenLauncher`" -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$startScript`""
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 30)
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
$description = "Starts the VoidBot stack when this workstation user logs on so bot and worker do not stay dead after a reboot."
Register-ScheduledTask -TaskName $taskNameValue -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Description $description -Force | Out-Null
Write-Host "Installed scheduled task: $taskNameValue"
Write-Host "Trigger: at logon for $($env:USERDOMAIN)\$($env:USERNAME)"
Write-Host "Task runs as: $($env:USERDOMAIN)\$($env:USERNAME)"
Write-Host "Logon mode: Interactive"
Write-Host "Launcher: wscript.exe hidden PowerShell shim"
Write-Host "Execution time limit: 30 minutes"
if ($RunNow) {
Start-ScheduledTask -TaskName $taskNameValue
Write-Host "Started task immediately."
}

View file

@ -8,3 +8,4 @@
{"ts":"2026-04-29T17:36:43Z","type":"implementation","status":"ok","note":"Operations watchdog now checks Repixelizer public health plus recent Yggdrasil journald pressure signals and routes queue-full, job-failed, stale-cancel, and slow-job issues through the existing owner DM notification path."}
{"ts":"2026-04-29T18:20:00+01:00","type":"architecture","status":"ok","note":"Adjacent-service fire signals now load through an ignored local watchdog extension script instead of living in tracked VoidBot code, so the owner alert path stays useful without baking private cross-project monitoring into the public repo."}
{"ts":"2026-04-29T19:06:00+01:00","type":"implementation","status":"ok","note":"The watchdog owner-DM path was failing on Windows because Discord JSON bodies were being shoved through curl command-line quoting and arriving malformed; writing the JSON to a temp file and posting it with --data-binary fixed the alert path."}
{"ts":"2026-04-30T00:00:00+01:00","type":"operations","status":"ok","note":"A reboot proved bot and worker would stay dead without a dedicated startup task; the stack now has a logon task installer and the watchdog checks that the startup task remains installed and enabled."}

View file

@ -54,7 +54,7 @@ data_flow:
active_subgoals:
- Make the new project-state surfaces the default continuity path for nontrivial VoidBot work.
- Keep public-lane limits, moderation, and provider behavior constrained enough that one 4060 Ti box does not get flayed for sport.
- Preserve boring observability around Postgres, Qdrant, Ollama, Docker, backups, watchdogs, and any private local fire-signal extensions.
- Preserve boring observability and startup discipline around Postgres, Qdrant, Ollama, Docker, backups, scheduled tasks, watchdogs, and any private local fire-signal extensions.
- Continue splitting oversized runtime files where concern boundaries are already obvious, starting with the remaining bot, worker, and memory-policy monarchies.
accepted_design:
@ -71,6 +71,7 @@ landed_baseline:
- 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.
- 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.
- Source-grounding enforcement and tool-driven retrieval are live.
- The operations watchdog can now load ignored local extension checks, so adjacent-service fire signals can piggyback on the owner DM path without turning the public repo into cross-project gossip.
@ -88,5 +89,5 @@ rejected_paths:
reason: vibes produce plausible slop that sounds smart and lies with a straight face
current_status:
summary: VoidBot now has a real project-memory spine plus four legibility-hardening passes on major runtime organs. The owner Codex lane, Discord bot lane, interaction-memory logic, and state-storage persistence layer were all split into smaller seams, and the ops spine now supports ignored local watchdog extensions for adjacent fire signals without baking private infrastructure gossip into the public repo. The stack is still dense, but it no longer depends purely on transcript memory or one-file monarchies to stay coherent.
summary: VoidBot now has a real project-memory spine plus four legibility-hardening passes on major runtime organs. The owner Codex lane, Discord bot lane, interaction-memory logic, and state-storage persistence layer were all split into smaller seams, and the ops spine now includes both ignored local watchdog extensions for adjacent fire signals and a dedicated logon startup task path for stack resurrection after reboot. The stack is still dense, but it no longer depends purely on transcript memory or one-file monarchies to stay coherent.
next_action: Keep using the canonical state files for re-entry, then continue decomposing the next oversized organ with the clearest concern boundaries, likely the MCP server or local LLM provider.