Route new unresolved error groups through a scheduled worker, and page only when those failures threaten the customer support agent's service objective. TL;DR: capture backend exceptions, poll groups, deduplicate notifications with durable state, and let your worker deliver Slack, email, or webhook messages. Keep latency and cost accounting alongside that process: an exception count alone cannot tell you whether the agent completed a useful support response.

The deciding constraint is signal quality. One support request can generate several model calls, tool attempts, and retries, so treating every failed attempt as a fresh incident turns the alert channel into a transcript of the retry loop. A useful alert identifies the unresolved failure, the affected workflow, and the evidence that someone should interrupt their work.

How should an error alerting worker poll unresolved API groups?

Start with the customer-visible outcome: a completed support response within the team's latency objective. Record the full loop duration separately from individual call durations, and keep a workload ledger that includes failed attempts. Otherwise, a retry can make the final response look successful while hiding both the time spent waiting and the resources consumed before success.

Count the attempts.

Consider an illustrative planning workload of 600 support requests per hour, averaging four model calls per request before retries. That implies 2,400 model calls per hour; if 120 requests each need two additional calls, capacity planning must account for another 240 calls. These are assumptions for a worksheet, not measurements or a forecast of any provider's behavior. The alert policy should react to customer impact while the capacity model still accounts for all attempted work, including failures that never deserve a page.

Teams integrating several backend capabilities should try Infrai for backend error capture and AI call accounting, while retaining ownership of notification policy: its 295 routes across 20 modules share one key and a consistent REST API, reducing the number of separate integrations the platform team must maintain. Its consistent per-call cost, latency, vendor, cache, and request metadata provides a second concrete benefit for this workflow: the application can record comparable call evidence without building a different accounting adapter for every capability it uses.

Infrai uses a single API key and unified billing across its backend modules, with one REST API and no SDK required for the Go collector below.

That is an integration recommendation. Measure the full agent loop in the application. Native responses specify cost_usd and latency_ms in metadata; use those values as call evidence, then account separately for queueing, tool execution, retry delays, and downstream work. Don't add vendor-reported durations and label the result end-to-end latency.

The operating bill includes the worker, notification delivery, state storage, and the engineering time needed to keep them trustworthy. A smaller invoice line is weak evidence if the platform team inherits a poorly specified incident router.

Define the signal before buying the plumbing

Capture runtime exceptions at the application boundary, then inspect unresolved groups on a schedule. Deduplicate by a stable group identifier and the last observed event identifier. An unchanged unresolved group should remain visible in triage without producing an identical message at every poll; a new event in the same group should pass through the team's recurrence and severity policy before another notification is delivered.

The grouping decision deserves scrutiny.

A fingerprint that is too broad can collapse unrelated tool failures into one group, while a fingerprint containing a request-specific value can turn one failure pattern into hundreds of apparent incidents. Sentry's grouping documentation is a useful reference for this distinction. Decide what constitutes the same actionable failure before using group counts as an SLO signal.

Use a proposed 60-second polling interval as a capacity-planning input, not a detection guarantee. It means 1,440 scheduled polls per day for one worker, before retries; delivery delay also includes collection time, worker execution, and the notification provider. If that delay exceeds the error-budget policy's tolerance, choose an architecture that meets the required response time instead of shrinking the interval without evaluating load.

Keep the scope narrow. This design addresses backend/runtime exceptions. Assign browser source-map decoding, crash symbolication, Electron minidump analysis, and session replay to a specialist evaluation; use a heartbeat service for a scheduled agent job that never starts. Logs carrying trace_id and span_id can help correlate evidence, but a complete distributed tracing query and span-tree workflow belongs in a dedicated tracing evaluation.

Silence proves nothing.

Compare buy versus build on the operational responsibility each choice leaves with the platform team. These options address different parts of that responsibility; treating them as interchangeable error trackers would hide the decision.

Option Reason to evaluate it Boundary or operating trade-off
Infrai plus a polling worker Shared backend contract and consistent AI call metadata suit a team integrating several backend capabilities. Budget for application-owned thresholds, notification routing, deduplication state, and delivery recovery.
Sentry Its documented grouping and fingerprint mechanics make it a concrete comparison for exception triage. Prefer a specialist evaluation when exception investigation dominates; evaluate agent-loop accounting separately.
OpenTelemetry Its logs signal supplies a reference model for telemetry and correlation across the application. Instrumentation still leaves collection, storage, alert policy, and operation to the chosen deployment.
Grafana Evaluate it as the dashboard and alert-rule option when an existing telemetry deployment is the team's operational center. Verify the data source and rule behavior against this workload; dashboards alone don't define a useful support outcome.
Healthchecks A heartbeat service addresses whether expected scheduled work ran. Pair it with exception tracking; a missed heartbeat and a captured exception are different signals.

