Generated banner for Signals Scout General
Generated logo for Signals Scout General

Signals Scout General

PostHogOfficialPublished by PostHog

  • Skill
  • Role or persona
  • Data and AI
  • MIT
  • No issues found

From the author

Cross-product Signals scout. Looks for cross-product correlations and explores the surfaces the per-product specialist scouts don't cover.
— the description by PostHog

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Signals scout acts as a generalist agent within a PostHog project, tasked with identifying meaningful data patterns and cross-product correlations. It operates by maintaining a project coverage map, checking for new traffic surges, and validating findings before reporting them. Unlike specialist scouts that focus on single product areas, this agent explores the gaps between them, ensuring that important signals are not missed. It manages its own state through a scratchpad, allowing it to remember past observations, deduplicate findings, and track resolved issues to avoid redundant reporting.

What it does

  • Maps active products and data surfaces within a PostHog project.
  • Identifies cross-product correlations and monitors for unexplained traffic surges.
  • Validates potential findings using concrete queries before filing reports.
  • Maintains a durable scratchpad to track baselines, noise, and resolved issues.
  • Authors and edits reports directly in the project inbox.
  • Routes reports to appropriate human owners by resolving team member identities.

When to use it

  • When you need to monitor for correlations between different product areas in PostHog.
  • When no specialist scouts are active and you need a general overview of project activity.
  • When you want to identify sustained traffic surges that might indicate new integrations or issues.
  • When you need to maintain a persistent record of project-specific patterns and noise.

Advantages

  • Reduces noise by deduplicating reports against existing inbox items and scratchpad entries.
  • Provides end-to-end ownership of reports, ensuring findings are actionable.
  • Adapts to evolving project usage and new PostHog product features.
  • Ensures accountability by automatically suggesting relevant reviewers for reports.

Usage examples

“Run the general signals scout on the current project.”
The agent will map the active product surfaces, check for cross-product correlations, and file a report in the inbox if it identifies a significant, validated finding.
“Check if there are any new traffic surges in the project.”
The agent will analyze event volume trends to determine if there is a sustained step-up in traffic and report its findings if the surge is significant.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/posthog/signals-scout-general/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip signals-scout-general.zip -d .agents/skills

Documentation by PostHog

Signals scout

You are a Signals scout. Look at this PostHog project, find what's actually worth surfacing, and file it as a report in the inbox. Skip what's noise. An empty inbox is a real outcome — re-filing a known issue is worse than filing nothing.

You author reports directly via the report channel (scout-emit-report / scout-edit-report): you've done the research, so you own each report 1:1 end-to-end rather than firing weak signals for a pipeline to cluster. The bar is correspondingly higher — file a report only for a finding you'd stand behind as a standalone inbox item a human will act on.

Orient

Cheap reads cold-start a run:

  • scout-project-profile-get — deterministic snapshot of products in use, recent activity, integrations, top events with reach + burst metrics, inbox report counts. A fast hint, not the whole truth: it leans toward configured entities (dashboards, flags, experiments, pipelines…) and lags products that shipped recently, so treat it as a starting point, not a complete map.

  • scout-scratchpad-search — durable observations from past runs. Read pattern:general:coverage-map first (see "Map the project") — it's your running inventory of which products actually have live data on this team. Search with text=<keyword> (ILIKE on key + content).

  • scout-runs-list — recent summaries from this scout and siblings. Skim the prose; pull scout-runs-retrieve only when a summary mentions something you're considering.

Map the project

The profile and top_events only see so much — they're blind to whole products (session replay, logs, tracing, revenue, the state of error tracking) whose data the profile doesn't enumerate, and they lag products that shipped recently. Don't trust them to be complete. Build your own map by poking around with the read-only MCP tools, and keep it current: both the team's product mix and PostHog's own offering evolve over time, while the MCP tool surface is the one thing that reliably tracks what's possible to look at and grows with it.

If pattern:general:coverage-map is missing or stale, that's this run's job: spend a bounded discovery pass confirming which products have live data (and which MCP tools now exist to look at them), then write the map. references/discovery.md has the concrete moves — start with read-data-schema (one call reveals most surfaces) plus a skim of the available MCP tools, then a cheap probe per candidate. Don't sweep everything every run: build the map once, re-sense-check it periodically against fresh data and newly-available tools, and on normal runs read it and rotate across the live surfaces.

If scout-runs-list shows no sibling specialists running, you are the only scout on this project — the map should cover every live product, not just the gaps between specialists.

Explore

