LLM fallback chain

Each alert is triaged by a 5-model chain (OpenRouter, all free). If the primary model rate-limits (429) or is missing (404), Remo tries the next:

  1. nvidia/nemotron-3-super-120b-a12b:free
  2. meta-llama/llama-3.3-70b-instruct:free
  3. qwen/qwen3-coder:free
  4. nvidia/nemotron-3-nano-30b-a3b:free
  5. openai/gpt-oss-20b:free

The tenant’s preferred model goes first, then the chain.

Structured output

The LLM must answer with strict JSON:

{
  "severity_assessment": "critical|high|medium|low",
  "diagnosis": "2-3 sentences",
  "probable_cause": "string",
  "impact": "string",
  "recommended_actions": ["..."],
  "escalation_needed": true,
  "confidence": 0.85
}

Severity is re-assessed by impact, not just what the monitor said.

Cache-first

Repeated alerts hit the KV cache (computeCacheKey(tenant, parsed)), reusing the last diagnosis (TTL default 1h). Saves tokens and keeps latency low.

Enrichment (R-007)

For DB alerts, Remo pulls live context from Bonito before triage so the diagnosis is precise (exact PID, query text, baseline):

  • isDBAlert() matches postgres|mysql|mongo|deadlock|slow_query|lock|blocking|regression...
  • fetches bonito-api /queries/top, /locks/active, /baseline/{fp}
  • appends an ENRICHMENT DATA block to the LLM prompt

Failure-safe: 3s timeout + Promise.allSettled — if Bonito is unreachable, triage runs without enrichment (the alert is never blocked or lost).

# tenant config
enrichment:
  enabled: true
  bonito_endpoint: https://bonito-api.sofe.dev
  bonito_api_key: <key>
  bonito_timeout_ms: 3000

Example enriched diagnosis: “PID 829705 holding transactionid lock, blocking PID 829718. pg_cancel_backend(829705); · CREATE INDEX idx_orders_id ON orders(id);

See Integrations → Bonito.