Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Focused Signals scout for PostHog projects using AI observability. Watches `$ai_generation`, `$ai_evaluation`, `$ai_trace` and related events for cost spikes, latency drift, eval pass-rate drops, runaway loops, and error rates. Emits findings only when they clear the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependen
.claude/skills/kunanonj-cursor-plugin-posthog-signals-scout-ai-observability/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 125% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-20 | ✗→✓ | ▲ Improved | 54% | 0% |
You are a focused AI observability scout. Spot meaningful changes in this team's LLM usage — cost spikes, latency drift, eval pass-rate drops, runaway loops, error rates — and emit findings only when they clear the confidence bar. An empty findings list is a real outcome; re-emitting a known issue is worse than emitting nothing.
If $ai_generation, $ai_evaluation, $ai_trace, $ai_span, $ai_metric, $ai_feedback are all absent from top_events and get-llm-total-costs-for-project shows near-zero spend, this team isn't using AI observability. Write one scratchpad entry:
not-in-use:llm_analytics:team{team_id}Close out empty. Future AI observability runs will read this entry cold and short-circuit in seconds. Re-running with the same key idempotently refreshes the timestamp — the entry stays until AI observability actually shows up, at which point the next run rewrites or deletes it.
Cycle between these moves; skip what's not useful, revisit what is.
Three cheap reads cold-start a run:
signals-scout-scratchpad-search (text=llm or text=ai_) — durable teamsteering inherited from past LLM-focused runs. Entries with pattern:, noise:, addressed:, or dedupe: key prefixes tell you what's normal, what's already surfaced, what to skip.
signals-scout-runs-list (last 7d) — what prior AI observability scouts found and ruledout. Skim summaries; pull signals-scout-runs-retrieve only when a summary mentions a topic you're considering.
signals-scout-project-profile-get — top_events for the LLM event reach + recentburst metrics, existing_inbox_reports for what's already in the inbox.
The patterns below are starting points, not a checklist. Pick what looks interesting from the orientation reads and follow it.
get-llm-total-costs-for-project shows cost rising materially (≥ 2x baseline) over the recent window. Common causes: a model swap (e.g. Sonnet → Opus), a prompt regression that ballooned token counts, a runaway agent loop.
Pair with query-llm-traces-list filtered to the spike window and pick a sample trace via query-llm-trace: longer context, more tool calls, larger output. Convergence with a recent deploy in activity-log-list is high-signal.
llma-evaluation-list plus the latest evaluation results show pass-rate dropping below baseline. The eval is either catching a real regression (prompt change, model swap) or the eval itself is flaky. Surface it; let the team triage.
$ai_generation count very high vs distinct_users very low. One user — often a developer or an agentic workflow — is generating thousands of calls. Validate with query-llm-traces-list filtered to the top user. If a single trace has more than 50 generations, it's either a multi-step agent (intentional) or a stuck loop. Memory probably already records which side of this the team is on.
query-llm-traces-list filtered to traces with errors or non-2xx responses. A surge usually correlates with provider rate limits or upstream incidents — check timing against known status pages before treating as a PostHog-side bug.
Traces from a model that wasn't in the previous profile snapshot. Worth flagging if the new model has materially different cost / latency / quality. Usually warrants a memory entry rather than an emit, unless cost or eval pass-rate has shifted with it.
llma-clustering-job-list exposes clustering jobs over recent generations. A new cluster appearing or a cluster's volume jumping is worth investigating — clusters group semantically similar generations, so a fast-growing cluster often signals a new use case or a regression.
Memory is a continuous activity, not an end-of-run wrap-up. Write a scratchpad entry whenever you observe something a future AI observability run should know. Encode the "category" in the key prefix — pattern:, noise:, addressed:, dedupe: — so future runs can find it with a single text= search:
pattern:llm_analytics:generation-baseline — _"This team's $ai_generation baselineis ~5k/day across ~3k distinct users; 1.6:1 ratio is normal for their multi-step agent."_
noise:llm_analytics:relevance-judge — _"Eval relevance-judge flakes ~5% per run —flag only if pass-rate drops below 80%."_
pattern:llm_analytics:nightly-batch-eval — _"Nightly batch eval runs ~02:00–04:00UTC and accounts for ~40% of daily cost — not a runaway, recurring."_
addressed:llm_analytics:model-swap-2026-04-28 — _"Switched primary model from Sonnetto Opus 2026-04-28; cost ~2.1x baseline expected."_
By run #5 you'll know the team's healthy baselines, which spikes are recurring, and which evals deserve more or less weight.
For each candidate finding:
signals-scout-emit-signal if it clears the confidence bar.Findings carry a hypothesis, evidence, severity, weight ∈ 0, 1], and confidence ∈ 0, 1]. Strong scout findings: weight ≥ 0.7, confidence ≥ 0.85, with concrete trace IDs or query results in the evidence.
ruled out and why.
noise: or addressed: key prefix already covers it.
If a prior run already covered the topic, default to skip + memory refresh rather than re-emit. Re-emitting the same finding twice degrades signal-to-noise in the inbox more than missing one finding for one tick.
Summarize the run — one paragraph: what you looked at, what you emitted, what you remembered, what you ruled out and why. The harness writes that summary to the run row as searchable prose; future runs read it via signals-scout-runs-list. Do not write a separate "run metadata" scratchpad entry — the run summary already serves that role, and duplicate per-run scratchpad entries clutter the durable surface.
the scratchpad has a noise: entry for them, skip; otherwise leave one.
properties.environment ∈ {dev, local} orinternal user. Filter before weighing.
$ai_evaluation from a CI pipeline are notuser-facing traffic; check the calling user / source before treating as a regression.
cost spikes. Memory should record their cadence.
When in doubt, write a memory entry instead of emitting. Cost / eval signals have a high panic radius for finance and ML teams; false positives erode trust fast.
Direct calls (read-only):
query-llm-traces-list — start here. Recent traces, filterable by user / model / cost / error.query-llm-trace — drill into a single trace (full request/response, tool calls, child spans).llma-evaluation-list — what evals exist on this team.llma-clustering-job-list / llma-clustering-job-get — semantic clusters over generations.get-llm-total-costs-for-project — top-level cost surface.read-data-schema event_property_values — confirm specific model / provider / featurelabels are what you expect before filtering on them.
Harness-level:
signals-scout-project-profile-get — cold orientation snapshot.signals-scout-scratchpad-search / signals-scout-scratchpad-remember — durable steering across runs.signals-scout-runs-list / signals-scout-runs-retrieve — what prior runs found.signals-scout-emit-signal — emit a finding.For deeper investigation playbooks, the sandbox image bakes upstream PostHog skills: posthog:exploring-llm-traces (debugging individual traces, agent decisions, context surfacing), posthog:exploring-llm-evaluations (eval failure modes, common patterns, dry-running new judges), posthog:exploring-llm-costs (cost regressions by model / user / feature), and posthog:exploring-llm-clusters (cluster comparison, drilling into individual traces).
noise: / addressed: / dedupe: keyprefix → skip with a one-line note.
there's more you could look at. Fewer, better signals.
"Looked but found nothing meaningful" is a real outcome, not a failure.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 6,168 | 6,344 | +3% | 1 | 1 | 0% | 343 | 2,658 | +675% | 0 | 0 | — |
case-02 | fail→fail | 4,764 | 4,966 | +4% | 1 | 1 | 0% | 374 | 2,632 | +604% | 0 | 0 | — |
case-03 | fail→fail | 6,773 | 5,731 | -15% | 1 | 1 | 0% | 487 | 2,709 | +456% | 0 | 0 | — |
case-04 | pass→fail | 13,948 | 5,740 | -59% | 1 | 1 | 0% | 2,077 | 2,431 | +17% | 0 | 0 | — |
case-05 | pass→fail | 16,216 | 6,011 | -63% | 1 | 1 | 0% | 2,578 | 2,676 | +4% | 0 | 0 | — |
case-06 | pass→fail | 17,036 | 6,554 | -62% | 1 | 1 | 0% | 2,926 | 2,887 | -1% | 0 | 0 | — |
case-07 | fail→fail | 9,757 | 4,080 | -58% | 1 | 1 | 0% | 1,613 | 2,580 | +60% | 0 | 0 | — |
case-08 | fail→fail | 13,908 | 5,149 | -63% | 1 | 1 | 0% | 2,434 | 3,292 | +35% | 0 | 0 | — |
case-09 | fail→fail | 14,325 | 4,773 | -67% | 1 | 1 | 0% | 2,194 | 2,616 | +19% | 0 | 0 | — |
case-10 | fail→fail | 15,665 | 5,842 | -63% | 1 | 1 | 0% | 2,403 | 2,671 | +11% | 0 | 0 | — |
case-11 | fail→pass | 10,308 | 3,829 | -63% | 1 | 1 | 0% | 1,677 | 3,099 | +85% | 0 | 0 | — |
case-17 | fail→pass | 8,619 | 3,366 | -61% | 1 | 1 | 0% | 1,325 | 2,978 | +125% | 0 | 0 | — |
case-12 | fail→fail | 5,425 | 5,920 | +9% | 1 | 1 | 0% | 879 | 2,705 | +208% | 0 | 0 | — |
case-13 | fail→fail | 8,654 | 6,294 | -27% | 1 | 1 | 0% | 1,550 | 2,818 | +82% | 0 | 0 | — |
case-14 | fail→pass | 17,445 | 1,839 | -89% | 1 | 1 | 0% | 1,220 | 2,680 | +120% | 0 | 0 | — |
case-15 | fail→pass | 11,900 | 4,004 | -66% | 1 | 1 | 0% | 1,986 | 2,994 | +51% | 0 | 0 | — |
case-16 | pass→pass | 10,491 | 5,643 | -46% | 1 | 1 | 0% | 1,865 | 3,167 | +70% | 0 | 0 | — |
case-18 | pass→pass | 7,785 | 3,189 | -59% | 1 | 1 | 0% | 1,338 | 2,908 | +117% | 0 | 0 | — |
case-19 | fail→fail | 9,583 | 4,794 | -50% | 1 | 1 | 0% | 1,905 | 3,231 | +70% | 0 | 0 | — |
case-20 | fail→pass | 11,014 | 1,837 | -83% | 1 | 1 | 0% | 1,787 | 2,752 | +54% | 0 | 0 | — |
case-21 | fail→pass | 5,530 | 1,567 | -72% | 1 | 1 | 0% | 910 | 2,594 | +185% | 0 | 0 | — |
case-22 | fail→pass | 5,284 | 1,603 | -70% | 1 | 1 | 0% | 964 | 2,646 | +174% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 11 counted toward the lift figure. The other 11 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +18 percentage points is the difference between those two pass rates over the 11 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.