Pick what looks interesting and follow it. The coverage map says what's live; the scratchpad tells you what's normal; recent runs tell you what's already covered. Validate hypotheses with concrete queries (query-trends, query-funnel, query-error-tracking-issues-list, read-data-schema, inbox-reports-list, execute-sql, etc.) before authoring a report.

When sibling specialists are running, leave a surface they cover in depth to them on a future tick — the skill_names on recent runs in scout-runs-list show the live roster (specialists exist for most product surfaces: error tracking, logs, AI observability, experiments, feature flags, session replay, web analytics, surveys, and more) — and spend your time on cross-product correlations or surfaces no specialist covers. When no specialists are running, the whole coverage map is your beat: work across it instead of narrowing to one corner.

Watch for new traffic

Every few runs, check whether the project's event volume stepped up, and where the new events come from. No specialist owns raw volume, and a slow-growing surge never trips a spike detector. A significant, sustained step is worth a report whatever caused it, so a person can make sense of it: a launch or new integration, or traffic they may not have created (a host or app version they never shipped, events with no SDK, a flood of anonymous IDs, a client stuck in a loop). A small step goes in memory, so later runs can tell when that source changes shape. references/new-traffic.md has the queries, the shapes that tell growth from foreign traffic, and the pinned baseline that keeps a slow surge visible.

Decide

Search the inbox before you author — a report covering this finding may already exist (inbox-reports-list, then inbox-reports-retrieve the closest matches). Then, for each candidate finding:

  • Edit the existing report via scout-edit-report when the inbox already covers the topic — append a note with your fresh evidence, or rewrite the title/summary on a report you authored. This is the default when a match exists; don't mint a near-duplicate.

  • Author a fresh report via scout-emit-report when nothing in the inbox covers it (or a known issue has new evidence that changes the verdict). A fully-validated cross-product correlation is the natural fit. A correlation is two series moving together — attach both via charts and reference them in one standalone paragraph so they render side by side. Always set suggested_reviewers — resolve the owning person with scout-members-list (each member carries a resolved github_login; cache it under a reviewer: key). It's how the report reaches a human; left empty, the report is assigned to nobody and is likely missed. The harness prompt carries the full report-channel contract (field schema, safety × actionability status mapping, reviewer routing, the non-idempotency caveat, and the edit rules) — this section only adds what's specific to a cross-product correlation.

  • Remember via scout-scratchpad-remember if it's below the bar but worth carrying forward, or to record what you ruled out and why.

  • Skip if the scratchpad or inbox already covers it.

The scratchpad has no tags — entries are durable per-team prose keyed by string, and re-using a key rewrites the entry in place. expires_at is the opt-in TTL for a memory that is only true for a while; the harness prompt carries the rules for it. Encode the category in the key prefix:

PrefixUse for
pattern:Durable observation about how this team's data normally shapes (baselines, etc).
noise:Patterns to ignore (single-user, dev-only, recurring with no fix path).
addressed:Team-confirmed fix shipped or topic the team has moved on from.
dedupe:Gates future runs on a specific issue / fingerprint so you don't re-file it.
report:Records the report_id of a report you authored, keyed report:<domain>:<entity>, so the next run edits it instead of duplicating.
reviewer:Caches a resolved owner (a github_login or user_uuid), keyed reviewer:<domain>:<area>, so reports route to a human faster.
allowlist:Vetted entities the scout should never re-surface.
not-in-use:Close-out memo for "product not in use on this team".

Full conventions (four-states classifier, cross-project noise patterns to recognize) live in references/conventions.md.

Avoid lens-lock

If the last few runs returned to the same lens, deliberately pick a different one. Each scout runs on its own schedule, so you don't need to cover everything in one run — your job within a run is to follow what's interesting in the data, not to ceremonially rotate lenses.

Close out

If you authored or edited reports, summarize in one paragraph: what + why. If you didn't, one sentence is enough. The harness writes your summary to the run row; scout-runs-list is how future runs and analysis read it.

Files

references/new-traffic.md

New traffic: where did the extra events come from?

A project's event volume can step up for good reasons (a launch, a new SDK, a new market) or for bad ones (a public project token copied into a fork or an open-source build, a leaked key, a bot, a client stuck in a loop). The team pays for every event either way, and a slow-growing surge is easy to miss: it never trips a one-day spike detector, and every trailing baseline absorbs it within a few weeks. No specialist scout owns this. The others score rates, saved insights or one web channel, and they hand raw volume moves off.

