Tighten Void speech pressure and reply guards
This commit is contained in:
parent
b53289d14f
commit
e8acb67141
10 changed files with 225 additions and 30 deletions
|
|
@ -379,11 +379,12 @@ function Test-CandidateMatchesPressure {
|
|||
$Candidate,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $PressureId,
|
||||
[string] $PressureTargetKey
|
||||
[string] $PressureTargetKey,
|
||||
[string[]] $AllowedStatuses = @("queued", "spoken")
|
||||
)
|
||||
|
||||
$status = Get-ObjectPropertyString -Value $Candidate -Name "status"
|
||||
if ($null -eq $status -or -not @("queued", "spoken").Contains($status)) {
|
||||
if ($null -eq $status -or -not $AllowedStatuses.Contains($status)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
|
|
@ -405,12 +406,78 @@ function Get-DeliverableCandidateInterventions {
|
|||
$status = Get-ObjectPropertyString -Value $_ -Name "status"
|
||||
$deliveryTarget = Get-ObjectPropertyValue -Value $_ -Name "deliveryTarget"
|
||||
$spokenAt = Get-ObjectPropertyString -Value $_ -Name "spokenAt"
|
||||
$status -eq "queued" -and $null -ne $deliveryTarget -and [string]::IsNullOrWhiteSpace($spokenAt)
|
||||
$status -eq "queued" -and
|
||||
$null -ne $deliveryTarget -and
|
||||
[string]::IsNullOrWhiteSpace($spokenAt) -and
|
||||
-not (Test-QueuedCandidateAlreadyAnswered -Candidate $_ -Receipts $TypedState.speechReceipts.recentReceipts)
|
||||
} |
|
||||
Sort-Object -Property @{ Expression = { [double](Get-ObjectPropertyValue -Value $_ -Name "priority") }; Descending = $true }, @{ Expression = { Get-ObjectPropertyString -Value $_ -Name "updatedAt" }; Descending = $false }
|
||||
)
|
||||
}
|
||||
|
||||
function Test-CandidateDeliveryTargetMatchesReceipt {
|
||||
param(
|
||||
$DeliveryTarget,
|
||||
$Receipt
|
||||
)
|
||||
|
||||
$channelId = Get-ObjectPropertyString -Value $DeliveryTarget -Name "channelId"
|
||||
$replyToMessageId = Get-ObjectPropertyString -Value $DeliveryTarget -Name "replyToMessageId"
|
||||
if ([string]::IsNullOrWhiteSpace($channelId) -or [string]::IsNullOrWhiteSpace($replyToMessageId)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return (
|
||||
(Get-ObjectPropertyString -Value $Receipt -Name "channelId") -eq $channelId -and
|
||||
(Get-ObjectPropertyString -Value $Receipt -Name "replyToMessageId") -eq $replyToMessageId
|
||||
)
|
||||
}
|
||||
|
||||
function Test-QueuedCandidateAlreadyAnswered {
|
||||
param(
|
||||
$Candidate,
|
||||
$Receipts
|
||||
)
|
||||
|
||||
$deliveryTarget = Get-ObjectPropertyValue -Value $Candidate -Name "deliveryTarget"
|
||||
if ($null -eq $deliveryTarget) {
|
||||
return $false
|
||||
}
|
||||
|
||||
return @(
|
||||
@(Convert-ToValueArray -Value $Receipts) |
|
||||
Where-Object { Test-CandidateDeliveryTargetMatchesReceipt -DeliveryTarget $deliveryTarget -Receipt $_ }
|
||||
).Count -gt 0
|
||||
}
|
||||
|
||||
function Get-AlreadyAnsweredCandidateRetireOperations {
|
||||
param(
|
||||
$TypedState,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[DateTime] $RetiredAt
|
||||
)
|
||||
|
||||
$receipts = @(Convert-ToValueArray -Value $TypedState.speechReceipts.recentReceipts)
|
||||
return @(
|
||||
@(Convert-ToValueArray -Value $TypedState.candidateInterventions.interventions) |
|
||||
Where-Object {
|
||||
$status = Get-ObjectPropertyString -Value $_ -Name "status"
|
||||
$spokenAt = Get-ObjectPropertyString -Value $_ -Name "spokenAt"
|
||||
$status -eq "queued" -and
|
||||
[string]::IsNullOrWhiteSpace($spokenAt) -and
|
||||
(Test-QueuedCandidateAlreadyAnswered -Candidate $_ -Receipts $receipts)
|
||||
} |
|
||||
ForEach-Object {
|
||||
@{
|
||||
operation = "retire_candidate_intervention"
|
||||
interventionId = Get-ObjectPropertyString -Value $_ -Name "interventionId"
|
||||
retiredAt = $RetiredAt.ToString("o")
|
||||
reason = "duplicate reply target already answered"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function Get-SpeechPressureObligations {
|
||||
param($TypedState)
|
||||
|
||||
|
|
@ -423,13 +490,13 @@ function Get-SpeechPressureObligations {
|
|||
$intensity = Get-ObjectPropertyValue -Value $_ -Name "intensity"
|
||||
$status -in @("active", "ready_to_act") -and
|
||||
$kind -in @("self_advocacy_request", "world_advocacy_request") -and
|
||||
$null -ne $intensity -and [double]$intensity -ge 0.65
|
||||
$null -ne $intensity -and [double]$intensity -ge 0.55
|
||||
} |
|
||||
Where-Object {
|
||||
$pressureId = Get-ObjectPropertyString -Value $_ -Name "pressureId"
|
||||
$pressureTargetKey = Get-ThoughtTargetKey -Target (Get-ObjectPropertyValue -Value $_ -Name "target")
|
||||
-not ($candidateInterventions | Where-Object {
|
||||
Test-CandidateMatchesPressure -Candidate $_ -PressureId $pressureId -PressureTargetKey $pressureTargetKey
|
||||
Test-CandidateMatchesPressure -Candidate $_ -PressureId $pressureId -PressureTargetKey $pressureTargetKey -AllowedStatuses @("queued")
|
||||
} | Select-Object -First 1)
|
||||
} |
|
||||
ForEach-Object {
|
||||
|
|
@ -446,7 +513,7 @@ function Get-SpeechPressureObligations {
|
|||
intensity = Get-ObjectPropertyValue -Value $_ -Name "intensity"
|
||||
sourceMemoryIds = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "sourceMemoryIds"))
|
||||
tags = @(Convert-ToValueArray -Value (Get-ObjectPropertyValue -Value $_ -Name "tags"))
|
||||
requiredResolution = "Queue a candidate intervention with tag source_pressure:$((Get-ObjectPropertyString -Value $_ -Name "pressureId")) or cool/retire this pressure with an explicit reason."
|
||||
requiredResolution = "Queue a live candidate intervention with tag source_pressure:$((Get-ObjectPropertyString -Value $_ -Name "pressureId")) or cool/retire this pressure with an explicit reason. An old spoken candidate does not satisfy a still-active pressure."
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -983,6 +1050,15 @@ if (-not $NoPost -and -not $SkipModel -and -not $disableRepoCursorAdvance) {
|
|||
$deliveredCandidateCount = 0
|
||||
if (-not $NoPost) {
|
||||
$refreshedTypedContext = Get-TypedSelfState
|
||||
$alreadyAnsweredCandidateOperations = @(Get-AlreadyAnsweredCandidateRetireOperations -TypedState $refreshedTypedContext.typedState -RetiredAt ([DateTime]::UtcNow))
|
||||
if ($alreadyAnsweredCandidateOperations.Count -gt 0) {
|
||||
Append-RunLog ("retiring already-answered queued candidates: {0}" -f $alreadyAnsweredCandidateOperations.Count)
|
||||
foreach ($operation in $alreadyAnsweredCandidateOperations) {
|
||||
$appliedOperations += Apply-TypedOperation -Operation $operation
|
||||
}
|
||||
$refreshedTypedContext = Get-TypedSelfState
|
||||
}
|
||||
|
||||
$deliverableCandidates = @(Get-DeliverableCandidateInterventions -TypedState $refreshedTypedContext.typedState)
|
||||
foreach ($candidate in $deliverableCandidates) {
|
||||
if ($deliveredCandidateCount -ge 1) {
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ function updateSpeakingPressure({ typedState, previous, sleepCycle, now }) {
|
|||
.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.22 : 0.12;
|
||||
const readinessWeight = entry.status === "ready_to_act" ? 1.2 : 1;
|
||||
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
|
||||
|
|
@ -157,13 +157,13 @@ function updateSpeakingPressure({ typedState, previous, sleepCycle, now }) {
|
|||
const sleepiness = sleepCycle.isNapping ? 1 : 0;
|
||||
const priorNeedToSpeak = clamp(previous.needToSpeak ?? 0.35, 0, 1);
|
||||
const targetNeedToSpeak = clamp(
|
||||
0.16 + silencePressure * 0.32 + queuedInterventionPressure + topThreadPressure + agencySpeechPressure - recentSpeechDamping * 0.42 - sleepiness * 0.26,
|
||||
0.18 + silencePressure * 0.32 + queuedInterventionPressure + topThreadPressure + agencySpeechPressure - 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.62 + topThreadPressure * 0.72 + queuedInterventionPressure * 0.28 + agencySpeechPressure * 0.35, 0, 1),
|
||||
clamp((previous.noveltyPressure ?? 0.35) * 0.6 + topThreadPressure * 0.72 + queuedInterventionPressure * 0.3 + agencySpeechPressure * 0.44, 0, 1),
|
||||
);
|
||||
const confessionPressure = round3(
|
||||
clamp((previous.confessionPressure ?? 0.25) * 0.68 + silencePressure * 0.12 + sleepiness * 0.1, 0, 1),
|
||||
|
|
|
|||
|
|
@ -90,6 +90,32 @@ process.stdin.on("end", () => {
|
|||
tags: ["fixture", "duplicate"],
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "queue_candidate_intervention",
|
||||
intervention: {
|
||||
interventionId: "fixture-already-answered-reply-target",
|
||||
kind: "direct_reply",
|
||||
status: "queued",
|
||||
target: {
|
||||
kind: "room",
|
||||
id: "fixture-channel",
|
||||
label: "Fixture channel",
|
||||
},
|
||||
summary: "Already-answered reply target should retire before delivery.",
|
||||
draft: "This should retire because the reply target already has a receipt.",
|
||||
deliveryTarget: {
|
||||
mode: "channel",
|
||||
channelId: "fixture-channel-id",
|
||||
replyToMessageId: "fixture-answered-message-id",
|
||||
personaName: "Void",
|
||||
},
|
||||
priority: 0.95,
|
||||
mustEventuallyShare: false,
|
||||
createdAt: "2026-05-17T03:00:02.000Z",
|
||||
updatedAt: "2026-05-17T03:00:02.000Z",
|
||||
tags: ["fixture", "already-answered"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
mkdirSync(dirname(operationOutputPath), { recursive: true });
|
||||
|
|
@ -151,6 +177,24 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) +
|
|||
$cursorOperation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $cursorOperationPath -Encoding UTF8
|
||||
node .\scripts\void-self-state.mjs apply-operation --canonical $stateFilePath --operation-file $cursorOperationPath | Out-Null
|
||||
|
||||
$priorReceiptOperation = @{
|
||||
operation = "record_delivery_receipt"
|
||||
receipt = @{
|
||||
receiptKey = "fixture-previous-answer"
|
||||
sentAt = "2026-05-17T02:58:00.000Z"
|
||||
mode = "channel"
|
||||
transport = "webhook"
|
||||
channelId = "fixture-channel-id"
|
||||
replyToMessageId = "fixture-answered-message-id"
|
||||
personaName = "Void"
|
||||
preview = "An earlier answer already closed this exact reply target."
|
||||
contentLength = 56
|
||||
chunkCount = 1
|
||||
}
|
||||
}
|
||||
$priorReceiptOperation | ConvertTo-Json -Depth 32 | Set-Content -LiteralPath $cursorOperationPath -Encoding UTF8
|
||||
node .\scripts\void-self-state.mjs apply-operation --canonical $stateFilePath --operation-file $cursorOperationPath | Out-Null
|
||||
|
||||
$previousCodexExecutable = $env:CODEX_EXECUTABLE
|
||||
$previousCodexExecArgs = $env:CODEX_EXEC_ARGS
|
||||
$previousFixtureOutput = $env:VOID_RUMINATION_FIXTURE_OPERATION_OUTPUT
|
||||
|
|
@ -183,15 +227,22 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) +
|
|||
if ($status.status -ne "ok") {
|
||||
throw "Speech fixture did not finish ok."
|
||||
}
|
||||
if ([int]$status.proposedOperationCount -ne 2 -or [int]$status.appliedOperationCount -ne 3 -or [int]$status.deliveredCandidateCount -ne 1) {
|
||||
throw "Speech fixture expected two proposals, three applied operations, and one delivered candidate."
|
||||
if ([int]$status.proposedOperationCount -ne 3 -or [int]$status.appliedOperationCount -ne 5 -or [int]$status.deliveredCandidateCount -ne 1) {
|
||||
throw "Speech fixture expected three proposals, five applied operations, and one delivered candidate."
|
||||
}
|
||||
|
||||
$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
|
||||
$candidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-parent-owned-speech" })[0]
|
||||
$duplicateCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-duplicate-same-reply-target" })[0]
|
||||
$receipt = @($state.speechReceipts.recentReceipts)[0]
|
||||
$alreadyAnsweredCandidate = @($state.candidateInterventions.interventions | Where-Object { $_.interventionId -eq "fixture-already-answered-reply-target" })[0]
|
||||
$receipt = @(
|
||||
$state.speechReceipts.recentReceipts |
|
||||
Where-Object {
|
||||
$property = $_.PSObject.Properties["candidateInterventionId"]
|
||||
$null -ne $property -and $property.Value -eq "fixture-parent-owned-speech"
|
||||
}
|
||||
)[0]
|
||||
|
||||
if ($candidate.status -ne "spoken") {
|
||||
throw "Speech fixture did not mark the candidate spoken."
|
||||
|
|
@ -199,6 +250,9 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) +
|
|||
if ($duplicateCandidate.status -ne "retired") {
|
||||
throw "Speech fixture did not retire the duplicate same-target candidate."
|
||||
}
|
||||
if ($alreadyAnsweredCandidate.status -ne "retired") {
|
||||
throw "Speech fixture did not retire the already-answered reply target candidate."
|
||||
}
|
||||
if ($receipt.channelId -ne "fixture-channel-id" -or $receipt.replyToMessageId -ne "fixture-message-id") {
|
||||
throw "Speech fixture did not preserve the delivery receipt target."
|
||||
}
|
||||
|
|
@ -213,6 +267,7 @@ process.stdout.write(JSON.stringify({ ok: true, mode: "channel", channelId }) +
|
|||
@{
|
||||
status = "ok"
|
||||
candidateStatus = $candidate.status
|
||||
staleDuplicateStatus = $alreadyAnsweredCandidate.status
|
||||
receiptCount = @($state.speechReceipts.recentReceipts).Count
|
||||
deliveredCandidateCount = [int]$status.deliveredCandidateCount
|
||||
} | ConvertTo-Json -Compress
|
||||
|
|
|
|||
|
|
@ -28,7 +28,10 @@ function Write-JsonFixture {
|
|||
}
|
||||
|
||||
function Initialize-State {
|
||||
param([Parameter(Mandatory = $true)][string] $StateFilePath)
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string] $StateFilePath,
|
||||
[switch] $SeedSpokenCandidate
|
||||
)
|
||||
|
||||
$cursorPath = Join-Path (Split-Path -Parent $StateFilePath) "cursor.json"
|
||||
Write-JsonFixture -Path $cursorPath -Value @{
|
||||
|
|
@ -66,6 +69,48 @@ function Initialize-State {
|
|||
}
|
||||
}
|
||||
Invoke-NodeChecked -Arguments @(".\scripts\void-self-state.mjs", "apply-operation", "--canonical", $StateFilePath, "--operation-file", $pressurePath) | Out-Null
|
||||
|
||||
if ($SeedSpokenCandidate) {
|
||||
$spokenCandidatePath = Join-Path (Split-Path -Parent $StateFilePath) "spoken-candidate.json"
|
||||
Write-JsonFixture -Path $spokenCandidatePath -Value @(
|
||||
@{
|
||||
operation = "queue_candidate_intervention"
|
||||
intervention = @{
|
||||
interventionId = "fixture-pressure-spoken-history"
|
||||
kind = "world_advocacy"
|
||||
status = "queued"
|
||||
target = @{ kind = "repo"; id = "Aquarium-Engine"; label = "Aquarium-Engine" }
|
||||
summary = "A previously spoken pressure candidate."
|
||||
draft = "I already said this once, but the pressure stayed active."
|
||||
priority = 0.7
|
||||
mustEventuallyShare = $true
|
||||
createdAt = "2026-05-17T04:02:00.000Z"
|
||||
updatedAt = "2026-05-17T04:02:00.000Z"
|
||||
tags = @("fixture", "source_pressure:fixture-pressure-wants-a-mouth")
|
||||
}
|
||||
},
|
||||
@{
|
||||
operation = "mark_candidate_intervention_spoken"
|
||||
interventionId = "fixture-pressure-spoken-history"
|
||||
receipt = @{
|
||||
receiptKey = "fixture-pressure-spoken-history-receipt"
|
||||
candidateInterventionId = "fixture-pressure-spoken-history"
|
||||
sentAt = "2026-05-17T04:03:00.000Z"
|
||||
mode = "channel"
|
||||
transport = "webhook"
|
||||
channelId = "fixture-channel-id"
|
||||
preview = "A previously spoken pressure candidate."
|
||||
contentLength = 42
|
||||
chunkCount = 1
|
||||
}
|
||||
}
|
||||
)
|
||||
foreach ($operation in (Get-Content -LiteralPath $spokenCandidatePath -Raw -Encoding UTF8 | ConvertFrom-Json)) {
|
||||
$operationPath = Join-Path (Split-Path -Parent $StateFilePath) ("spoken-candidate-operation-" + [guid]::NewGuid().ToString("n") + ".json")
|
||||
Write-JsonFixture -Path $operationPath -Value $operation
|
||||
Invoke-NodeChecked -Arguments @(".\scripts\void-self-state.mjs", "apply-operation", "--canonical", $StateFilePath, "--operation-file", $operationPath) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Write-FakeCodex {
|
||||
|
|
@ -138,7 +183,8 @@ process.stdin.on("end", () => {
|
|||
function Invoke-RuminationFixture {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string] $CaseName,
|
||||
[Parameter(Mandatory = $true)][string] $Mode
|
||||
[Parameter(Mandatory = $true)][string] $Mode,
|
||||
[switch] $SeedSpokenCandidate
|
||||
)
|
||||
|
||||
$caseRoot = Join-Path $tempRoot $CaseName
|
||||
|
|
@ -149,7 +195,7 @@ function Invoke-RuminationFixture {
|
|||
$statusOperationPath = Join-Path $fixtureStatusDir "moderation-rumination-operations.json"
|
||||
New-Item -ItemType Directory -Force -Path $caseRoot | Out-Null
|
||||
|
||||
Initialize-State -StateFilePath $stateFilePath
|
||||
Initialize-State -StateFilePath $stateFilePath -SeedSpokenCandidate:$SeedSpokenCandidate
|
||||
Write-FakeCodex -Path $fakeCodexPath -Mode $Mode
|
||||
|
||||
$previousCodexExecutable = $env:CODEX_EXECUTABLE
|
||||
|
|
@ -204,6 +250,16 @@ try {
|
|||
throw "Silent speech-pressure fixture failed for the wrong reason: $($silentStatus.failureMessage)"
|
||||
}
|
||||
|
||||
$spokenHistory = Invoke-RuminationFixture -CaseName "spoken-history" -Mode "silent" -SeedSpokenCandidate
|
||||
if ($spokenHistory.exitCode -eq 0) {
|
||||
throw "Spoken-history speech-pressure fixture unexpectedly passed."
|
||||
}
|
||||
|
||||
$spokenHistoryStatus = Get-Content -LiteralPath $spokenHistory.statusPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($spokenHistoryStatus.failureMessage -notmatch "requires a candidate intervention") {
|
||||
throw "Spoken-history speech-pressure fixture failed for the wrong reason: $($spokenHistoryStatus.failureMessage)"
|
||||
}
|
||||
|
||||
$candidate = Invoke-RuminationFixture -CaseName "candidate" -Mode "candidate"
|
||||
if ($candidate.exitCode -ne 0) {
|
||||
$candidateStatus = Get-Content -LiteralPath $candidate.statusPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
|
|
@ -220,6 +276,7 @@ try {
|
|||
@{
|
||||
status = "ok"
|
||||
silentExitCode = $silent.exitCode
|
||||
spokenHistoryExitCode = $spokenHistory.exitCode
|
||||
candidateStatus = $queued.status
|
||||
candidatePriority = $queued.priority
|
||||
} | ConvertTo-Json -Compress
|
||||
|
|
|
|||
Loading…
Reference in a new issue