| name | gossipper-analyze |
| description | Interpret Gossipper output artifacts (summary JSON, trace CSVs, call records JSONL, OTLP events) and produce a structured findings report. Use whenever the user wants to classify failures from a Gossipper run, evaluate latency or media quality verdicts, decide whether a run passed health thresholds, propose threshold values for the next run, convert RTCP jitter from RTP timestamp units to milliseconds, surface Call-IDs and time windows for HOMER/qryn correlation, interpret exit code 2, or read a "Run report" handed over from gossipper-run. |
gossipper-analyze — Output Interpretation
Interpret Gossipper outputs and produce structured findings. Pinned to Gossipper 0.1.60-rtcpmuxpatch2 (sipcapture/gossipper on main, audited 2026-05-18).
When to use this skill
- Read a "Run report" from gossipper-run and start an analysis.
- Classify failures from per-call records (
success: false + error string) and summary counters; surface dominant failure cause.
- Compute latency verdicts (
invite_rtt, call_length, custom rtd timers).
- Compute media-quality verdicts (RTCP loss, jitter, per-call RTP floor).
- Convert RTCP jitter fields (
media.RTCPMaxJitter / RTCPMinJitter / etc., in RTP timestamp units) to ms.
- Decide whether a run passed health thresholds (or apply them post-hoc if
-health_* wasn't set).
- Propose threshold values for the next run, by intent.
- Surface Call-IDs, time windows,
hep_capture_id, self_tag for downstream HOMER/qryn correlation.
- Interpret exit code 2 and what to do about it.
When NOT to use this skill
| Task | Owner |
|---|
| Invoke gossipper, pick flags, deploy | gossipper-run |
| Write or modify scenario XML | gossipper-build |
| Query HOMER/qryn ClickHouse directly | separate query layer (out of scope here) |
| Decide whether to escalate findings to humans / autonomous controllers | caller of this skill |
This skill reads files and produces verdicts. It does not run, write, or query.
Inputs from gossipper-run
After every run, gossipper-run surfaces a structured "Run report" block in chat. Parse it — the file paths and correlation IDs come from there.
Run report
----------
Invocation: <command line or profile path + alias>
Started / ended: <ISO 8601> / <ISO 8601>
Exit code: <0 | 1 | 2>
Outputs produced:
summary_json: <path>
trace_stat: <path or omitted>
call_records_jsonl: <path or omitted>
message_file: <path or omitted>
error_file: <path or omitted>
wav_dir: <path or omitted>
Correlation identifiers:
hep_capture_id: <int or omitted>
self_tag: <id or omitted>
hep_addr: <host:port or omitted>
otel_endpoint: <host:port or omitted>
Notes: <operational context>
Two things to check before opening any file:
-
Exit code sets expectations.
0 → summary JSON exists, clean run.
1 → summary JSON may be missing. Read *_err.log first.
2 → summary JSON exists with health + findings populated.
-
Required channels. If anything you need is missing, do not fudge — state what's needed and ask the user to request those flags on the next gossipper-run invocation:
-summary_json <path> — always required.
-trace_stat -fd <n> — required for time-series drill-down.
-call_records_jsonl <path> — required for per-call diagnostics.
-trace_err -error_file <path> — required when transport_error / parse_error shows up.
This skill does not control run's invocation. It tells the user what to ask for; run wires it.
Analysis workflow
- Exit code first. 0 / 1 / 2 sets expectations.
- Summary JSON next. Read in order (field paths verified against
gossipper_summary_v1, Gossipper 0.1.60-rtcpmuxpatch2):
total_calls / success_ratio → basic verdict.
timeouts + retransmits (top-level aggregate counters) → high-level signal. The summary does NOT carry a per-class failures.{timeout|unexpected_sip|...} breakdown — that comes from parsing per-call records.
invite_rtt + call_length (each is an object with average/min/max/stddev/buckets) + any custom rtd timer keys → signaling timing. Latency values are in nanoseconds. Divide by 1_000_000 for ms.
media.* → only if scenario uses RTP. Field names are snake_case in the summary (rtp_packets_sent, rtcp_max_fraction_lost, rtcp_max_jitter_ts if present). Convert any RTP-TS jitter to ms via codec clock. (See "Codec → ms" section.)
findings[] (always present as array of strings if summary is) and health.passed / health.findings[] → final verdict if -health_* thresholds were set at runtime.
- Drill into trace CSVs if summary numbers raise questions:
*_rtt.log for per-call latency distribution / percentiles.
*_stats.log for time-series — when did failures spike?
*_error_codes.log for SIP-status breakdown.
- Per-call records JSONL for individual failed-call diagnostics — find failed calls by
success == false and pull their call_id. Each failed call has an error free-text string (e.g. "sip recv: timed out after 27.997s ..."); classify by regex if needed. Field list is enumerated in references/output-schema-reference.md; if schema_version differs from gossipper_call_record_v1, re-inventory via the drift-detection recipe in that doc.
- Surface correlation pointers for downstream HOMER/qryn lookup. Do not attempt the query.
- Read
*_msg.log / *_err.log only when steps 1–5 don't explain it — last-resort context.
Full output-channel matrix and field references → references/output-schema-reference.md.
Failure classification
The 0.1.60-rtcpmuxpatch2 binary does NOT emit a structured failure-class enum. Per-call records carry success: bool plus a free-text error string on failures. To classify, parse the error field with the regex table below — these classes are derived, not binary-native.
| Class | Match on error (case-insensitive substring or regex) | Means |
|---|
timeout | sip recv: timed out, recv timeout, timed out after | Expected recv didn't arrive within -recv_timeout_ms (or -recv_bye_timeout_ms for BYE) |
unexpected_sip | unexpected, did not match, no matching recv | SIP message didn't match current recv AND _unexp.main fallback didn't recover |
transport_error | connection refused, connection reset, i/o timeout (transport context), tls handshake | Socket-level failure |
parse_error | parse error, malformed, invalid sip | SIP message malformed beyond parser tolerance |
scenario_error | scenario, divide by zero, missing variable, bad xml | Action failed at runtime |
cancelled | cancelled, canceled, context deadline exceeded (run-level) | Run terminated externally while call was active |
First analysis question: count success == false records per derived class. Which class dominates?
Aggregate signal at summary level: only timeouts is broken out as a top-level counter; everything else (failed_calls, success_ratio) is undifferentiated. Lean on per-call parsing for breakdown.
Triage workflow per class → references/patterns.md.
Codec → ms conversion (the jitter gotcha)
Per-call record fields (PascalCase): media.RTCPMaxJitter, RTCPMinJitter, RTCPJitterSum (with RTCPJitterSamples for averaging), and RTPRecvInterarrivalJitterPeak.
Summary JSON fields (snake_case, when present): media.rtcp_max_jitter_ts, rtcp_avg_jitter_ts, rtcp_min_jitter_ts, rtp_recv_interarrival_jitter_peak_ts.
Both surfaces report jitter in RTP timestamp units, not milliseconds. Conversion depends on the negotiated codec's sample rate.
| Codec(s) | Clock | Divide by |
|---|
| PCMU / PCMA / G722 / iLBC | 8 kHz | 8 |
| OPUS | 48 kHz | 48 |
| H264 | 90 kHz | 90 |
Always report jitter in ms. Always state the codec assumption.
Codec inference rules:
- Built-in scenarios (
-sn uac, -sn uas): assume PCMU at 8000 Hz, 20 ms ptime. Record codec_assumed in the findings report's concerns[] (see "Concerns schema" section below). Do not prompt the user.
- Custom scenarios (
-sf <file>): if the SDP / scenario doesn't declare a codec, ask the user. Built-in defaults do NOT apply.
- Override: user may supply
codec=<g711a|g722|opus|...> as an analyze-time hint, or annotate the run-profile JSON with a codec_assumption field. Override always wins; record the user's value in findings inputs.codec_assumption.
Health threshold authoring
When the user asks for threshold values for -health_* on the next run, propose by intent. The user (or caller) confirms; gossipper-run wires them. This skill never sets flags itself.
Sample threshold sets per run intent → references/patterns.md (SOC monitor / soak / regression / smoke).
-health_* flag list:
| Flag | Compared against | Pass condition |
|---|
-health_min_success_ratio <0..1> | summary success_ratio | >= threshold |
-health_max_failed_calls <n> | summary failed_calls | < threshold (0 = any failure fails) |
-health_max_timeouts <n> | summary timeouts (top-level counter) | <= threshold |
-health_max_rtcp_fraction_lost <0..1> | summary media.rtcp_max_fraction_lost (when present) | <= threshold |
-health_max_rtcp_jitter_ts <int> | summary media.rtcp_max_jitter_ts (when present); per-call media.RTCPMaxJitter | <= threshold (RTP TS units!) |
-health_min_rtp_packets_recv <n> | summary media.rtp_packets_received | >= threshold |
-health_min_rtp_packets_recv_per_call <n> | computed across per-call records' media.RTPPacketsReceived (min) | >= threshold |
Note — -health_max_rtcp_jitter_ts takes RTP timestamp units, not ms. When proposing it, multiply your ms target by the codec sample-rate divisor (e.g. 30 ms × 8 = 240 for PCMU). State the conversion in the proposal.
If -health_* was NOT set on the run, apply thresholds post-hoc against raw counters — same logic, in the findings report.
Exit code interpretation
| Exit | Means | What to do |
|---|
0 | Clean run, no health breach (or -health_* not set) | Read summary JSON. Check no false-positive successes — cancelled mid-run shouldn't count as benign. |
1 | Engine / parse / transport / scenario error before finalization | Summary JSON may be missing. Read *_err.log first. Hand back to build (XML problem) or run (flag/transport problem). |
2 | Health threshold breach | Summary JSON has health + findings. Lead with which dimension breached and by how much. |
Always-remember gotchas
- RTCP jitter fields are RTP timestamp units, not ms. Convert using the codec clock. Always declare the codec assumption. Field names: per-call PascalCase (
media.RTCPMaxJitter etc.); summary snake_case with _ts suffix (media.rtcp_max_jitter_ts etc.).
dropped_logs > 0 in OTLP engine.stopped events signals OTLP buffer overflow; treat OTLP stream as incomplete. The summary JSON does NOT carry an equivalent engine.stopped.dropped_logs field directly — this is an OTLP-only signal. Check the OTLP collector side, not the summary.
cancelled is timing-dependent. End-of-soak SIGINT or -timeout_global produces benign cancellations. Mid-run cancellation in a per-call error string is a real failure. Inspect timestamps before concluding.
unexpected_sip followed by _unexp.main recovery may still classify as success. The fallback exists to recover. Don't flag every _unexp.main jump as a failure. (Per-call records that recovered will have success: true.)
- No packet-level RTP/RTCP in HEP. For per-packet analysis (loss bursts, codec-level issues), the user needs local PCAP via heplify on the gossipper host — not the HEP mirror. Tell them.
gossipper_call_record_v1 field list is enumerated in references/output-schema-reference.md. Be defensive about field presence — failure-mode records omit some media subfield meaningful values (they're present but zeroed). If schema_version differs from gossipper_call_record_v1, follow the drift-detection recipe in the schema reference.
- TLS handshake failures show in
error strings containing transport-level wording (e.g. tls handshake, connection refused). Look in *_err.log and per-call error, not in auth events. Failures are NOT pre-classified — derive class from the error text.
- Summary JSON exists only after clean shutdown. SIGKILL → no summary. SIGINT → drain + summary write. If summary is missing on exit 1, that's why.
max_failed_calls: 0 means "any failure fails health" — strict pass/fail gate, not "unlimited failures allowed."
- Built-in
-sn uac/uas scenarios don't emit named rtd timers by default — only invite_rtt and call_length. Custom timers require custom XML.
- Summary latency values are in nanoseconds.
average_call_latency, average_invite_rtt, and count.average/min/max/last/stddev inside invite_rtt / call_length objects are ns — divide by 1_000_000 for ms. Always disclose units in the findings report.
Going deeper
Read on demand:
references/output-schema-reference.md — full schema lookup: output channel matrix, gossipper_summary_v1 field reference (top-level + latencies + counters + media + health + findings), gossipper_call_record_v1 known shape + inventory recipe, three contracted CSV headers verbatim, three non-contracted CSVs, OTLP Kind enum with attrs, HEP3 mirror semantics, live API surfaces.
references/patterns.md — how-to-act: per-class failure triage, codec → ms conversion details, health threshold authoring by run intent (SOC / soak / regression / smoke) with sample sets, correlation strategy (Call-ID selection + time-window inference), findings-report filling notes, jq/awk recipes for common extractions, data-quality flag handling, rerun-vs-escalate decision patterns.
Verdict vocabulary
Verdict: in the findings report below is one of three values:
| Verdict | Meaning |
|---|
pass | Exit code 0 AND no post-hoc threshold breach. The run is good. |
breach | Exit code 2 (a -health_* threshold was set and breached at runtime) OR exit code 0 with a post-hoc threshold violation surfaced during analysis. |
hard_error | Exit code 1 — engine/parse/transport/scenario failure. Summary JSON may be missing. |
Soft signals (data-quality, codec assumptions, single-stat outliers) do not change the verdict — they go in the concerns[] array. See "Concerns schema" section below.
Findings report (handoff)
After analysis, surface this structured block in chat. Symmetric with gossipper-run's "Run report" — chat content, not a file artifact. The caller (human or autonomous controller) acts on it.
Findings report
---------------
Run identifier: <self_tag / hep_capture_id>
Verdict: pass | breach | hard_error
Inputs:
codec_assumption: <e.g. PCMU/8000 (assumed for built-in scenario)>
Failure breakdown (derived from per-call error parsing):
timeout: <n> unexpected_sip: <n> transport_error: <n>
parse_error: <n> scenario_error: <n> cancelled: <n>
(unclassified: <n>)
Latency (all values in ms, converted from ns):
invite_rtt: avg=<>ms max=<>ms
call_length: avg=<>ms
custom_rtds: <name>=avg=<>ms ...
Media (if applicable; codec: <>):
rtcp_max_fraction_lost: <0..1>
rtcp_max_jitter_ms: <> (converted from <> RTP TS units)
per_call_min_rtp_packets: <>
Health (if -health_* was set, or applied post-hoc):
passed: <bool>
breached: <thresholds with actual vs target>
Correlation pointers (for HOMER/qryn follow-up):
hep_capture_id: <int>
self_tag: <id>
time_window: <start ISO 8601> .. <end ISO 8601>
key_call_ids: <failed-call IDs, capped at N>
Threshold proposals for next run (if any):
-health_min_success_ratio <val>
-health_max_timeouts <val>
...
Concerns (see "Concerns schema" section; may be empty):
- code: <code> severity: <info|notice> message: <one-line>
Recommended next step:
<rerun with X via gossipper-run | scenario change Y via gossipper-build |
escalate: production-side HOMER/qryn correlation needed>
Do NOT paste raw summary JSON alongside this block — the report is the synthesis, not a wrapper. Paths live in the Run report; the caller re-opens them if needed.
Concerns schema
Verdict is pass | breach | hard_error. Concerns carry soft signal that doesn't change the verdict but a reviewer should see. Always emit as a top-level concerns[] array in the structured findings (alongside the chat-format report above); empty array is fine. The chat-format report's "Concerns" section enumerates them one bullet per entry.
Shape (structured findings)
{
"verdict": "pass",
"inputs": {
"self_tag": "...",
"codec_assumption": "PCMU/8000"
},
"concerns": [
{
"code": "jitter_variance_high",
"severity": "info",
"message": "Jitter mean 8ms (within 30ms budget) but stddev 12ms — high variance",
"metric": { "name": "jitter_stddev_ms", "value": 12, "threshold": null }
}
]
}
severity is one of info | notice. Anything that should fail a run goes into verdict: breach, not concerns[].
Trigger codes (initial set)
| Code | Trigger condition | Severity | Example metric |
|---|
jitter_variance_high | Jitter mean within budget, but stddev > 50% of mean | info | jitter_stddev_ms |
retransmit_present | Summary retransmits > 0, even if calls completed successfully | info | retransmits |
codec_assumed | Codec inferred via the PCMU default rule for -sn uac/uas runs | info | n/a |
partial_data | Summary JSON missing expected fields (findings[] empty, summary missing entirely on exit 1, etc.) | notice | depends |
unclassified_failures | Per-call records contain success: false with error strings not matching any class regex | notice | unclassified_count |
Add new codes only when a recurring case warrants one — don't proliferate single-use codes.
When to emit which
pass + non-empty concerns[] → "passed, but reviewer should see X." Common case.
breach + concerns[] → "failed on threshold Y, also worth noting Z." Concerns are additive, not redundant with breach detail.
hard_error + concerns[] → rare but valid; e.g. "summary missing — verdict provisional based on exit code alone."