Working with Signals scouts

SkillMonitoring & ops

Lets your agent set up and manage PostHog Scouts, automated watchers that monitor your project and file reports.

Available today. Use it from your connected AI after setup.

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

About this skill

Work 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 fee

What this skill tells your AI

The instructions your AI receives, as published by posthog/posthog in products/signals/skills/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:

SkillCovers
authoring-scoutsWriting, editing, and tuning scouts: skill bodies, config, the notes write side, the test loop
exploring-scoutsRead-only observability: the fleet roster, run history, scratchpad memory, health assessment
inbox-explorationThe 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), and status / pause_reason. A paused or dry-run scout explains most "scouts aren't doing anything" complaints before any deeper digging. Also check summary.emit_eligibility on posthog:scout-project-profile-get (pass summary_only=true when you only need the gate). When can_emit is false, scout writes cannot reach the inbox. Show its remediation before promising coverage. blocking_reason identifies the gate: ai_processing_not_approved or source_disabled applies to every scout on the team. scout_emit_disabled applies 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; pass run_id to 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 the signals_scout source config via posthog:inbox-source-configs-list and 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:

  1. Delegate — decide what the fleet should watch, and get the right scout watching it.
  2. Receive — reports land in the inbox, routed to a suggested reviewer when the scout could name one.
  3. Act — verify the finding, fix it (or decide not to), and record the outcome on the report.
  4. Feed back — the outcome and your written reasons flow back to the scout, along with any notes you leave.
  5. 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:

SituationDo this
A canonical scout already covers the surfaceNothing 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 focusLeave 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 structuralAdapt 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 computeAuthor 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, onceDon'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 quick posthog:execute-sql count 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: resolved when 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_note is forwarded to the scout that filed the report, and every future run reads it as prior context (a wrong_repo dismissal 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 holds task: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-list with an origin of report_discussion, report_feedback, or report_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 whose reviewer: 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 a user_uuid reviewer 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 the signal_scout:write / llm_skill:write key 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-list shows each check's status and last_outcome. A check still open means a verdict is coming, so there is no need to re-measure by hand. Reading the rows is exploring-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.
  • 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 by user_uuid (any org member, no GitHub account needed) or by github_login (matched against the member's linked GitHub identity), and is_suggested_reviewer flips 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-create with no skill_name, "route billing-adjacent reports to Dana"), or steer the scout (note or skill edit) toward the right owner for the area. A pipeline:report-research note steers only the reports the pipeline builds from clustered signals; a scout that authors reports directly sets suggested_reviewers itself 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:

  1. Read the run (posthog:scout-runs-retrieve) for started_at, completed_at, metadata.write_scopes (present only when the run actually held a grant), and the close-out summary. The run prompt asks a granted scout to name every object it changed.
  2. 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.
  3. 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.
  4. 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 scopeActivity scopes value
dashboard:writeDashboard
insight:writeInsight
annotation:writeAnnotation
alert:writeAlertConfiguration
warehouse_view:writeDataWarehouseSavedQuery, DataQualityCheck
warehouse_table:writeDataQualityCheck
llm_skill:writePersonalAPIKey

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: false never holds the granted scopes, so it writes no rows under the scopes in the table above. It keeps notebook:write, the floor write every scout holds, so a dry run can still create, edit, or delete a notebook. Keep Notebook in 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
40k
Forks
3k
Last commit
Sep 2026
Advanced
Item type
skill
Key
working-with-scouts
Source
github.com/posthog/posthog