param( [string]$TaskDirectory = "", [string]$RunLabel = "autopilot", [string]$Implementer = "", [string[]]$Reviewers = @(), [switch]$UseCodexMaster = $true, [switch]$AutoFix = $true, [switch]$DryRun ) . (Join-Path $PSScriptRoot "Common.ps1") $ralphRoot = Get-RalphRoot $repoRoot = Get-RepoRoot $roots = Get-RalphTaskRoots $automationConfig = Get-RalphAutomationConfig if ([string]::IsNullOrWhiteSpace($TaskDirectory)) { $TaskDirectory = Join-Path $ralphRoot "tasks\current" } if ([string]::IsNullOrWhiteSpace($Implementer)) { $Implementer = Get-DefaultImplementer } if ($Reviewers.Count -eq 0) { $Reviewers = Get-DefaultReviewers } $taskPack = Read-TaskPack -TaskDirectory $TaskDirectory $submissionPath = Join-Path $TaskDirectory "submission.json" $submissionMetadata = $null if (Test-Path $submissionPath) { try { $submissionMetadata = Read-JsonFile -Path $submissionPath } catch { $submissionMetadata = $null } } $runId = New-RunId -Label $RunLabel $runDir = Join-Path $ralphRoot ("runs\" + $runId) $worktreePath = Join-Path $ralphRoot ("worktrees\" + $runId + "-" + $Implementer) Ensure-Directory -Path $runDir Ensure-Directory -Path (Join-Path $runDir "prompts") Ensure-Directory -Path (Join-Path $runDir "outputs") Ensure-Directory -Path (Join-Path $runDir "reviews") Copy-Item -Path $taskPack.Files.Task -Destination (Join-Path $runDir "TASK.md") -Force Copy-Item -Path $taskPack.Files.Acceptance -Destination (Join-Path $runDir "ACCEPTANCE.md") -Force Copy-Item -Path $taskPack.Files.Context -Destination (Join-Path $runDir "CONTEXT.md") -Force $script:runState = [ordered]@{ run_id = $runId status = "initializing" stage = "initializing" started_at = (Get-Date).ToString("o") finished_at = $null task_directory = $TaskDirectory run_dir = $runDir worktree = $worktreePath implementer = [ordered]@{ name = $Implementer status = "pending" started_at = $null finished_at = $null output_file = "" } reviewers = @() codex_master = [ordered]@{ enabled = [bool]$UseCodexMaster status = $(if ($UseCodexMaster) { "pending" } else { "disabled" }) started_at = $null finished_at = $null output_file = "" verdict = "" acceptance_passed = $false } fix_pass = [ordered]@{ enabled = [bool]$AutoFix status = $(if ($AutoFix) { "pending" } else { "disabled" }) started_at = $null finished_at = $null output_file = "" } latest_message = "Run initialized" errors = @() } foreach ($reviewer in $Reviewers) { $script:runState.reviewers += [ordered]@{ name = $reviewer status = "pending" started_at = $null finished_at = $null output_file = "" } } function Get-FollowupGeneration { if ($null -eq $submissionMetadata) { return 0 } if ($submissionMetadata.PSObject.Properties.Name -contains "followup_generation") { try { return [int]$submissionMetadata.followup_generation } catch { return 0 } } return 0 } function Save-RunState { Set-RalphCurrentRunState -State $script:runState } function Set-RunPhase { param( [Parameter(Mandatory = $true)][string]$Stage, [Parameter(Mandatory = $true)][string]$Status, [Parameter(Mandatory = $true)][string]$Message ) $script:runState.stage = $Stage $script:runState.status = $Status $script:runState.latest_message = $Message Save-RunState Add-RalphEvent -RunId $script:runState.run_id -Stage $Stage -Status $Status -Message $Message } function Set-ActorState { param( [Parameter(Mandatory = $true)][ValidateSet("implementer", "reviewer", "codex_master", "fix_pass")][string]$ActorType, [Parameter(Mandatory = $true)][string]$Status, [Parameter(Mandatory = $true)][string]$Message, [string]$ActorName = "", [string]$OutputFile = "" ) $timestamp = (Get-Date).ToString("o") $target = $null $actorLabel = $ActorName switch ($ActorType) { "implementer" { $target = $script:runState.implementer $actorLabel = $script:runState.implementer.name } "reviewer" { $target = $script:runState.reviewers | Where-Object { $_.name -eq $ActorName } | Select-Object -First 1 $actorLabel = $ActorName } "codex_master" { $target = $script:runState.codex_master $actorLabel = "codex_master" } "fix_pass" { $target = $script:runState.fix_pass $actorLabel = $script:runState.implementer.name } } if ($null -ne $target) { $target.status = $Status if ($Status -eq "running" -and -not $target.started_at) { $target.started_at = $timestamp } if ($Status -in @("completed", "failed", "skipped")) { $target.finished_at = $timestamp } if ($OutputFile) { $target.output_file = $OutputFile } } $script:runState.latest_message = $Message Save-RunState Add-RalphEvent -RunId $script:runState.run_id -Stage $ActorType -Status $Status -Actor $actorLabel -Message $Message -Data @{ output_file = $OutputFile } } function Invoke-CodexReviewPass { param( [Parameter(Mandatory = $true)][string]$StageName, [Parameter(Mandatory = $true)][string]$PromptFileName, [Parameter(Mandatory = $true)][string]$OutputFileName, [Parameter(Mandatory = $true)][string]$RunningMessage, [Parameter(Mandatory = $true)][string]$CompletedMessage, [string[]]$ExtraSections = @() ) $promptPath = Join-Path $runDir ("prompts\" + $PromptFileName) $outputPath = Join-Path $runDir ("reviews\" + $OutputFileName) New-PromptDocument ` -TemplatePath (Join-Path $ralphRoot "templates\CODEX_REVIEW_PROMPT.md") ` -TaskPack $taskPack ` -OutputPath $promptPath ` -ExtraSections $ExtraSections Set-RunPhase -Stage $StageName -Status "running" -Message $RunningMessage Set-ActorState -ActorType "codex_master" -Status "running" -Message $RunningMessage -OutputFile $outputPath $verdict = & (Join-Path $PSScriptRoot "Invoke-CodexMaster.ps1") ` -PromptFile $promptPath ` -OutputFile $outputPath ` -WorkingDirectory $worktreePath $script:runState.codex_master.verdict = [string]$verdict.verdict $script:runState.codex_master.acceptance_passed = [bool]$verdict.acceptance_passed Set-ActorState -ActorType "codex_master" -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")") -OutputFile $outputPath Set-RunPhase -Stage $StageName -Status "completed" -Message ($CompletedMessage + " (verdict: " + $verdict.verdict + ")") Save-RunState return $verdict } function Invoke-AutoFollowupTask { param( [Parameter(Mandatory = $true)][string]$OutcomeStatus, [Parameter(Mandatory = $true)][string]$OutcomeSummary ) if ($null -eq $automationConfig) { return $null } if (-not ($automationConfig.PSObject.Properties.Name -contains "auto_followup")) { return $null } $autoFollowup = $automationConfig.auto_followup $enabled = $false try { $enabled = [bool]$autoFollowup.enabled } catch { $enabled = $false } if (-not $enabled) { return $null } if (-not $UseCodexMaster) { return $null } $trigger = $false if ($OutcomeStatus -eq "failed") { try { $trigger = [bool]$autoFollowup.trigger_on_failure } catch { $trigger = $false } } elseif ($OutcomeStatus -eq "completed") { try { $trigger = [bool]$autoFollowup.trigger_on_success } catch { $trigger = $false } } if (-not $trigger) { return $null } $maxDepth = 0 try { $maxDepth = [int]$autoFollowup.max_chain_depth } catch { $maxDepth = 0 } $currentDepth = Get-FollowupGeneration if ($maxDepth -gt 0 -and $currentDepth -ge $maxDepth) { return $null } $targetRelative = "docs\autopilot" if ($autoFollowup.PSObject.Properties.Name -contains "target_directory" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.target_directory)) { $targetRelative = [string]$autoFollowup.target_directory } $targetDirectory = Join-Path $repoRoot $targetRelative Ensure-Directory -Path $targetDirectory $prefix = "AUTOFOLLOWUP" if ($autoFollowup.PSObject.Properties.Name -contains "title_prefix" -and -not [string]::IsNullOrWhiteSpace([string]$autoFollowup.title_prefix)) { $prefix = [string]$autoFollowup.title_prefix } $nextPromptOutput = Join-Path $runDir "NEXT_TASK.md" $extraSections = @( "## PREVIOUS OUTCOME SUMMARY`n$OutcomeSummary", "## RUN SUMMARY FILE`n$(Join-Path $runDir 'SUMMARY.md')", "## RUN OUTPUTS DIRECTORY`n$(Join-Path $runDir 'outputs')", "## RUN REVIEWS DIRECTORY`n$(Join-Path $runDir 'reviews')" ) $null = & (Join-Path $PSScriptRoot "Invoke-CodexNextTask.ps1") ` -TaskDirectory $TaskDirectory ` -RunDirectory $runDir ` -OutputFile $nextPromptOutput ` -RunStatus $OutcomeStatus ` -WorkingDirectory $worktreePath ` -ExtraSections $extraSections $dateLabel = Get-Date -Format "yyyyMMdd-HHmmss" $fileName = "{0}-{1}.md" -f $dateLabel, (Convert-ToRalphSlug -Text ($prefix + "-" + $taskPack.Task.Substring(0, [Math]::Min($taskPack.Task.Length, 32)))) $finalMdPath = Join-Path $targetDirectory $fileName Copy-Item -Path $nextPromptOutput -Destination $finalMdPath -Force $taskInfo = New-RalphTaskPackFromMarkdown ` -MarkdownPath $finalMdPath ` -AdditionalMetadata @{ auto_generated = $true parent_run_id = $runId parent_task_directory = $TaskDirectory followup_generation = ($currentDepth + 1) source_run_status = $OutcomeStatus } Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "queued" -Actor "codex_master" -Message ("Auto-followup task queued: " + $taskInfo.title) -Data @{ task_directory = $taskInfo.task_directory source_markdown = $finalMdPath } Send-TelegramNotification ` -EventName "task_queued" ` -Title "Auto-followup queued" ` -Message ($taskInfo.title + "`n" + $taskInfo.task_directory) ` -RunId $runId ` -Stage "auto_followup" ` -Status "queued" | Out-Null return [ordered]@{ title = $taskInfo.title task_directory = $taskInfo.task_directory source_markdown = $finalMdPath followup_generation = ($currentDepth + 1) } } Save-RunState Add-RalphEvent -RunId $runId -Stage "initializing" -Status "started" -Message "Ralph run initialized" -Data @{ implementer = $Implementer reviewers = $Reviewers worktree = $worktreePath } Send-TelegramNotification ` -EventName "run_started" ` -Title "Run started" ` -Message ("Implementer: " + $Implementer + "`nReviewers: " + ($Reviewers -join ", ")) ` -RunId $runId ` -Stage "initializing" ` -Status "started" | Out-Null $gitStatus = & git -C $repoRoot status --short Write-Utf8File -Path (Join-Path $runDir "repo_status_before.txt") -Content (($gitStatus -join "`n") + "`n") $gitStat = & git -C $repoRoot diff --stat Write-Utf8File -Path (Join-Path $runDir "repo_diff_stat_before.txt") -Content (($gitStat -join "`n") + "`n") $implementerPrompt = Join-Path $runDir "prompts\implementer.md" New-PromptDocument ` -TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") ` -TaskPack $taskPack ` -OutputPath $implementerPrompt ` -ExtraSections @( "## WORKTREE`nEdit only inside this worktree:`n`n$worktreePath", "## REQUIRED RUN OUTPUT`nWrite a file named CHANGES.md in this run directory:`n`n$runDir" ) $summaryLines = @( "# Ralph Run", "", "- Run ID: $runId", "- Implementer: $Implementer", "- Reviewers: $(($Reviewers -join ', '))", "- Worktree: $worktreePath", "- Codex master review: $(if ($UseCodexMaster) { 'enabled' } else { 'disabled' })", "- Auto fix pass: $(if ($AutoFix) { 'enabled' } else { 'disabled' })", "- Dry run: $(if ($DryRun) { 'yes' } else { 'no' })" ) if ($DryRun) { Set-RunPhase -Stage "dry_run" -Status "completed" -Message "Dry run prepared successfully" $script:runState.finished_at = (Get-Date).ToString("o") Save-RunState Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n") Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md") return } Set-RunPhase -Stage "worktree" -Status "running" -Message "Creating isolated worktree" & git -C $repoRoot worktree add --detach $worktreePath HEAD | Out-Null Set-RunPhase -Stage "worktree" -Status "completed" -Message "Worktree ready" $reviewOutputs = @() $codexFinalVerdict = $null $autoFollowupResult = $null $script:heartbeatJob = $null $script:heartbeatSignalPath = "" $codexReviewSections = @( "## IMPLEMENTER WORKTREE`n$worktreePath", "## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')", "## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')" ) function Start-HeartbeatMonitor { param( [Parameter(Mandatory = $true)][string]$Stage, [Parameter(Mandatory = $true)][string]$Title, [Parameter(Mandatory = $true)][string]$Message, [int]$IntervalSeconds = 300 ) Stop-HeartbeatMonitor $signalPath = Join-Path $runDir ("heartbeat-" + $Stage + ".lock") Write-Utf8File -Path $signalPath -Content ((Get-Date).ToString("o")) $commonPath = Join-Path $PSScriptRoot "Common.ps1" $script:heartbeatSignalPath = $signalPath $script:heartbeatJob = Start-Job -ArgumentList $commonPath, $signalPath, $Stage, $Title, $Message, $runId, $IntervalSeconds -ScriptBlock { param($CommonPath, $SignalPath, $StageName, $TitleText, $MessageText, $RunIdValue, $IntervalValue) . $CommonPath while (Test-Path $SignalPath) { Start-Sleep -Seconds $IntervalValue if (-not (Test-Path $SignalPath)) { break } Send-TelegramNotification ` -EventName "run_heartbeat" ` -Title $TitleText ` -Message $MessageText ` -RunId $RunIdValue ` -Stage $StageName ` -Status "running" | Out-Null } } } function Stop-HeartbeatMonitor { if (-not [string]::IsNullOrWhiteSpace($script:heartbeatSignalPath) -and (Test-Path $script:heartbeatSignalPath)) { Remove-Item -LiteralPath $script:heartbeatSignalPath -Force -ErrorAction SilentlyContinue } $script:heartbeatSignalPath = "" if ($null -ne $script:heartbeatJob) { Wait-Job -Job $script:heartbeatJob -Timeout 1 | Out-Null if ($script:heartbeatJob.State -eq "Running") { Stop-Job -Job $script:heartbeatJob -Force | Out-Null } Remove-Job -Job $script:heartbeatJob -Force -ErrorAction SilentlyContinue $script:heartbeatJob = $null } } try { $implementerOutput = Join-Path $runDir "outputs\implementer.json" Set-RunPhase -Stage "implementer" -Status "running" -Message ("Implementer " + $Implementer + " started") Set-ActorState -ActorType "implementer" -Status "running" -Message ("Implementer " + $Implementer + " received task pack") -OutputFile $implementerOutput Start-HeartbeatMonitor -Stage "implementer" -Title "Run heartbeat" -Message ("Implementer running: " + $Implementer) & (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") ` -ProviderName $Implementer ` -PromptFile $implementerPrompt ` -OutputFile $implementerOutput ` -WorkingDirectory $worktreePath ` -AddDirectories @($runDir, $repoRoot) Stop-HeartbeatMonitor Set-ActorState -ActorType "implementer" -Status "completed" -Message ("Implementer " + $Implementer + " finished first pass") -OutputFile $implementerOutput Send-TelegramNotification ` -EventName "implementer_completed" ` -Title "Implementer finished" ` -Message ("Implementer: " + $Implementer) ` -RunId $runId ` -Stage "implementer" ` -Status "completed" | Out-Null $worktreeStatus = & git -C $worktreePath status --short Write-Utf8File -Path (Join-Path $runDir "implementer_status.txt") -Content (($worktreeStatus -join "`n") + "`n") $worktreeDiff = & git -C $worktreePath diff --no-ext-diff Write-Utf8File -Path (Join-Path $runDir "implementer.patch") -Content (($worktreeDiff -join "`n") + "`n") foreach ($reviewer in $Reviewers) { if ($reviewer -eq $Reviewers[0]) { Send-TelegramNotification ` -EventName "reviewers_started" ` -Title "Reviewers started" ` -Message (($Reviewers -join ", ")) ` -RunId $runId ` -Stage "review" ` -Status "running" | Out-Null } $promptPath = Join-Path $runDir ("prompts\review-" + $reviewer + ".md") New-PromptDocument ` -TemplatePath (Join-Path $ralphRoot "templates\REVIEWER_PROMPT.md") ` -TaskPack $taskPack ` -OutputPath $promptPath ` -ExtraSections @( "## IMPLEMENTER WORKTREE`n$worktreePath", "## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')", "## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')" ) $reviewOutput = Join-Path $runDir ("reviews\" + $reviewer + ".json") Set-RunPhase -Stage "review" -Status "running" -Message ("Reviewer " + $reviewer + " started") Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "running" -Message ("Reviewer " + $reviewer + " analyzing diff") -OutputFile $reviewOutput & (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") ` -ProviderName $reviewer ` -PromptFile $promptPath ` -OutputFile $reviewOutput ` -WorkingDirectory $worktreePath ` -AddDirectories @($runDir, $repoRoot) Set-ActorState -ActorType "reviewer" -ActorName $reviewer -Status "completed" -Message ("Reviewer " + $reviewer + " completed review") -OutputFile $reviewOutput $reviewOutputs += $reviewOutput } Set-RunPhase -Stage "review" -Status "completed" -Message "All provider reviews completed" $codexReviewSections += "## REVIEW FILES`n$($reviewOutputs -join "`n")" if ($UseCodexMaster -and $AutoFix) { $null = Invoke-CodexReviewPass ` -StageName "codex_review_pre_fix" ` -PromptFileName "codex_master_review_pre_fix.md" ` -OutputFileName "codex_master_pre_fix.json" ` -RunningMessage "Codex master pre-fix review started" ` -CompletedMessage "Codex master pre-fix review completed" ` -ExtraSections $codexReviewSections } if ($AutoFix) { $fixPrompt = Join-Path $runDir "prompts\fix_pass.md" $fixExtraSections = @( "## FIX PASS`nRead the reviewer outputs and fix the highest-signal issues only.", "## REVIEW FILES`n$($reviewOutputs -join "`n")" ) if ($UseCodexMaster) { $fixExtraSections += "## CODEX REVIEW FILE`n$(Join-Path $runDir 'reviews\codex_master_pre_fix.json')" } else { $fixExtraSections += "## CODEX REVIEW FILE`nCodex review disabled for this run." } $fixExtraSections += "## REQUIRED RUN OUTPUT`nUpdate CHANGES.md in this run directory after the fix pass:`n`n$runDir" New-PromptDocument ` -TemplatePath (Join-Path $ralphRoot "templates\IMPLEMENTER_PROMPT.md") ` -TaskPack $taskPack ` -OutputPath $fixPrompt ` -ExtraSections $fixExtraSections Set-RunPhase -Stage "fix_pass" -Status "running" -Message ("Fix pass started with " + $Implementer) Set-ActorState -ActorType "fix_pass" -Status "running" -Message ("Implementer " + $Implementer + " applying review fixes") -OutputFile (Join-Path $runDir "outputs\fix_pass.json") Send-TelegramNotification ` -EventName "fix_pass_started" ` -Title "Fix pass started" ` -Message ("Implementer: " + $Implementer) ` -RunId $runId ` -Stage "fix_pass" ` -Status "running" | Out-Null Start-HeartbeatMonitor -Stage "fix_pass" -Title "Run heartbeat" -Message ("Fix pass running: " + $Implementer) & (Join-Path $PSScriptRoot "Invoke-ClaudeProvider.ps1") ` -ProviderName $Implementer ` -PromptFile $fixPrompt ` -OutputFile (Join-Path $runDir "outputs\fix_pass.json") ` -WorkingDirectory $worktreePath ` -AddDirectories @($runDir, $repoRoot) Stop-HeartbeatMonitor Set-ActorState -ActorType "fix_pass" -Status "completed" -Message ("Implementer " + $Implementer + " finished fix pass") -OutputFile (Join-Path $runDir "outputs\fix_pass.json") Set-RunPhase -Stage "fix_pass" -Status "completed" -Message "Fix pass completed" $finalDiff = & git -C $worktreePath diff --no-ext-diff Write-Utf8File -Path (Join-Path $runDir "final.patch") -Content (($finalDiff -join "`n") + "`n") } $finalStatus = & git -C $worktreePath status --short Write-Utf8File -Path (Join-Path $runDir "final_status.txt") -Content (($finalStatus -join "`n") + "`n") if ($UseCodexMaster) { $finalDiffPath = Join-Path $runDir "final.patch" if (-not (Test-Path $finalDiffPath)) { $finalDiffPath = Join-Path $runDir "implementer.patch" } $codexFinalSections = @( "## IMPLEMENTER WORKTREE`n$worktreePath", "## FINAL DIFF FILE`n$finalDiffPath", "## FINAL STATUS FILE`n$(Join-Path $runDir 'final_status.txt')", "## REVIEW FILES`n$($reviewOutputs -join "`n")" ) if ($AutoFix) { $codexFinalSections += "## FIX PASS OUTPUT`n$(Join-Path $runDir 'outputs\\fix_pass.json')" $codexFinalSections += "## PRE-FIX CODEX REVIEW`n$(Join-Path $runDir 'reviews\\codex_master_pre_fix.json')" } $codexFinalVerdict = Invoke-CodexReviewPass ` -StageName "codex_review_final" ` -PromptFileName "codex_master_review_final.md" ` -OutputFileName "codex_master_final.json" ` -RunningMessage "Codex master final review started" ` -CompletedMessage "Codex master final review completed" ` -ExtraSections $codexFinalSections if (([string]$codexFinalVerdict.verdict) -ne "pass" -or -not [bool]$codexFinalVerdict.acceptance_passed) { Send-TelegramNotification ` -EventName "codex_failed" ` -Title "Codex gate rejected run" ` -Message ([string]$codexFinalVerdict.summary) ` -RunId $runId ` -Stage "codex_review_final" ` -Status "failed" | Out-Null throw ("Codex master rejected the run with verdict '{0}': {1}" -f $codexFinalVerdict.verdict, $codexFinalVerdict.summary) } } $summaryLines += @( "", "## Outputs", "", "- Run directory: $runDir", "- Worktree: $worktreePath", "- Implementer output: $(Join-Path $runDir 'outputs\implementer.json')", "- Final status: $(Join-Path $runDir 'final_status.txt')", "- Codex final verdict: $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { 'not-run' })" ) try { $autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "completed" -OutcomeSummary "Run completed successfully" } catch { $script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message) Save-RunState Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message } if ($null -ne $autoFollowupResult) { $summaryLines += @( "", "## Auto Followup", "", "- Title: $($autoFollowupResult.title)", "- Task directory: $($autoFollowupResult.task_directory)", "- Source markdown: $($autoFollowupResult.source_markdown)" ) } Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n") $script:runState.status = "completed" $script:runState.stage = "completed" $script:runState.finished_at = (Get-Date).ToString("o") $script:runState.latest_message = "Run completed successfully" Save-RunState Add-RalphEvent -RunId $runId -Stage "completed" -Status "completed" -Message "Ralph run completed successfully" -Data @{ run_dir = $runDir worktree = $worktreePath } Send-TelegramNotification ` -EventName "run_completed" ` -Title "Run completed" ` -Message ("Implementer: " + $Implementer + "`nCodex verdict: " + $(if ($UseCodexMaster -and $null -ne $codexFinalVerdict) { [string]$codexFinalVerdict.verdict } else { "not-run" })) ` -RunId $runId ` -Stage "completed" ` -Status "completed" | Out-Null Get-Content -Raw -Path (Join-Path $runDir "SUMMARY.md") } catch { Stop-HeartbeatMonitor $failureMessage = $_.Exception.Message if ($UseCodexMaster -and $null -eq $codexFinalVerdict) { try { $failureSections = @( "## FAILURE CONTEXT`nThe autopilot run failed before final acceptance.", "## FAILURE MESSAGE`n$failureMessage", "## IMPLEMENTER WORKTREE`n$worktreePath", "## IMPLEMENTER DIFF FILE`n$(Join-Path $runDir 'implementer.patch')", "## IMPLEMENTER STATUS FILE`n$(Join-Path $runDir 'implementer_status.txt')", "## REVIEW FILES`n$($reviewOutputs -join "`n")" ) $null = Invoke-CodexReviewPass ` -StageName "codex_review_failure" ` -PromptFileName "codex_master_review_failure.md" ` -OutputFileName "codex_master_failure.json" ` -RunningMessage "Codex master failure review started" ` -CompletedMessage "Codex master failure review completed" ` -ExtraSections $failureSections } catch { $script:runState.errors += ("Codex failure review error: " + $_.Exception.Message) Add-RalphEvent -RunId $runId -Stage "codex_review_failure" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message } } $script:runState.status = "failed" $script:runState.stage = "failed" $script:runState.finished_at = (Get-Date).ToString("o") $script:runState.latest_message = $failureMessage $script:runState.errors += $failureMessage Save-RunState Add-RalphEvent -RunId $runId -Stage "failed" -Status "failed" -Message $failureMessage Send-TelegramNotification ` -EventName "run_failed" ` -Title "Run failed" ` -Message $failureMessage ` -RunId $runId ` -Stage "failed" ` -Status "failed" | Out-Null try { $autoFollowupResult = Invoke-AutoFollowupTask -OutcomeStatus "failed" -OutcomeSummary $failureMessage } catch { $script:runState.errors += ("Auto-followup generation error: " + $_.Exception.Message) Save-RunState Add-RalphEvent -RunId $runId -Stage "auto_followup" -Status "failed" -Actor "codex_master" -Message $_.Exception.Message } $summaryLines += @( "", "## Failure", "", $failureMessage ) if ($null -ne $autoFollowupResult) { $summaryLines += @( "", "## Auto Followup", "", "- Title: $($autoFollowupResult.title)", "- Task directory: $($autoFollowupResult.task_directory)", "- Source markdown: $($autoFollowupResult.source_markdown)" ) } Write-Utf8File -Path (Join-Path $runDir "SUMMARY.md") -Content (($summaryLines -join "`n") + "`n") throw }