Add attention reasons to Assistant briefings

This commit is contained in:
rcourtman
2026-05-06 23:39:33 +01:00
parent 6b51a15dfe
commit 1fc66cf0cb
8 changed files with 121 additions and 40 deletions
@@ -369,13 +369,13 @@ profile and assignment columns, but embedded table framing must route through
lifecycle transition. Unified finding lifecycle facts, latest lifecycle
event briefing lines, and detailed lifecycle context included in Assistant
handoff context remain Patrol/AI review metadata and must not become agent
lifecycle state or fleet-control authority. Primary finding recency,
evidence, verification, and operator-decision facts in the operator briefing
and related root-cause or correlated finding summaries resolved for the same
Assistant handoff follow that same isolation rule, including any recency or
latest lifecycle facts attached to those related summaries: they may explain
Patrol context to the model and seed model-only handoff resources, but they
are not enrollment evidence,
lifecycle state or fleet-control authority. Primary finding attention,
recency, evidence, verification, and operator-decision facts in the operator
briefing and related root-cause or correlated finding summaries resolved for
the same Assistant handoff follow that same isolation rule, including any
recency or latest lifecycle facts attached to those related summaries: they
may explain Patrol context to the model and seed model-only handoff
resources, but they are not enrollment evidence,
command-websocket identity, agent command grants, or auto-approval policy.
Structured Assistant handoff action references from the same Patrol finding
remain AI/runtime review metadata only; lifecycle code must not treat
@@ -207,10 +207,11 @@ runtime cost control, and shared AI transport surfaces.
resolved timestamps, recurrence, regression, and recent lifecycle events so
Assistant explains the current Patrol record rather than only the original
investigation narrative. The operator briefing must surface the primary
finding's current recency facts, bounded evidence snapshot, verification
summary, and explicit operator decision framing before investigation
guidance, may surface the latest lifecycle event as the current handoff
state, and detailed lifecycle events must stay in a bounded
finding's current attention reason, recency facts, bounded evidence
snapshot, verification summary, and explicit operator decision framing
before investigation guidance, may surface the latest lifecycle event as
the current handoff state, and detailed lifecycle events must stay in a
bounded
`[Finding Lifecycle Context]` block with an explicit model-only boundary.
Assistant runtime may also hydrate canonical
resource-policy context for those handoff resources, using the same
@@ -963,12 +964,13 @@ top-N slices.
Assistant finding handoffs now also receive a model-only operator briefing
derived from the current unified finding and structured Patrol investigation
record before the lower-level finding context. That briefing must summarize the
finding, resource, priority, current recency facts, bounded evidence and
verification summaries, investigation confidence, recommended next step,
operator decision framing, latest lifecycle event, and governed action posture
as operator guidance, while leaving detailed lifecycle history, current
resource-state, timeline, related-finding, and action-audit hydration in the
existing canonical AI runtime handoff builders. Related root-cause and
finding, resource, priority, current attention reason, current recency facts,
bounded evidence and verification summaries, investigation confidence,
recommended next step, operator decision framing, latest lifecycle event, and
governed action posture as operator guidance, while leaving detailed lifecycle
history, current resource-state, timeline, related-finding, and action-audit
hydration in the existing canonical AI runtime handoff builders. Related
root-cause and
correlated finding records may be summarized from current unified finding state,
including their recency and latest lifecycle facts, and may seed their own
handoff resources for canonical policy, state, topology, and timeline
@@ -784,14 +784,14 @@ the canonical monitored-system blocked payload.
snoozed/dismissed/suppressed status, detection/last-seen/resolution
timestamps, recurrence/regression facts, and recent lifecycle events, so API
consumers do not reduce Assistant context to an outdated investigation
summary. The briefing must carry the primary finding's current recency facts,
bounded evidence snapshot, verification summary, and explicit operator
decision framing before investigation guidance and may carry the latest
lifecycle event as the current handoff state, while the detailed lifecycle
list must stay bounded and model-only. Chat execution may also resolve
root-cause and correlated finding IDs from that current unified finding into
compact related-finding summaries and structured handoff resources. Those
related summaries may include the
summary. The briefing must carry the primary finding's current attention
reason, recency facts, bounded evidence snapshot, verification summary, and
explicit operator decision framing before investigation guidance and may
carry the latest lifecycle event as the current handoff state, while the
detailed lifecycle list must stay bounded and model-only. Chat execution may
also resolve root-cause and correlated finding IDs from that current unified
finding into compact related-finding summaries and structured handoff
resources. Those related summaries may include the
related record's current recency and latest lifecycle facts, but those
related records remain model-only explanation context and must not become
saved user text, disclosure authority, lifecycle authority, approval
@@ -2935,10 +2935,10 @@ The `/api/ai/chat` finding handoff contract must also include a model-only
`[Operator Briefing]` generated from the unified finding and structured Patrol
investigation record before detailed `[Finding Context]`. The briefing is the
canonical operator-facing frame for Assistant: it carries the finding summary,
resource, priority, current recency facts, bounded evidence and verification
summaries, investigation confidence, recommended next step, and
operator-decision framing plus approval/proposed-fix posture without raw
command text, and the downstream chat service then hydrates live resource
resource, priority, current attention reason, current recency facts, bounded
evidence and verification summaries, investigation confidence, recommended next
step, and operator-decision framing plus approval/proposed-fix posture without
raw command text, and the downstream chat service then hydrates live resource
state, timeline, and action audit context around that same handoff.
Patrol run-history serialization and persistence must also preserve full field
parity across API responses and restart boundaries, including
@@ -131,10 +131,10 @@ Patrol-specific presentation helpers.
Assistant handoffs from Patrol findings must also include a concise operator
briefing derived from the unified finding and structured investigation record
before the detailed finding context, so Assistant leads with the current risk,
recency, evidence snapshot, verification summary, conclusion, latest
lifecycle event, recommended next step, explicit operator decision framing,
and governed approval/proposed-fix posture instead of behaving like a generic
chat over a pasted incident dump. The assembled handoff must still pass
attention reason, recency, evidence snapshot, verification summary,
conclusion, latest lifecycle event, recommended next step, explicit operator
decision framing, and governed approval/proposed-fix posture instead of
behaving like a generic chat over a pasted incident dump. The assembled handoff must still pass
through the Assistant runtime's
resource-policy sanitizer before prompt injection, so Patrol-owned prose
cannot leak governed resource names, IDs, aliases, nodes, paths, or
@@ -427,13 +427,13 @@ bypass the API fail-closed execution gate.
review metadata only. Unified finding lifecycle facts, latest lifecycle
event briefing lines, and detailed lifecycle context carried by the same
handoff remain Patrol/AI review metadata and must not become backup recency,
restore support, or storage-local lifecycle state. Primary finding recency,
evidence, verification, and operator-decision facts in the operator
briefing and related root-cause or correlated finding summaries resolved
for that Assistant handoff are also adjacent AI/runtime explanation context
only, including any recency or latest lifecycle facts attached to those
related summaries; storage and recovery surfaces must not reinterpret those
related records or their seeded handoff resources as backup freshness,
restore support, or storage-local lifecycle state. Primary finding
attention, recency, evidence, verification, and operator-decision facts in
the operator briefing and related root-cause or correlated finding summaries
resolved for that Assistant handoff are also adjacent AI/runtime explanation
context only, including any recency or latest lifecycle facts attached to
those related summaries; storage and recovery surfaces must not reinterpret
those related records or their seeded handoff resources as backup freshness,
restore eligibility, recovery execution authority, or storage-local
capability truth. Current resource-state, source-health,
incident, metric, and capability summaries carried by that handoff are also
+76
View File
@@ -977,6 +977,9 @@ func appendUnifiedFindingOperatorBriefingContext(b *strings.Builder, f *unified.
appendChatContextLine(b, "Finding", formatUnifiedFindingBriefingFinding(f))
appendChatContextLine(b, "Resource", formatUnifiedFindingBriefingResource(f))
appendChatContextLine(b, "Priority", formatUnifiedFindingBriefingPriority(f))
if attention := unifiedFindingBriefingAttention(f); attention != "" {
appendChatContextLine(b, "Attention Reason", attention)
}
appendChatContextLine(b, "Recency", formatUnifiedFindingBriefingRecencyFacts(f))
appendChatContextLine(b, "Investigation", formatUnifiedFindingBriefingInvestigation(f))
if evidence := unifiedFindingBriefingEvidence(f); evidence != "" {
@@ -1066,6 +1069,79 @@ func formatUnifiedFindingBriefingPriority(f *unified.UnifiedFinding) string {
return strings.Join(parts, "; ")
}
func unifiedFindingBriefingAttention(f *unified.UnifiedFinding) string {
if f == nil {
return ""
}
now := time.Now()
parts := make([]string, 0, 8)
status := unifiedFindingChatStatus(f, now)
switch status {
case "active":
if severity := strings.TrimSpace(string(f.Severity)); severity != "" {
parts = append(parts, "active "+severity+" finding")
} else {
parts = append(parts, "active finding")
}
case "resolved":
if f.RegressionCount > 0 {
parts = append(parts, "resolved after prior regression")
} else {
parts = append(parts, "resolved finding")
}
case "snoozed":
parts = append(parts, "snoozed finding")
case "dismissed":
parts = append(parts, "dismissed finding")
case "suppressed":
parts = append(parts, "suppressed finding")
}
if f.RegressionCount > 0 {
parts = append(parts, fmt.Sprintf("regressed %d times", f.RegressionCount))
} else if f.TimesRaised > 1 {
parts = append(parts, fmt.Sprintf("raised %d times", f.TimesRaised))
}
if f.LastRegressionAt != nil {
parts = append(parts, "last regression "+f.LastRegressionAt.Format(time.RFC3339))
}
if loopState := strings.TrimSpace(f.LoopState); loopState != "" {
parts = append(parts, "loop "+loopState)
}
rec := f.InvestigationRecord
outcome := strings.TrimSpace(f.InvestigationOutcome)
if rec != nil {
outcome = strings.TrimSpace(string(rec.Outcome))
if approvalID := strings.TrimSpace(rec.ApprovalID); approvalID != "" {
parts = append(parts, "approval "+approvalID)
}
if rec.ProposedFix != nil && rec.ProposedFix.Destructive {
parts = append(parts, "destructive proposed fix")
}
}
switch aicontracts.InvestigationOutcome(outcome) {
case aicontracts.OutcomeFixQueued:
parts = append(parts, "fix queued for governed review")
case aicontracts.OutcomeFixExecuted:
parts = append(parts, "fix executed awaiting verification")
case aicontracts.OutcomeFixFailed:
parts = append(parts, "fix failed")
case aicontracts.OutcomeFixVerificationFailed:
parts = append(parts, "verification failed")
case aicontracts.OutcomeFixVerificationUnknown:
parts = append(parts, "verification inconclusive")
case aicontracts.OutcomeNeedsAttention:
parts = append(parts, "needs operator attention")
case aicontracts.OutcomeCannotFix:
parts = append(parts, "Patrol cannot safely fix")
case aicontracts.OutcomeTimedOut:
parts = append(parts, "Patrol timed out")
}
return formatBriefingStringList(parts, 8, "attention facts")
}
func formatUnifiedFindingBriefingRecency(f *unified.UnifiedFinding) string {
if f == nil {
return ""
+1
View File
@@ -732,6 +732,7 @@ func TestHandleChat_IncludesInvestigationRecordContext(t *testing.T) {
assert.Contains(t, reqArg.HandoffContext, "Finding: High CPU usage (critical, performance, active)")
assert.Contains(t, reqArg.HandoffContext, "Resource: web-server (vm) [vm-100] on pve-1")
assert.Contains(t, reqArg.HandoffContext, "Priority: critical performance; status active; loop awaiting_approval; raised 3 times; regressed 2 times")
assert.Contains(t, reqArg.HandoffContext, "Attention Reason: active critical finding; regressed 2 times; last regression 2026-05-06T12:06:00Z; loop awaiting_approval; approval approval-123; destructive proposed fix; fix queued for governed review")
assert.Contains(t, reqArg.HandoffContext, "Recency: detected 2026-05-06T12:00:00Z; last seen 2026-05-06T12:02:00Z; raised 3 times; regressed 2 times; last regression 2026-05-06T12:06:00Z")
assert.Contains(t, reqArg.HandoffContext, "Investigation: completed; outcome fix_queued; confidence high; attempts 1")
assert.Contains(t, reqArg.HandoffContext, "Evidence Snapshot: metrics: CPU stayed above 95% for 10 minutes")
+2
View File
@@ -175,6 +175,8 @@ func TestContract_AssistantFindingContextUsesModelOnlyHandoff(t *testing.T) {
"[Operator Briefing]",
`appendChatContextLine(b, "Recency", formatUnifiedFindingBriefingRecencyFacts(f))`,
"formatUnifiedFindingBriefingRecencyFacts(f)",
`appendChatContextLine(b, "Attention Reason", attention)`,
"unifiedFindingBriefingAttention(f)",
`appendChatContextLine(b, "Evidence Snapshot", evidence)`,
`appendChatContextLine(b, "Verification", verification)`,
"formatInvestigationRecordEvidenceBriefing",