Oodle Triage — Right-Side Observability
Turn an alert into evidence, not narrative. This skill gathers production context
and files disciplined tickets. It closes the loop opened on the left: it reads the
O11y Change Context manifest on recent PRs to
connect a firing alert to the change that likely caused it.
Prime directive: separate what is proven from what is inferred at every
step. A confirmed symptom, a leading hypothesis, and the exact missing evidence
beats a tidy root-cause story built on correlation. Evidence before assertions.
Modes
- Mode A — Triage context: input is a single alert, a tracker ticket id, or a
symptom description → produce a context report and optionally update the ticket.
- Mode B — Auto oncall triage: input is a time window (and a tracker parent
issue) → enumerate everything that fired, dedupe, and file/update sub-issues.
Designed to run on a
/loop cadence.
If the input names one alert/ticket/symptom, use Mode A. If it asks to triage a
window or "everything that fired," use Mode B (which reuses Mode A per alert).
Discovery (do this first)
Resolve backends by capability and tool shape, never by a hard-coded server
name (names vary: oodle-ai-us1, ap1, staging, dev, or customer-custom).
- Observability MCP — find tools for alerts/monitors, metric (PromQL) queries,
logs, and traces. If the suite matches Oodle's shape, use the tight path: select
the environment that owns the service, and load a matching
load_skill__*
workflow when one fits the problem (e.g. Kubernetes debugging, alert-noise
analysis) before low-level calls.
- Issue-tracker MCP — find the tracker in use. Linear is the worked example
below; Jira is the analog (issue → sub-issue, states, comments map across).
- If a backend is missing, do what the available signal allows and state the gap.
Mode A — Triage context
- Establish ground truth, in writing. If given a ticket, fetch it for scope.
Pin service/component, cluster/env, pod(s), container, and the precise time
window before querying anything. Convert the window to both UTC and the
reporter's local zone — mixing them silently is a classic error.
- Confirm the symptom with hard signals before theorizing: restart/OOM
reason, error rate, latency, resource usage vs limit, event logs. State the
confirmed symptom in one line; everything after is explanation.
- Learn the instrumentation from the code. Read the relevant service code for
the actual metric names, log strings, and span attributes — do not guess
them. Note explicitly what is and isn't captured; knowing what is missing
reveals which questions the telemetry cannot answer.
- Triangulate across telemetry — don't lean on one source. Metrics (grouped
by every dimension that localizes blame: service, tenant, operation, pod,
status), logs from caller and callee on the path (read ERROR/WARN and
access logs, widen the filter if a reported log isn't found), traces end to
end, and profiles for resource incidents.
- Correlate with recent changes (loop closes here). Find recent PRs touching
the alerting service (via the GitHub or Linear MCP, or the
gh CLI when no
GitHub MCP is present) and read their
O11y Change Context blocks. watch items
and type focus the hypotheses (a watch: N+1 ... becomes the leading
hypothesis); guarded-by confirms the alert↔code link; gaps explains
evidence the telemetry was never built to provide.
- Track and label every finding
Confirmed | Inferred | Unknown. A
configured ceiling or default is not a measurement; co-occurrence is not
causation. Actively try to falsify the leading hypothesis with one
measurement — eliminating a candidate is worth more than adding support.
- When confirmation is impossible, stop and produce a ranked missing-evidence list:
for each item, which hypothesis it would confirm/refute, where it likely lives,
and whether it is accessible. Then ask to unblock the top item rather than
guessing past it.
Output — a short, honest report: confirmed Symptom (with UTC+local
timestamps), Attribution (who/what/where, to the extent proven), Leading
hypothesis (proven vs inferred, plainly), Ruled out (with the measurement),
Missing evidence (the ranked list). Offer to post it as a comment on the
tracker ticket.
Mode B — Auto oncall triage
Run Mode A's investigation per alert, wrapped in enumerate → suppress → dedupe →
file. Match-check strictly precedes any create so reruns never double-file.
- Enumerate what fired in the window — the alert instances that reached the
firing state. Never treat a pending/for-not-yet-satisfied condition as a
fire: it sends no notification and must not be triaged or filed. Confirm a
real fire from the alert-state signal or trigger history.
- Enrich each: still-active now, noise category (flapping/storm/perpetual/
auto-resolving/boundary) and trigger count over ~7d, muted? (an active
muting rule whose matchers all match this alert's monitor + scope), and
routed? (does the monitor page anyone, or is it unrouted).
- Suppress muted and unrouted alerts — they are not actionable oncall signal.
Do not file them. If a stale ticket exists for a now-muted or now-unrouted
alert, close it with a comment stating the reason (muting rule id + matchers,
or unrouted monitor id). Suppress the specific instance, not a whole alert
class — routing is per-monitor per-env.
- Collapse storms — many series of one monitor firing together become one
logical alert → one ticket, not N.
- Investigate each distinct fired alert with Mode A, depth scaled to
severity. Manifest correlation feeds probable-cause and suggested-fix.
- Dedupe & file against the tracker by semantic match anchored on the
fingerprint (env + monitor + scope): no match → create; match open → comment
the new occurrence; match closed → reopen and comment. Set priority from
severity. Retry a failed write once; never leave a half-created duplicate.
Rerunning the same window is idempotent.
Fingerprint block
Every filed ticket carries a stable fingerprint so future runs match it. Keep the
format constant:
## Fingerprint
- env: <env>
- monitor: <monitor id/name>
- scope: <key=value scope labels>
- links: <deep links to the backend>
Genericity vs Oodle-tight
The discipline is vendor-neutral: confirmed-vs-inferred labeling, firing-only
enumeration, suppressing muted/unrouted noise, storm collapse, fingerprint dedup,
and manifest correlation apply to any backend. The mechanics are backend-
specific: Oodle exposes an ALERTS-style state metric, muting_rules with typed
matchers, and a monitor routing flag; a different vendor's MCP supplies its own
equivalents. Use the equivalent where it exists; skip a step where the concept is
genuinely absent — never invent a mechanic a backend does not have.
For the Oodle CLI path when deeper queries help, lean on
oodle-metrics,
oodle-logs, and
oodle-traces rather than restating them here.
1---2name: oodle-triage3description: Triages production alerts using observability signal. Mode A gathers confirmed-vs-inferred context for a single alert or ticket and updates the tracker; Mode B fans out over alerts that fired in a window, suppresses muted and unrouted noise, dedupes via a stable fingerprint, and files or updates issues idempotently. Correlates alerts to recent changes through the O11y Change Context manifest. Use when investigating an alert or incident, or running scheduled oncall triage.4---56# Oodle Triage — Right-Side Observability78Turn an alert into evidence, not narrative. This skill gathers production context9and files disciplined tickets. It closes the loop opened on the left: it reads the10[O11y Change Context](../oodle-o11y-context/SKILL.md) manifest on recent PRs to11connect a firing alert to the change that likely caused it.1213> **Prime directive:** separate what is *proven* from what is *inferred* at every14> step. A confirmed symptom, a leading hypothesis, and the exact missing evidence15> beats a tidy root-cause story built on correlation. Evidence before assertions.1617## Modes1819- **Mode A — Triage context:** input is a single alert, a tracker ticket id, or a20 symptom description → produce a context report and optionally update the ticket.21- **Mode B — Auto oncall triage:** input is a time window (and a tracker parent22 issue) → enumerate everything that fired, dedupe, and file/update sub-issues.23 Designed to run on a `/loop` cadence.2425If the input names one alert/ticket/symptom, use Mode A. If it asks to triage a26window or "everything that fired," use Mode B (which reuses Mode A per alert).2728## Discovery (do this first)2930Resolve backends by **capability and tool shape**, never by a hard-coded server31name (names vary: `oodle-ai-us1`, `ap1`, `staging`, `dev`, or customer-custom).3233- **Observability MCP** — find tools for alerts/monitors, metric (PromQL) queries,34 logs, and traces. If the suite matches Oodle's shape, use the tight path: select35 the environment that owns the service, and load a matching `load_skill__*`36 workflow when one fits the problem (e.g. Kubernetes debugging, alert-noise37 analysis) **before** low-level calls.38- **Issue-tracker MCP** — find the tracker in use. Linear is the worked example39 below; Jira is the analog (issue → sub-issue, states, comments map across).40- If a backend is missing, do what the available signal allows and state the gap.4142## Mode A — Triage context43441. **Establish ground truth, in writing.** If given a ticket, fetch it for scope.45 Pin service/component, cluster/env, pod(s), container, and the precise **time46 window** before querying anything. Convert the window to **both UTC and the47 reporter's local zone** — mixing them silently is a classic error.482. **Confirm the symptom with hard signals** before theorizing: restart/OOM49 reason, error rate, latency, resource usage vs limit, event logs. State the50 confirmed symptom in one line; everything after is explanation.513. **Learn the instrumentation from the code.** Read the relevant service code for52 the *actual* metric names, log strings, and span attributes — do not guess53 them. Note explicitly **what is and isn't captured**; knowing what is missing54 reveals which questions the telemetry cannot answer.554. **Triangulate across telemetry** — don't lean on one source. Metrics (grouped56 by every dimension that localizes blame: service, tenant, operation, pod,57 status), logs from **caller and callee** on the path (read ERROR/WARN and58 access logs, widen the filter if a reported log isn't found), traces end to59 end, and profiles for resource incidents.605. **Correlate with recent changes (loop closes here).** Find recent PRs touching61 the alerting service (via the GitHub or Linear MCP, or the `gh` CLI when no62 GitHub MCP is present) and read their63 [O11y Change Context](../oodle-o11y-context/SKILL.md) blocks. `watch` items64 and `type` focus the hypotheses (a `watch: N+1 ...` becomes the leading65 hypothesis); `guarded-by` confirms the alert↔code link; `gaps` explains66 evidence the telemetry was never built to provide.676. **Track and label every finding `Confirmed | Inferred | Unknown`.** A68 configured ceiling or default is not a measurement; co-occurrence is not69 causation. Actively try to **falsify the leading hypothesis with one70 measurement** — eliminating a candidate is worth more than adding support.717. **When confirmation is impossible, stop and produce a ranked missing-evidence list:**72 for each item, which hypothesis it would confirm/refute, where it likely lives,73 and whether it is accessible. Then ask to unblock the top item rather than74 guessing past it.7576**Output** — a short, honest report: confirmed **Symptom** (with UTC+local77timestamps), **Attribution** (who/what/where, to the extent proven), **Leading78hypothesis** (proven vs inferred, plainly), **Ruled out** (with the measurement),79**Missing evidence** (the ranked list). Offer to post it as a comment on the80tracker ticket.8182## Mode B — Auto oncall triage8384Run Mode A's investigation per alert, wrapped in enumerate → suppress → dedupe →85file. Match-check strictly precedes any create so reruns never double-file.86871. **Enumerate what fired** in the window — the alert instances that reached the88 **firing** state. **Never treat a pending/for-not-yet-satisfied condition as a89 fire:** it sends no notification and must not be triaged or filed. Confirm a90 real fire from the alert-state signal or trigger history.912. **Enrich** each: still-active now, noise category (flapping/storm/perpetual/92 auto-resolving/boundary) and trigger count over ~7d, **muted?** (an active93 muting rule whose matchers all match this alert's monitor + scope), and94 **routed?** (does the monitor page anyone, or is it unrouted).953. **Suppress muted and unrouted alerts** — they are not actionable oncall signal.96 Do not file them. If a stale ticket exists for a now-muted or now-unrouted97 alert, close it with a comment stating the reason (muting rule id + matchers,98 or unrouted monitor id). Suppress the specific instance, not a whole alert99 class — routing is per-monitor per-env.1004. **Collapse storms** — many series of one monitor firing together become **one**101 logical alert → one ticket, not N.1025. **Investigate** each distinct fired alert with Mode A, depth scaled to103 severity. Manifest correlation feeds probable-cause and suggested-fix.1046. **Dedupe & file** against the tracker by **semantic match anchored on the105 fingerprint** (env + monitor + scope): no match → create; match open → comment106 the new occurrence; match closed → reopen and comment. Set priority from107 severity. Retry a failed write once; never leave a half-created duplicate.108 Rerunning the same window is idempotent.109110## Fingerprint block111112Every filed ticket carries a stable fingerprint so future runs match it. Keep the113format constant:114115```116## Fingerprint117- env: <env>118- monitor: <monitor id/name>119- scope: <key=value scope labels>120- links: <deep links to the backend>121```122123## Genericity vs Oodle-tight124125The **discipline** is vendor-neutral: confirmed-vs-inferred labeling, firing-only126enumeration, suppressing muted/unrouted noise, storm collapse, fingerprint dedup,127and manifest correlation apply to any backend. The **mechanics** are backend-128specific: Oodle exposes an `ALERTS`-style state metric, `muting_rules` with typed129matchers, and a monitor routing flag; a different vendor's MCP supplies its own130equivalents. Use the equivalent where it exists; skip a step where the concept is131genuinely absent — never invent a mechanic a backend does not have.132133For the Oodle CLI path when deeper queries help, lean on134[oodle-metrics](../oodle-metrics/SKILL.md),135[oodle-logs](../oodle-logs/SKILL.md), and136[oodle-traces](../oodle-traces/SKILL.md) rather than restating them here.