Run memory maintenance during sleep
This commit is contained in:
parent
e3735fb9c4
commit
868e242ce4
9 changed files with 151 additions and 21 deletions
|
|
@ -348,13 +348,32 @@ $codexExecArgs = if ($envValues.ContainsKey("CODEX_EXEC_ARGS")) { Split-CommandA
|
|||
|
||||
$typedContext = Get-TypedSelfState
|
||||
$typedState = $typedContext.typedState
|
||||
$isSleepMaintenance = [bool]$typedState.scheduledRuntime.sleepCycle.isNapping
|
||||
$memoryCount = @(Convert-ToValueArray -Value $typedState.thoughtMemory.memories).Count
|
||||
$incubationCount = @(Convert-ToValueArray -Value $typedState.thoughtMemory.incubation).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 = $memoryCount + $incubationCount + $activeCandidateCount
|
||||
|
||||
Write-JsonFile -Path $contextPath -Data @{
|
||||
generated = "now"
|
||||
stateFile = $stateFilePath
|
||||
selfStateSummary = $typedContext.summary
|
||||
mode = if ($typedState.scheduledRuntime.sleepCycle.isNapping) { "sleep_maintenance" } else { "awake_memory_maintenance" }
|
||||
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 1)
|
||||
maintenancePressure = [int]$maintenancePressure
|
||||
memoryCount = [int]$memoryCount
|
||||
incubationCount = [int]$incubationCount
|
||||
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."
|
||||
}
|
||||
memories = @(Project-MemoriesForRumination -Memories $typedState.thoughtMemory.memories -Now $startedAtUtc)
|
||||
incubation = @(Project-IncubationForRumination -Threads $typedState.thoughtMemory.incubation -Now $startedAtUtc)
|
||||
candidateInterventions = @(Project-InterventionsForRumination -Interventions $typedState.candidateInterventions.interventions -Now $startedAtUtc)
|
||||
|
|
@ -417,6 +436,10 @@ if (-not (Test-Path $operationOutputPath)) {
|
|||
$proposedOperations = @(Convert-ToOperationArray -Value (Read-JsonFile -Path $operationOutputPath))
|
||||
$appliedOperations = @()
|
||||
|
||||
if ((-not $SkipModel) -and $isSleepMaintenance -and $maintenancePressure -gt 1 -and @($proposedOperations).Count -eq 0) {
|
||||
throw "Sleep memory maintenance returned no operations despite maintenance pressure $maintenancePressure."
|
||||
}
|
||||
|
||||
foreach ($operation in $proposedOperations) {
|
||||
Assert-AllowedMemoryMaintenanceOperation -Operation $operation
|
||||
$appliedOperations += Apply-TypedOperation -Operation $operation
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
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";
|
||||
|
|
@ -15,6 +16,9 @@ const statusPath = resolve(repoRoot, ".voidbot/status/void-mood-drift.json");
|
|||
const lockPath = resolve(repoRoot, ".voidbot/status/void-mood-drift.lock");
|
||||
const lastSpeechPath = resolve(repoRoot, ".voidbot/status/void-last-speech.json");
|
||||
const moderationLockPath = resolve(repoRoot, ".voidbot/status/moderation-rumination.lock");
|
||||
const memoryMaintenanceStatusPath = resolve(repoRoot, ".voidbot/status/void-memory-maintenance.json");
|
||||
const memoryMaintenanceScriptPath = resolve(repoRoot, "scripts/run-void-memory-maintenance.ps1");
|
||||
const args = new Set(process.argv.slice(2));
|
||||
|
||||
async function main() {
|
||||
const now = new Date();
|
||||
|
|
@ -53,12 +57,27 @@ async function main() {
|
|||
},
|
||||
);
|
||||
|
||||
const memoryMaintenance = runSleepMemoryMaintenanceIfDue({ sleepCycle });
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -157,6 +176,83 @@ function readJsonSafe(path) {
|
|||
}
|
||||
}
|
||||
|
||||
function runSleepMemoryMaintenanceIfDue({ sleepCycle }) {
|
||||
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 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");
|
||||
|
|
|
|||
Loading…
Reference in a new issue