Compare commits

...

3 Commits

2 changed files with 168 additions and 32 deletions

View File

@@ -146,8 +146,9 @@ AwoooI SRE Telegram command loop:
- Operators and project AI Agents can issue commands in the SRE Telegram group with `/agent99 ...`.
- Agent99 records these as `source=telegram-sre-war-room`.
- Completed automated repairs send `operator_command_result` back to Telegram.
- Results include source, instruction, mode, exit code, evidence path, problem count, and KM path.
- Failed or escalated automated repairs send `operator_command_result` back to Telegram.
- Successful automated repairs are evidence-first: they write queue, evidence, problem count, KM, and RAG receipts, then suppress success-only Telegram noise.
- Failure results include source, failure instruction, mode, exit code, evidence path, and problem count.
- The command path must not depend on Codex quota.
- The command path must not depend only on the default `TELEGRAM_CHAT_ID`, because that can point to a private chat. Agent99 also accepts command updates from Telegram chats whose title matches `AwoooI SRE|AWOOOI SRE|SRE`.
- When the command comes from a matched Telegram group, the completion report is sent back to that same Telegram origin; stored evidence redacts the raw chat id as `telegram-origin`.
@@ -172,7 +173,8 @@ Producer-side Agent99 bridge:
- Fallback transport: when `AGENT99_SRE_ALERT_INBOX_PATH` is configured and relay URL is empty, the bridge writes sanitized alert JSON directly to the Agent99 SRE inbox contract.
- If relay/path is empty or unavailable, the bridge fails open and logs; it must not cause Alertmanager retries.
- Relay safety: only LAN/local source prefixes are accepted by default; optional `AGENT99_SRE_RELAY_TOKEN` / `AGENT99_SRE_ALERT_RELAY_TOKEN` values stay in env/Secret and are never stored in repo or evidence.
- Provider freshness bridge output must require provider window, last seen, direct reference, and verifier readback before dashboard green is trusted.
- Provider freshness bridge output must run direct readback, then require provider window, last seen, direct reference, and verifier readback before dashboard green is trusted.
- Provider freshness no-false-green rule: route 200 or dashboard up is not enough. The Agent99 verifier must read `https://awoooi.wooo.work/api/v1/platform/events/dossier/recurrence?project_id=awoooi&limit=20` and record `providerWindowMinutes`, `lastSeen`, `directReference`, and `verifierReadback`.
Approved controlled actions for the 99 agent:
@@ -257,7 +259,8 @@ Recovery is complete only when all of the following are captured:
- Current 110 runner action requires `load_per_core_critical`.
- Memory/disk pressure alerts do not execute the runner pause action.
- Telegram audit:
- Agent99 emits Telegram attempts for all operation start/complete events, warnings, critical events, and load-shed actions.
- Agent99 emits Telegram attempts for warnings, critical events, failed or escalated operator commands, and load-shed actions.
- Successful operator commands are not Telegram noise; they are recorded in evidence, problem counts, KM, and RAG.
- Agent99 supports local env aliases and an existing-token relay.
- Current relay: 110 `stockplatform-v2-api-1`, using that container's existing `TELEGRAM_BOT_TOKEN` / `TELEGRAM_CHAT_ID`.
- 2026-07-09 17:27 verification: `Wooo-Agent99-Performance-Guard` `LastTaskResult=0`; latest evidence `agent99-Perf-20260709-172712.json`; `tgSent=2`; `relayOk=True`.
@@ -359,8 +362,8 @@ Recovery is complete only when all of the following are captured:
- Group matching uses `allowedCommandChatTitlePattern=AwoooI SRE|AWOOOI SRE|SRE`.
- Loop prevention:
- Agent99's own `Agent99 Alert` / `operator_command_result` messages are ignored by monitoring auto-ingest.
- Operator command result Telegram reports bypass dedupe so every completed repair is reported.
- Provider freshness signals are not passive dashboard health; they create a read-only freshness candidate and require provider window, last seen, direct reference, and verifier readback.
- Successful operator command results are suppressed from Telegram and kept in evidence/KM/RAG; failed or escalated command results remain alertable.
- Provider freshness signals are not passive dashboard health; they create a read-only freshness candidate, run direct AWOOI readback, and require provider window, last seen, direct reference, and verifier readback.
- Provider freshness guardrail: do not switch provider, call paid model, edit env, or reload.
- Synthetic monitoring alert:
- `Alertmanager FIRING critical public_route_502 service=awoooi host=192.168.0.120 HTTP 502`
@@ -389,6 +392,37 @@ Recovery is complete only when all of the following are captured:
- problem key: `agent99-sre-alert-inbox_awoooi_192.168.0.120_public_route_502`
- problem occurrences: `2`
## Agent99 Alert Noise And Provider Freshness Readback Receipt
2026-07-10 10:00 Asia/Taipei:
- Runtime patch deployed on 99:
- Script: `C:\Wooo\Agent99\bin\agent99-control-plane.ps1`.
- Parser readback: `parse_ok`.
- Source commit: `0a7a0115 fix(agent99): reduce alert noise and verify freshness`.
- Provider freshness behavior:
- Evidence: `C:\Wooo\Agent99\evidence\agent99-ProviderFreshness-20260710-100028.json`.
- Status: `direct_readback_ok_no_source_events`.
- Missing fields after automatic readback: `lastSeen` only.
- `providerWindowMinutes=120`.
- `directReference=https://awoooi.wooo.work/api/v1/platform/events/dossier/recurrence?project_id=awoooi&limit=20`.
- `verifierReadback.ok=True`, `httpStatus=200`, `sourceEventTotal=0`.
- Telegram noise control:
- Evidence log: `C:\Wooo\Agent99\evidence\agent99-ProviderFreshness-20260710-100028.log`.
- Same provider-freshness state was suppressed: `telegram_dedupe_skip type=provider_freshness_triage severity=warning ageMinutes=5.83 cooldown=30`.
- Evidence log: `C:\Wooo\Agent99\evidence\agent99-ControlTick-20260710-100027.log`.
- Successful operator command was evidence-only: `telegram_operator_success_suppressed type=operator_command_result mode=ProviderFreshness id=operator-suppress-smoke-20260710-100026`.
- Control evidence: `C:\Wooo\Agent99\evidence\agent99-ControlTick-20260710-100027.json`, `processedCount=1`, `pendingCount=0`, `telegram=[]`.
- Scheduled entrypoints verified on 99:
- `Wooo-Agent99-Control-Loop`.
- `Wooo-Agent99-SRE-Alert-Inbox`.
- `Wooo-Agent99-Telegram-Inbox`.
- `Wooo-Agent99-SRE-Alert-Relay` is running.
- Operational rule:
- Do not treat repeated success receipts as user-facing alerts.
- Send user-facing Telegram only for warnings, criticals, unresolved blockers, failed/escalated operator commands, and controlled-remediation actions.
- Keep successful automation in evidence, problem counters, KM, and RAG so repeated incidents trend down without flooding the SRE group.
## OpenClaw Cold-Start Readback
2026-07-09 17:38 Asia/Taipei:

