(req *pb.AgenticStepRequest)
| 610 | } |
| 611 | |
| 612 | func buildAgenticStepPrompt(req *pb.AgenticStepRequest) string { |
| 613 | var sb strings.Builder |
| 614 | |
| 615 | sb.WriteString(fmt.Sprintf(`You are a Kubernetes SRE agent. You are autonomously remediating an active incident by executing actions one at a time. After each action, you observe the result and decide the next step. |
| 616 | |
| 617 | Incident Details: |
| 618 | - Issue: %s |
| 619 | - Namespace: %s |
| 620 | - Resource: %s/%s |
| 621 | - Signal Type: %s |
| 622 | - Severity: %s |
| 623 | - Description: %s |
| 624 | - Risk Score: %d/100`, |
| 625 | req.IssueName, req.Namespace, req.ResourceKind, req.ResourceName, |
| 626 | req.SignalType, req.Severity, req.Description, req.RiskScore)) |
| 627 | |
| 628 | // GAP-01 fix: inject AIInsight's prior conclusion as PRIMARY guidance, so the |
| 629 | // agentic loop cannot silently contradict its own root-cause analysis. The |
| 630 | // chaos test (2026-05-23) showed the loop ignoring "scale to 0" insights and |
| 631 | // proposing diagnostics that got blocked by the allowlist, wasting attempts. |
| 632 | if req.InsightAnalysis != "" || len(req.InsightSuggestedActions) > 0 { |
| 633 | sb.WriteString("\n\nPRIMARY GUIDANCE FROM PRIOR AIInsight ANALYSIS") |
| 634 | sb.WriteString(" (authoritative — follow unless new live evidence directly contradicts it):") |
| 635 | if req.InsightConfidence > 0 { |
| 636 | sb.WriteString(fmt.Sprintf("\n- Confidence: %.2f", req.InsightConfidence)) |
| 637 | } |
| 638 | if req.InsightAnalysis != "" { |
| 639 | sb.WriteString(fmt.Sprintf("\n- Root-cause analysis:\n<DATA>\n%s\n</DATA>", sanitizeForPrompt(req.InsightAnalysis))) |
| 640 | } |
| 641 | if len(req.InsightRecommendations) > 0 { |
| 642 | sb.WriteString("\n- Recommendations:") |
| 643 | for _, rec := range req.InsightRecommendations { |
| 644 | sb.WriteString(fmt.Sprintf("\n - %s", sanitizeForPrompt(rec))) |
| 645 | } |
| 646 | } |
| 647 | if len(req.InsightSuggestedActions) > 0 { |
| 648 | sb.WriteString("\n- Suggested actions (in priority order):") |
| 649 | for i, a := range req.InsightSuggestedActions { |
| 650 | sb.WriteString(fmt.Sprintf("\n %d. %s", i+1, sanitizeForPrompt(a.Action))) |
| 651 | if a.Description != "" { |
| 652 | sb.WriteString(fmt.Sprintf(" — %s", sanitizeForPrompt(a.Description))) |
| 653 | } |
| 654 | if len(a.Params) > 0 { |
| 655 | sb.WriteString(fmt.Sprintf(" %v", a.Params)) |
| 656 | } |
| 657 | } |
| 658 | sb.WriteString("\n\nIf your next_action deviates from the first suggested action above, you MUST populate `divergence_reason` in the response with a concrete justification (new evidence that invalidates the prior analysis). An empty divergence_reason while diverging will cause the operator to reject the action.") |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | if req.KubernetesContext != "" { |
| 663 | sb.WriteString(fmt.Sprintf(` |
| 664 | |
| 665 | Current Kubernetes Cluster State (LIVE — refreshed before each step): |
| 666 | %s`, req.KubernetesContext)) |
| 667 | } |
| 668 | |
| 669 | sb.WriteString(` |
no test coverage detected