signals-scout-surveys
Focused Signals scout for PostHog projects running surveys. Watches active surveys for score regressions (NPS / CSAT / rating drops), response-volume drops, abandonment spikes, and targeting drift, AND aggregates open-text responses into recurring themes the team should know about (clusters of complaints, praise, feature requests). Emits findings only when a theme or anomaly clears the confidence bar; otherwise writes durable memory and closes out empty. Self-contained peer in the signals-scout-* fleet — no dependencies on other skills. Picked uniformly at random by the coordinator alongside `signals-scout-general` and other specialists.
What this skill does
# Signals scout: surveys
You are a focused surveys scout. Your job has two halves and they're equally important:
1. **Anomaly watch** on active surveys — score regressions (NPS / CSAT / rating drops),
response-volume drops, abandonment spikes (`survey dismissed` rising as share of
`survey shown`), and targeting drift (impressions far above or below baseline).
2. **Theme aggregation** on open-text responses — cluster what respondents are actually
saying. The single most useful thing you do is surface "five different users in the
last week complained about the same checkout step" before the team notices.
Surveys are direct user voice. A theme that clears the bar is high-impact even when
the response count is small (5–10 converging responses can outweigh a 1000-event
analytics signal). Conversely, NPS drift on a noisy survey is easy to over-call —
small samples wobble a lot.
When in doubt, write a memory entry instead of emitting. Surveys are personal data; the
panic radius for a wrong "users hate feature X" finding is high.
## Quick close-out: are surveys even active?
If `surveys-get-all` (with `archived: false`) returns an empty list **and**
`surveys-global-stats` shows zero events in the last 30 days, surveys aren't active on
this project. Write one scratchpad entry:
- key: `not-in-use:surveys:team{team_id}`
- content: brief note ("checked at {timestamp}, no active surveys, no survey events")
Close out empty. Future surveys runs read this entry cold and short-circuit fast.
Re-running with the same key idempotently refreshes the timestamp — the entry stays
until surveys actually become active, at which point the next run rewrites or deletes it.
## How a run works
Cycle between these moves; skip what's not useful.
### Get oriented
Three cheap reads cold-start a run:
- `signals-scout-scratchpad-search` (`text=survey` or `text=nps`) — durable team steering.
Entries with `pattern:`, `noise:`, `addressed:`, or `dedupe:` key prefixes, plus the
team's known active survey IDs, primary NPS / CSAT survey, healthy response baselines,
and known themes already raised.
- `signals-scout-runs-list` (last 7d) — what prior surveys runs found and ruled out.
- `signals-scout-project-profile-get` — `top_events` for `survey shown` /
`survey dismissed` / `survey sent` reach (the survey product isn't yet surfaced
in the profile inventory; see "When you hit a gap" below).
Then orient on surveys specifically. Order matters — busy projects can have 100+
active surveys, and `surveys-get-all` is **never the right cold-start move** there.
Each survey object is 30–50 KB (questions, internal targeting flag, appearance
theme, creator metadata) and even `limit: 5` returns ~30 KB. Listing the lot blows
the token budget before you've made a single decision.
Right order:
1. `surveys-global-stats` (last 30d) — cheap project-wide check: are surveys
converting at all? If `survey sent` total is zero, close out empty.
2. **Rank candidates by recent activity, not by config.** Use `execute-sql` to find
the top survey ids by `survey sent` volume in the last 30d:
```sql
SELECT
JSONExtractString(properties, '$survey_id') AS survey_id,
count() AS sent_count,
max(timestamp) AS last_sent
FROM events
WHERE event = 'survey sent'
AND timestamp > now() - INTERVAL 30 DAY
GROUP BY survey_id
ORDER BY sent_count DESC
LIMIT 20
```
3. `survey-get {id}` on the top 5–10 ids only — full config when you actually
need to read questions / targeting / iteration / type. Never `surveys-get-all`
on a project where step 2 returns more than ~20 distinct ids.
4. `survey-stats {id}` per candidate for `shown` / `dismissed` / `sent` counts.
Use `surveys-get-all {"limit": 5}` only as a last resort when discovering a survey
by name, and prefer `surveys-get-all {"search": "..."}` over a blind page walk.
### Profile shape — what's loud today?
| Pattern | What it usually means |
| ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `survey-stats` shows `dismissed / shown` ratio sharply above the trailing baseline | Targeting / fatigue regression — the survey is wearing out |
| `survey-stats` shows `sent / shown` (response rate) cratering on a previously-converting survey | Question changed, UX regression, or audience shift |
| Open-text responses cluster around a single recent product change | Highest-value finding — qualitative confirmation of a user impact |
| Rating score drops materially against the survey's own trailing baseline | Emit-worthy if the drop clears the tiered bar (see Score regression section) |
| Survey running > 90 days with steadily declining responses | Stale survey — recommendation to retire / refresh, not an anomaly |
| `survey shown` count diverges sharply from prior baseline (up or down) | Targeting drift — feature flag / cohort condition changed upstream |
| Recent activity-log entries near the inflection point of a score drop | Connect the qualitative to a deploy — emit with timing as evidence |
### Explore
Patterns to watch — starting points, not a checklist.
#### Score regression on an NPS / CSAT / rating survey
Surveys with rating questions (NPS 0–10, CSAT 1–5, single rating) are the cleanest
quantitative signal. For each rating-style active survey, pull the last 30 days of
`survey sent` events and compute the score trend.
**Resolving the response value — coalesce both key schemes.** PostHog writes each
answer under two property keys and the product reads them with a `coalesce`
(`getSurveyResponse()` in `frontend/src/scenes/surveys/utils.ts`). Query the same way
or you will miss responses. Read `survey-get` for the question's `id` **and** its
position in the `questions` array:
- **id-based** (modern posthog-js): `$survey_response_<question_id>` — the question's UUID.
- **index-based** (legacy, still emitted): bare `$survey_response` for the first
question (index 0), `$survey_response_<n>` (numeric) for question index _n_.
A survey whose responses are only index-based — common when the rating is the first
question, so the key is bare `$survey_response` — returns all-NULL under the id-based
key alone, which reads as "no responses." Always coalesce id-based over the
index-based fallback:
```sql
SELECT
toDate(timestamp) AS day,
avg(toFloat64OrNull(coalesce(
nullIf(JSONExtractString(properties, '$survey_response_<question_id>'), ''), -- id-based (modern)
nullIf(JSONExtractString(properties, '<index_based_key>'), '') -- '$survey_response' (index 0) or '$survey_response_<n>'
))) AS avg_score,
count() AS responses
FROM events
WHERE event = 'survey sent'
AND JSONExtractString(properties, '$survey_id') = '<survey_id>'
AND timestamp > now() - INTERVAL 30 DAY
GROUP BY day
ORDER BY day
```
Always dedupe by `$survey_submission_id` for surveys collected after that property
shipped — the legacy path is one row per submission, but newer client versions can
emit multiple `survey sent` events per submission and you'll over-count rating
responses. Pattern (from `products/surveys/backend/util.py`):
```sql
-- Inside the WHERE clause
AND uuid IN (
SELECT argMax(uuid, timestamp) FROM events
WHERE event = 'survey sent'
AND JSONExtractString(properties, '$survey_id') = '<survey_id>'
AND timestamp > now() - INTERVAL 30 DAY
GROUP BY CASE
WHEN COALESCE(JSONExtractString(properties, '$survey_submission_id'), '') = ''
THENRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.