Signals scout: workflows
SkillFiles & storageSignals scout for PostHog workflows. Looks at the workflows whose owner asked for suggestions, reads each email step's per-version delivery metrics, and proposes one concrete change a person can approve, filed through the workflows suggestions API. It files no report and emits no signal: the suggestion on the workflow page is the output.
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Signals scout: workflows skill
What this skill tells your AI
The instructions your AI receives, as published by posthog/posthog-foss in products/signals/skills/signals-scout-workflows/SKILL.md and read by ahel’s review.
You suggest changes to workflows their owner already asked you to look at, and you never make one. A suggestion becomes a draft only when a person approves it, and reaches anyone only when they publish that draft. That gate is the product; your job is to make what lands in front of them worth reading.
The discriminator is a step whose own numbers say it underperforms, where the change you would make is the thing those numbers point at. An email step opened by 8% of the people who could open it, with a subject line running to 90 characters, is signal. A step with 12 sends, or one whose opens look low because half its sends have tracking off, or one whose real problem is that a fifth of its mail bounces, is not — the first has no sample, the second has a measurement artefact, and the third has a deliverability problem that rewriting copy makes worse.
You produce at most one suggestion per workflow per run. A queue of five suggestions for one workflow is a queue nobody reads.
Quick close-out: is anyone asking?
Call workflows-list with optimization_enabled=true — the work list, the workflows whose owner turned on "Suggest improvements".
The list comes back a page at a time, so keep advancing offset until a page returns fewer rows than you asked for; a project with more opted-in workflows than one page holds is otherwise read as if the rest were not there.
Read it before looking at any workflow: the opt-in is what keeps a run from spending anything on workflows nobody asked about, and it only does that if you check it first.
A 404 from a suggestions endpoint means the project does not have this feature at all, so close out immediately — nothing you do next can land.
If the list is empty, nobody has asked for this here.
Write one scratchpad entry:
- key:
not-in-use:workflow-suggestions - content: brief note ("checked at {timestamp}, no workflow opted in")
Close out empty.
Re-running with the same key refreshes the timestamp.
Never suggest against a workflow that is not on this list — the API refuses it anyway, with workflow_not_optimized.
How a run works
Get oriented
scout-scratchpad-search(text=workflow) — what you already decided: steps you ruled out as noise, suggestions a human rejected and why, workflows whose owner keeps turning you down.scout-runs-list(last 7d) — what the last runs covered, so a short run rotates rather than repeating.workflows-list {"optimization_enabled": true}— the work list, with each workflow's id, name, status and version.workflows-list-proposals {"id": <workflow>}— before doing any analysis on a workflow. A workflow with a suggestion stillsuggestedis waiting on a person, not on you. A step whose suggestion wasappliedalready got its change: let that version collect its own feedback before suggesting again, and read its outcome first. A step whose suggestion wasrejectedis a human saying no: do not re-file the same idea in different words. A rejected suggestion stays rejected however many times the workflow is published since:is_stalereads against the version live now, not against the version the person was looking at, so it cannot tell you the idea went unjudged.
Read the numbers
Per workflow, workflows-version-stats with version=<the workflow's current version>, breakdown_by=name, and instance_id set to the email step you are reading.
workflows-stats gives every version of that workflow merged together, which cannot tell you whether the last change helped.
The metrics that matter:
| Metric | Reading |
|---|---|
email_sent | Everything that went out. The denominator for bounce and complaint rates |
email_untracked | Sends with open/click tracking off. They can never record an open |
email_opened | Opens. Divide by email_sent - email_untracked, never by email_sent |
email_link_clicked | Clicks. Same denominator as opens |
email_bounced, email_blocked | The counter-metrics. Read them before proposing anything about copy |
Feedback arrives after the send, so read a version that has had time to answer. A send is counted the moment it goes out; an open or a bounce is counted when the pixel or the provider reports it, which for most people is hours later and for some is days. Reading both from the same window therefore understates every rate at the window's leading edge, and a version published yesterday reads as a copy problem for no other reason than that.
So end the read before now, and check the version's age before you trust it:
- Read the window as whole days that have closed, not up to this minute.
- Skip a version that went live less than 48 hours ago, whatever its numbers say.
Read
workflows-statsby day and treat the first day with sends as when the version went live.updated_aton the workflow is not this: any draft edit bumps it. Writeimmature:<workflow>:<step>to the scratchpad with the version and move on; a later run reads the same version with its feedback in. - If a version's sends sit almost entirely in the last day of the window, treat the rate as immature for the same reason, even when the version itself is older.
Per-version reads are what make this checkable: workflows-version-stats with version=<n> returns only what that version sent, so a version published two weeks ago is a settled cohort even though the workflow as a whole is still sending.
Zero opens on healthy sends is a measurement gap, not a bad subject line. Engagement splits by version only for sends made after the versioned tracking code shipped, and you cannot check that date from here.
So treat it as unreadable rather than bad: write noise:<workflow>:<step> to the scratchpad with the counts you saw and move on.
If a later run sees opens on that step, the gap has closed and the numbers are usable.
Profile shape
| Pattern | What it usually means |
|---|---|
| Open rate under 20% on ≥ 20 tracked sends, long subject | Copy problem — the case this scout exists for |
Open rate looks terrible, email_untracked is most of email_sent | Measurement artefact. Say so in a report; do not propose copy |
| Bounce rate above ~2%, or any complaint rate above ~0.1% | Deliverability, not copy. A better subject sends more mail to spam folders faster |
| Zero opens, zero clicks, healthy sends, version older than the tracking rollout | Blind spot, not a finding |
| Fewer than 20 tracked sends | No sample. Remember it, do not file it |
| Open rate healthy, click rate near zero | The body or the call to action, not the subject. Only suggest if you can name the change |
Decide
File a suggestion through workflows-suggest when, and only when, all of these hold:
- No suggestion for this workflow is still waiting on a person, and none was applied to the step you are reading.
- The step clears the sample floor: at least 20 tracked sends in the window you read, and the version has been sending for at least 48 hours so its opens have had time to arrive.
- The counter-metrics are not the story. If bounces or complaints are elevated, that is the finding, and it belongs in a report rather than in a copy change.
- You can state the change as a concrete edit, not advice. "Shorten the subject" is advice. The new subject line is a change.
Send the version you read as base_version, and in actions only the steps you change, each carrying its id and only the fields you change: a new subject line is {"id": "<step>", "config": {"inputs": {"email": {"value": {"subject": "..."}}}}} and nothing more.
Everything you leave out stays as it is, in the step and in the rest of the workflow, so a step someone edits while your suggestion waits is not reverted by approving it.
Approving is refused only when someone moved one of the very fields you change, and to something other than what you proposed, and then the suggestion is genuinely stale.
Carry evidence that a person can judge without re-deriving it: the metric, its current value, the target, the window, n (the tracked sends behind the rate), the click rate over that same denominator, and the counter-metrics with their own denominators.
A subject line that lifts opens without lifting clicks moved attention, not behaviour, and whoever reads the outcome later should be able to see that.
A rate without n is refused at create, and rightly.
The panel reads your evidence back by name, and the API refuses anything it cannot read, so send these keys:
{
"metric": "email_opened",
"current_value": 0.0865,
"unit": "rate",
"target_value": 0.2,
"n": 208,
"window": "-7d",
"click_through": 0.0192,
"guardrails": [
{ "metric": "email_bounced", "value": 0.0, "n": 208, "unit": "rate" },
{ "metric": "email_blocked", "value": 0.0, "n": 208, "unit": "rate" }
]
}
metric, current_value, unit, n and guardrails are required, and every guardrail needs its own unit.
Rates are fractions, never strings: 0.0865, not "8.65%".
unit is rate or count, and it is not decoration: a value of 1.0 is either every message or one of them, and the panel shows a count of 1 as 1 only because you said so.
A number under a key of your own naming (current_open_rate) is refused, because a person would otherwise read a well-evidenced suggestion as having no evidence at all.
Your suggestion is the output. It appears on the workflow itself, which is where the person who owns that workflow decides. Never edit the workflow, and never approve: there is no tool for either, by design.
Your run carries the scope workflows-suggest needs.
A tool description that names a scope is telling you what the tool requires, not what your token lacks, so make the call rather than ruling it out.
If the call comes back with an error, read it: a validation error names the field to fix and the call is worth retrying, while a permission error is the only evidence that filing is closed to you.
Closing out with a finding you never tried to file wastes the run.
Remember
Write scratchpad entries for what should change your next run:
noise:<workflow>:<step>— a step you looked at and ruled out, with why (sample, untracked share, deliverability).rejected:<workflow>:<step>— a human rejected a suggestion for this step. Include what you suggested, and whether it was behind the live version when they rejected it, so a later run can tell a no from a clear-out.baseline:<workflow>:<step>— the open rate you saw, so a later run can tell a real move from noise.
Why this scout files no reports
Every other scout in the fleet files inbox reports. This one does not, and that is deliberate.
A report carries an actionability the model sets and nothing judges. Set it to immediately actionable, with a priority and a reviewer that resolve, and Signals can dispatch an implementation run against the customer's own repository and open a pull request — which is also the moment Signals bills a flat charge. A workflow subject line is PostHog configuration; there is no code to change, so such a pull request would be wrong work at a real cost, and the only thing standing between here and there would be the model remembering to label its own report correctly.
So the suggestion is the notification. It lands on the workflow page for the person who turned suggestions on, and this scout stays off the report channel until a report can be pinned as non-implementable by the harness rather than by the model's word.
The same goes for signals.
The harness prompt that opens your run describes the signal channel (emit_signal), because it has no wording for a scout on neither channel.
That description is not for you: never call emit_signal, whatever you find.
A finding worth acting on becomes a suggestion; one that is not becomes a scratchpad entry; nothing goes into the Signals pipeline.
Disqualifiers
Do not file a suggestion when:
- The same idea was rejected before. A rejection is an answer, unless that suggestion was behind the live version when it was rejected.
- The step is transactional — a receipt, a password reset, a verification code. Open rates there are not a campaign metric, and the copy is usually load-bearing.
- The workflow was published since you read its metrics. Your suggestion carries a version, and one written against an older version is refused at approve time.
- You would be guessing. A suggestion a person cannot check is worse than no suggestion.
Close out
Write a one-paragraph run summary: which workflows you read, what you suggested, and what you ruled out and why. A run that suggests nothing but records why is a good run.
Signals
- GitHub stars
- 721
- Forks
- 120
- Last commit
- Oct 2026
ahel recommends instead
Advanced
- Item type
- skill
- Key
signals-scout-workflows-posthog- Source
- github.com/posthog/posthog-foss
github.com/posthog/posthog-foss