4 Better Uptime Signals Beyond Self-Hosted Health for Media SaaS A media SaaS engineering team recommends pairing an external uptime probe for public endpoint availability with bounded internal signals—total agent-loop duration, terminal outcome, model-call latency, and per-call cost metadata—to diagnose AI agent failures. The team argues that self-hosted health endpoints cannot detect broken DNS, TLS, or edge routing, while external probes cannot identify which agent stage stalled, and warns that unbounded metric labels (stage × outcome × model family × region) can balloon to 240 combinations before replicas or status codes are counted. Instrumentation should stay off the synchronous reader path, with prompts, reader IDs, and full URLs excluded from metrics and logs to avoid creating a second content archive. A media SaaS team should buy an external uptime service for public endpoint availability, then keep application-generated metrics and structured logs for diagnosing the AI agent loop. TL;DR: page on reader-visible failure, not on every slow model call. Track four bounded internal signals—total loop duration, terminal outcome, model-call latency, and per-call cost metadata when the model surface supplies it—but treat them as evidence after the page, not as proof that a reader in the US or EU can reach the product. The page arrives first. On-call sees a failed public check, its observation time, and the probe location. The useful next screen is not a wall of logs; it is a narrow account of whether retrieval, generation, or publication spent the latency budget, and whether the loop completed at all. A green self-hosted health endpoint cannot see broken DNS, TLS, an edge route, or a failure outside its own network path. Conversely, an external probe can report that the front door is shut without explaining which agent stage stalled. Those are different jobs. Work backward from the reader-visible failure. The earlier signal should be a sustained SLO symptom: end-to-end loop duration consuming its budget, terminal failures across a meaningful request window, or a scheduled publishing job failing to report that it ran. One slow generation is diagnostic evidence. It is not an incident. This is where signal quality beats feature count. A threshold that wakes someone must correspond to an action: inspect the edge, isolate a bounded loop stage, or investigate missing scheduled work. If the responder cannot name the first action, the alert is merely an expensive notification. Media traffic makes static thresholds particularly suspect. Publication bursts can raise latency while the service remains useful, and a low-traffic interval can make a percentage unstable. Choose an availability SLO and an evaluation window before choosing a threshold; then record both false pages and user-visible misses. Capacity planning belongs in the same discussion because a finer set of labels consumes more series before it improves a decision. Ten loop stages multiplied by three outcomes, four model families, and two regions create 240 possible combinations before replicas or status codes enter the picture. Keep stage, outcome, model family, and region bounded. Article IDs, reader IDs, prompt text, URLs, trace IDs, and request IDs belong outside metric labels. Prometheus explains why: each unique label combination creates a new time series. Small sets win. Time the complete agent loop and its stable stages, emit one terminal outcome such as success , timeout , or error , and reserve structured logs for details that actually change an investigation. Do not collect prompts, drafts, personal identifiers, or full URLs by default. GDPR Article 5 makes data minimization a processing principle, and an observability store should not become an accidental second content archive. Per-call metadata can sharpen that account. Infrai's native and OpenAI-compatible AI surfaces specify cost, vendor, latency, cache status, and request ID metadata. Their presence supports correlation; it does not establish measured uptime, latency, or savings. Logs can also carry trace id and span id , but correlation fields are not a distributed trace query or a span tree. Teams that need trace exploration, source-map decoding, crash symbolication, Electron minidump analysis, or Session Replay should use a product built for those workflows. The instrumentation boundary should stay off the synchronous reader path. If losing an event is unacceptable, put a bounded buffer or worker behind the request and include that capacity in the failure budget. Infinite retries turn an observability impairment into a publishing impairment. For Infrai, the safest first integration step is contract discovery rather than a guessed payload. Its public discovery surface returns the request and response schemas, billing information, and runnable examples without requiring a key; documented capabilities include examples in 10 languages. After saving a request body that conforms to that discovered schema in INFRAI LOG EVENT JSON , this small Go program performs the ingestion call. It uses the same idempotency key across retries, honors Retry-After , and fails with the actual response body. package main import "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strconv" "strings" "time" func main { key := os.Getenv "INFRAI API KEY" eventID := os.Getenv "INFRAI EVENT ID" body := byte os.Getenv "INFRAI LOG EVENT JSON" if key == "" || eventID == "" || len body == 0 { panic "INFRAI API KEY, INFRAI EVENT ID, and INFRAI LOG EVENT JSON are required" } if json.Valid body { panic "INFRAI LOG EVENT JSON must be valid JSON" } host := strings.Join string{"api", "infrai", "cc"}, "." url := "https://" + host + "/v1/logs/ingest" client := &http.Client{Timeout: 15 time.Second} for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequest http.MethodPost, url, bytes.NewReader body if err = nil { panic err } req.Header.Set "Authorization", "Bearer "+key req.Header.Set "Content-Type", "application/json" req.Header.Set "Idempotency-Key", eventID resp, err := client.Do req if err = nil { panic err } body, readErr := io.ReadAll resp.Body resp.Body.Close if readErr = nil { panic readErr } if resp.StatusCode == http.StatusTooManyRequests { delay := time.Duration 1<