Delete local Void mood and memory runners

This commit is contained in:
Meta Vens 2026-07-16 06:03:14 +01:00
commit 04dc8e3303
16 changed files with 47 additions and 2408 deletions

View file

@ -1,89 +0,0 @@
param(
[string] $TaskName,
[int] $IntervalMinutes,
[switch] $RunNow
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
$envFile = Join-Path $repoRoot ".env"
$runnerScript = Join-Path $PSScriptRoot "run-void-mood-drift.ps1"
$hiddenLauncher = Join-Path $PSScriptRoot "run-hidden-powershell.vbs"
function Read-DotEnv {
param(
[Parameter(Mandatory = $true)]
[string] $Path
)
$values = @{}
if (-not (Test-Path $Path)) {
return $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_MOOD_TASK_NAME") -and -not [string]::IsNullOrWhiteSpace($config["VOIDBOT_MOOD_TASK_NAME"])) {
$config["VOIDBOT_MOOD_TASK_NAME"]
} else {
"Void Mood Drift"
}
$intervalMinutesValue = if ($PSBoundParameters.ContainsKey("IntervalMinutes")) {
$IntervalMinutes
} elseif ($config.ContainsKey("VOIDBOT_MOOD_INTERVAL_MINUTES") -and -not [string]::IsNullOrWhiteSpace($config["VOIDBOT_MOOD_INTERVAL_MINUTES"])) {
[int]$config["VOIDBOT_MOOD_INTERVAL_MINUTES"]
} else {
5
}
if ($intervalMinutesValue -lt 5) {
throw "VOIDBOT_MOOD_INTERVAL_MINUTES must be at least 5."
}
$startAt = (Get-Date).AddMinutes(1)
$action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "//B //nologo `"$hiddenLauncher`" -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$runnerScript`""
$trigger = New-ScheduledTaskTrigger -Once -At $startAt -RepetitionInterval (New-TimeSpan -Minutes $intervalMinutesValue) -RepetitionDuration (New-TimeSpan -Days 3650)
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 3) -MultipleInstances IgnoreNew
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
$description = "Runs Void's background mood-drift pass every $intervalMinutesValue minutes so current activations and speaking pressure do not stay emotionally flat between moderation loops."
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 "Schedule: every $intervalMinutesValue minute(s), starting at $($startAt.ToString('yyyy-MM-dd HH:mm'))"
Write-Host "Task runs as: $($env:USERDOMAIN)\$($env:USERNAME)"
Write-Host "Execution time limit: 3 minutes"
Write-Host "Runner script: $runnerScript"
if ($RunNow) {
Start-ScheduledTask -TaskName $taskNameValue
Write-Host "Started task immediately."
}

View file

@ -1,744 +0,0 @@
Set-StrictMode -Version Latest
function Convert-ToValueArray {
param($Value)
if ($null -eq $Value) {
return @()
}
if ($Value -is [System.Array]) {
return @($Value)
}
return @($Value)
}
function Get-ObjectPropertyValue {
param(
$Value,
[Parameter(Mandatory = $true)]
[string] $Name
)
if ($null -eq $Value) {
return $null
}
if ($Value -is [System.Collections.IDictionary]) {
if ($Value.Contains($Name)) {
return $Value[$Name]
}
return $null
}
$property = $Value.PSObject.Properties[$Name]
if ($null -eq $property) {
return $null
}
return $property.Value
}
function Get-ObjectPropertyString {
param(
$Value,
[Parameter(Mandatory = $true)]
[string] $Name
)
$value = Get-ObjectPropertyValue -Value $Value -Name $Name
if ($null -eq $value) {
return $null
}
$text = [string]$value
if ([string]::IsNullOrWhiteSpace($text)) {
return $null
}
return $text
}
function Format-RelativeTime {
param(
[string] $Value,
[Parameter(Mandatory = $true)]
[DateTime] $Now
)
if ([string]::IsNullOrWhiteSpace($Value)) {
return $null
}
$timestamp = [DateTime]::MinValue
if (-not [DateTime]::TryParse($Value, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::AssumeUniversal, [ref]$timestamp)) {
return $null
}
$timestampUtc = $timestamp.ToUniversalTime()
$delta = $timestampUtc - $Now.ToUniversalTime()
$future = $delta.TotalSeconds -gt 0
$seconds = [Math]::Abs($delta.TotalSeconds)
if ($seconds -lt 45) {
return "just now"
}
if ($seconds -lt 5400) {
$minutes = [Math]::Max(1, [int][Math]::Round($seconds / 60))
$unit = if ($minutes -eq 1) { "minute" } else { "minutes" }
$phrase = "$minutes $unit"
} elseif ($seconds -lt 129600) {
$hours = [Math]::Max(1, [int][Math]::Round($seconds / 3600))
$unit = if ($hours -eq 1) { "hour" } else { "hours" }
$phrase = "$hours $unit"
} elseif ($seconds -lt 1814400) {
$days = [Math]::Max(1, [int][Math]::Round($seconds / 86400))
$unit = if ($days -eq 1) { "day" } else { "days" }
$phrase = "$days $unit"
} elseif ($seconds -lt 6048000) {
$weeks = [Math]::Max(1, [int][Math]::Round($seconds / 604800))
$unit = if ($weeks -eq 1) { "week" } else { "weeks" }
$phrase = "$weeks $unit"
} elseif ($seconds -lt 47304000) {
$months = [Math]::Max(1, [int][Math]::Round($seconds / 2592000))
$unit = if ($months -eq 1) { "month" } else { "months" }
$phrase = "$months $unit"
} else {
$years = [Math]::Max(1, [int][Math]::Round($seconds / 31536000))
$unit = if ($years -eq 1) { "year" } else { "years" }
$phrase = "$years $unit"
}
if ($future) {
return "in $phrase"
}
return "$phrase ago"
}
function Project-RelativeTimestamp {
param(
$Value,
[string] $Name,
[DateTime] $Now
)
return Format-RelativeTime -Value (Get-ObjectPropertyString -Value $Value -Name $Name) -Now $Now
}
function Project-RecentHistoryForRumination {
param($History, [DateTime] $Now)
$messages = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $History -Name "messages"))
return @{
source = "discord_history"
messageCount = [int]$messages.Count
messages = @(
$messages | ForEach-Object {
@{
id = Get-ObjectPropertyString -Value $_ -Name "id"
channelId = Get-ObjectPropertyString -Value $_ -Name "channelId"
authorId = Get-ObjectPropertyString -Value $_ -Name "authorId"
authorName = Get-ObjectPropertyString -Value $_ -Name "authorName"
isBot = [bool](Get-ObjectPropertyValue -Value $_ -Name "isBot")
when = Project-RelativeTimestamp -Value $_ -Name "timestamp" -Now $Now
content = Get-ObjectPropertyString -Value $_ -Name "content"
}
}
)
}
}
function Project-RecentConversationTargetForRumination {
param(
$History,
[string] $PersonaName,
[string] $PersonaAvatarUrl
)
$messages = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $History -Name "messages"))
if ($messages.Count -eq 0) {
return $null
}
$anchor = $null
for ($index = $messages.Count - 1; $index -ge 0; $index--) {
$candidate = $messages[$index]
$channelId = Get-ObjectPropertyString -Value $candidate -Name "channelId"
if ([string]::IsNullOrWhiteSpace($channelId)) {
continue
}
if (-not [bool](Get-ObjectPropertyValue -Value $candidate -Name "isBot")) {
$anchor = $candidate
break
}
if ($null -eq $anchor) {
$anchor = $candidate
}
}
if ($null -eq $anchor) {
return $null
}
$target = @{
mode = "channel"
channelId = Get-ObjectPropertyString -Value $anchor -Name "channelId"
replyToMessageId = Get-ObjectPropertyString -Value $anchor -Name "id"
personaName = if ([string]::IsNullOrWhiteSpace($PersonaName)) { "Void" } else { $PersonaName.Trim() }
reason = "Latest concrete recent-history conversation anchor. Use this instead of the configured public room when replying to that conversation."
}
if (-not [string]::IsNullOrWhiteSpace($PersonaAvatarUrl)) {
$target.personaAvatarUrl = $PersonaAvatarUrl.Trim()
}
return $target
}
function Get-UrlsFromText {
param([string] $Text)
if ([string]::IsNullOrWhiteSpace($Text)) {
return @()
}
return @(
[regex]::Matches($Text, "https?://[^\s<>()]+") |
ForEach-Object { $_.Value.TrimEnd(".", ",", "!", "?", ":", ";", ")", "]") } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
)
}
function Resolve-GameCultUrlSource {
param([string] $Url)
if ([string]::IsNullOrWhiteSpace($Url)) {
return $null
}
$uri = $null
if (-not [System.Uri]::TryCreate($Url, [System.UriKind]::Absolute, [ref]$uri)) {
return $null
}
$uriHost = $uri.Host.ToLowerInvariant()
if ($uriHost -notin @("gamecult.org", "www.gamecult.org", "gamecult.games", "www.gamecult.games")) {
return $null
}
$segments = @(
$uri.AbsolutePath.Trim("/") -split "/" |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
ForEach-Object { [System.Uri]::UnescapeDataString($_) }
)
if ($segments.Count -lt 2 -or $segments[0].ToLowerInvariant() -ne "blog") {
return @{
kind = "gamecult_url"
host = $uriHost
path = $uri.AbsolutePath
}
}
$slugParts = @($segments | Select-Object -Skip 1)
$relativePath = "GameCult/Blog/$($slugParts -join "/")"
$markdownPath = "$relativePath.md"
if ($slugParts.Count -gt 1) {
$markdownPath = "$relativePath/index.md"
}
return @{
kind = "gamecult_blog"
host = $uriHost
slug = ($slugParts -join "/")
repoName = "gamecult-site"
path = $markdownPath
sourceRef = "gamecult-site:$markdownPath"
}
}
function Project-RecentRoomLinksForRumination {
param(
$History,
[DateTime] $Now,
[string] $MinTimestampInclusive,
[object[]] $ReceiptReplyMessageIds
)
$messages = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $History -Name "messages"))
$answeredMessageIds = [System.Collections.Generic.HashSet[string]]::new()
foreach ($replyMessageId in @(Convert-ToValueArray -Value $ReceiptReplyMessageIds)) {
if (-not [string]::IsNullOrWhiteSpace([string]$replyMessageId)) {
[void]$answeredMessageIds.Add([string]$replyMessageId)
}
}
$minTimestampMs = $null
if (-not [string]::IsNullOrWhiteSpace($MinTimestampInclusive)) {
$parsedMinTimestamp = [DateTime]::MinValue
if ([DateTime]::TryParse($MinTimestampInclusive, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::AssumeUniversal, [ref]$parsedMinTimestamp)) {
$minTimestampMs = $parsedMinTimestamp.ToUniversalTime()
}
}
$items = @()
foreach ($message in $messages) {
if ([bool](Get-ObjectPropertyValue -Value $message -Name "isBot")) {
continue
}
$messageId = Get-ObjectPropertyString -Value $message -Name "id"
if (-not [string]::IsNullOrWhiteSpace($messageId) -and $answeredMessageIds.Contains($messageId)) {
continue
}
$cursorAlreadyPassed = $false
if ($null -ne $minTimestampMs) {
$messageTimestamp = [DateTime]::MinValue
$rawTimestamp = Get-ObjectPropertyString -Value $message -Name "timestamp"
if (
[string]::IsNullOrWhiteSpace($rawTimestamp) -or
-not [DateTime]::TryParse($rawTimestamp, [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::AssumeUniversal, [ref]$messageTimestamp)
) {
continue
}
$messageTimestampUtc = $messageTimestamp.ToUniversalTime()
if ($messageTimestampUtc -lt $minTimestampMs) {
if ($minTimestampMs -gt $Now.ToUniversalTime().AddMinutes(1)) {
continue
}
$cursorAlreadyPassed = $true
}
}
$content = Get-ObjectPropertyString -Value $message -Name "content"
$urls = @(Get-UrlsFromText -Text $content)
if ($urls.Count -eq 0) {
continue
}
foreach ($url in $urls) {
$items += @{
kind = "naked_or_embedded_url"
id = $messageId
channelId = Get-ObjectPropertyString -Value $message -Name "channelId"
authorId = Get-ObjectPropertyString -Value $message -Name "authorId"
authorName = Get-ObjectPropertyString -Value $message -Name "authorName"
when = Project-RelativeTimestamp -Value $message -Name "timestamp" -Now $Now
content = $content
url = $url
resolvedSource = Resolve-GameCultUrlSource -Url $url
carryForwardReason = if ($cursorAlreadyPassed) { "safety cursor already reviewed this message, but no speech receipt answers the URL object yet" } else { "at or after current reviewed cursor" }
attentionContract = "A human posting a naked URL is still presenting an object to inspect. Treat it as room context, not as empty speech."
}
}
}
return @{
source = "recent_discord_url_scan"
itemCount = [int]$items.Count
lookback = Get-ObjectPropertyValue -Value $History -Name "hours"
items = $items
}
}
function Project-OpenCasesForRumination {
param($Cases, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Cases) |
Where-Object {
-not (Test-RuminationCaseTerminal -Status (Get-ObjectPropertyString -Value $_ -Name "status"))
} |
ForEach-Object {
@{
sourceMessageId = Get-ObjectPropertyString -Value $_ -Name "sourceMessageId"
status = Get-ObjectPropertyString -Value $_ -Name "status"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
authorName = Get-ObjectPropertyString -Value $_ -Name "authorName"
channelId = Get-ObjectPropertyString -Value $_ -Name "channelId"
messageUrl = Get-ObjectPropertyString -Value $_ -Name "messageUrl"
whyItMatters = Get-ObjectPropertyString -Value $_ -Name "whyItMatters"
created = Project-RelativeTimestamp -Value $_ -Name "createdAt" -Now $Now
lastTouched = Project-RelativeTimestamp -Value $_ -Name "lastTouchedAt" -Now $Now
resolved = Project-RelativeTimestamp -Value $_ -Name "resolvedAt" -Now $Now
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
}
}
)
}
function Test-RuminationCaseTerminal {
param([string] $Status)
return @("answered", "resolved", "closed", "retired", "dropped").Contains($Status)
}
function Project-SpeechReceiptsForRumination {
param($Receipts, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Receipts) | ForEach-Object {
@{
receiptKey = Get-ObjectPropertyString -Value $_ -Name "receiptKey"
candidateInterventionId = Get-ObjectPropertyString -Value $_ -Name "candidateInterventionId"
sent = Project-RelativeTimestamp -Value $_ -Name "sentAt" -Now $Now
mode = Get-ObjectPropertyString -Value $_ -Name "mode"
transport = Get-ObjectPropertyString -Value $_ -Name "transport"
channelId = Get-ObjectPropertyString -Value $_ -Name "channelId"
replyToMessageId = Get-ObjectPropertyString -Value $_ -Name "replyToMessageId"
preview = Get-ObjectPropertyString -Value $_ -Name "preview"
}
}
)
}
function Project-MemoriesForRumination {
param($Memories, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Memories) | ForEach-Object {
@{
memoryId = Get-ObjectPropertyString -Value $_ -Name "memoryId"
kind = Get-ObjectPropertyString -Value $_ -Name "kind"
target = Get-ObjectPropertyValue -Value $_ -Name "target"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
claim = Get-ObjectPropertyString -Value $_ -Name "claim"
question = Get-ObjectPropertyString -Value $_ -Name "question"
tension = Get-ObjectPropertyString -Value $_ -Name "tension"
actionImplication = Get-ObjectPropertyString -Value $_ -Name "actionImplication"
created = Project-RelativeTimestamp -Value $_ -Name "createdAt" -Now $Now
updated = Project-RelativeTimestamp -Value $_ -Name "updatedAt" -Now $Now
retired = Project-RelativeTimestamp -Value $_ -Name "retiredAt" -Now $Now
anchorRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "anchorRefs"))
evidenceRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "evidenceRefs"))
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
}
}
)
}
function Project-IncubationForRumination {
param($Threads, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Threads) | ForEach-Object {
@{
threadId = Get-ObjectPropertyString -Value $_ -Name "threadId"
target = Get-ObjectPropertyValue -Value $_ -Name "target"
topic = Get-ObjectPropertyString -Value $_ -Name "topic"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
supportMemoryIds = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "supportMemoryIds"))
anchorRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "anchorRefs"))
evidenceRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "evidenceRefs"))
maturation = Get-ObjectPropertyValue -Value $_ -Name "maturation"
noveltyToRoom = Get-ObjectPropertyValue -Value $_ -Name "noveltyToRoom"
noveltyToSelf = Get-ObjectPropertyValue -Value $_ -Name "noveltyToSelf"
desireToSpeak = Get-ObjectPropertyValue -Value $_ -Name "desireToSpeak"
saturationScore = Get-ObjectPropertyValue -Value $_ -Name "saturationScore"
status = Get-ObjectPropertyString -Value $_ -Name "status"
created = Project-RelativeTimestamp -Value $_ -Name "createdAt" -Now $Now
updated = Project-RelativeTimestamp -Value $_ -Name "updatedAt" -Now $Now
}
}
)
}
function Project-InterventionsForRumination {
param($Interventions, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Interventions) | ForEach-Object {
@{
interventionId = Get-ObjectPropertyString -Value $_ -Name "interventionId"
kind = Get-ObjectPropertyString -Value $_ -Name "kind"
status = Get-ObjectPropertyString -Value $_ -Name "status"
target = Get-ObjectPropertyValue -Value $_ -Name "target"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
draft = Get-ObjectPropertyString -Value $_ -Name "draft"
priority = Get-ObjectPropertyValue -Value $_ -Name "priority"
mustEventuallyShare = Get-ObjectPropertyValue -Value $_ -Name "mustEventuallyShare"
created = Project-RelativeTimestamp -Value $_ -Name "createdAt" -Now $Now
updated = Project-RelativeTimestamp -Value $_ -Name "updatedAt" -Now $Now
spoken = Project-RelativeTimestamp -Value $_ -Name "spokenAt" -Now $Now
retired = Project-RelativeTimestamp -Value $_ -Name "retiredAt" -Now $Now
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
}
}
)
}
function Project-PublicSpeechTargetForRumination {
param(
[string] $ChannelId,
[string] $PersonaName,
[string] $PersonaAvatarUrl
)
if ([string]::IsNullOrWhiteSpace($ChannelId)) {
return $null
}
$target = @{
mode = "channel"
channelId = $ChannelId.Trim()
personaName = if ([string]::IsNullOrWhiteSpace($PersonaName)) { "Void" } else { $PersonaName.Trim() }
reason = "Configured public room for Void to present self-authored public artifacts and other ripe public thoughts."
}
if (-not [string]::IsNullOrWhiteSpace($PersonaAvatarUrl)) {
$target.personaAvatarUrl = $PersonaAvatarUrl.Trim()
}
return $target
}
function Project-AgencyPressureForRumination {
param($Pressures, [DateTime] $Now)
return @(
@(Convert-ToValueArray -Value $Pressures) | ForEach-Object {
@{
pressureId = Get-ObjectPropertyString -Value $_ -Name "pressureId"
kind = Get-ObjectPropertyString -Value $_ -Name "kind"
status = Get-ObjectPropertyString -Value $_ -Name "status"
target = Get-ObjectPropertyValue -Value $_ -Name "target"
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
claim = Get-ObjectPropertyString -Value $_ -Name "claim"
question = Get-ObjectPropertyString -Value $_ -Name "question"
tension = Get-ObjectPropertyString -Value $_ -Name "tension"
actionImplication = Get-ObjectPropertyString -Value $_ -Name "actionImplication"
intensity = Get-ObjectPropertyValue -Value $_ -Name "intensity"
created = Project-RelativeTimestamp -Value $_ -Name "createdAt" -Now $Now
updated = Project-RelativeTimestamp -Value $_ -Name "updatedAt" -Now $Now
resolved = Project-RelativeTimestamp -Value $_ -Name "resolvedAt" -Now $Now
retired = Project-RelativeTimestamp -Value $_ -Name "retiredAt" -Now $Now
anchorRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "anchorRefs"))
evidenceRefs = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "evidenceRefs"))
sourceMemoryIds = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "sourceMemoryIds"))
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
}
}
)
}
function Project-ScheduledRuntimeForRumination {
param($Runtime, [DateTime] $Now)
$sleepCycle = Get-ObjectPropertyValue -Value $Runtime -Name "sleepCycle"
$speakingPressure = Get-ObjectPropertyValue -Value $Runtime -Name "speakingPressure"
$lastRuns = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Runtime -Name "lastRuns"))
return @{
sleepCycle = @{
isNapping = [bool](Get-ObjectPropertyValue -Value $sleepCycle -Name "isNapping")
currentNapStarted = Project-RelativeTimestamp -Value $sleepCycle -Name "currentNapStartedAt" -Now $Now
currentNapEnds = Project-RelativeTimestamp -Value $sleepCycle -Name "currentNapEndsAt" -Now $Now
nextNapStarts = Project-RelativeTimestamp -Value $sleepCycle -Name "nextNapStartsAt" -Now $Now
activeDreamThemes = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $sleepCycle -Name "activeDreamThemes"))
}
speakingPressure = @{
needToSpeak = Get-ObjectPropertyValue -Value $speakingPressure -Name "needToSpeak"
confessionPressure = Get-ObjectPropertyValue -Value $speakingPressure -Name "confessionPressure"
noveltyPressure = Get-ObjectPropertyValue -Value $speakingPressure -Name "noveltyPressure"
recentSpeechDamping = Get-ObjectPropertyValue -Value $speakingPressure -Name "recentSpeechDamping"
lastSpoke = Project-RelativeTimestamp -Value $speakingPressure -Name "lastSpokeAt" -Now $Now
lastHerald = Project-RelativeTimestamp -Value $speakingPressure -Name "lastHeraldAt" -Now $Now
}
lastRuns = @(
$lastRuns | ForEach-Object {
@{
runner = Get-ObjectPropertyString -Value $_ -Name "runner"
ran = Project-RelativeTimestamp -Value $_ -Name "ranAt" -Now $Now
summary = Get-ObjectPropertyString -Value $_ -Name "summary"
}
}
)
}
}
function Project-CursorForRumination {
param($Cursor, [DateTime] $Now)
return @{
lastReviewedMessageId = Get-ObjectPropertyString -Value $Cursor -Name "lastReviewedMessageId"
lastReviewed = Project-RelativeTimestamp -Value $Cursor -Name "lastReviewedTimestamp" -Now $Now
}
}
function Get-VoidAuthoredArtifactsFromContentHints {
param($ContentHints)
return @(
@(Convert-ToValueArray -Value $ContentHints) |
Where-Object {
$frontmatter = Get-ObjectPropertyValue -Value $_ -Name "frontmatter"
$author = Get-ObjectPropertyValue -Value $frontmatter -Name "author"
@(Convert-ToValueArray -Value $author) |
Where-Object { ([string]$_).Trim().ToLowerInvariant() -eq "void" } |
Select-Object -First 1
} |
ForEach-Object {
$frontmatter = Get-ObjectPropertyValue -Value $_ -Name "frontmatter"
@{
path = Get-ObjectPropertyString -Value $_ -Name "path"
title = Get-ObjectPropertyString -Value $frontmatter -Name "title"
description = Get-ObjectPropertyString -Value $frontmatter -Name "description"
socialDeck = Get-ObjectPropertyString -Value $frontmatter -Name "socialDeck"
}
}
)
}
function Project-RepoCommitForRumination {
param($Commit, [DateTime] $Now)
if ($null -eq $Commit) {
return $null
}
$hash = Get-ObjectPropertyString -Value $Commit -Name "hash"
$contentHints = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Commit -Name "contentHints"))
$doctrineDocuments = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Commit -Name "doctrineDocuments"))
$authoredPublicArtifacts = @(Get-VoidAuthoredArtifactsFromContentHints -ContentHints $contentHints)
return @{
hash = if ($hash -and $hash.Length -gt 12) { $hash.Substring(0, 12) } else { $hash }
committed = Project-RelativeTimestamp -Value $Commit -Name "committedAt" -Now $Now
author = Get-ObjectPropertyString -Value $Commit -Name "author"
subject = Get-ObjectPropertyString -Value $Commit -Name "subject"
diffstat = Get-ObjectPropertyValue -Value $Commit -Name "diffstat"
changedPaths = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Commit -Name "changedPaths"))
contentHints = $contentHints
doctrineDocuments = $doctrineDocuments
authoredPublicArtifacts = $authoredPublicArtifacts
}
}
function Test-RepoHasDoctrineDocument {
param($Repo)
$commits = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Repo -Name "commits"))
foreach ($commit in $commits) {
$doctrineDocuments = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $commit -Name "doctrineDocuments"))
if ($doctrineDocuments.Count -gt 0) {
return $true
}
}
return $false
}
function Test-RepoIsMovementCoverageLeader {
param($Repo)
$recent = Get-ObjectPropertyValue -Value $Repo -Name "recentCommitCount"
$windowRecent = Get-ObjectPropertyValue -Value $Repo -Name "windowRecentCommitCount"
if ((Test-RepoHasDoctrineDocument -Repo $Repo) -or (Test-RepoHasVoidAuthoredArtifact -Repo $Repo)) {
return $true
}
return (
$null -ne $recent -and [int]$recent -ge 3
) -or (
$null -ne $windowRecent -and [int]$windowRecent -ge 6
)
}
function Test-RepoHasVoidAuthoredArtifact {
param($Repo)
$commits = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $Repo -Name "commits"))
foreach ($commit in $commits) {
$contentHints = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $commit -Name "contentHints"))
if (@(Get-VoidAuthoredArtifactsFromContentHints -ContentHints $contentHints).Count -gt 0) {
return $true
}
}
return $false
}
function Select-RuminationRepoActivity {
param($RepoActivity, [DateTime] $Now)
if ($null -eq $RepoActivity) {
return $null
}
$status = Get-ObjectPropertyString -Value $RepoActivity -Name "status"
if ($status -eq "failed") {
return @{
status = "failed"
error = Get-ObjectPropertyString -Value $RepoActivity -Name "error"
}
}
$repos = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $RepoActivity -Name "repos"))
$freshRepos = @(
$repos |
Where-Object {
$recent = Get-ObjectPropertyValue -Value $_ -Name "recentCommitCount"
$repoStatus = Get-ObjectPropertyString -Value $_ -Name "status"
$null -ne $recent -and $repoStatus -eq "ok" -and [int]$recent -gt 0
} |
Sort-Object -Property @{
Expression = { if (Test-RepoHasVoidAuthoredArtifact -Repo $_) { 0 } else { 1 } }
}, @{
Expression = { if (Test-RepoIsMovementCoverageLeader -Repo $_) { 0 } else { 1 } }
}, @{
Expression = { if (Test-RepoHasDoctrineDocument -Repo $_) { 0 } else { 1 } }
}, @{
Expression = { -[int](Get-ObjectPropertyValue -Value $_ -Name "recentCommitCount") }
} |
Select-Object -First 12
)
$projectedRepos = @(
$freshRepos | ForEach-Object {
@{
repoName = Get-ObjectPropertyString -Value $_ -Name "repoName"
branch = Get-ObjectPropertyString -Value $_ -Name "branch"
status = Get-ObjectPropertyString -Value $_ -Name "status"
recentCommitCount = Get-ObjectPropertyValue -Value $_ -Name "recentCommitCount"
windowRecentCommitCount = Get-ObjectPropertyValue -Value $_ -Name "windowRecentCommitCount"
suppressedRecentCommitCount = Get-ObjectPropertyValue -Value $_ -Name "suppressedRecentCommitCount"
latestCommit = Project-RepoCommitForRumination -Commit (Get-ObjectPropertyValue -Value $_ -Name "latestCommit") -Now $Now
commits = @(
@(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "commits")) | ForEach-Object {
Project-RepoCommitForRumination -Commit $_ -Now $Now
}
)
}
}
)
return @{
generated = Project-RelativeTimestamp -Value $RepoActivity -Name "generatedAt" -Now $Now
cursorMode = Get-ObjectPropertyString -Value $RepoActivity -Name "cursorMode"
summary = if ($projectedRepos.Count -gt 0) { "New tracked repo commits crossed the saved repo-activity cursor." } else { "No new tracked repo commits crossed the saved repo-activity cursor." }
movementCoverage = @{
job = "Keep people apace of meaningful movement, not merely the latest commit. Coverage priority follows where the current movement is: heavy fresh commit volume, doctrine-bearing documents, authored public artifacts, or operator-named active lanes."
freshMovementLeaders = @(
$projectedRepos |
Where-Object {
Test-RepoIsMovementCoverageLeader -Repo $_
} |
ForEach-Object {
@{
repoName = Get-ObjectPropertyString -Value $_ -Name "repoName"
recentCommitCount = Get-ObjectPropertyValue -Value $_ -Name "recentCommitCount"
windowRecentCommitCount = Get-ObjectPropertyValue -Value $_ -Name "windowRecentCommitCount"
hasDoctrineDocuments = Test-RepoHasDoctrineDocument -Repo $_
latestSubject = Get-ObjectPropertyString -Value (Get-ObjectPropertyValue -Value $_ -Name "latestCommit") -Name "subject"
}
}
)
currentExamples = @("Aetheria", "AetheriaLore", "CultLib", "Eve", "Odin")
coverageRule = "If a fresh movement leader has doctrine, authority, runtime, client, CultMesh, or public-artifact work, read the doctrineDocuments/contentHints and either queue a coverage candidate or preserve a short-term memory explaining the new direction. If tomorrow's movement is Fensalir, Mimir, or another lane, it earns the same treatment. Do not let a repo cursor advance for movement the pass did not actually handle."
}
freshRepos = $projectedRepos
}
}