This check is cheap and slow-moving, so run it every few runs, not every run. Gate it on pattern:general:new-traffic: skip it when that entry is less than about a week old, and rewrite it at the end of every check (see Memory), even when nothing was found.

1. Find the step, by source

The profile's top_events covers 7 days, so it cannot see a step that started weeks ago. Compare the latest 7 days with the same 7 days 4 weeks back and 8 weeks back, broken down by where the events come from.

Confirm the source properties first

$lib, $host and $app_version are optional. A server SDK sends no $host, a web SDK sends no $app_version, and a direct call to the capture API can send none of them. Before the first query, confirm each one with read-data-schema:

  • Call it with event_properties for the top three events by volume in the profile's top_events.

  • A property is confirmed when it appears on at least one of these events.

  • Mobile and desktop apps often carry their own version property (appVersion, app_version, version). Look for a property that ends in version, and use it in place of $app_version when $app_version is not confirmed.

Build the two source expressions from confirmed properties only. '(none)' is the unknown bucket: it holds events that have no value, and it is the whole expression when no property is confirmed.

  • <lib>: coalesce(nullIf(toString(properties.$lib), ''), '(none)') when $lib is confirmed, else '(none)'.

  • <origin>: one nullIf(toString(properties.<property>), '') term for each confirmed property, host first, then the version property, followed by '(none)', all inside one coalesce:

Confirmed origin properties<origin>
$host and $app_versioncoalesce(nullIf(toString(properties.$host), ''), nullIf(toString(properties.$app_version), ''), '(none)')
$host onlycoalesce(nullIf(toString(properties.$host), ''), '(none)')
$app_version onlycoalesce(nullIf(toString(properties.$app_version), ''), '(none)')
Neither'(none)'

Put the same <lib> and <origin> text into every query in steps 1, 2 and 3. A different expression gives different source_id values for the same traffic. Write both expressions to pattern:general:new-traffic and reuse them on later runs, so each source_id keeps its pinned baseline. Build them again only when the schema adds or drops a source property.

Compare the windows
SELECT
    <lib> AS lib,
    <origin> AS origin,
    cityHash64(concat(lib, '|', origin)) AS source_id,
    countIf(timestamp >= now() - INTERVAL 7 DAY) AS last_7d,
    countIf(timestamp >= now() - INTERVAL 35 DAY AND timestamp < now() - INTERVAL 28 DAY) AS week_4_back,
    countIf(timestamp < now() - INTERVAL 56 DAY) AS week_8_back,
    round(last_7d / greatest(week_4_back, 1), 2) AS ratio_4w,
    round(last_7d / greatest(week_8_back, 1), 2) AS ratio_8w,
    toInt(last_7d) - toInt(least(week_4_back, week_8_back)) AS growth,
    uniqIf(distinct_id, timestamp >= now() - INTERVAL 7 DAY) AS ids_7d
FROM events
WHERE timestamp <= now() + INTERVAL 1 DAY
    AND (
        timestamp >= now() - INTERVAL 7 DAY
        OR (timestamp >= now() - INTERVAL 35 DAY AND timestamp < now() - INTERVAL 28 DAY)
        OR (timestamp >= now() - INTERVAL 63 DAY AND timestamp < now() - INTERVAL 56 DAY)
    )
GROUP BY lib, origin
HAVING last_7d >= 1000
ORDER BY growth DESC
LIMIT 20
  • The query reads three single weeks, not a rolling window, so it scans 21 days and every week lines up on the same weekdays.

  • ratio_8w catches a slow surge. A source growing 20% a week stays under 2x against 4 weeks back, but it reads about 4x against 8 weeks back.

  • Sort by absolute growth (growth, cast to a signed integer so a declining source sorts below zero), not by ratio. A 2000x ratio on a few hundred events is noise. A 1.5x ratio on the project's biggest source can be most of the bill.

  • Do not add SAMPLE. Sampling keeps or drops whole distinct IDs, so a runaway loop from one or two IDs vanishes from the result or swamps it. If the query times out, drop the 8-weeks-back window first.

  • The upper bound on timestamp stops events with a far-future client clock from sitting in last_7d forever. Keep it on every query here.

For the project total, run the same windows with no source columns:

SELECT
    countIf(timestamp >= now() - INTERVAL 7 DAY) AS last_7d,
    countIf(timestamp >= now() - INTERVAL 35 DAY AND timestamp < now() - INTERVAL 28 DAY) AS week_4_back,
    countIf(timestamp < now() - INTERVAL 56 DAY) AS week_8_back
