Working with Signals scouts
SkillMonitoring & opsWork with PostHog Signals scouts: scheduled agents that monitor a project and write reports into the Signals inbox. Use to assign monitoring work, schedule quality scoring, find which scout covers a surface, act on reports, reduce noise, investigate missing findings, or improve the fleet through feedback and calibration. Also covers follow-up checks that verify whether a reported problem stays fixed. Use `authoring-scouts` for edits, `exploring-scouts` for run observability, and `inbox-exploration` for report triage. Trigger on "work with my scouts", "get more out of scouts", "have a scout watch X", "tell me if Y spikes", "score X on a schedule", "measure quality of Y", "what do I do with this scout report", "calibrate/review my scout fleet".
Available today. Use it from your connected AI after setup.
No other account needed.
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 Working with Signals scouts skill
What this skill tells your AI
The instructions your AI receives, as published by posthog/skills in skills/omnibus/working-with-scouts/SKILL.md and read by ahel’s review.
A scout is a scheduled agent that wakes on its own interval, looks at one PostHog project, and either writes a finding into the Signals inbox as a report or closes out empty. Think of the fleet as a team of junior analysts you've hired to watch things for you: they work unattended, they hold a high evidence bar, and — critically — they get better the more you work with them. Every dismissal reason you write, every note you leave, every report you act on feeds back into what they do next.
This skill is the operating manual for that working relationship: how to delegate a watching job, how to act on what comes back, and how to steer the fleet so it converges on what your team actually cares about. Three sibling skills carry the mechanics — reach for them when a workflow below hands off:
| Skill | Covers |
|---|---|
authoring-scouts | Writing, editing, and tuning scouts: skill bodies, config, the notes write side, the test loop |
exploring-scouts | Read-only observability: the fleet roster, run history, scratchpad memory, health assessment |
inbox-exploration | The inbox itself: triaging, drilling into, acting on, and resolving / dismissing / snoozing reports |
First: is the fleet running?
Don't delegate to a fleet that isn't there.
Two reads answer it: posthog:scout-metadata-get says whether the project is enrolled to run scouts at all, and posthog:scout-config-list is the roster — one row per scout with its schedule, enabled, emit posture, and description.
Check enrollment first, whatever the roster shows — config rows outlive enrollment, so a drained project can carry a roster of enabled scouts that never run (stale last_run_at across the board is the tell).
(Scout tools were recently renamed from signals-scout-* to scout-*; if a scout-* name comes back unknown, try the legacy signals-scout-* name.)
One access rule covers everything here: scout rows live on the project's canonical parent, so every scout read and write — this roster read included, plus the notes and config steering below — returns 403 for a credential scoped only to a child environment; work from the parent project (or a credential that covers it).
- Not enrolled — point the user at the Signals scout settings / PostHog Desktop onboarding rather than inventing activity.
- Enrolled, empty roster — likely newly enrolled and awaiting the first coordinator tick (configs auto-register then); say so instead of re-sending the user through onboarding.
- Enrolled, rows exist — note each scout's
enabled,emit(false= dry-run: it runs but writes nothing), andstatus/pause_reason. A paused or dry-run scout explains most "scouts aren't doing anything" complaints before any deeper digging. Also checksummary.emit_eligibilityonposthog:scout-project-profile-get(passsummary_only=truewhen you only need the gate). Whencan_emitis false, scout writes cannot reach the inbox. Show itsremediationbefore promising coverage.blocking_reasonidentifies the gate:ai_processing_not_approvedorsource_disabledapplies to every scout on the team.scout_emit_disabledapplies to one scout's dry-run setting. That scout continues its investigation without emitting findings or reports. Outside a run, the tool returns the team-wide answer; passrun_idto check one scout's write eligibility. (For read callers the profile is a cached snapshot built by scout runs, so a 404 means no fresh profile exists — not ineligibility; fall back to checking thesignals_scoutsource config viaposthog:inbox-source-configs-listand treat eligibility as unknown rather than blocking on the profile.)
The description on each row says what that scout watches — scan it to answer "which scout covers X?" without loading any skill bodies.
(A row with an empty description is usually an orphan whose skill was since deleted — it can't run, so don't count it as coverage.)
PostHog ships specialists for most product surfaces (error tracking, logs, web analytics, AI observability, experiments, feature flags, session replay, surveys, revenue, and more) plus a cross-product generalist, and teams add custom scouts beyond that (any skill name works; only the signals-scout- prefix gets a config auto-registered, so a scout named anything else comes in through posthog:scout-create).
Each row also carries scout_origin (canonical or custom), which tells a PostHog-seeded scout from a team-authored one. It cannot tell you whether a canonical scout has been edited in place and so diverged from upstream: that row still reads canonical. When divergence matters, compare the row's body with the canonical source in the PostHog repository (products/signals/skills/<skill>/SKILL.md); posthog:skill-get returns the team's live row, which is the edited copy.
The working loop
Everything in this skill is one loop, run continuously:
- Delegate — decide what the fleet should watch, and get the right scout watching it.
- Receive — reports land in the inbox, routed to a suggested reviewer when the scout could name one.
- Act — verify the finding, fix it (or decide not to), and record the outcome on the report.
- Feed back — the outcome and your written reasons flow back to the scout, along with any notes you leave.
- Calibrate — periodically review fleet health and promote recurring steers into permanent policy.
The single most important habit: never let a report just sit.
Acting on reports — even dismissing them with a reason — is what trains the fleet, and a scout whose inbox reports nobody ever touches is automatically warned and then paused (pause_reason=ignored; Slack-delivered scouts are exempt, since consumption there can't be measured).
An untended inbox doesn't just decay; it switches the fleet off.
Delegating a job
When you want something watched, pick the cheapest path that gets it watched — most jobs don't need a new scout:
| Situation | Do this |
|---|---|
| A canonical scout already covers the surface | Nothing to build: confirm it's enabled, and leave it a note if you want its attention pointed somewhere specific. |
| The surface is covered but you want a temporary or specific focus | Leave a note (optionally with expires_at): "watch the EU signup funnel this week", "we shipped a new checkout Tuesday, shifts after that are expected". |
| A covered scout keeps missing (or over-reporting) something structural | Adapt it: a disqualifier, threshold, or scope edit via authoring-scouts. Prefer a new differently-named scout for purely additive behavior, since editing a canonical scout's row marks it diverged and stops upstream improvements. |
| No scout covers it (a custom event, a niche funnel, an external system) | Author a custom scout via authoring-scouts (posthog:scout-create). In the inbox's scouts tab, the "Suggested for this project" strip proposes scouts from the project's own data, and "Suggest a scout" opens a chat that drafts one. A custom suggestion lands on the same create call; a canonical suggestion is an existing PostHog scout that is switched off, so accepting it enables that config rather than creating a new scout. |
| You want a recurring metric, not reports: a subjective quality/classification score no query can compute | Author a measurement scout on the structured-output channel: it judges a sample every run and records schema-validated $scout_structured_output events you chart in insights (and a workflow can act on), filing a report only on a material shift. See the recurring measurement / LLM-judge pattern in authoring-scouts, and signals-scout-mcp-tool-calls for a shipped scout that already records one (its references/metrics-dashboard.md is the chart recipe). |
| You want an answer now, once | Don't use a scout at all: just query the data directly. Scouts are for standing watches, not one-off questions. |
references/delegation-recipes.md has worked recipes for the common asks — watching a freshly shipped event, a time-boxed funnel watch, a daily digest, an external status page, quieting a noisy fleet, and more.
Two delegation habits that pay off:
- Ground the job in real data first. Before pointing a scout at an event or surface, confirm it exists (
posthog:scout-project-profile-get,posthog:read-data-schema) and actually has volume (a quickposthog:execute-sqlcount over a recent window — the profile and schema tools don't return counts for a new or rare event) — a watch on data the project doesn't capture is dead on arrival. - State the job in terms of what's worth interrupting a human for. Scouts hold a report bar ("would you own this finding end-to-end?"); a steer like "tell me about anything interesting" produces noise, while "tell me when checkout conversion drops while entrants hold steady" produces signal.
Acting on what comes back
Report triage mechanics live in inbox-exploration; what matters here is how acting doubles as steering.
- Verify before implementing. A scout report is an LLM diagnosis, not ground truth — confirm the cited entities and behavior against the live data or code before fixing. A report that doesn't hold up is a dismissal candidate, and dismissing it well is valuable work (see below).
- Close every report with the honest state:
resolvedwhen the work landed (PR-backed fixes resolve themselves on merge — don't resolve at PR-open time),suppressed(dismissed) when it's not real or not worth fixing,potential(snoozed) when it's real but deferred. - The dismissal note is a steering message. On a dismiss, snooze, or restore, the
dismissal_noteis forwarded to the scout that filed the report, and every future run reads it as prior context (awrong_repodismissal forwards even without a note, since the repositories it names are the feedback). Write it for that reader: name the evidence that settles it ("staging traffic — hosts match*.dev.example.com, ignore this pattern"), not just the verdict. A well-written dismissal is the cheapest scout edit you will ever make; a bare dismissal teaches nothing and the report comes back. Two caveats: forwarding is best-effort and requires the dismisser to hold scout-steering (skill-editor) access on the canonical project, and a dismissal made on a child environment's report never forwards at all; in both cases the note still lands on the report but never reaches the scout, so for a steer that must stick, confirm it arrived (posthog:scout-notes-list, with a credential that also holdstask:read, since derived notes are hidden without it) or have someone authorized leave a note directly. The forwarded note also expires after ~30 days — it becomes durable only if the scout folds it into scratchpad memory, so a steer that must outlive that belongs up the ladder as a skill edit. - Three more inbox actions steer the same way. A question typed into a report's "Discuss" box, a note left with a thumbs rating on a report, and adding or removing a suggested reviewer each also become a scout note (visible in
posthog:scout-notes-listwith anoriginofreport_discussion,report_feedback, orreport_reviewer_correction). A reviewer correction is the strongest routing evidence the fleet gets: it reaches the scouts that filed or edited the report and the scouts whosereviewer:memory names a removed login (capped at twenty targets per correction, so on a very busy report a few can miss it), so fixing a misrouted report in place teaches the fleet who owns the surface. The forwarded note carries GitHub logins and is written only when the corrector holds skill-editor access on the canonical project, so a correction made without that access, or one that only adds or removes auser_uuidreviewer with no linked GitHub account, stays on the report and is not forwarded. The discussion and rating paths demand the full notes-write authorization (skill-editor access plus thesignal_scout:write/llm_skill:writekey scopes), so a note typed by someone without it is not forwarded: a discussion question still lives on the report's thread, but a rating note survives only in the analytics event, so if it must reach the scout, have someone authorized leave it as a scout note. - Resolving a report is not the end of it — a check measures whether the fix held. A check is a follow-up measurement a scout (or the report pipeline) attaches to a report: one expectation, and a time to test it. Resolving the report starts its clock, and after a soak window — 24 hours by default, longer for a fix that reaches users slowly, like a mobile release or a cached client bundle — it re-measures, and the verdict lands on the report. So "did that actually work?" becomes a stored fact instead of something somebody has to remember to go and re-derive.
Three things this changes in how you work:
- Read a resolved report's checks before you call it done.
posthog:inbox-report-checks-listshows each check'sstatusandlast_outcome. A check still open means a verdict is coming, so there is no need to re-measure by hand. Reading the rows isexploring-scouts' job. - A failed check means the fix did not hold, and the relapse is filed as a fresh report linked back to the resolved one rather than reopening it — a resolved report has left the inbox, so nothing there would be read. Treat that new report as the live item.
- Resolve honestly, and resolve on the merge. A check dated from a premature resolve measures the window before the fix shipped and can fail a fix that worked.
Checks are written only by scout runs and by the pipeline; there is no create surface for a person, so if a resolved report should be re-measured and carries no check, the lever is the scout — a note asking it to attach one, or a skill edit via
authoring-scouts.
- Read a resolved report's checks before you call it done.
- Reports route to people. A scout that can name a plausible owner sets
suggested_reviewers, and the inbox floats those reports to the top of that person's view. A reviewer is a PostHog user: a scout routes byuser_uuid(any org member, no GitHub account needed) or bygithub_login(matched against the member's linked GitHub identity), andis_suggested_reviewerflips for the viewer on either match. If reports for a surface keep landing unrouted or misrouted, that's fixable: correct the reviewers on the report itself (the correction is forwarded as above), leave a fleet-wide routing note (posthog:scout-notes-createwith noskill_name, "route billing-adjacent reports to Dana"), or steer the scout (note or skill edit) toward the right owner for the area. Apipeline:report-researchnote steers only the reports the pipeline builds from clustered signals; a scout that authors reports directly setssuggested_reviewersitself and never reads that audience.
Auditing what a scout changed
A scout that holds write_scopes (granted via authoring-scouts) changes real objects in the project, and each change lands in the project's activity log like any other edit.
The row names the scout's acting user, the person whose identity the run mints its token as, and carries the server-derived scout:<skill_name> client tag.
The tag identifies the scout but not the run or the scopes it held.
Auditing one run still requires its time window and a cross-check against its close-out.
To reconstruct one run's changes:
- Read the run (
posthog:scout-runs-retrieve) forstarted_at,completed_at,metadata.write_scopes(present only when the run actually held a grant), and the close-outsummary. The run prompt asks a granted scout to name every object it changed. - Check history availability. Follow the reader guidance supplied by MCP when activity history is available. If a reader is unavailable or access is denied, record that limitation and stop using that reader for the run; do not retry its discovery or probe endpoints to bypass the restriction. Other advertised, authorized readers, including per-object history, remain usable. Skip only checks that have no available reader.
- Confirm attribution. The run window can include the acting user's other writes. History that cannot distinguish those writes does not establish which changes the scout made.
- Cross-check against the close-out. A row the summary does not mention, or a change the summary claims with no row behind it, is the thing to look at.
Which scopes value each granted scope writes under:
| Granted write scope | Activity scopes value |
|---|---|
dashboard:write | Dashboard |
insight:write | Insight |
annotation:write | Annotation |
alert:write | AlertConfiguration |
warehouse_view:write | DataWarehouseSavedQuery, DataQualityCheck |
warehouse_table:write | DataQualityCheck |
llm_skill:write | PersonalAPIKey |
Pass Notebook as well, whatever the grant says: notebooks are the floor write every scout holds, so any scout can leave rows under that scope.
Some grants also reach objects they are not named for: both warehouse grants reach the data quality checks on their subject, and llm_skill:write reaches the skill-store install command, which mints or rotates the acting user's marketplace credential.
The scope-named objects themselves still log nothing: a skill body edit (including another scout's) and a warehouse table write leave no row, so read the skill's version history instead.
Four caveats change what the answer means:
- The window is not an attribution. Without a scout tag, the window can include the acting user's other writes. Even with a tag, overlapping runs of the same scout can share it. Compare actors, items, and timestamps with the close-out.
- History access is optional. Permissions, the Cloud Audit Logs entitlement, and the plan's retention window can make history unavailable. Missing history does not establish that a run made no changes. Defer conclusions that depend on it and continue independent checks; a confirmed access restriction is not a missing-tool defect.
- A dry run drops the grant, not the floor. A scout on
emit: falsenever holds the granted scopes, so it writes no rows under the scopes in the table above. It keepsnotebook:write, the floor write every scout holds, so a dry run can still create, edit, or delete a notebook. KeepNotebookin the filter for a dry-run window. - A refused write is not logged, because it never happened. The grant is an upper bound and the acting user's own permissions still apply, so a close-out that reports a refused write will have no matching row. That is the expected pairing, not a discrepancy.
The steering ladder
When you want a scout to behave differently, climb this ladder from cheapest to most permanent — and stop at the lowest rung that does the job:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 71
- Forks
- 7
- Last commit
- Sep 2026
ahel recommends instead
Advanced
- Item type
- skill
- Key
working-with-scouts-2- Source
- github.com/posthog/skills