View File

@@ -145,15 +145,22 @@ function Format-AgentTelegramText {
$hitCount = if ($Data -and $Data.PSObject.Properties["hitCount"]) { $Data.hitCount } else { 0 }
$missing = if ($Data -and $Data.PSObject.Properties["missingFields"]) { @($Data.missingFields) } else { @() }
$candidatePath = if ($Data -and $Data.PSObject.Properties["candidatePath"]) { $Data.candidatePath } else { $null }
$lines += "Event: provider freshness triage"
$readback = if ($Data -and $Data.PSObject.Properties["verifierReadback"]) { $Data.verifierReadback } else { $null }
$status = if ($Data -and $Data.PSObject.Properties["status"]) { $Data.status } else { "unknown" }
$lines += "Event: provider freshness auto triage"
$lines += "Target: $targetName"
$lines += "Candidate: $candidateId"
$lines += "Status: $status"
$lines += "Evidence hits: $hitCount"
if (@($missing).Count -gt 0) {
$lines += "Missing: $($missing -join ', ')"
if ($readback) {
$lines += "Direct readback: ok=$($readback.ok), sourceEvents=$($readback.sourceEventTotal), latest=$($readback.latestReceivedAt)"
}
$lines += "Action: require provider window, last seen, direct reference, and verifier readback before trusting dashboard green."
$lines += "Guard: no provider switch, no paid model call, no env change, no reload."
if (@($missing).Count -gt 0) {
$lines += "Still missing: $($missing -join ', ')"
} else {
$lines += "Missing: direct readback fields completed"
}
$lines += "Action: candidate and KM/RAG were written; duplicate same-state alerts are suppressed."
$lines += "Guard: no provider switch, paid model call, env change, or reload."
if ($candidatePath) {
$lines += "Evidence: $candidatePath"
}
@@ -186,22 +193,18 @@ function Format-AgentTelegramText {
$instruction = if ($Data -and $Data.PSObject.Properties["instruction"]) { $Data.instruction } else { $null }
$exitCode = if ($Data -and $Data.PSObject.Properties["exitCode"]) { $Data.exitCode } else { $null }
$evidencePath = if ($Data -and $Data.PSObject.Properties["evidence"]) { $Data.evidence } else { $null }
$lines += "Event: operator command"
$lines += "Command: $commandId"
$lines += "Source: $source"
$lines += "Event: Agent99 automation result"
$lines += "Mode: $modeName"
if ($instruction) {
$lines += "Instruction: $instruction"
}
$lines += "Source: $source"
$lines += "Result: exitCode=$exitCode"
if ($Data -and $Data.PSObject.Properties["problem"]) {
$problem = $Data.problem
if ($problem -and $problem.PSObject.Properties["occurrences"]) {
$lines += "Problem count: occurrences=$($problem.occurrences), resolved=$($problem.resolved), failed=$($problem.failed)"
}
if ($problem -and $problem.PSObject.Properties["kmPath"]) {
$lines += "KM: $($problem.kmPath)"
}
}
if ($instruction -and $exitCode -ne 0) {
$lines += "Failure instruction: $instruction"
}
if ($evidencePath) {
$lines += "Evidence: $evidencePath"
@@ -354,19 +357,48 @@ function Record-AgentEvent {
$alertOperatorCommands = [bool]$Config.telegram.alertOperatorCommands
}
$operatorCommandAlert = [bool]($alertOperatorCommands -and $EventType -match "^operator_command_")
if ($operatorCommandAlert -and $Data) {
$operatorOk = $false
$operatorMode = ""
$operatorId = ""
$operatorOkValue = $null
$operatorExitCode = $null
if ($Data -is [System.Collections.IDictionary]) {
if ($Data.Contains("mode")) { $operatorMode = [string]$Data["mode"] }
if ($Data.Contains("id")) { $operatorId = [string]$Data["id"] }
if ($Data.Contains("ok")) { $operatorOkValue = $Data["ok"] }
if ($Data.Contains("exitCode")) { $operatorExitCode = $Data["exitCode"] }
} else {
$modeProp = $Data.PSObject.Properties.Match("mode") | Select-Object -First 1
$idProp = $Data.PSObject.Properties.Match("id") | Select-Object -First 1
$okProp = $Data.PSObject.Properties.Match("ok") | Select-Object -First 1
$exitCodeProp = $Data.PSObject.Properties.Match("exitCode") | Select-Object -First 1
if ($modeProp) { $operatorMode = [string]$modeProp.Value }
if ($idProp) { $operatorId = [string]$idProp.Value }
if ($okProp) { $operatorOkValue = $okProp.Value }
if ($exitCodeProp) { $operatorExitCode = $exitCodeProp.Value }
}
if ($null -ne $operatorOkValue -and [string]$operatorOkValue -match "^(?i:true|1)$") { $operatorOk = $true }
$parsedExitCode = -999
if ($null -ne $operatorExitCode -and [int]::TryParse([string]$operatorExitCode, [ref]$parsedExitCode) -and $parsedExitCode -eq 0) { $operatorOk = $true }
$alertSuccessfulOperatorCommands = $false
if ($Config.telegram -and $Config.telegram.PSObject.Properties["alertSuccessfulOperatorCommands"]) {
$alertSuccessfulOperatorCommands = [bool]$Config.telegram.alertSuccessfulOperatorCommands
}
if ($operatorOk -and -not $alertSuccessfulOperatorCommands) {
Write-AgentLog "telegram_operator_success_suppressed type=$EventType mode=$operatorMode id=$operatorId"
$operatorCommandAlert = $false
}
}
$shouldAlert = ($Severity -in @("warning", "critical")) -or $operatorCommandAlert -or ($Alert -and $Severity -ne "info") -or ($alertAll -and ($Severity -ne "info" -or $alertInfo))
if ($shouldAlert) {
if ($operatorCommandAlert) {
Send-AgentTelegram $Severity $EventType $Message $Data
return
}
$dedupeMinutes = 30
if ($Config.telegram -and $Config.telegram.PSObject.Properties["dedupeMinutes"]) {
$dedupeMinutes = [int]$Config.telegram.dedupeMinutes
}
$dedupeParts = @($EventType, $Severity)
if ($Data) {
foreach ($propName in @("host", "name", "path", "reason")) {
foreach ($propName in @("host", "name", "path", "reason", "target", "status", "mode", "alertKind", "source")) {
if ($Data.PSObject.Properties[$propName]) {
$dedupeParts += "$propName=$($Data.PSObject.Properties[$propName].Value)"
}
@@ -374,10 +406,16 @@ function Record-AgentEvent {
if ($Data.PSObject.Properties["reasons"]) {
$dedupeParts += "reasons=$(@($Data.reasons) -join ',')"
}
if ($Data.PSObject.Properties["missingFields"]) {
$dedupeParts += "missing=$(@($Data.missingFields) -join ',')"
}
if ($Data.PSObject.Properties["problem"] -and $Data.problem -and $Data.problem.PSObject.Properties["key"]) {
$dedupeParts += "problem=$($Data.problem.key)"
}
}
if ($dedupeParts.Count -le 2) {
$normalizedMessage = $Message
foreach ($pattern in @("evidence=[^ ]+", "loadPerCore=[^ ]+", "memAvailPct=[^ ]+", "diskUsedPct=[^ ]+", "ageMinutes=[^ ]+")) {
foreach ($pattern in @("candidate=[^ ]+", "evidence=[^ ]+", "loadPerCore=[^ ]+", "memAvailPct=[^ ]+", "diskUsedPct=[^ ]+", "ageMinutes=[^ ]+")) {
$normalizedMessage = $normalizedMessage -replace $pattern, ($pattern.Split("=")[0] + "=<value>")
}
$dedupeParts += $normalizedMessage
@@ -1971,6 +2009,50 @@ function Invoke-AgentControlTick {
$result
}
function Invoke-AgentProviderFreshnessDirectReadback {
param(
[int]$LookbackMinutes
)
$baseUrl = "https://awoooi.wooo.work/api/v1/platform/events/dossier/recurrence?project_id=awoooi&limit=20"
$startedAt = Get-Date
try {
$response = Invoke-RestMethod -Method Get -Uri $baseUrl -TimeoutSec 12
$summary = if ($response -and $response.PSObject.Properties["summary"]) { $response.summary } else { $null }
$latest = if ($summary -and $summary.PSObject.Properties["latest_received_at"]) { $summary.latest_received_at } else { $null }
$sourceTotal = if ($summary -and $summary.PSObject.Properties["source_event_total"]) { [int]$summary.source_event_total } else { 0 }
$itemsCount = if ($response -and $response.PSObject.Properties["items"]) { @($response.items).Count } else { 0 }
return [pscustomobject]@{
ok = $true
verifier = "awoooi-platform-events-recurrence"
directReference = $baseUrl
providerWindowMinutes = $LookbackMinutes
latestReceivedAt = $latest
sourceEventTotal = $sourceTotal
recurrenceGroupTotal = if ($summary -and $summary.PSObject.Properties["recurrence_group_total"]) { [int]$summary.recurrence_group_total } else { 0 }
itemsCount = $itemsCount
httpStatus = 200
durationSeconds = [math]::Round(((Get-Date) - $startedAt).TotalSeconds, 2)
checkedAt = (Get-Date -Format o)
}
} catch {
return [pscustomobject]@{
ok = $false
verifier = "awoooi-platform-events-recurrence"
directReference = $baseUrl
providerWindowMinutes = $LookbackMinutes
latestReceivedAt = $null
sourceEventTotal = 0
recurrenceGroupTotal = 0
itemsCount = 0
httpStatus = $null
durationSeconds = [math]::Round(((Get-Date) - $startedAt).TotalSeconds, 2)
checkedAt = (Get-Date -Format o)
error = ($_ | Out-String).Trim()
}
}
}
function Invoke-AgentProviderFreshnessTriage {
$providerConfig = if ($Config.PSObject.Properties["providerFreshness"]) { $Config.providerFreshness } else { $null }
$lookbackMinutes = if ($providerConfig -and $providerConfig.PSObject.Properties["lookbackMinutes"]) { [int]$providerConfig.lookbackMinutes } else { 120 }
@@ -2047,8 +2129,23 @@ function Invoke-AgentProviderFreshnessTriage {
}
}
$missingFields = @("providerWindow", "lastSeen", "directReference", "verifierReadback")
$status = if (@($hits).Count -gt 0) { "candidate_created_waiting_direct_readback" } else { "candidate_created_no_alert_body_seen" }
$verifierReadback = Invoke-AgentProviderFreshnessDirectReadback -LookbackMinutes $lookbackMinutes
$directReference = if ($verifierReadback -and $verifierReadback.PSObject.Properties["directReference"]) { $verifierReadback.directReference } else { $null }
$latestSeen = if ($verifierReadback -and $verifierReadback.PSObject.Properties["latestReceivedAt"]) { $verifierReadback.latestReceivedAt } else { $null }
$missingFields = @()
if (-not $lookbackMinutes) { $missingFields += "providerWindow" }
if (-not $latestSeen) { $missingFields += "lastSeen" }
if (-not $directReference) { $missingFields += "directReference" }
if (-not ($verifierReadback -and $verifierReadback.ok -eq $true)) { $missingFields += "verifierReadback" }
$status = if ($verifierReadback -and $verifierReadback.ok -eq $true -and $latestSeen) {
"direct_readback_ok"
} elseif ($verifierReadback -and $verifierReadback.ok -eq $true) {
"direct_readback_ok_no_source_events"
} elseif (@($hits).Count -gt 0) {
"candidate_created_waiting_direct_readback"
} else {
"candidate_created_no_alert_body_seen"
}
$candidate = [pscustomobject]@{
timestamp = (Get-Date -Format o)
candidateId = $candidateId
@@ -2060,15 +2157,20 @@ function Invoke-AgentProviderFreshnessTriage {
hits = @($hits | Sort-Object lastWrite -Descending | Select-Object -First 20)
targets = $targets
missingFields = $missingFields
providerWindowMinutes = $lookbackMinutes
lastSeen = $latestSeen
directReference = $directReference
verifierReadback = $verifierReadback
requiredVerifierReadback = @("provider_window", "last_seen", "direct_reference", "source_trace_or_sentry_signoz_ref")
noFalseGreenRisk = $true
allowedActions = @("create_candidate", "read_existing_evidence", "request_direct_reference", "write_km")
allowedActions = @("create_candidate", "read_existing_evidence", "provider_freshness_direct_readback", "request_direct_reference", "write_km")
prohibitedActions = @("provider_switch", "paid_model_call", "env_change", "reload")
candidatePath = $candidatePath
}
$candidate | ConvertTo-Json -Depth 10 | Set-Content -Path $candidatePath -Encoding UTF8
Write-AgentLog "provider_freshness candidate=$candidateId hits=$(@($hits).Count) status=$status path=$candidatePath"
Record-AgentEvent "provider_freshness_triage" "warning" "candidate=$candidateId hits=$(@($hits).Count) missing=$($missingFields -join ',') evidence=$candidatePath" $candidate -Alert
Write-AgentLog "provider_freshness candidate=$candidateId hits=$(@($hits).Count) status=$status missing=$($missingFields -join ',') path=$candidatePath"
$eventSeverity = if (@($missingFields).Count -gt 0) { "warning" } else { "info" }
Record-AgentEvent "provider_freshness_triage" $eventSeverity "target=provider_freshness_signal status=$status missing=$($missingFields -join ',')" $candidate -Alert
$candidate
}