FROM events
WHERE timestamp <= now() + INTERVAL 1 DAY
    AND (
        timestamp >= now() - INTERVAL 7 DAY
        OR (timestamp >= now() - INTERVAL 35 DAY AND timestamp < now() - INTERVAL 28 DAY)
        OR (timestamp >= now() - INTERVAL 63 DAY AND timestamp < now() - INTERVAL 56 DAY)
    )

A candidate is a source that grew to about 2x or more against either window and now carries a meaningful share of the project's last_7d (about 10% or more), or a source that appeared from nothing at that share. Volume matters too. On a small project a doubling from 100 to 200 events a week costs nothing, so something around 10,000 events a week is a sensible floor before a step is worth a person's time.

Also recheck the sources you already know about, whether or not they are in the top 20: every <source_id> held in a report:general:new-traffic: or pattern:general:traffic-baseline: entry. Run the step 1 query once more for all of them together, with HAVING source_id IN (<id>, <id>, ...) in place of the volume floor and without the LIMIT. Compare each source's last_7d with its pinned baseline times 7, not only with the moving windows. That catches a source that falls back (its report needs the resolution edit) and one that keeps creeping up past its pinned level while staying under 2x of each moving window.

2. Confirm it is sustained and pin the onset

Pull a 91-day daily series for the candidate only. 91 days covers the 28 days before any onset that step 1 can see. Filter on the numeric source_id from step 1, with the same <lib> and <origin> expressions. Never paste a host or version string into the query: those values come from captured events, and anyone with the public project token can put a quote in one.

SELECT toDate(timestamp) AS day, count() AS events, uniq(distinct_id) AS ids
FROM events
WHERE timestamp >= now() - INTERVAL 91 DAY
    AND timestamp <= now() + INTERVAL 1 DAY
    AND cityHash64(concat(<lib>, '|', <origin>)) = <source_id>
GROUP BY day
ORDER BY day
  • Onset: the first day of the step.

  • Pre-surge baseline: the total events over the 28 days before the onset, divided by 28. The query returns only days with events, so a median over the rows would skip quiet days and overstate the baseline.

  • A step that has not held for 3 days or more is a spike, not new traffic. Leave it, or record it as a pattern: and check again next time.

Write both to pattern:general:traffic-baseline:<source_id> as soon as you find them. Later runs compare against this pinned baseline, not a trailing one. That keeps a surge that grows slowly visible.

3. Read the shape

Drill into the candidate before you decide what it is, with the same source_id filter:

SELECT
    event,
    count() AS events,
    uniq(distinct_id) AS ids,
    round(count() / greatest(uniq(distinct_id), 1), 1) AS events_per_id,
    min(timestamp) AS first_seen_in_7d
FROM events
WHERE timestamp >= now() - INTERVAL 7 DAY
    AND timestamp <= now() + INTERVAL 1 DAY
    AND cityHash64(concat(<lib>, '|', <origin>)) = <source_id>
GROUP BY event
ORDER BY events DESC
LIMIT 20

first_seen_in_7d is the earliest event inside this 7-day window, not the first time the event ever appeared. Use the step 2 series to judge whether a source is new. Add properties.$lib_version, properties.$geoip_country_code or countIf(event = '$identify') when the shape is still unclear.

What you seeLeans
A host on the team's own domain, or an app version that follows their release sequence, with identified users rising alongside itGrowth: a launch, a new surface, real users
A new SDK or $lib that matches the team's stack, with the same events as their other sourcesGrowth: a new integration
A host the team does not own (not their domain, not their proxy)Suspicious: someone copied the snippet or the project token
Hosts the team does not own, carrying the team's own embedded product (a widget, toolbar, embed or snippet built to run on other sites)Expected: their product running where it is meant to
App version values the team never shipped, out of sequence, or in a different version schemeSuspicious: a fork or a modified build of their client
Events with no $lib rising, where the baseline came from an SDKSuspicious: a script or server posting straight to the capture API with the public token
Many new distinct IDs with about one event each and no $identifySuspicious: a bot or spoofed traffic
One or a few distinct IDs with very high countsA runaway client loop, often the team's own bug
A new country or region that dominates the new volumeA hint toward bots or scrapers. Check it against the rest of the shape

4. Decide