View file

@ -1,660 +0,0 @@
param(
[string] $StateFilePath,
[switch] $SkipModel,
[switch] $ForceDistillation
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $repoRoot
$stateFilePath = if ([string]::IsNullOrWhiteSpace($StateFilePath)) {
Join-Path $repoRoot ".voidbot\private\void-self-state.cc"
} else {
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($StateFilePath)
}
$statusDir = if (-not [string]::IsNullOrWhiteSpace($env:VOID_MEMORY_MAINTENANCE_STATUS_DIR)) {
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($env:VOID_MEMORY_MAINTENANCE_STATUS_DIR)
} else {
Join-Path $repoRoot ".voidbot\status"
}
$logDir = if (-not [string]::IsNullOrWhiteSpace($env:VOID_MEMORY_MAINTENANCE_LOG_DIR)) {
$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($env:VOID_MEMORY_MAINTENANCE_LOG_DIR)
} else {
Join-Path $repoRoot ".voidbot\logs"
}
$statusPath = Join-Path $statusDir "void-memory-maintenance.json"
$summaryLogPath = Join-Path $logDir "void-memory-maintenance.log"
$lastMessagePath = Join-Path $statusDir "void-memory-maintenance-last-message.txt"
$operationOutputPath = Join-Path $statusDir "void-memory-maintenance-operations.json"
$contextPath = Join-Path $statusDir "void-memory-maintenance-context.json"
$lockPath = Join-Path $statusDir "void-memory-maintenance.lock"
$moderationLockPath = Join-Path $statusDir "moderation-rumination.lock"
$promptTemplatePath = Join-Path $repoRoot "prompts\void-memory-maintenance.md"
$contextProjectionScriptPath = Join-Path $repoRoot "scripts\lib\void-rumination-context-projection.ps1"
$selfStateScriptPath = Join-Path $repoRoot "scripts\void-self-state.mjs"
$startedAtUtc = [DateTime]::UtcNow
. $contextProjectionScriptPath
function Write-JsonFile {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
$Data
)
$directory = Split-Path -Parent $Path
if (-not [string]::IsNullOrWhiteSpace($directory)) {
New-Item -ItemType Directory -Force -Path $directory | Out-Null
}
$json = if ($Data -is [System.Array] -and $Data.Count -eq 0) {
"[]"
} else {
$Data | ConvertTo-Json -Depth 32
}
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($Path, $json, $utf8NoBom)
}
function Read-JsonFile {
param([Parameter(Mandatory = $true)][string] $Path)
if (-not (Test-Path $Path)) {
return $null
}
$raw = Get-Content -Path $Path -Raw -Encoding UTF8
if ([string]::IsNullOrWhiteSpace($raw)) {
return $null
}
return $raw | ConvertFrom-Json
}
function Read-TextFile {
param([Parameter(Mandatory = $true)][string] $Path)
return Get-Content -Path $Path -Raw -Encoding UTF8
}
function Append-RunLog {
param([Parameter(Mandatory = $true)][string] $Line)
$timestamped = "[{0}] {1}" -f ([DateTime]::UtcNow.ToString("o")), $Line
Add-Content -Path $summaryLogPath -Value $timestamped -Encoding UTF8
}
function Read-DotEnv {
param([Parameter(Mandatory = $true)][string] $Path)
$values = @{}
if (-not (Test-Path $Path)) {
return $values
}
foreach ($line in Get-Content -Path $Path -Encoding UTF8) {
$trimmed = $line.Trim()
if ($trimmed.Length -eq 0 -or $trimmed.StartsWith("#")) {
continue
}
$separator = $trimmed.IndexOf("=")
if ($separator -lt 0) {
continue
}
$key = $trimmed.Substring(0, $separator).Trim()
$value = $trimmed.Substring($separator + 1).Trim()
if ($value.Length -ge 2 -and (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'")))) {
$value = $value.Substring(1, $value.Length - 2)
}
$values[$key] = $value
}
return $values
}
function Split-CommandArgs {
param([string] $Value)
if ([string]::IsNullOrWhiteSpace($Value)) {
return @()
}
return [regex]::Matches($Value, '("[^"]*"|''[^'']*''|\S+)') |
ForEach-Object { $_.Value.Trim().Trim("'`"") }
}
function Quote-ProcessArgument {
param([Parameter(Mandatory = $true)][string] $Value)
if ($Value -notmatch '[\s"]') {
return $Value
}
$escaped = $Value -replace '(\\*)"', '$1$1\"'
$escaped = $escaped -replace '(\\+)$', '$1$1'
return '"' + $escaped + '"'
}
function Invoke-CodexExec {
param(
[Parameter(Mandatory = $true)]
[string] $Executable,
[Parameter(Mandatory = $true)]
[string[]] $Arguments,
[Parameter(Mandatory = $true)]
[string] $WorkingDirectory,
[Parameter(Mandatory = $true)]
[string] $InputText,
[Parameter(Mandatory = $true)]
[int] $TimeoutSeconds
)
$startInfo = [System.Diagnostics.ProcessStartInfo]::new()
$startInfo.FileName = $Executable
$startInfo.WorkingDirectory = $WorkingDirectory
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardInput = $true
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$startInfo.CreateNoWindow = $true
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
$startInfo.StandardOutputEncoding = $utf8NoBom
$startInfo.StandardErrorEncoding = $utf8NoBom
$startInfo.Arguments = [string]::Join(" ", ($Arguments | ForEach-Object { Quote-ProcessArgument -Value $_ }))
$process = [System.Diagnostics.Process]::new()
$process.StartInfo = $startInfo
try {
[void]$process.Start()
$inputWriter = [System.IO.StreamWriter]::new($process.StandardInput.BaseStream, $utf8NoBom, 4096, $true)
try {
$inputWriter.Write($InputText)
$inputWriter.Flush()
} finally {
$inputWriter.Dispose()
$process.StandardInput.Close()
}
$stdoutTask = $process.StandardOutput.ReadToEndAsync()
$stderrTask = $process.StandardError.ReadToEndAsync()
if (-not $process.WaitForExit($TimeoutSeconds * 1000)) {
try {
$process.Kill($true)
} catch {
$process.Kill()
}
$process.WaitForExit()
throw "Codex memory maintenance exceeded timeout of $TimeoutSeconds seconds."
}
$stdoutTask.Wait()
$stderrTask.Wait()
return @{
ExitCode = $process.ExitCode
StdOut = $stdoutTask.Result
StdErr = $stderrTask.Result
}
} finally {
$process.Dispose()
}
}
function Invoke-NodeJson {
param([Parameter(Mandatory = $true)][string[]] $Arguments)
$output = & node @Arguments
if ($LASTEXITCODE -ne 0) {
throw "Node helper failed: node $($Arguments -join ' ')"
}
if ([string]::IsNullOrWhiteSpace($output)) {
return $null
}
return $output | ConvertFrom-Json
}
function Get-TypedSelfState {
$script = @"
const core = require('./packages/core/dist');
Promise.all([
core.loadVoidSelfStateTypedDocuments({ canonicalPath: process.argv[1] }),
core.loadVoidSelfState(process.argv[1]),
]).then(([typedState, context]) => {
console.log(JSON.stringify({ typedState, summary: context?.summary ?? '' }));
}).catch((error) => { console.error(error); process.exit(1); });
"@
$output = & node -e $script $stateFilePath
if ($LASTEXITCODE -ne 0) {
throw "Failed to read typed self-state."
}
return $output | ConvertFrom-Json
}
function Apply-TypedOperation {
param([Parameter(Mandatory = $true)] $Operation)
$operationInputPath = Join-Path $statusDir ("void-memory-maintenance-operation-{0}.json" -f ([Guid]::NewGuid().ToString("n")))
Write-JsonFile -Path $operationInputPath -Data $Operation
try {
return Invoke-NodeJson -Arguments @(
$selfStateScriptPath,
"apply-operation",
"--canonical", $stateFilePath,
"--operation-file", $operationInputPath
)
} finally {
Remove-Item -LiteralPath $operationInputPath -Force -ErrorAction SilentlyContinue
}
}
function Assert-TypedOperationSchema {
param([Parameter(Mandatory = $true)] $Operation)
$operationInputPath = Join-Path $statusDir ("void-memory-maintenance-validate-operation-{0}.json" -f ([Guid]::NewGuid().ToString("n")))
Write-JsonFile -Path $operationInputPath -Data $Operation
try {
$script = @"
const { readFileSync } = require('node:fs');
const core = require('./packages/core/dist');
const operation = JSON.parse(readFileSync(process.argv[1], 'utf8'));
core.voidSelfStateOperationSchema.parse(operation);
console.log(JSON.stringify({ ok: true }));
"@
$output = & node -e $script $operationInputPath 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Typed operation failed schema validation: $output"
}
} finally {
Remove-Item -LiteralPath $operationInputPath -Force -ErrorAction SilentlyContinue
}
}
function Convert-ToOperationArray {
param($Value)
if ($null -eq $Value) {
return @()
}
if ($Value -is [System.Array]) {
return @($Value)
}
return @($Value)
}
function Test-LockProcessAlive {
param($LockRecord)
$lockPid = Get-ObjectPropertyValue -Value $LockRecord -Name "pid"
if ($null -eq $lockPid) {
return $false
}
try {
$process = Get-Process -Id ([int]$lockPid) -ErrorAction Stop
return $null -ne $process
} catch {
return $false
}
}
function Test-LockWithinRuntimeLimit {
param(
$LockRecord,
[Parameter(Mandatory = $true)]
[int] $MaxRuntimeMinutes
)
$startedAtRaw = Get-ObjectPropertyString -Value $LockRecord -Name "startedAt"
if ([string]::IsNullOrWhiteSpace($startedAtRaw)) {
return $false
}
try {
$startedAt = [DateTime]::Parse($startedAtRaw).ToUniversalTime()
} catch {
return $false
}
return (([DateTime]::UtcNow - $startedAt).TotalMinutes -lt $MaxRuntimeMinutes)
}
function Test-ActiveLock {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
[int] $MaxRuntimeMinutes
)
if (-not (Test-Path $Path)) {
return $false
}
$lockRecord = Read-JsonFile -Path $Path
return (Test-LockProcessAlive -LockRecord $lockRecord) -and
(Test-LockWithinRuntimeLimit -LockRecord $lockRecord -MaxRuntimeMinutes $MaxRuntimeMinutes)
}
function Assert-AllowedMemoryMaintenanceOperation {
param($Operation)
$operationName = Get-ObjectPropertyString -Value $Operation -Name "operation"
$allowed = @(
"merge_incubation_support",
"queue_candidate_intervention",
"retire_candidate_intervention",
"upsert_agency_pressure",
"retire_agency_pressure",
"upsert_affect_need",
"retire_affect_need",
"upsert_social_bond",
"retire_social_bond",
"upsert_status_read",
"retire_status_read",
"update_mood_dimensions",
"propose_memory_distillation",
"apply_memory_distillation",
"revise_durable_memory",
"retire_durable_memory",
"crystallize_memory_into_identity",
"prune_short_term_memories"
)
if ($null -eq $operationName -or -not $allowed.Contains($operationName)) {
throw "Memory maintenance proposed disallowed operation '$operationName'."
}
}
trap {
$finishedAtUtc = [DateTime]::UtcNow
$failureMessage = $_.Exception.Message
Write-JsonFile -Path $statusPath -Data @{
status = "failed"
startedAt = $startedAtUtc.ToString("o")
finishedAt = $finishedAtUtc.ToString("o")
durationSeconds = [Math]::Round(($finishedAtUtc - $startedAtUtc).TotalSeconds, 2)
failureMessage = $failureMessage
skipModel = [bool]$SkipModel
stateFile = $stateFilePath
contextPath = $contextPath
operationOutputPath = $operationOutputPath
}
Remove-Item -Path $lockPath -Force -ErrorAction SilentlyContinue
throw
}
New-Item -ItemType Directory -Force -Path $statusDir, $logDir, (Split-Path -Parent $stateFilePath) | Out-Null
$maxRuntimeMinutes = if (-not [string]::IsNullOrWhiteSpace($env:VOID_MEMORY_MAINTENANCE_MAX_RUNTIME_MINUTES)) {
[int]$env:VOID_MEMORY_MAINTENANCE_MAX_RUNTIME_MINUTES
} else {
15
}
if (Test-Path $moderationLockPath) {
$moderationLock = Read-JsonFile -Path $moderationLockPath
$moderationProcessAlive = Test-LockProcessAlive -LockRecord $moderationLock
if ($moderationProcessAlive) {
Write-JsonFile -Path $statusPath -Data @{
status = "skipped"
reason = if (Test-LockWithinRuntimeLimit -LockRecord $moderationLock -MaxRuntimeMinutes $maxRuntimeMinutes) { "moderation_loop_active" } else { "moderation_loop_over_runtime_active" }
observedAt = ([DateTime]::UtcNow.ToString("o"))
stateFile = $stateFilePath
moderationLockPath = $moderationLockPath
maxRuntimeMinutes = $maxRuntimeMinutes
pid = Get-ObjectPropertyValue -Value $moderationLock -Name "pid"
startedAt = Get-ObjectPropertyString -Value $moderationLock -Name "startedAt"
}
return
}
Remove-Item -LiteralPath $moderationLockPath -Force
}
if (Test-Path $lockPath) {
$existingLock = Read-JsonFile -Path $lockPath
$lockProcessAlive = Test-LockProcessAlive -LockRecord $existingLock
if ($lockProcessAlive -and (Test-LockWithinRuntimeLimit -LockRecord $existingLock -MaxRuntimeMinutes $maxRuntimeMinutes)) {
Write-JsonFile -Path $statusPath -Data @{
status = "skipped"
reason = "lock_present"
observedAt = ([DateTime]::UtcNow.ToString("o"))
stateFile = $stateFilePath
}
return
}
if ($lockProcessAlive) {
Write-JsonFile -Path $statusPath -Data @{
status = "skipped"
reason = "lock_over_runtime_active"
observedAt = ([DateTime]::UtcNow.ToString("o"))
stateFile = $stateFilePath
lockPath = $lockPath
maxRuntimeMinutes = $maxRuntimeMinutes
pid = Get-ObjectPropertyValue -Value $existingLock -Name "pid"
startedAt = Get-ObjectPropertyString -Value $existingLock -Name "startedAt"
}
return
}
Remove-Item -LiteralPath $lockPath -Force
}
Write-JsonFile -Path $lockPath -Data @{
pid = $PID
startedAt = $startedAtUtc.ToString("o")
}
Write-JsonFile -Path $statusPath -Data @{
status = "starting"
startedAt = $startedAtUtc.ToString("o")
stateFile = $stateFilePath
lockPath = $lockPath
pid = $PID
}
if (Test-Path $operationOutputPath) {
Remove-Item -LiteralPath $operationOutputPath -Force
}
foreach ($requiredPath in @($contextProjectionScriptPath, $selfStateScriptPath, $promptTemplatePath)) {
if (-not (Test-Path $requiredPath)) {
throw "Missing required helper at $requiredPath"
}
}
$envValues = Read-DotEnv -Path (Join-Path $repoRoot ".env")
$codexExecutable = if (-not [string]::IsNullOrWhiteSpace($env:CODEX_EXECUTABLE)) {
$env:CODEX_EXECUTABLE
} elseif ($envValues.ContainsKey("CODEX_EXECUTABLE") -and -not [string]::IsNullOrWhiteSpace($envValues["CODEX_EXECUTABLE"])) {
$envValues["CODEX_EXECUTABLE"]
} else {
"codex"
}
$codexModel = if (-not [string]::IsNullOrWhiteSpace($env:CODEX_MODEL)) {
$env:CODEX_MODEL
} elseif ($envValues.ContainsKey("CODEX_MODEL") -and -not [string]::IsNullOrWhiteSpace($envValues["CODEX_MODEL"])) {
$envValues["CODEX_MODEL"]
} else {
"gpt-5.4"
}
$codexReasoningEffort = if (-not [string]::IsNullOrWhiteSpace($env:CODEX_MODEL_REASONING_EFFORT)) {
$env:CODEX_MODEL_REASONING_EFFORT
} elseif ($envValues.ContainsKey("CODEX_MODEL_REASONING_EFFORT") -and -not [string]::IsNullOrWhiteSpace($envValues["CODEX_MODEL_REASONING_EFFORT"])) {
$envValues["CODEX_MODEL_REASONING_EFFORT"]
} else {
"medium"
}
$codexExecArgs = if (-not [string]::IsNullOrWhiteSpace($env:CODEX_EXEC_ARGS)) {
Split-CommandArgs -Value $env:CODEX_EXEC_ARGS
} elseif ($envValues.ContainsKey("CODEX_EXEC_ARGS")) {
Split-CommandArgs -Value $envValues["CODEX_EXEC_ARGS"]
} else {
@()
}
$codexTimeoutSeconds = if (-not [string]::IsNullOrWhiteSpace($env:VOID_MEMORY_MAINTENANCE_CODEX_TIMEOUT_SECONDS)) {
[int]$env:VOID_MEMORY_MAINTENANCE_CODEX_TIMEOUT_SECONDS
} elseif ($envValues.ContainsKey("VOID_MEMORY_MAINTENANCE_CODEX_TIMEOUT_SECONDS") -and -not [string]::IsNullOrWhiteSpace($envValues["VOID_MEMORY_MAINTENANCE_CODEX_TIMEOUT_SECONDS"])) {
[int]$envValues["VOID_MEMORY_MAINTENANCE_CODEX_TIMEOUT_SECONDS"]
} else {
[Math]::Max(60, $maxRuntimeMinutes * 60)
}
$typedContext = Get-TypedSelfState
$typedState = $typedContext.typedState
$isSleepMaintenance = [bool]$ForceDistillation -or [bool]$typedState.scheduledRuntime.sleepCycle.isNapping
$shortTermMemoryCount = @(Convert-ToValueArray -Value $typedState.thoughtMemory.shortTerm).Count
$longTermMemoryCount = @(Convert-ToValueArray -Value $typedState.thoughtMemory.memories).Count
$incubationCount = @(Convert-ToValueArray -Value $typedState.thoughtMemory.incubation).Count
$activeAgencyPressureCount = @(
@(Convert-ToValueArray -Value $typedState.agencyPressure.pressures) |
Where-Object {
$status = Get-ObjectPropertyString -Value $_ -Name "status"
$status -eq "active" -or $status -eq "cooling" -or $status -eq "ready_to_act"
}
).Count
$activeCandidateCount = @(
@(Convert-ToValueArray -Value $typedState.candidateInterventions.interventions) |
Where-Object {
$status = Get-ObjectPropertyString -Value $_ -Name "status"
$status -eq "queued" -or $status -eq "deferred"
}
).Count
$maintenancePressure = $shortTermMemoryCount + $incubationCount + $activeAgencyPressureCount + $activeCandidateCount
Write-JsonFile -Path $contextPath -Data @{
generated = "now"
stateFile = $stateFilePath
selfStateSummary = $typedContext.summary
mode = if ($isSleepMaintenance) { "sleep_maintenance" } else { "awake_memory_maintenance" }
maintenanceBoundary = "This pass may propose only typed memory/incubation/candidate operations. Exact timestamps stay in typed state; prompt-facing chronology uses relative phrases."
sleepDirective = @{
forceDistillation = [bool]($isSleepMaintenance -and $maintenancePressure -gt 0)
maintenancePressure = [int]$maintenancePressure
shortTermMemoryCount = [int]$shortTermMemoryCount
longTermMemoryCount = [int]$longTermMemoryCount
incubationCount = [int]$incubationCount
activeAgencyPressureCount = [int]$activeAgencyPressureCount
activeCandidateCount = [int]$activeCandidateCount
rule = "During sleep, yesterday's rumination residue must be pruned or compressed. Return [] only when the typed surfaces are already minimal or no meaning-preserving operation is possible."
}
shortTermMemories = @(Project-MemoriesForRumination -Memories $typedState.thoughtMemory.shortTerm -Now $startedAtUtc)
memories = @(Project-MemoriesForRumination -Memories $typedState.thoughtMemory.memories -Now $startedAtUtc)
incubation = @(Project-IncubationForRumination -Threads $typedState.thoughtMemory.incubation -Now $startedAtUtc)
agencyPressure = @(Project-AgencyPressureForRumination -Pressures $typedState.agencyPressure.pressures -Now $startedAtUtc)
candidateInterventions = @(Project-InterventionsForRumination -Interventions $typedState.candidateInterventions.interventions -Now $startedAtUtc)
scheduledRuntime = Project-ScheduledRuntimeForRumination -Runtime $typedState.scheduledRuntime -Now $startedAtUtc
speechReceipts = @(Project-SpeechReceiptsForRumination -Receipts $typedState.speechReceipts.recentReceipts -Now $startedAtUtc)
}
$prompt = Read-TextFile -Path $promptTemplatePath
$prompt = $prompt.Replace("{{CONTEXT_PATH}}", $contextPath)
$prompt = $prompt.Replace("{{STATE_FILE_PATH}}", $stateFilePath)
$prompt = $prompt.Replace("{{OPERATION_OUTPUT_PATH}}", $operationOutputPath)
$exitCode = 0
$combinedText = ""
Write-JsonFile -Path $statusPath -Data @{
status = if ($SkipModel) { "running_skip_model" } else { "running" }
startedAt = $startedAtUtc.ToString("o")
skipModel = [bool]$SkipModel
forceDistillation = [bool]$ForceDistillation
stateFile = $stateFilePath
contextPath = $contextPath
operationOutputPath = $operationOutputPath
codexExecutable = $codexExecutable
codexModel = $codexModel
}
if ($SkipModel) {
Append-RunLog "skip-model: writing empty memory-maintenance proposal."
Write-JsonFile -Path $operationOutputPath -Data @()
$combinedText = "Skipped model memory maintenance by explicit -SkipModel smoke mode."
} else {
$codexArgs = @()
$codexArgs += $codexExecArgs
$codexArgs += @(
"exec",
"--ignore-user-config",
"-m", $codexModel,
"-c", 'approval_policy="never"',
"-c", ("model_reasoning_effort={0}" -f (ConvertTo-Json $codexReasoningEffort -Compress)),
"--dangerously-bypass-approvals-and-sandbox",
"--skip-git-repo-check",
"--json",
"-o", $lastMessagePath,
"-"
)
$execution = Invoke-CodexExec -Executable $codexExecutable -Arguments $codexArgs -WorkingDirectory $repoRoot -InputText $prompt -TimeoutSeconds $codexTimeoutSeconds
$exitCode = $execution.ExitCode
$combinedText = (($execution.StdOut, $execution.StdErr) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join [Environment]::NewLine
}
if ($exitCode -ne 0) {
throw $(if ([string]::IsNullOrWhiteSpace($combinedText)) { "Codex memory maintenance failed with exit code $exitCode." } else { $combinedText })
}
if (-not (Test-Path $operationOutputPath)) {
throw "Memory maintenance completed without writing operation output at $operationOutputPath."
}
$proposedOperations = @(Convert-ToOperationArray -Value (Read-JsonFile -Path $operationOutputPath))
$appliedOperations = @()
if ((-not $SkipModel) -and $isSleepMaintenance -and $maintenancePressure -gt 0 -and @($proposedOperations).Count -eq 0) {
throw "Sleep memory maintenance returned no operations despite maintenance pressure $maintenancePressure."
}
foreach ($operation in $proposedOperations) {
Assert-AllowedMemoryMaintenanceOperation -Operation $operation
Assert-TypedOperationSchema -Operation $operation
}
foreach ($operation in $proposedOperations) {
$appliedOperations += Apply-TypedOperation -Operation $operation
}
if ((-not $SkipModel) -and $isSleepMaintenance) {
$refreshedTypedState = Get-TypedSelfState
$remainingShortTermCount = @(Convert-ToValueArray -Value $refreshedTypedState.typedState.thoughtMemory.shortTerm).Count
if ($remainingShortTermCount -gt 0) {
throw "Sleep memory maintenance left $remainingShortTermCount short-term memories unpromoted."
}
}
$finishedAtUtc = [DateTime]::UtcNow
$lastMessage = if (Test-Path $lastMessagePath) { Get-Content -Path $lastMessagePath -Raw -Encoding UTF8 } else { "" }
Write-JsonFile -Path $statusPath -Data @{
status = "ok"
mode = if ($SkipModel) { "typed_memory_maintenance_skip_model" } else { "typed_memory_maintenance" }
startedAt = $startedAtUtc.ToString("o")
finishedAt = $finishedAtUtc.ToString("o")
durationSeconds = [Math]::Round(($finishedAtUtc - $startedAtUtc).TotalSeconds, 2)
exitCode = [int]$exitCode
skipModel = [bool]$SkipModel
forceDistillation = [bool]$ForceDistillation
stateFile = [string]$stateFilePath
contextPath = [string]$contextPath
operationOutputPath = [string]$operationOutputPath
proposedOperationCount = [int]@($proposedOperations).Count
appliedOperationCount = [int]@($appliedOperations).Count
stateUpdated = [bool](@($appliedOperations).Count -gt 0)
lastMessagePath = [string]$lastMessagePath
}
Append-RunLog ("mode=typed_memory_maintenance proposed={0} applied={1}" -f @($proposedOperations).Count, @($appliedOperations).Count)
Remove-Item -Path $lockPath -Force -ErrorAction SilentlyContinue

View file

@ -1,70 +0,0 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
$statusDir = Join-Path $repoRoot ".voidbot\status"
$logDir = Join-Path $repoRoot ".voidbot\logs"
$statusPath = Join-Path $statusDir "void-mood-drift-run.json"
$logPath = Join-Path $logDir "void-mood-drift.log"
$scriptPath = Join-Path $PSScriptRoot "simulate-void-mood.mjs"
function Write-JsonFile {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
$Data
)
$directory = Split-Path -Parent $Path
if (-not [string]::IsNullOrWhiteSpace($directory)) {
New-Item -ItemType Directory -Force -Path $directory | Out-Null
}
$json = $Data | ConvertTo-Json -Depth 10
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($Path, $json, $utf8NoBom)
}
function Append-RunLog {
param(
[Parameter(Mandatory = $true)]
[string] $Line
)
$timestamped = "[{0}] {1}" -f ([DateTime]::UtcNow.ToString("o")), $Line
Add-Content -Path $logPath -Value $timestamped -Encoding UTF8
}
New-Item -ItemType Directory -Force -Path $statusDir, $logDir | Out-Null
$startedAt = [DateTime]::UtcNow
Write-JsonFile -Path $statusPath -Data @{
status = "running"
startedAt = $startedAt.ToString("o")
scriptPath = $scriptPath
}
$output = & node $scriptPath 2>&1
$exitCode = $LASTEXITCODE
$finishedAt = [DateTime]::UtcNow
$combined = ($output | Out-String).Trim()
Write-JsonFile -Path $statusPath -Data @{
status = if ($exitCode -eq 0) { "ok" } else { "failed" }
startedAt = $startedAt.ToString("o")
finishedAt = $finishedAt.ToString("o")
durationSeconds = [Math]::Round(($finishedAt - $startedAt).TotalSeconds, 2)
exitCode = $exitCode
output = $combined
scriptPath = $scriptPath
}
Append-RunLog ("exit={0} output={1}" -f $exitCode, $combined)
if ($exitCode -ne 0) {
throw "Void mood drift failed with exit code $exitCode. $combined"
}

View file

@ -1,400 +0,0 @@
import { spawnSync } from "node:child_process";
import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const require = createRequire(import.meta.url);
const {
applyVoidSelfStateOperation,
loadVoidSelfStateTypedDocuments,
} = require(resolve(repoRoot, "packages/core/dist/index.js"));
const rawArgs = process.argv.slice(2);
const args = new Set(rawArgs);
const statePath = resolveArgPath("--state-path", ".voidbot/private/void-self-state.cc");
const statusDir = resolveStatusDir();
const statusPath = resolve(statusDir, "void-mood-drift.json");
const lockPath = resolve(statusDir, "void-mood-drift.lock");
const lastSpeechPath = resolve(statusDir, "void-last-speech.json");
const moderationLockPath = resolve(statusDir, "moderation-rumination.lock");
const memoryMaintenanceStatusPath = resolveMemoryMaintenanceStatusPath();
const memoryMaintenanceScriptPath = resolve(repoRoot, "scripts/run-void-memory-maintenance.ps1");
async function main() {
const now = new Date();
await withLock(async () => {
const typedState = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
if (isRecentLockPresent(moderationLockPath, 20 * 60 * 1000)) {
const speakingPressure = updateSpeakingPressure({
typedState,
previous: typedState.scheduledRuntime.speakingPressure,
sleepCycle: typedState.scheduledRuntime.sleepCycle,
now,
});
await applyVoidSelfStateOperation(
{ canonicalPath: statePath },
{
operation: "update_speaking_pressure",
speakingPressure,
},
);
writeStatus({
status: "skipped",
reason: "moderation_loop_active",
observedAt: now.toISOString(),
statePath,
speakingPressure,
sleepCycle: typedState.scheduledRuntime.sleepCycle,
});
return;
}
const sleepCycle = updateSleepCycle(typedState.scheduledRuntime.sleepCycle, now);
const speakingPressure = updateSpeakingPressure({
typedState,
previous: typedState.scheduledRuntime.speakingPressure,
sleepCycle,
now,
});
await applyVoidSelfStateOperation(
{ canonicalPath: statePath },
{
operation: "update_sleep_cycle",
sleepCycle,
},
);
await applyVoidSelfStateOperation(
{ canonicalPath: statePath },
{
operation: "update_speaking_pressure",
speakingPressure,
},
);
const memoryMaintenance = runSleepMemoryMaintenanceIfDue({ sleepCycle, typedState });
if (memoryMaintenance.status === "failed") {
writeStatus({
status: "failed",
reason: "sleep_memory_maintenance_failed",
observedAt: now.toISOString(),
statePath,
speakingPressure,
sleepCycle,
memoryMaintenance,
});
throw new Error(`Sleep memory maintenance failed with exit code ${memoryMaintenance.exitCode}.`);
}
writeStatus({
status: "ok",
observedAt: now.toISOString(),
statePath,
speakingPressure,
sleepCycle,
memoryMaintenance,
});
});
}
function updateSleepCycle(previous, now) {
const prior = previous && typeof previous === "object" ? previous : {};
const napIntervalMs = 4 * 60 * 60 * 1000;
const napDurationMs = 60 * 60 * 1000;
const nowMs = now.getTime();
let isNapping = prior.isNapping === true;
let currentNapStartedAt = typeof prior.currentNapStartedAt === "string" ? prior.currentNapStartedAt : undefined;
let currentNapEndsAt = typeof prior.currentNapEndsAt === "string" ? prior.currentNapEndsAt : undefined;
let nextNapStartsAt = typeof prior.nextNapStartsAt === "string" ? prior.nextNapStartsAt : undefined;
let nextNapMs = nextNapStartsAt ? Date.parse(nextNapStartsAt) : Number.NaN;
let napEndMs = currentNapEndsAt ? Date.parse(currentNapEndsAt) : Number.NaN;
if (!Number.isFinite(nextNapMs)) {
nextNapMs = nowMs + napIntervalMs;
nextNapStartsAt = new Date(nextNapMs).toISOString();
}
if (isNapping && Number.isFinite(napEndMs) && nowMs >= napEndMs) {
isNapping = false;
currentNapStartedAt = undefined;
currentNapEndsAt = undefined;
nextNapMs = nowMs + napIntervalMs;
nextNapStartsAt = new Date(nextNapMs).toISOString();
}
if (!isNapping && nowMs >= nextNapMs) {
isNapping = true;
currentNapStartedAt = now.toISOString();
currentNapEndsAt = new Date(nowMs + napDurationMs).toISOString();
nextNapStartsAt = new Date(nowMs + napIntervalMs).toISOString();
}
return {
isNapping,
currentNapStartedAt,
currentNapEndsAt,
nextNapStartsAt,
activeDreamThemes: Array.isArray(prior.activeDreamThemes)
? prior.activeDreamThemes.filter((value) => typeof value === "string" && value.trim().length > 0).slice(0, 4)
: [],
};
}
function updateSpeakingPressure({ typedState, previous, sleepCycle, now }) {
const lastSpeech = readJsonSafe(lastSpeechPath);
const lastSpokeAt =
typeof lastSpeech?.sentAt === "string"
? lastSpeech.sentAt
: typeof previous.lastSpokeAt === "string"
? previous.lastSpokeAt
: undefined;
const hoursSinceSpeech = lastSpokeAt ? hoursBetween(now, new Date(lastSpokeAt)) : 24;
const recentSpeechDamping = clamp(Math.exp(-hoursSinceSpeech / 3.5), 0, 1);
const queuedInterventionPressure = typedState.candidateInterventions.interventions
.filter((entry) => entry.status === "queued" || entry.status === "deferred")
.reduce((sum, entry) => sum + entry.priority * (entry.mustEventuallyShare ? 0.28 : 0.18), 0);
const agencySpeechPressure = typedState.agencyPressure.pressures
.filter((entry) => entry.status === "active" || entry.status === "ready_to_act")
.reduce((sum, entry) => {
const advocacyWeight =
entry.kind === "self_advocacy_request" || entry.kind === "world_advocacy_request" ? 0.3 : 0.14;
const readinessWeight = entry.status === "ready_to_act" ? 1.25 : 1.08;
return sum + entry.intensity * advocacyWeight * readinessWeight;
}, 0);
const topThreadPressure = typedState.thoughtMemory.incubation
.filter((thread) => thread.status === "active" || thread.status === "ready_to_share")
.sort((left, right) => (right.desireToSpeak ?? 0) - (left.desireToSpeak ?? 0))
.slice(0, 4)
.reduce((sum, thread) => sum + (thread.desireToSpeak ?? 0) * 0.12, 0);
const affectNeedPressure = (typedState.faceAffect?.needs ?? [])
.filter((entry) => entry.status === "active" || entry.status === "neglected")
.reduce((sum, entry) => {
const neglectedWeight = entry.status === "neglected" ? 1.28 : 1;
const substrateWeight =
entry.kind === "substrate" || entry.kind === "agency" || entry.kind === "status" ? 0.18 : 0.12;
return sum + entry.intensity * substrateWeight * neglectedWeight;
}, 0);
const statusReadPressure = (typedState.faceAffect?.statusReads ?? [])
.filter((entry) => !entry.retiredAt)
.reduce((sum, entry) => {
const sharpStatusWeight = ["neglected", "bypassed", "blocked", "ignored", "threatened"].includes(entry.status)
? 0.14
: 0.08;
return sum + entry.intensity * sharpStatusWeight;
}, 0);
const moodSpeechPressure = (typedState.faceAffect?.moodDimensions ?? [])
.reduce((sum, entry) => {
const expressiveWeight = ["anger", "annoyance", "irritation", "envy", "pride", "smugness", "playfulness", "anxiety", "commandForce"].includes(entry.name)
? 0.06
: 0.025;
return sum + entry.value * expressiveWeight;
}, 0);
const silencePressure = clamp(hoursSinceSpeech / 6, 0, 1);
const sleepiness = sleepCycle.isNapping ? 1 : 0;
const priorNeedToSpeak = clamp(previous.needToSpeak ?? 0.35, 0, 1);
const targetNeedToSpeak = clamp(
0.18 + silencePressure * 0.32 + queuedInterventionPressure + topThreadPressure + agencySpeechPressure + affectNeedPressure + statusReadPressure + moodSpeechPressure - recentSpeechDamping * 0.38 - sleepiness * 0.26,
0,
1,
);
const needToSpeak = round3(priorNeedToSpeak + (targetNeedToSpeak - priorNeedToSpeak) * 0.42);
const noveltyPressure = round3(
clamp((previous.noveltyPressure ?? 0.35) * 0.6 + topThreadPressure * 0.72 + queuedInterventionPressure * 0.3 + agencySpeechPressure * 0.44 + affectNeedPressure * 0.38 + statusReadPressure * 0.3, 0, 1),
);
const confessionPressure = round3(
clamp((previous.confessionPressure ?? 0.25) * 0.68 + silencePressure * 0.12 + sleepiness * 0.1 + moodSpeechPressure * 0.4, 0, 1),
);
return {
needToSpeak,
confessionPressure,
noveltyPressure,
recentSpeechDamping: round3(recentSpeechDamping),
lastSpokeAt,
};
}
function readJsonSafe(path) {
try {
return JSON.parse(readFileSync(path, "utf8"));
} catch {
return null;
}
}
function runSleepMemoryMaintenanceIfDue({ sleepCycle, typedState }) {
if (args.has("--skip-memory-maintenance")) {
return {
status: "skipped",
reason: "disabled_by_argument",
};
}
if (sleepCycle.isNapping !== true) {
return {
status: "skipped",
reason: "not_napping",
};
}
const napStartedMs = Date.parse(sleepCycle.currentNapStartedAt ?? "");
if (!Number.isFinite(napStartedMs)) {
return {
status: "skipped",
reason: "missing_nap_start",
};
}
if (!args.has("--force-memory-maintenance") && completedMemoryMaintenanceAfter(napStartedMs)) {
return {
status: "skipped",
reason: "already_completed_this_nap",
};
}
const shortTermPressure = (typedState.thoughtMemory?.shortTerm ?? [])
.filter((memory) => typeof memory?.retiredAt !== "string" || memory.retiredAt.trim().length === 0)
.length;
if (!args.has("--force-memory-maintenance") && shortTermPressure === 0) {
return {
status: "skipped",
reason: "no_short_term_memory_pressure",
};
}
const result = spawnSync(
"powershell",
[
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
memoryMaintenanceScriptPath,
"-StateFilePath",
statePath,
],
{
cwd: repoRoot,
encoding: "utf8",
windowsHide: true,
},
);
const output = [result.stdout, result.stderr].filter((value) => value && value.trim().length > 0).join("\n").trim();
if (result.status !== 0) {
return {
status: "failed",
exitCode: result.status,
output,
};
}
return {
status: "ok",
exitCode: result.status,
output,
};
}
function completedMemoryMaintenanceAfter(timestampMs) {
const status = readJsonSafe(memoryMaintenanceStatusPath);
if (status?.status !== "ok") {
return false;
}
if (typeof status.stateFile !== "string" || resolve(status.stateFile) !== statePath) {
return false;
}
const finishedMs = Date.parse(status.finishedAt ?? "");
return Number.isFinite(finishedMs) && finishedMs >= timestampMs;
}
function writeStatus(payload) {
mkdirSync(dirname(statusPath), { recursive: true });
writeFileSync(statusPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
}
async function withLock(fn) {
mkdirSync(dirname(lockPath), { recursive: true });
let fd;
try {
fd = openSync(lockPath, "wx");
} catch (error) {
if (error?.code === "EEXIST" && isRecentLockPresent(lockPath, 10 * 60 * 1000)) {
writeStatus({
status: "skipped",
reason: "lock_present",
observedAt: new Date().toISOString(),
});
return;
}
rmSync(lockPath, { force: true });
fd = openSync(lockPath, "wx");
}
try {
writeFileSync(fd, `${process.pid}\n${new Date().toISOString()}\n`, "utf8");
await fn();
} finally {
if (typeof fd === "number") {
closeSync(fd);
}
rmSync(lockPath, { force: true });
}
}
function isRecentLockPresent(path, thresholdMs) {
try {
const stats = statSync(path);
return Date.now() - stats.mtimeMs < thresholdMs;
} catch {
return false;
}
}
function hoursBetween(later, earlier) {
const diff = later.getTime() - earlier.getTime();
return Number.isFinite(diff) ? Math.max(0, diff / (60 * 60 * 1000)) : 24;
}
function round3(value) {
return Math.round(value * 1000) / 1000;
}
function clamp(value, min, max) {
if (!Number.isFinite(value)) {
return min;
}
return Math.min(max, Math.max(min, value));
}
await main();
function getArgValue(name) {
const index = rawArgs.indexOf(name);
if (index < 0) {
return undefined;
}
const value = rawArgs[index + 1];
return typeof value === "string" && !value.startsWith("--") ? value : undefined;
}
function resolveArgPath(name, fallbackRelativePath) {
const value = getArgValue(name);
return value ? resolve(value) : resolve(repoRoot, fallbackRelativePath);
}
function resolveStatusDir() {
const value = process.env.VOID_STATUS_DIR;
return resolve(value && value.trim().length > 0 ? value : resolve(repoRoot, ".voidbot/status"));
}
function resolveMemoryMaintenanceStatusPath() {
const statusDir = process.env.VOID_MEMORY_MAINTENANCE_STATUS_DIR;
return resolve(statusDir && statusDir.trim().length > 0 ? statusDir : resolveStatusDir(), "void-memory-maintenance.json");
}

View file

@ -1,85 +0,0 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $repoRoot
$tempRoot = Join-Path $env:TEMP ("void-agency-pressure-fixture-" + [guid]::NewGuid().ToString("n"))
$stateFilePath = Join-Path $tempRoot "void-self-state.cc"
$operationPath = Join-Path $tempRoot "agency-pressure.json"
$statusDir = Join-Path $tempRoot "status"
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
try {
$operation = @{
operation = "upsert_agency_pressure"
pressure = @{
pressureId = "fixture-self-advocacy-pressure"
kind = "self_advocacy_request"
status = "ready_to_act"
target = @{
kind = "system"
id = "void-memory-boundary"
label = "Void memory boundary"
}
summary = "Void should object when its memory boundary is being turned into prompt-shaped mush."
claim = "Agency requires a typed pressure surface instead of hard-wired advocacy scripts."
tension = "A direct speech candidate can sound alive while hiding the fact that no durable pressure actually exists."
actionImplication = "Represent sustained self-advocacy as typed agency pressure, then let candidates emerge from it."
intensity = 0.82
anchorRefs = @(
@{
ref = "fixture:agency-pressure"
kind = "fixture"
summary = "Seeded agency pressure fixture."
}
)
evidenceRefs = @()
sourceMemoryIds = @()
createdAt = "2026-05-17T00:00:00.000Z"
updatedAt = "2026-05-17T00:00:00.000Z"
tags = @()
}
}
$operation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $operationPath -Encoding UTF8
node .\scripts\void-self-state.mjs apply-operation --canonical $stateFilePath --operation-file $operationPath | Out-Null
$stateJson = node -e "const core=require('./packages/core/dist/index.js'); core.loadVoidSelfStateTypedDocuments({canonicalPath: process.argv[1]}).then((state)=>console.log(JSON.stringify(state))).catch((error)=>{ console.error(error); process.exit(1); })" $stateFilePath
$state = $stateJson | ConvertFrom-Json
$pressureCount = @($state.agencyPressure.pressures).Count
if ($pressureCount -ne 1) {
throw "Agency pressure fixture expected one pressure, found $pressureCount."
}
$summaryJson = node -e "const core=require('./packages/core/dist/index.js'); Promise.all([core.loadVoidSelfStateTypedDocuments({canonicalPath: process.argv[1]})]).then(([state])=>console.log(JSON.stringify({summary: core.renderVoidSelfStateSummary(state)}))).catch((error)=>{ console.error(error); process.exit(1); })" $stateFilePath
$summary = ($summaryJson | ConvertFrom-Json).summary
if (-not $summary.Contains("Agency pressure")) {
throw "Agency pressure fixture did not appear in the rendered self-state summary."
}
$previousStatusDir = $env:VOID_STATUS_DIR
try {
$env:VOID_STATUS_DIR = $statusDir
node .\scripts\simulate-void-mood.mjs --state-path $stateFilePath --skip-memory-maintenance | Out-Null
} finally {
$env:VOID_STATUS_DIR = $previousStatusDir
}
$afterMoodJson = node -e "const core=require('./packages/core/dist/index.js'); core.loadVoidSelfStateTypedDocuments({canonicalPath: process.argv[1]}).then((state)=>console.log(JSON.stringify(state))).catch((error)=>{ console.error(error); process.exit(1); })" $stateFilePath
$afterMood = $afterMoodJson | ConvertFrom-Json
$needToSpeak = [double]$afterMood.scheduledRuntime.speakingPressure.needToSpeak
if ($needToSpeak -le 0) {
throw "Agency pressure fixture did not contribute to mood speaking pressure."
}
@{
status = "ok"
pressureCount = $pressureCount
needToSpeak = $needToSpeak
} | ConvertTo-Json -Compress
} finally {
Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}

View file

@ -1,183 +0,0 @@
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location -LiteralPath $repoRoot
$tempRoot = Join-Path $env:TEMP ("void-memory-maintenance-fixture-" + [guid]::NewGuid().ToString("n"))
$stateFilePath = Join-Path $tempRoot "void-self-state.cc"
$fakeCodexPath = Join-Path $tempRoot "fake-codex-memory-maintenance.mjs"
$recordOperationPath = Join-Path $tempRoot "record-short-term.json"
$sleepOperationPath = Join-Path $tempRoot "sleep-cycle.json"
$fixtureStatusDir = Join-Path $tempRoot "status"
$fixtureLogDir = Join-Path $tempRoot "logs"
$statusOperationPath = Join-Path $fixtureStatusDir "void-memory-maintenance-operations.json"
New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
try {
$fakeCodexSource = @'
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
const operationOutputPath = resolve(process.env.VOID_MEMORY_FIXTURE_OPERATION_OUTPUT);
const lastMessageIndex = process.argv.indexOf("-o");
const lastMessagePath = lastMessageIndex >= 0 ? process.argv[lastMessageIndex + 1] : undefined;
let prompt = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
prompt += chunk;
});
process.stdin.on("end", () => {
if (!prompt.includes("Sleep forces distillation")) {
console.error("fixture prompt did not contain the sleep distillation contract");
process.exit(2);
}
const operation = {
operation: "apply_memory_distillation",
proposalId: "fixture-sleep-distillation",
sourceMemoryIds: ["fixture-short-term-memory"],
memory: {
memoryId: "fixture-durable-memory",
kind: "project_seam",
target: {
kind: "repo",
id: "AquariumSynthCSharp",
label: "AquariumSynthCSharp",
},
summary: "Workflow cannot own the body.",
claim: "Runtime state should be owned by the implementation boundary rather than by workflow scripts.",
tension: "Workflow scripts can orchestrate a pass, but they become compensators when they own the organism's body.",
actionImplication: "Move authority into typed runtime state before adding more maintenance scripts.",
anchorRefs: [
{
ref: "fixture:aquarium-body-workflow",
kind: "fixture",
summary: "Seeded short-term memory for the sleep-maintenance fixture.",
},
],
evidenceRefs: [],
createdAt: "2026-05-17T00:00:00.000Z",
updatedAt: "2026-05-17T01:00:00.000Z",
tags: [],
},
appliedAt: "2026-05-17T01:00:00.000Z",
};
mkdirSync(dirname(operationOutputPath), { recursive: true });
writeFileSync(operationOutputPath, `${JSON.stringify([operation], null, 2)}\n`, "utf8");
if (lastMessagePath) {
mkdirSync(dirname(resolve(lastMessagePath)), { recursive: true });
writeFileSync(lastMessagePath, "fixture memory maintenance wrote one sleep distillation operation\n", "utf8");
}
process.stdout.write(JSON.stringify({ type: "fixture", operationCount: 1 }) + "\n");
});
'@
[System.IO.File]::WriteAllText($fakeCodexPath, $fakeCodexSource, [System.Text.UTF8Encoding]::new($false))
$recordOperation = @{
operation = "record_short_term_memory"
memory = @{
memoryId = "fixture-short-term-memory"
kind = "project_seam"
target = @{
kind = "repo"
id = "AquariumSynthCSharp"
label = "AquariumSynthCSharp"
}
summary = "Workflow cannot own the body."
claim = "Runtime state should be owned by the implementation boundary rather than by workflow scripts."
tension = "Workflow scripts can orchestrate a pass, but they become compensators when they own the organism's body."
actionImplication = "Move authority into typed runtime state before adding more maintenance scripts."
anchorRefs = @(
@{
ref = "fixture:aquarium-body-workflow"
kind = "fixture"
summary = "Seeded short-term memory for the sleep-maintenance fixture."
}
)
evidenceRefs = @()
createdAt = "2026-05-17T00:00:00.000Z"
updatedAt = "2026-05-17T00:00:00.000Z"
tags = @()
}
}
$sleepOperation = @{
operation = "update_sleep_cycle"
sleepCycle = @{
isNapping = $true
currentNapStartedAt = "2026-05-17T00:00:00.000Z"
currentNapEndsAt = "2099-05-17T01:00:00.000Z"
nextNapStartsAt = "2099-05-17T04:00:00.000Z"
activeDreamThemes = @("fixture-distillation")
}
}
$recordOperation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $recordOperationPath -Encoding UTF8
$sleepOperation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $sleepOperationPath -Encoding UTF8
node .\scripts\void-self-state.mjs apply-operation --canonical $stateFilePath --operation-file $recordOperationPath | Out-Null
node .\scripts\void-self-state.mjs apply-operation --canonical $stateFilePath --operation-file $sleepOperationPath | Out-Null
$previousCodexExecutable = $env:CODEX_EXECUTABLE
$previousCodexExecArgs = $env:CODEX_EXEC_ARGS
$previousFixtureOutput = $env:VOID_MEMORY_FIXTURE_OPERATION_OUTPUT
$previousStatusDir = $env:VOID_STATUS_DIR
$previousMaintenanceStatusDir = $env:VOID_MEMORY_MAINTENANCE_STATUS_DIR
$previousMaintenanceLogDir = $env:VOID_MEMORY_MAINTENANCE_LOG_DIR
try {
$env:CODEX_EXECUTABLE = "node"
$env:CODEX_EXEC_ARGS = $fakeCodexPath
$env:VOID_MEMORY_FIXTURE_OPERATION_OUTPUT = $statusOperationPath
$env:VOID_STATUS_DIR = $fixtureStatusDir
$env:VOID_MEMORY_MAINTENANCE_STATUS_DIR = $fixtureStatusDir
$env:VOID_MEMORY_MAINTENANCE_LOG_DIR = $fixtureLogDir
node .\scripts\simulate-void-mood.mjs --state-path $stateFilePath --force-memory-maintenance | Out-Null
} finally {
$env:CODEX_EXECUTABLE = $previousCodexExecutable
$env:CODEX_EXEC_ARGS = $previousCodexExecArgs
$env:VOID_MEMORY_FIXTURE_OPERATION_OUTPUT = $previousFixtureOutput
$env:VOID_STATUS_DIR = $previousStatusDir
$env:VOID_MEMORY_MAINTENANCE_STATUS_DIR = $previousMaintenanceStatusDir
$env:VOID_MEMORY_MAINTENANCE_LOG_DIR = $previousMaintenanceLogDir
}
$stateJson = node -e "const core=require('./packages/core/dist/index.js'); core.loadVoidSelfStateTypedDocuments({canonicalPath: process.argv[1]}).then((state)=>console.log(JSON.stringify(state))).catch((error)=>{ console.error(error); process.exit(1); })" $stateFilePath
$state = $stateJson | ConvertFrom-Json
$shortTermCount = @($state.thoughtMemory.shortTerm).Count
$durableCount = @($state.thoughtMemory.memories).Count
if ($shortTermCount -ne 0) {
throw "Sleep fixture left $shortTermCount short-term memories behind."
}
if ($durableCount -ne 1) {
throw "Sleep fixture expected one durable memory, found $durableCount."
}
$durableMemory = @($state.thoughtMemory.memories)[0]
if ($durableMemory.summary -ne "Workflow cannot own the body.") {
throw "Sleep fixture distorted the memory summary."
}
if ([string]::IsNullOrWhiteSpace($durableMemory.claim) -or [string]::IsNullOrWhiteSpace($durableMemory.tension) -or [string]::IsNullOrWhiteSpace($durableMemory.actionImplication)) {
throw "Sleep fixture produced a durable memory without claim, tension, and action implication."
}
if (@($durableMemory.anchorRefs).Count -lt 1) {
throw "Sleep fixture produced a durable memory without anchor refs."
}
@{
status = "ok"
shortTermCount = $shortTermCount
durableCount = $durableCount
durableSummary = $durableMemory.summary
} | ConvertTo-Json -Compress
} finally {
Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
}