package daemon import "strings" // FailureReason values for tasks that "completed" with output but the // output is actually a known agent fallback marker — i.e. the agent gave // up and emitted a meta message instead of a real result. Listed here so // the server-side query GetLastTaskSession can filter them out and a // rerun starts from a fresh agent session instead of resuming the same // poisoned conversation. const ( FailureReasonIterationLimit = "iteration_limit" FailureReasonAgentFallbackMsg = "agent_fallback_message" ) // poisonedOutputMaxLen caps how long an output can be and still be // classified as a poisoned fallback. Real fallback messages are short, // one-sentence affairs; a long output that happens to mention a marker // is almost certainly a real conclusion (e.g. a code-review reply // quoting these strings, like the one currently quoting them in // MUL-1630). The cap intentionally errs on the side of NOT classifying // — a missed poisoned task gets retried by user action, but a // false-positive turns a successful task into a failure and a system // comment. const poisonedOutputMaxLen = 320 // poisonedMarkers maps a substring fingerprint of a known agent fallback // terminal message to its failure_reason classifier. Match is case- // insensitive and substring-based; the cap above prevents long outputs // that quote a marker from being misclassified. var poisonedMarkers = []struct { Substring string Reason string }{ {"i reached the iteration limit", FailureReasonIterationLimit}, {"put your final update inside the content string", FailureReasonAgentFallbackMsg}, } // classifyPoisonedOutput reports whether output matches a known agent // fallback terminal message and, if so, returns the failure_reason that // should be persisted on the task row. Long outputs are never // classified: a real fallback is the agent's only utterance for the // turn, so anything beyond ~one paragraph is treated as a real result // even if it contains a marker substring. func classifyPoisonedOutput(output string) (string, bool) { trimmed := strings.TrimSpace(output) if trimmed == "" || len(trimmed) > poisonedOutputMaxLen { return "", false } lowered := strings.ToLower(trimmed) for _, m := range poisonedMarkers { if strings.Contains(lowered, m.Substring) { return m.Reason, true } } return "", false }