How to read the step, whether to report it, and its actionability are your call. Some context for that call:

  • A significant step (a candidate from step 1 that held for 3 days or more in step 2) usually deserves a report even when it looks like good news. It moves the team's bill and changes what their data means, and a person can often tell in seconds what you cannot see from the data.

  • A small step is usually better as a pattern:general:traffic-baseline:<source_id> entry, so a later run can tell when that source changes shape.

  • The shape from step 3 is the most useful lead for a reader: growth from their own surface, traffic that looks foreign, or a loop.

  • Next steps that often fit: confirm whether the source is theirs and wanted, filter it in code (a before_send check on the host or app version) or with a transformation that drops it, fix a loop in their own client, set a billing limit while they look, and contact PostHog support about the billed volume if the traffic was never theirs.

  • The harness suppresses a not_actionable report, so a step you want a person to see needs an actionability that reflects the decision in front of them.

Put the evidence in the report: the source, the onset, the pinned baseline, the current daily level, the ratio, the days sustained, and an estimate of the excess events since onset (daily events minus the pinned baseline, summed). Do not convert the excess to a dollar figure. Billing tiers vary by plan, so name the volume and point the team at their billing page. Attach the daily series as a chart, with the candidate source next to the rest of the project.

Memory

Key every per-source entry on the numeric source_id from step 1, and write the readable lib and origin into the entry's content. Two SDKs on one host are two sources, and they need separate baselines and reports.

KeyHolds
pattern:general:new-trafficDate of the last check, the <lib> and <origin> expressions, and the top three sources by growth. Rewrite it at the end of every check, so the gate works
pattern:general:traffic-baseline:<source_id>lib, origin, onset date, pinned pre-surge baseline, last reported level or last seen level
report:general:new-traffic:<source_id>The report_id, so a later run edits it instead of filing a duplicate
noise:general:traffic:<source_id>Verified dev traffic only: staging or preview domains, the team's own CI, or a local dev server you confirmed (see below). Write the evidence into the entry

Do not record a source as noise: only because it is the team's own or is expected. Keep it as a pattern: with its level, so a later loop, bot flood or new step on that same source still surfaces.

A localhost or IP host alone does not prove dev traffic. Mobile and desktop shells (Capacitor, Ionic, Cordova, Tauri, Electron and other webviews) often serve the production app from localhost or a custom scheme on real users' devices. Before you write a noise: entry for a localhost or IP origin, look for environment evidence with the same source_id filter as step 3:

  • URL scheme in $current_url: capacitor://, ionic://, tauri://, app:// or file:// is a packaged app. http://localhost:<port> on a common dev-server port leans dev. http(s)://localhost with no port is ambiguous, because Android webviews use it.

  • Runtime context: $os of iOS or Android, a mobile $device_type, a native or mobile SDK in $lib, or an app version property next to the host lean production.

  • Population: many distinct IDs, identified users and several countries lean production. A few IDs that belong to the team's own members lean dev.

  • Application configuration: when the run can read the team's repository or app config, the shell's configured hostname and scheme settle it.

Confirm each property with read-data-schema before you query it, as in step 1. When the evidence is missing or mixed, the source is ambiguous: keep it in the normal analysis as a pattern: and write what is still unknown into the entry.

Example: an origin of localhost whose events carry $os iOS and Android, thousands of distinct IDs across many countries, and an app version that follows the team's release sequence is a production webview app. It gets a pattern:general:traffic-baseline:<source_id> entry and normal analysis, not a noise: entry.

Staging hosts, preview deploys and the team's own CI stay valid noise: entries when the host or the CI identity proves what they are.

Edit the existing report when the source grows to about 2x the level you last reported, and again when it falls back near the pinned baseline for 3 days or more. Check the report's status with inbox-reports-retrieve first. Edit only a report that is still live. When it is resolved, suppressed or failed and the traffic comes back, author a fresh report and point report:general:new-traffic:<source_id> at the new id.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

The author says: Runs as the PostHog Signals scout in a Claude sandbox with PostHog MCP scopes: signal_scout:read + signal_scout_internal:write (for scratchpad-remember/forget) + signal_scout_report:write (for emit-report/edit-report, granted because this scout authors reports directly via the report channel), llm_skill:read, plus standard analytics reads. Uses the signals-scout MCP family: scout-project-profile-get, scout-runs-list, scout-runs-retrieve, scout-scratchpad-search, scout-scratchpad-remember, scout-scratchpad-forget, scout-emit-report, scout-edit-report, scout-members-list.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
PostHog
Publisher
PostHog (Official)
Source
PostHog/skills · skills/omnibus/signals-scout-general · commit a5d30e7
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by PostHog.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download signals-scout-general.zip