diff --git a/products/user_interviews/skills/planning-voice-agent-user-interviews/SKILL.md b/products/user_interviews/skills/planning-voice-agent-user-interviews/SKILL.md index d1b6394d9bda..18968f712ed2 100644 --- a/products/user_interviews/skills/planning-voice-agent-user-interviews/SKILL.md +++ b/products/user_interviews/skills/planning-voice-agent-user-interviews/SKILL.md @@ -100,13 +100,13 @@ Topics snapshot their audience at create time. When the user picks a cohort, you When the user describes who they want to talk to in behavioral terms, find them in the project's own data: 1. **Find the right event.** Call `read-data-schema` to list events that actually exist in the project. Don't guess event names from training data — PostHog event taxonomies are bespoke. Match the user's description to one or two candidate events; if multiple plausible matches exist, list them and ask which behavior they care about. -2. **Query for users.** Call `execute-sql` with HogQL. Filter by the chosen event over the last 60 days, group by person — prefer `person.properties.email` (directly usable as `interviewee_emails`), fall back to `distinct_id` (for `interviewee_distinct_ids`). Keep both kinds of rows. The aggregates in each template (`event_count`, `last_seen`, `days_since_last_seen`) are what feed Step 5's per-interviewee context. Replace `` with the chosen event, and `` with `person.properties.email` or `distinct_id`: - - **Heavy users** — `SELECT AS id, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY HAVING count() >= 5 ORDER BY count() DESC LIMIT 20` - - **Drop-offs** (tried once or twice and never came back) — `SELECT AS id, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY HAVING count() <= 2 AND dateDiff('day', max(timestamp), now()) > 14 ORDER BY count() ASC LIMIT 20` - - **At-risk** (was active, now dormant) — `SELECT AS id, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY HAVING count() >= 3 AND dateDiff('day', max(timestamp), now()) > 14 ORDER BY days_since_last_seen DESC LIMIT 20` +2. **Query for users.** Call `execute-sql` with HogQL. Filter by the chosen event over the last 60 days and group per person with `coalesce(person.properties.email, distinct_id) AS id` — this keeps both kinds of rows in a single query: people with an email group under it (directly usable as `interviewee_emails`), while emailless people fall back to their own `distinct_id` (for `interviewee_distinct_ids`) instead of collapsing into one junk `None` bucket that would sort to the top under `ORDER BY count() DESC` and eat a slot of the sample. The selected `email` column tells you which is which when routing (see below). The aggregates in each template (`event_count`, `last_seen`, `days_since_last_seen`) are what feed Step 5's per-interviewee context. Replace `` with the chosen event: + - **Heavy users** — `SELECT coalesce(person.properties.email, distinct_id) AS id, person.properties.email AS email, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY id, email HAVING count() >= 5 ORDER BY count() DESC LIMIT 20` + - **Drop-offs** (tried once or twice and never came back) — `SELECT coalesce(person.properties.email, distinct_id) AS id, person.properties.email AS email, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY id, email HAVING count() <= 2 AND dateDiff('day', max(timestamp), now()) > 14 ORDER BY count() ASC LIMIT 20` + - **At-risk** (was active, now dormant) — `SELECT coalesce(person.properties.email, distinct_id) AS id, person.properties.email AS email, count() AS event_count, max(timestamp) AS last_seen, dateDiff('day', max(timestamp), now()) AS days_since_last_seen FROM events WHERE event = '' AND timestamp > now() - INTERVAL 60 DAY GROUP BY id, email HAVING count() >= 3 AND dateDiff('day', max(timestamp), now()) > 14 ORDER BY days_since_last_seen DESC LIMIT 20` 3. **Build a balanced sample.** Unless the user asked for one specific segment, mixing 5 heavy users + 3 drop-offs + 2 at-risk users yields the most actionable interviews: you learn what works, what blocks adoption, and what breaks retention. Adjust counts to match what the user actually wants. -Pass the email rows as `interviewee_emails` and the distinct-ID rows as `interviewee_distinct_ids` — both can be set on the same topic. Keep `event_count` and `days_since_last_seen` per person so Step 5 can synthesise context like "used checkout 47 times in last 60 days; last seen 2 days ago". +Route each row by its `email` column: rows where `email` is non-null go into `interviewee_emails`, and rows where `email` is null (so `id` holds the `distinct_id`) go into `interviewee_distinct_ids` — both can be set on the same topic. Keep `event_count` and `days_since_last_seen` per person so Step 5 can synthesise context like "used checkout 47 times in last 60 days; last seen 2 days ago". ## Step 3: Capture the topic @@ -217,7 +217,7 @@ identifier,context abc-distinct-id-1,churned from Scale last month — be empathetic ``` -Parse the CSV, then call `user-interview-topics-interviewees-create` once per row with the captured `topic_id`. Skip blank lines. Quote-escape commas inside the context cell — standard CSV rules. +Parse the CSV, then create the rows with the captured `topic_id`. For more than a couple of rows, prefer `user-interview-topics-interviewees-bulk-create` — it takes an `items` array of `(interviewee_identifier, agent_context)` and creates up to 500 rows in one request, reporting `inserted_count`, `skipped_count`, and `skipped_identifiers` for pairs that already exist. Fall back to `user-interview-topics-interviewees-create` for a single row. Skip blank lines. Quote-escape commas inside the context cell — standard CSV rules. If a row's identifier isn't present in the parent topic's `interviewee_emails` or `interviewee_distinct_ids`, warn the user before creating — the voice agent looks up context by exact string match, so a mismatched identifier just gets ignored at runtime.