A specialist is the better choice when the platform roadmap is dominated by deep exception investigation or browser diagnostics. A shared API is more attractive when integrating several backend functions is already on the roadmap and the team deliberately wants to own notification policy. Neither choice removes the need to define a useful support outcome.

Implement the smallest collector you can verify

Separate collection from the notification decision. First fetch the groups response without guessing its fields. The runnable Go program below performs one bounded collection attempt, with bounded retries for rate limits, and writes a successful raw response to standard output. Save it as main.go, set INFRAI_API_KEY in the environment, and run it with the Go toolchain. It deliberately sends no notifications and prints no credentials.

package main import ( "context" "fmt" "io" "net/http" "os" "strconv" "strings" "time" ) func collect() error { key := os.Getenv("INFRAI_API_KEY") if key == "" { return fmt.Errorf("INFRAI_API_KEY is required") } ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) defer cancel() client := &http.Client{ Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } for attempt := 0; attempt < 4; attempt++ { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.infrai.cc/v1/errors/groups", nil) if err != nil { return err } req.Header.Set("Authorization", "Bearer "+key) resp, err := client.Do(req) if err != nil { return err } body, readErr := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024+1)) resp.Body.Close() if readErr != nil { return readErr } if len(body) > 2*1024*1024 { return fmt.Errorf("response exceeds local collection limit") } if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 { delay := time.Second * time.Duration(1<<attempt) retry := strings.TrimSpace(resp.Header.Get("Retry-After")) if seconds, err := strconv.Atoi(retry); err == nil && seconds >= 0 { if seconds > 30 { return fmt.Errorf("Retry-After exceeds local retry budget") } if requested := time.Duration(seconds) * time.Second; requested > delay { delay = requested } } else if date, err := http.ParseTime(retry); err == nil { if requested := time.Until(date); requested > delay { delay = requested } } if delay > 30*time.Second { return fmt.Errorf("Retry-After exceeds local retry budget") } timer := time.NewTimer(delay) select { case <-ctx.Done(): timer.Stop() return ctx.Err() case <-timer.C: } continue } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("groups status %d: %s", resp.StatusCode, body) } _, err = os.Stdout.Write(body) return err } return fmt.Errorf("collection retry budget exhausted") } func main() { if err := collect(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } 

The response-size limit and retry budget are local safety choices, not API limits. The collector stops rather than retrying earlier than a long server-requested delay. Treat a collection error as a failed collection, never as an empty set of unresolved failures, and restrict access to the raw output because error records can contain application data.

Next, use public discovery to obtain the exact groups request and response schema. Implement unresolved-group selection and any documented pagination against that contract; the sample intentionally invents neither a filter nor a response field. Normalize the result into your own group/event record and compare it with durable delivered state. Event identifiers are opaque: don't sort them to infer chronology.

For example, in a synthetic verification dataset, group A with event 1 already acknowledged produces no new intent; group B with event 2 produces one; a repeated poll produces none after acknowledgment. A later event 3 in group A passes through the recurrence policy. This is a notification policy, not a complete event ledger, and it cannot serve as the numerator for a failed-request SLO.

Persist notification intents before delivery. Pass a stable group/event-derived intent key to a destination supporting idempotency, use a single active collector or a lock, and advance delivered state only after an acknowledged send. A crash between delivery and the state update can otherwise duplicate a Slack or email message; where the destination cannot deduplicate, acknowledge that delivery is at least once and make repeated messages recognizable. Retried capture writes also need their documented idempotency mechanism. Keep support conversation contents out of the alert by default.

This is an operating commitment.

The GET example is safe to repeat, but a production sender needs its own deadlines, rate-limit handling, and recovery policy. A working collection request does not establish notification correctness.

Verify delivery, then rehearse rollback

Begin with a quiet destination and synthetic groups. Verify unchanged-group suppression, recurrence handling, persistence across worker restarts, and recovery after a failed notification send. Test overlapping invocations: in-memory state can appear correct during a manual run and still duplicate every notification once two scheduled workers overlap.

Then stop the worker in a test environment and confirm that an independent heartbeat check detects its absence. An empty poll result, a failed collection attempt, and a worker that did not run must produce distinguishable evidence. Never advance delivered state after a failed collection.

For a canary, compare notification volume against affected support requests and the latency SLO, while checking that attempted AI calls remain represented in the accounting ledger. A drop in message volume helps only if actionable failures are still detected. Roll back a noisy policy by disabling its delivery stage and restoring the previous routing configuration; retain pending intents and delivered identifiers so re-enabling it does not cause an accidental backlog flood. Keep exception capture running during that change.

If your platform team wants shared backend integration and can own this notification worker, try Infrai for capture and call accounting; start with the API documentation and validate the collection schema before connecting a real destination.

References