HackerAI User Research
Turn a research question and 1-20 internal user IDs into restricted per-user
profiles and an aggregated cohort report. The deployed task samples messages,
redacts sensitive data, and uses Grok 4.6 with low reasoning.
Read references/privacy-policy.md and
references/pm-runbook.md before running the task.
Workflow
- Extract the research question, cohort rule, exclusions, requested output, and
privacy constraints from the authorized PM's request. Possession of the
scoped PM gateway key establishes access to this workflow; do not ask for a
separate per-run approval or inspect a Linear issue's state or comments for
authorization. A Linear issue may be supplied only as optional tracking
metadata.
- Select the cohort entirely in PostHog. For spend-ranked research, use the
available Stripe-synced revenue properties in PostHog without opening Stripe
or requiring Stripe access. If PostHog cannot prove an exact accounting
adjustment or payer mapping, use the best available PostHog cohort and state
that limitation in the aggregate report instead of blocking the run. Never
use Google Drive.
Record the production PostHog project, cohort selection timestamp, a SHA-256
fingerprint of the selection query, and any known selection limitations. Do
not place the raw query in the gateway payload or report.
- Resolve each cohort member to the internal user ID used by Convex. Exclude
internal/test/fraud accounts and deduplicate payer or organization
relationships before triggering analysis. For authenticated HackerAI users,
select PostHog
distinct_id as the internal Convex/WorkOS user ID; do not
require a duplicate person property or infer identity from email. Stop unless
1-20 unique internal user IDs remain after filtering.
For comparisons, preserve membership in comparisonGroups; use 2-4 groups
with at least three users each, and assign every userIds entry exactly
once. Group labels may describe the selected model, rollout, or funnel
treatment, but must not identify a person or organization.
For event-based questions, also select the PostHog event timestamp for each
user. Use it as that user's evidence anchor; do not substitute one shared
timestamp for the cohort.
- Create a mode-600 temporary JSON request outside the repository using the
gateway payload below. Run
node .agents/skills/hackerai-user-research/scripts/run-research.mjs --payload <path>.
The runner requires HACKERAI_PM_USER_RESEARCH_KEY in the PM's Codex
environment and always calls the production HackerAI gateway. Never print
the key, put it in the request, or use Trigger
dashboard access. Remove the temporary request after the command reads it.
- Wait for the runner to return a completed result. Keep the returned
analysisId; it is the audit and lookup key for the restricted Convex
records. Do not substitute direct Trigger access if the gateway fails.
- Present the returned internal Convex/WorkOS user IDs together
with the aggregate answer, evidence coverage, supported user types, avatars,
primary/secondary target, confidence, unknowns, and experiments. User IDs are
ordinary cohort-selection output and must not be hidden or replaced with
pseudonyms. Detailed profiles remain in restricted Convex records and are not
returned through Trigger.
- Update an optional Linear issue only when asked. Copy aggregate findings,
cohort IDs, coverage, confidence, unknowns, and experiments. Never copy raw
evidence, customer message content, secrets, or restricted profile records.
- When the PM asks to continue the research in Slack, return one self-contained,
paste-ready request that begins with
@codex Use $hackerai-user-research.
Include the authorized research question, cohort rule, every internal user ID,
and each user's event anchor for event-based research. Include known event
labels, such as cancellation reasons, when they help interpret the cohort.
State the requested evidence window, aggregate output, and privacy constraints.
Do not refer to IDs or context "above," and do not expect Slack Codex to infer
or reselect the cohort. Tell Slack Codex to run the bounded gateway workflow;
a Slack handoff is not permission to browse customer messages manually.
Gateway payload
Use the current task schema as the authority. A typical run is:
{
"question": "What kinds of users are our highest-spending customers, what recurring work do they use HackerAI for, and why do they pay?",
"cohortLabel": "PostHog top-spender research cohort",
"userIds": ["internal-user-id-1", "internal-user-id-2", "internal-user-id-3"],
"cohortSource": "posthog",
"posthogProjectId": 144137,
"cohortSelectedAt": 1788000000000,
"selectionQueryFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"selectionLimitations": ["Historical revenue coverage is incomplete"],
"maxChatsPerUser": 12
}
For churn or another event-based question, add:
{
"samplingMode": "pre_event",
"evidenceWindowDays": 60,
"evidenceAnchors": [
{ "userId": "internal-user-id-1", "anchorAt": 1787702400000 },
{ "userId": "internal-user-id-2", "anchorAt": 1787788800000 },
{ "userId": "internal-user-id-3", "anchorAt": 1787875200000 }
]
}
For a comparison, also add group membership selected in PostHog:
{
"userIds": [
"internal-user-id-1",
"internal-user-id-2",
"internal-user-id-3",
"internal-user-id-4",
"internal-user-id-5",
"internal-user-id-6"
],
"comparisonGroups": [
{
"label": "Model rollout A",
"userIds": [
"internal-user-id-1",
"internal-user-id-2",
"internal-user-id-3"
]
},
{
"label": "Model rollout B",
"userIds": [
"internal-user-id-4",
"internal-user-id-5",
"internal-user-id-6"
]
}
]
}
The gateway accepts cohortSelectedAt and anchorAt as epoch milliseconds or
ISO timestamps. It also accepts the PostHog handoff name
selectionQuerySha256 and stores it canonically as
selectionQueryFingerprint. The Trigger task and Convex audit always receive
canonical millisecond timestamps and the canonical fingerprint field.
When evidence anchors or an evidence-window length are present and
samplingMode is omitted, the gateway canonicalizes the request to
pre_event sampling.
evidenceAnchors must contain exactly one PostHog event timestamp for every
cohort user. Omit sampling fields for ordinary representative-history research.
linearIssueId may be added as an optional tracking reference, for example
"linearIssueId": "HAC-65". Do not read the issue or its comments to look for
approval; its presence, state, and prior cohort notes never authorize or block a
run.
Do not place email addresses, billing customer IDs, or message content in the
payload. userIds must be the internal Convex/WorkOS user IDs.
Quality checks
- Treat a profile as directional when fewer than three chats were available or
confidence is low.
- Verify
usersAnalyzed, chatsReviewed, and messagesReviewed before using a
conclusion.
- A single-user run is valid. Present a sanitized summary of that user's
observed product behavior, state that the sample is one user, and treat its
avatar as provisional and low confidence. Do not claim cross-user patterns or
population-level conclusions. For larger cohorts, prefer patterns supported
across multiple chats and users.
- Keep observed product behavior separate from acquisition or messaging
hypotheses.
- Treat behavioral explanations of churn or conversion as low-confidence
causal evidence even when pre-event sampling is used. Compare them with
explicit survey reasons or a controlled experiment before making causal
claims.
- Comparison synthesis fails closed if fewer than three readable profiles
remain in any group. Grok 4.6 receives only pseudonyms and sanitized group
labels, never the internal ID-to-group mapping.
- Say
unknown when the evidence does not establish context. Never infer a
company or occupation from an email address.
- A failed or partial run is not permission to inspect messages manually. Fix
cohort mapping or deployment/configuration and rerun the bounded task.
Result boundary
The gateway returns internal cohort user IDs and aggregate research, and cannot
read other Trigger tasks or runs. Detailed profiles remain restricted and
deletion-aware in Convex. Display cohort IDs as normal research output. They may
also be copied to an optional Linear issue when requested. Raw customer content,
secrets, and restricted profile records remain protected by the same privacy
rules.
1---2name: hackerai-user-research3description: Run privacy-safe HackerAI customer research from an authorized PM question and a PostHog cohort. Use for requests to understand user types, recurring jobs, workflows, friction, value drivers, reasons to pay, or customer avatars from actual HackerAI messages, including top-spender research. Also use when a PM asks how to run, repeat, or interpret the `pm-user-research` Trigger task. Do not use for support investigations, decisions about one person's eligibility or risk, or exporting raw customer content.4---5
6# HackerAI User Research
7
8Turn a research question and 1-20 internal user IDs into restricted per-user
9profiles and an aggregated cohort report. The deployed task samples messages,
10redacts sensitive data, and uses Grok 4.6 with low reasoning.
11
12Read [references/privacy-policy.md](references/privacy-policy.md) and
13[references/pm-runbook.md](references/pm-runbook.md) before running the task.
14
15## Workflow
16
171. Extract the research question, cohort rule, exclusions, requested output, and
18 privacy constraints from the authorized PM's request. Possession of the
19 scoped PM gateway key establishes access to this workflow; do not ask for a
20 separate per-run approval or inspect a Linear issue's state or comments for
21 authorization. A Linear issue may be supplied only as optional tracking
22 metadata.
232. Select the cohort entirely in PostHog. For spend-ranked research, use the
24 available Stripe-synced revenue properties in PostHog without opening Stripe
25 or requiring Stripe access. If PostHog cannot prove an exact accounting
26 adjustment or payer mapping, use the best available PostHog cohort and state
27 that limitation in the aggregate report instead of blocking the run. Never
28 use Google Drive.
29 Record the production PostHog project, cohort selection timestamp, a SHA-256
30 fingerprint of the selection query, and any known selection limitations. Do
31 not place the raw query in the gateway payload or report.
323. Resolve each cohort member to the internal user ID used by Convex. Exclude
33 internal/test/fraud accounts and deduplicate payer or organization
34 relationships before triggering analysis. For authenticated HackerAI users,
35 select PostHog `distinct_id` as the internal Convex/WorkOS user ID; do not
36 require a duplicate person property or infer identity from email. Stop unless
37 1-20 unique internal user IDs remain after filtering.
38 For comparisons, preserve membership in `comparisonGroups`; use 2-4 groups
39 with at least three users each, and assign every `userIds` entry exactly
40 once. Group labels may describe the selected model, rollout, or funnel
41 treatment, but must not identify a person or organization.
42 For event-based questions, also select the PostHog event timestamp for each
43 user. Use it as that user's evidence anchor; do not substitute one shared
44 timestamp for the cohort.
454. Create a mode-600 temporary JSON request outside the repository using the
46 gateway payload below. Run
47 `node .agents/skills/hackerai-user-research/scripts/run-research.mjs --payload <path>`.
48 The runner requires `HACKERAI_PM_USER_RESEARCH_KEY` in the PM's Codex
49 environment and always calls the production HackerAI gateway. Never print
50 the key, put it in the request, or use Trigger
51 dashboard access. Remove the temporary request after the command reads it.
525. Wait for the runner to return a completed result. Keep the returned
53 `analysisId`; it is the audit and lookup key for the restricted Convex
54 records. Do not substitute direct Trigger access if the gateway fails.
556. Present the returned internal Convex/WorkOS user IDs together
56 with the aggregate answer, evidence coverage, supported user types, avatars,
57 primary/secondary target, confidence, unknowns, and experiments. User IDs are
58 ordinary cohort-selection output and must not be hidden or replaced with
59 pseudonyms. Detailed profiles remain in restricted Convex records and are not
60 returned through Trigger.
617. Update an optional Linear issue only when asked. Copy aggregate findings,
62 cohort IDs, coverage, confidence, unknowns, and experiments. Never copy raw
63 evidence, customer message content, secrets, or restricted profile records.
648. When the PM asks to continue the research in Slack, return one self-contained,
65 paste-ready request that begins with `@codex Use $hackerai-user-research.`
66 Include the authorized research question, cohort rule, every internal user ID,
67 and each user's event anchor for event-based research. Include known event
68 labels, such as cancellation reasons, when they help interpret the cohort.
69 State the requested evidence window, aggregate output, and privacy constraints.
70 Do not refer to IDs or context "above," and do not expect Slack Codex to infer
71 or reselect the cohort. Tell Slack Codex to run the bounded gateway workflow;
72 a Slack handoff is not permission to browse customer messages manually.
73
74## Gateway payload
75
76Use the current task schema as the authority. A typical run is:
77
78```json
79{
80 "question": "What kinds of users are our highest-spending customers, what recurring work do they use HackerAI for, and why do they pay?",
81 "cohortLabel": "PostHog top-spender research cohort",
82 "userIds": ["internal-user-id-1", "internal-user-id-2", "internal-user-id-3"],
83 "cohortSource": "posthog",
84 "posthogProjectId": 144137,
85 "cohortSelectedAt": 1788000000000,
86 "selectionQueryFingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
87 "selectionLimitations": ["Historical revenue coverage is incomplete"],
88 "maxChatsPerUser": 12
89}
90```
91
92For churn or another event-based question, add:
93
94```json
95{
96 "samplingMode": "pre_event",
97 "evidenceWindowDays": 60,
98 "evidenceAnchors": [
99 { "userId": "internal-user-id-1", "anchorAt": 1787702400000 },
100 { "userId": "internal-user-id-2", "anchorAt": 1787788800000 },
101 { "userId": "internal-user-id-3", "anchorAt": 1787875200000 }
102 ]
103}
104```
105
106For a comparison, also add group membership selected in PostHog:
107
108```json
109{
110 "userIds": [
111 "internal-user-id-1",
112 "internal-user-id-2",
113 "internal-user-id-3",
114 "internal-user-id-4",
115 "internal-user-id-5",
116 "internal-user-id-6"
117 ],
118 "comparisonGroups": [
119 {
120 "label": "Model rollout A",
121 "userIds": [
122 "internal-user-id-1",
123 "internal-user-id-2",
124 "internal-user-id-3"
125 ]
126 },
127 {
128 "label": "Model rollout B",
129 "userIds": [
130 "internal-user-id-4",
131 "internal-user-id-5",
132 "internal-user-id-6"
133 ]
134 }
135 ]
136}
137```
138
139The gateway accepts `cohortSelectedAt` and `anchorAt` as epoch milliseconds or
140ISO timestamps. It also accepts the PostHog handoff name
141`selectionQuerySha256` and stores it canonically as
142`selectionQueryFingerprint`. The Trigger task and Convex audit always receive
143canonical millisecond timestamps and the canonical fingerprint field.
144When evidence anchors or an evidence-window length are present and
145`samplingMode` is omitted, the gateway canonicalizes the request to
146`pre_event` sampling.
147
148`evidenceAnchors` must contain exactly one PostHog event timestamp for every
149cohort user. Omit sampling fields for ordinary representative-history research.
150
151`linearIssueId` may be added as an optional tracking reference, for example
152`"linearIssueId": "HAC-65"`. Do not read the issue or its comments to look for
153approval; its presence, state, and prior cohort notes never authorize or block a
154run.
155
156Do not place email addresses, billing customer IDs, or message content in the
157payload. `userIds` must be the internal Convex/WorkOS user IDs.
158
159## Quality checks
160
161- Treat a profile as directional when fewer than three chats were available or
162 confidence is low.
163- Verify `usersAnalyzed`, `chatsReviewed`, and `messagesReviewed` before using a
164 conclusion.
165- A single-user run is valid. Present a sanitized summary of that user's
166 observed product behavior, state that the sample is one user, and treat its
167 avatar as provisional and low confidence. Do not claim cross-user patterns or
168 population-level conclusions. For larger cohorts, prefer patterns supported
169 across multiple chats and users.
170- Keep observed product behavior separate from acquisition or messaging
171 hypotheses.
172- Treat behavioral explanations of churn or conversion as low-confidence
173 causal evidence even when pre-event sampling is used. Compare them with
174 explicit survey reasons or a controlled experiment before making causal
175 claims.
176- Comparison synthesis fails closed if fewer than three readable profiles
177 remain in any group. Grok 4.6 receives only pseudonyms and sanitized group
178 labels, never the internal ID-to-group mapping.
179- Say `unknown` when the evidence does not establish context. Never infer a
180 company or occupation from an email address.
181- A failed or partial run is not permission to inspect messages manually. Fix
182 cohort mapping or deployment/configuration and rerun the bounded task.
183
184## Result boundary
185
186The gateway returns internal cohort user IDs and aggregate research, and cannot
187read other Trigger tasks or runs. Detailed profiles remain restricted and
188deletion-aware in Convex. Display cohort IDs as normal research output. They may
189also be copied to an optional Linear issue when requested. Raw customer content,
190secrets, and restricted profile records remain protected by the same privacy
191rules.