Generated banner for Exploring Scouts
Generated logo for Exploring Scouts

Exploring Scouts

PostHogOfficialPublished by PostHog

  • Skill
  • Research and analysis
  • Data and AI
  • MIT
  • Low risk
  • Has scripts

From the author

How to explore and make sense of PostHog Signals scouts — the scheduled agents that scan a project and write reports into the Signals inbox. Use when a user wants to understand what scouts they have, how each one is behaving, and whether the fleet is actually working. Covers surveying the fleet and its schedules, reading recent scout runs and drilling into a single run's reasoning, inspecting the durable scratchpad memory the fleet has built up, tracing a run to the reports it wrote or edited, reading the follow-up checks still measuring a report, and assessing a scout's health and performance over time (cadence, success rate, report rate, signal-to-noise). Read-only and exploratory — to write or tune a scout, use `authoring-scouts` instead. Trigger on "what are my scouts doing", "how is my <x> scout performing", "show me recent scout runs", "why did this scout find/report nothing", "what has the fleet learned", "explore scout run <id>", "is my scout working".
— the description by PostHog

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a comprehensive framework for exploring the behavior and health of PostHog Signals scouts. It enables users to survey the fleet, inspect individual run reasoning, analyze durable scratchpad memory, and assess performance metrics like success rates and signal-to-noise ratios. It is a read-only toolset designed to help users understand why a scout might be quiet, failing, or producing specific reports, serving as the observability counterpart to scout authoring.

What it does

  • Lists all active and configured scouts for a project.
  • Retrieves and summarizes recent scout execution runs.
  • Allows deep-dives into specific run reasoning and failure logs.
  • Inspects the durable scratchpad memory used by the fleet.
  • Tracks the reports authored or edited by individual scout runs.
  • Assesses scout health through cadence, success rates, and output analysis.

When to use it

  • When you need to verify if your scout fleet is running as expected.
  • When investigating why a scout is not producing reports.
  • When you want to understand the reasoning behind a specific scout's output.
  • When diagnosing recurring failures or performance issues in a scout.
  • When auditing the configuration and settings of the scout fleet.

Advantages

  • Provides clear visibility into the automated agent fleet.
  • Distinguishes between healthy quiet runs and actual system failures.
  • Offers structured data for performance assessment.
  • Integrates with task logs for detailed troubleshooting.
  • Helps identify configuration issues like dry-run mode or paused status.

Usage examples

“what are my scouts doing”
The agent will return a list of all configured scouts, their current status, and their most recent run activity.
“how is my error-tracking scout performing”
The agent will analyze the recent runs for the specified scout, reporting on success rates, report volume, and any recent errors.
“why did this scout find nothing”
The agent will examine the run summary and scratchpad memory to determine if the scout correctly identified no issues or if a failure occurred.
“explore scout run 12345”
The agent will retrieve the full details of the specified run, including its summary, output reports, and any associated error messages.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/posthog/exploring-scouts/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip exploring-scouts.zip -d .agents/skills

Documentation by PostHog

Exploring Signals scouts

A scout is a scheduled agent that wakes on its own interval, looks at one PostHog project, decides what's genuinely worth surfacing, and either writes it into the Signals inbox as a report or closes out empty (a real, valid outcome). PostHog ships a fleet of canonical scouts — a cross-product generalist (signals-scout-general) plus per-surface specialists (error tracking, logs, AI observability, experiments, feature flags, session replay, web analytics, surveys, and more). A project may also have custom scouts beyond the canonical fleet — any signals-scout-* skill a team authored (e.g. -brand-mentions, -mcp-feedback) shows up here too, so don't assume a fixed roster: scout-config-list is the authoritative roster for a project. (One caveat: a just-authored scout has no config row until the coordinator's next tick auto-registers one — or until someone registers it via the write-side scout-config-create — so a brand-new scout may briefly be missing from the list.)

This skill helps you understand and explore what a project's scouts are doing and how they're performing — entirely through read-only MCP tools. It is the observability counterpart to the authoring-scouts skill (which teaches writing and tuning) and to the inbox-exploration skill (which covers the inbox reports scouts feed into). (The scout tools were recently renamed from signals-scout-* to scout-*; if a scout-* name comes back unknown, the server may still expose it under the legacy signals-scout-* name — search the tool catalog and call whichever name it returns.)

A scout's output is inbox reports, written 1:1. Scouts list emit_report / edit_report in their allowed_tools and author or edit inbox reports directly; a run's output shows up as emitted_report_ids (reports it authored) and edited_report_ids (reports it updated). The run rows also carry emitted_count / emitted_finding_ids — legacy fields from the deprecated signal-emitting channel (weak emit_signal findings a pipeline consolidated). On a report-channel scout they stay 0 / empty even on a productive run; a non-zero tally means the run came from a scout still on the legacy channel (an old custom scout, or a canonical scout not yet ported) — real output for that run, not noise. When unsure of a scout's channel, check its allowed_tools via skill-get. Never read emitted_count: 0 as "did nothing" — check the report columns and the run summary first. A scout whose config carries a structured_output_schema has a third output channel next to reports: schema-validated measurement records, recorded as $scout_structured_output events in the project (only scalar top-level payload keys flatten to output_<key> properties — object and array fields live solely inside the full output property, so a missing output_<key> is not a missing value; subject names the judged entity) rather than as run-row columns. The events are the ground truth — metadata.derived.has_structured_output says the run had at least one batch accepted, which is a fast per-run screen but not delivery confirmation (a rare capture failure after acceptance leaves it true with fewer or no events behind it), so count the events when the number of records matters. See references/scout-data-model.md for the event shape. Of the scouts PostHog ships, signals-scout-mcp-tool-calls is the one that records a series: every run that finds MCP traffic writes a per-category roll-up of MCP tool health and per-tool session-share rows, whether or not it files a report, so a healthy category is distinguishable from an unmeasured one. No records is not by itself a broken channel. A run on a project with no recent $mcp_tool_call events stops early and records nothing, and a dry-run scout (emit off) has nowhere to record to. Check the run summary, the not-in-use:mcp_analytics scratchpad entry, and the config's emit before you call the channel broken. Find them with event = '$scout_structured_output' and properties.skill_name = 'signals-scout-mcp-tool-calls'; the scout's own references/metrics-dashboard.md carries the chart and alert recipes. Each run also carries a metadata map. Top-level: the provenance set harness_prompt_version / report_channel (none, emit, edit, or both) / skill_origin / github_guidance, saying which instructions the run was given; plus routing keys (model / runtime_adapter / reasoning_effort / service_tier) only when a gate or pin overrode the default. Nested under metadata.derived: booleans the harness computes at the end of the run (has_emit_report, has_edit_report, has_self_improvement, has_chart, has_self_validation, has_structured_output, has_not_in_use_closeout). When comparing runs (before/after a prompt change, one model against another), segment on all four provenance values first: runs differing on any of harness_prompt_version, report_channel, skill_origin, or github_guidance were given different instructions and aren't a like-for-like population. Runs predating this field have none of them, so treat missing provenance as unknown and exclude those runs from a comparison rather than pooling them. For "what kind of run was this?" questions — did it author a self-improvement report, did it validate its follow-up queue — read derived rather than parsing the prose summary. It's computed server-side from what the run actually did, so it can't disagree with the run's own output — with one exception: has_structured_output tracks batches the run had accepted, not events delivered, so it alone can be true with fewer or no records behind it (count the events, as above). No derived map at all means unknown, not "all false" — the run predates the field, failed before finishing, or its stamp failed. Most runs from before this shipped have no map, so don't read their absence as a finding.

To find pointless runs, read has_not_in_use_closeout. It is true when the run wrote a not-in-use: scratchpad entry (its quick close-out found the product off) and produced no report, edit, finding, or structured-output record. The harness counts the write against the run whose sandbox token made it, so a sibling scout's close-out on the same team is not credited to this run. A quiet run that looked and found nothing reads false. The pointless-run rate of a scout is the share of its finalized runs (runs that carry a derived map) with this flag true. Use it to rank which scout to gate with a pre-check next, and to confirm that a shipped pre-check moves the rate toward zero. Runs from before this flag shipped have no has_not_in_use_closeout key, so exclude them rather than count them as false.

There are six things you can observe about the fleet, each with its own tool:

What you want to knowToolWhat it tells you
Which scouts run, how often, in what posturescout-config-listOne row per scout: schedule (run_interval_minutes or run_cron_schedule), enabled, status / pause_reason, emit, output_destinations, write_scopes, mcp_gateway_server_ids, last_run_at, consecutive_failure_count, description, scout_origin, owners, tags
What the scouts actually did, run by runscout-runs-list / -retrievePer-run status, timing, end-of-run summary, emitted_report_ids / edited_report_ids, error / failure_reason on a failed run, deep-link; scope with skill_name
What the fleet has learned across runsscout-scratchpad-searchDurable per-team memory (baselines, noise, allowlists)
What the team has told the fleetscout-notes-listSteering notes humans/agents left for scouts (per-scout or fleet-wide, newest first)
Which reports a run wrote or editedthe run row itselfemitted_report_ids / edited_report_ids; resolve each id via inbox-reports-retrieve
What the scouts surfaced to the userinbox-reports-listThe scout-written reports, as the user sees them (scout: "<skill_name>" for one scout; source_product: "signals_scout" for the fleet)

scout-config-list takes a tags parameter (comma-separated) to narrow the roster to the scouts carrying any of the given labels — useful on a large fleet when the question is scoped to one area, e.g. tags=revenue. scout-runs-list takes skill_name (and optionally skill_version) to scope the dump to one scout, which is the normal way to answer any question about a single scout without paging through the fleet.

The orienting tool is scout-project-profile-get — the deterministic snapshot of "what's true about this project" that every scout cold-starts from. When a scout found nothing, this is usually why.

Output handling: expect to offload to a file

Two of these tools — scout-runs-list and especially tasks-runs-session-logs-retrieve — routinely return payloads that overflow an MCP client's token budget and get spilled to a file. This is the normal path, not an error. Plan for it up front rather than discovering it after a failed call:

  • Keep limit small on scout-runs-list (~10–15). Each row carries a long prose summary, and runs come back newest-first across the whole fleet, so even a modest page is large.

  • Session logs are large by nature. A single run's log is hundreds of KB to a few MB. Fetch it with call --json (so the saved file is real JSON, not the pretty text format — jq-able) and read the saved file with jq / a script rather than inline.

  • Don't hand-parse the session log. The bundled scripts/ do the reconstruction for you — see below.

Start here: is the fleet even set up?

Don't assume the project has scouts. The fleet only runs on teams enrolled via the signals-scout feature flag, and a project may have no configs, all-disabled scouts, or scouts stuck in dry-run. Run this first whenever a user asks about their scouts for the first time in a session.

scout-config-list

Read the result against three cases:

The config list is unpaginated — it comes back as { results: [...] } (a bare array), with no count field. Read the result against three cases:

  • Empty (results: []) — no scouts are registered. The project isn't enrolled in the scout fleet (or hasn't ticked yet). Say so plainly; don't go fishing for runs. Point the user at the Signals scout settings / PostHog Desktop onboarding rather than inventing activity.

  • Configs exist but all enabled: false — the fleet is registered but paused. Nothing is running. Tell the user which scouts exist and that they're all off — and say who switched each one off, which status carries: paused_by_user means a person (or a launch seed posture) turned it off, paused_by_system means an automatic pause with its cause in pause_reason (no_output / ignored / repeated_failures). Either kind resumes with enabled: true via scout-config-update. A repeated_failures pause is the failure breaker: the streak of scheduled failures (consecutive_failure_count; manual and workflow-triggered runs don't count) ran one past what the schedule fits in twelve hours, clamped to 5–25 (daily: 5, rolling hourly interval: 13, hourly cron: 15, since cron slots are counted over a window padded for daylight-saving shifts). It is half-open, so the coordinator probes the scout once a day and resumes it on a clean run (unless the project is at its enabled-scout cap, which leaves the row paused after a clean probe); read the newest run's failure_reason to say what kept failing.

  • At least one enabled: true — the fleet is registered and that scout is allowed to run. For each enabled scout note its cadence (run_cron_schedule when set, else run_interval_minutes; the cron wins), emit (false = dry-run, runs but writes nothing to the inbox), and last_run_at. A status of pending_pause means the scout still runs but the system has flagged it to pause soon (cause in pause_reason); any config edit clears the warning. One caveat before reporting "it's live": runs are gated by the signals-scout feature flag, not by enabled. A project that was enrolled and later drained from the flag keeps its enabled: true rows, but the coordinator no longer plans runs for it — so a stale or null last_run_at on an enabled scout usually means the project is no longer enrolled, not that the scout is idle.

    last_run_at is a dispatch stamp, not proof a run executed. The coordinator advances it the moment it enqueues a child workflow for a due scout — before any worker picks the run up. Child dispatch is fire-and-forget, so if workers are saturated or down the children just queue and no run ever materializes, yet last_run_at keeps marching forward each tick. So a recent last_run_at means "dispatched this tick," not "a run is genuinely happening." The authoritative liveness signal is the newest actual run row in scout-runs-list, not the config stamp. Cross-check them: if last_run_at is fresh but no run row has appeared for that scout beyond its expected schedule, check the recorded pre-check outcome before diagnosing queued or stranded runs. Don't report "it's running" off last_run_at alone.

    A scheduled pre-check can skip without a run row. First read last_precheck_at and last_precheck_outcome on the config, following authoring-scouts → references/scheduled-precheck.md → "Diagnose a tick with no run row". A recent recorded skip for the tick can explain quiet; stale or absent outcome data leaves the cause unknown. Keep scout-config-precheck-test for query testing: a check made now cannot establish what happened on an earlier tick, and your access can differ from the scheduled check's acting user. A skip does not establish overall scout health.

A scout that is enabled: true but emit: false is the most common source of "my scout isn't doing anything" confusion: it is running and reasoning every tick, it just isn't allowed to post reports yet. Always surface the emit posture when reporting on a scout.

See references/scout-data-model.md for every field on a config, run, and scratchpad entry, the run status values, and how the pieces link together.

Workflow: survey the fleet

"What scouts do I have / what are they doing?" — lead with scout-config-list, then enrich with the most recent run per scout so the user sees liveness, not just configuration.

  1. scout-config-list — the roster.

  2. scout-runs-list once with a small limit and pick the newest run per skill_name (runs come back newest-first across the whole fleet, so one call usually covers everyone); for a scout that doesn't appear in that page, call again with skill_name set. Report status and how long ago it ran.

Present it as a table the user can scan — scout, cadence, posture, last run, last outcome — and call out anything anomalous (never run, last run errored, stuck in dry-run for a long time).

Workflow: understand one scout end to end

"How does my error-tracking scout work / how is it doing?"

  1. Read its config — find the row in scout-config-list for signals-scout-error-tracking: schedule, posture, last run.

  2. Read its body — posthog:skill-get {"skill_name": "signals-scout-error-tracking"} returns the team's actual instruction set (which may be a canonical default or a diverged, hand-edited row). This is what the agent is told to do every run — its signal-vs-noise discriminator, explore patterns, and disqualifiers. To understand why a scout behaves the way it does, read its body.

  3. Read its recent runs: scout-runs-list with skill_name set to the scout (add text to search its summaries for a topic). The end-of-run summary on each run is the scout's own account of what it looked at and decided; a failed run carries failure_reason instead.

  4. Read what it remembered — scout-scratchpad-search (see below). The memory entries a scout wrote reveal the baselines and noise it has internalized about this project.

  5. Read what it was told — scout-notes-list {"skill_name": "signals-scout-error-tracking"} returns the steering notes humans left for this scout plus the general fleet-wide ones — exactly what its runs read as prior context. Each note carries an origin: human for one left directly, or a derived kind the inbox forwarded automatically: report_dismissal (a dismiss/snooze note), report_discussion (a question typed into a report's Discuss box), report_feedback (a note left with a thumbs rating), report_reviewer_correction (someone added or removed a suggested reviewer). Derived notes expire after ~30 days, and the list hides them from a caller without report read access (inbox:read), since they quote report content, so a credential with only signal_scout:read sees the human notes alone. Notes addressed to pipeline:report-research steer the report pipeline's research stage, not this scout, so they won't appear here. A behavior change that doesn't trace to a skill edit or a scratchpad entry often traces to a note. When asked to steer a scout with a note (rather than observe), hand off to the authoring-scouts skill, which covers the notes channel's write side.

Workflow: read recent runs

scout-runs-list returns the most recent runs across the whole fleet, newest first (capped at 100). Use it to answer "what happened lately?"

  • Scope to one scout with skill_name (and skill_version to isolate the runs of one body version): the primary scoping path whenever the question is about a single scout.

  • Scope to a window with date_from / date_to (ISO-8601; inclusive lower, exclusive upper on created_at). Walk backwards by passing an earlier date_to.

  • Search summaries with text — a case-insensitive substring match on each run's end-of-run summary. This is how the headless scout dedupes, and it's how you find "did any run already look at the checkout error spike?"

  • Filter by output with emitted — emitted=true returns only runs that authored at least one report (or, on legacy runs, emitted a finding), emitted=false only the runs that authored nothing. This is the direct way to answer "which runs actually wrote something?" without parsing prose. One caveat: a run that only edited an existing report doesn't count as emitted=true — check edited_report_ids before calling such a run quiet.

Each summary row carries run_id, skill_name, skill_version, status, started_at, completed_at, emitted_report_ids / edited_report_ids (the reports the run wrote or edited, its output), emitted_count / emitted_finding_ids (the legacy signal-channel tally, 0 / empty on current scouts), task_url (a deep-link into the Tasks UI for the full transcript), the summary prose, and, on a run that didn't complete cleanly, error (the full TaskRun error) plus failure_reason (a concise derived one-liner). Lead with the summary when narrating to the user — it's the scout's own plain-language close-out — and always offer the task_url for the full reasoning.

Workflow: drill into a single run

When the user wants the full story of one run (or pastes a run id / Tasks URL):

scout-runs-retrieve
{ "id": "<uuid>" }

Note the field name flip: scout-runs-list returns each run's id as run_id, but scout-runs-retrieve takes it as id. Pass the run_id value through as id.

Returns the full run: status, started_at / completed_at (compute duration from these), skill_name / skill_version (what ran, at what body version), the end-of-run summary, emitted_report_ids / edited_report_ids, metadata, and task_url. The transcript — the actual tool calls and reasoning — lives in the Tasks UI behind task_url, not in this payload; hand the user that link when they want to see every step. A failed run has an empty summary; read failure_reason first (a one-line diagnosis: a timeout at the run budget, a tool that kept erroring, a sandbox that never started) and error for the full TaskRun error text. Those two fields answer most "why did it fail?" questions on their own; reach for the transcript when the failure needs the sequence of calls that led to it.

You don't have to open the UI for that: tasks-runs-session-logs-retrieve returns the run's session log (every tool call, message, and reasoning step) as data — handy when you're diagnosing a failure or want to trace exactly what a run did without leaving the conversation. Pass the run's task_run_id as id and its task_id (both are on the run row).

The raw stream is large (hundreds of KB to a few MB) and will overflow inline, so fetch it with call --json and let it spill to a file, then run it through scripts/render_run_report.py rather than parsing it by hand. The tool returns 100 entries by default and at most 5000 per call, and its pagination state is not in the body, so pass limit: 5000 and repeat, advancing offset by the number of entries the previous page actually returned (not by limit), until a page comes back empty, then concatenate the pages before rendering; a short page is not the end, since a page also closes early at a byte cap, and stepping by limit past one would skip the entries it left out, so a single default page can drop the failure tail.

⚠️ Do not reach for exclude_types: "tool_call_update,…" to slim it down. It is tempting — the stream is dominated by incremental tool_call_update chunks — but each tool's actual input lives only in those chunks: the base tool_call event carries an empty rawInput, and the streamed updates build the input (and the final rawOutput) token by token. Excluding them leaves you with tool names but no idea what the scout actually queried. Fetch the full log and let the script reassemble each call (it groups by toolCallId, keeps the richest rawInput, and attaches the completion's rawOutput/status).

Whether a run wrote anything is a first-class field: emitted_report_ids / edited_report_ids. A non-empty emitted_report_ids lists the reports the run authored via emit_report, in order; edited_report_ids lists the reports it mutated via edit_report (which can target any inbox report, not just ones a scout authored). A productive run typically has one id there and a summary like Report authored: <id>; resolve any id via inbox-reports-retrieve to read the report itself. Don't parse the prose summary for output — a phrase like "already reported P1 … did not re-file" describes a prior run, so substring-matching the summary is unreliable; the id columns are the authoritative tally. One scout shape breaks the equivalence between "no report ids" and "wrote nothing": a measurement scout files no report on a normal run because its output is the record stream, so check metadata.derived.has_structured_output (and the events themselves) before calling such a run empty.

Legacy runs: emitted_count / emitted_finding_ids. Runs from the deprecated signal-emitting channel (a scout without the allowed_tools opt-in — an old custom scout, or a canonical scout not yet ported) tally their output as emitted_count weak findings instead; each finding_id maps to a Signal with source_id = run:<run_id>:finding:<finding_id>. For those runs only, scout-runs-emission-reports (pass the run_id) maps each emitted finding to the inbox report its signal grouped into (or null if it never surfaced). On report-channel scouts these fields are always 0 / empty — don't diagnose off them. See references/scout-data-model.md for the full field reference.

A run with status complete and an empty-handed summary ("surface at baseline, nothing to report") is a healthy outcome, not a failure — most runs should close out empty. Treat a stream of empty close-outs as the fleet doing its job, not as the fleet being broken.

Workflow: inspect what the fleet has learned

The scratchpad is the fleet's durable, per-team memory — prose entries scouts write so future runs get smarter and quieter. Reading it tells you what the fleet believes about this project.

scout-scratchpad-search
{ "text": "error_tracking" }

Returns entries newest-first: 20 by default, limit up to 1000; text matches content and key case-insensitively. Omit text to browse everything; pass keys_only=true for a cheap scan of what memories exist, or content_max_chars to preview long bodies, and date_to set to the oldest entry's updated_at to walk past the cap. Expired entries (an expires_at in the past) are excluded unless include_expired=true; every entry also carries created_by_skill, the original creator (a scout's skill name, or pipeline:report-research / pipeline:implementation for a pipeline stage). It is lineage, not authorship of the current text: an upsert by another writer on the same team-wide key keeps both the original creator and created_by_run_id, so neither field names the current writer of a shared key; treat it as unknown rather than attributing the content to that skill. Each entry's key carries a category prefix that tells you what kind of learning it is:

PrefixMeaning
pattern:A baseline: how this team's data normally shapes
watch:A live issue being tracked but still below the report bar
followup:A scout's validation queue: a probe a later run re-runs to confirm a fix or a recurrence
noise:A pattern the fleet has decided to ignore (dev-only, single-user…)
addressed:Something the team fixed or moved on from
dedupe:A gate on re-filing a specific issue / fingerprint
allowlist:Vetted entities never to re-surface
not-in-use:A product/surface this team doesn't use (close-out memo)
mcp-gap:A tooling gap a scout noticed worth raising later
improve:A custom scout's suggested change to its own skill body, awaiting owner review
reported:A canonical scout's record of a skill gap already fed back upstream to PostHog
report:A report a scout authored: stores the report_id so later runs edit/dedup against it
reviewer:A resolved owner (GitHub login) for an area, cached for suggested_reviewers routing

This is the common vocabulary, not a closed set — scouts coin their own prefixes and <domain> labels as needed (the live fleet uses watch: heavily, for example), so treat an unfamiliar prefix as just another category. Entries cross-reference each other with [[key]] wikilinks. Keys follow <prefix>:<domain>:<entity> (e.g. dedupe:error_tracking:019e8375-…).

When a user asks "why isn't my scout flagging X anymore?", search the scratchpad for noise:, addressed:, dedupe:, and allowlist: entries — the fleet may have deliberately learned to suppress it. The canonical prefix vocabulary and the four-state dedupe classifier the fleet reasons in terms of are documented in the authoring-scouts skill (references/dedupe-and-memory.md).

Custom scouts self-report skill improvements. A custom (team-authored) scout is invited by the harness to write an improve:<skill-name>:<topic> entry when a run produces concrete evidence its own skill body steered it wrong — the suggested change, the evidence, and a dated observed line, re-confirmed in place on later runs. A custom scout on the report channel escalates recurring or material suggestions further: it files an inbox report about itself (titled Scout self-improvement: <skill-name> – <topic>) and stashes the report_id in the improve: entry — so the strongest suggestions reach the owner through the inbox like any other report, not only via the scratchpad. When assessing a custom scout, search {"text": "improve:"} and surface these to the user: an entry re-confirmed across several runs is the highest-signal edit the owner can make. Reviewing and applying them is a write operation — hand off to the authoring-scouts skill. Canonical scouts never write improve: entries (their skill bodies are synced from PostHog's fleet), so an improve: entry under a canonical scout's domain is itself worth flagging. Instead, a canonical scout routes skill-content gaps upstream to the PostHog team via the agent-feedback MCP tool (feedback_type: "scout"), generalized so no project data travels, and keeps a reported:<skill-name>:<topic> entry as its local record of what it already submitted — so a reported: entry tells you a gap has been raised with PostHog, not with this team.

Workflow: see what scouts have written

Scout output reaches the user as inbox reports. Filter the inbox to one scout by its skill name (comma-separate several):

inbox-reports-list
{ "scout": "signals-scout-error-tracking", "limit": 20 }

Each row also carries scout_name, so a fleet-wide page tells you which scout authored what. For the whole fleet at once, filter on the source instead: { "source_product": "signals_scout", "limit": 20 }. Every report a scout authors carries backing signals tagged source_product="signals_scout", and that filter keeps any report whose contributing signals include the tag, so the result is the set of reports the fleet has authored. Neither filter captures edit-only work: a scout that edits an existing non-scout report (appending a note to a pipeline report, say) adds no signals_scout signal, so that report won't match; trace edits through the run rows' edited_report_ids instead. Suppressed reports are hidden by default, so add include_all_statuses: true (or status: "suppressed") when judging how much of a scout's output got filtered. Even then both the scout and the source_product filter miss a report the safety judge suppressed, because each resolves reports through their signals and an unsafe report's signals are never indexed; the run rows' emitted_report_ids are the complete enumeration for a signal-to-noise audit.

An empty result means the fleet hasn't authored any reports (yet), not that the filter is broken. Scouts hold a high bar — most runs close out without writing — so on a quiet or newly enrolled project zero scout reports is the normal, expected state. Note the inbox only shows surfaced reports: a report the safety judge suppressed (or one filed as not_actionable) persists with status SUPPRESSED but doesn't appear in the default inbox view.

For the per-run view, work from the runs instead: scout-runs-list?emitted=true lists every run that authored a report, and each run's emitted_report_ids / edited_report_ids name exactly which reports it wrote or updated — resolve them via inbox-reports-retrieve. The flip side matters when explaining a gap: a run can narrate "authored a report" in its summary yet have the write silently dropped by a preflight gate (dry-run at the time, the org hasn't approved AI processing, or the signals_scout source is disabled) — those leave emitted_report_ids empty, so a claimed-but-absent report is itself a diagnostic. To browse the inbox more broadly, use the inbox-exploration skill (statuses, suggested reviewers, drilling into a report's underlying signals). The report contract behind each report — the report bar, evidence, actionability, reviewer routing — is documented in the authoring-scouts skill (references/report-contract.md).

A report's status is not its whole state: read its checks too. A check is a follow-up measurement a scout or the report pipeline attached to a report — an expectation plus a time to test it — so a report can be resolved and still under measurement. inbox-report-checks-list returns every check on one report, newest first, with its status (pending, active, passed, failed, errored, expired, cancelled), its last_outcome, and its schedule (next_run_at, run_interval_minutes, runs_remaining, expires_at); inbox-report-checks-retrieve returns one check with its full config. Read the rows this way:

  • pending — the check is waiting for the report to resolve before its clock starts.

  • active with a future next_run_at — a verdict is on its way. Don't re-derive the answer by hand; say when it lands.

  • Everything else is terminal. A claim still worth watching needs a new check, which is a write — hand off to authoring-scouts.

The verdicts themselves are not on the check row: each one is a check_result artefact on the report, so read them through the report's artefact list. query and baseline_value read as null for a credential that cannot read the data they describe. A failed check on a resolved report usually has a fresh report behind it as well (the breach is re-surfaced as a new report linked to the resolved one), so look for that before reporting the relapse as unhandled. The full mechanics — the two check kinds, the soak window, what each verdict does next — are in the authoring-scouts skill (references/report-checks.md).

Workflow: assess health and performance

"Is my scout actually working / earning its cost?" There's no single metric — judge a scout over a window of runs. Pull the runs (scout-runs-list with a date_from), then reason across the dimensions below. The full playbook, including how to read each signal and the common failure modes, is in references/assessing-performance.md.

  • Cadence adherence: are runs landing roughly every run_interval_minutes, or on each run_cron_schedule slot? Judge a cron scout against its slots, not its interval: a weekday-only scout's weekend gap is the schedule, not a stall, and assess_health.py skips interval-based scoring for it (its cadence column reads cron). Large gaps mean the coordinator is skipping it (disabled, drained from the flag, or capped out on busy ticks) — or it's dispatching but the runs aren't materializing. Tell the two apart with last_run_at: if the config's last_run_at is also stale, the coordinator stopped planning it; if last_run_at is fresh but the newest run row is hours old, it's the dispatch-vs-execution divergence above (workers backed up / down, or runs stranded), which scout-runs-list alone hides.

  • Success rate — how many runs reach a clean status vs. error out? A run of errors is a broken scout, not a quiet one; group the failed runs by failure_reason to see whether it is one cause repeating.

  • Report rate — what fraction of runs wrote or edited a report vs. closed out empty. Read it straight off emitted_report_ids / edited_report_ids per run (or split the window with runs-list?emitted=true / ?emitted=false, remembering edit-only runs read as not-emitted). Near-zero over a long window on a live surface can mean the discriminator is too strict (or the surface really is quiet); near-100% usually means it's too noisy. Don't score a measurement scout on this at all — a near-zero report rate is its designed behavior, so read its record stream instead. Most healthy scouts write rarely.

  • Signal-to-noise — of what it wrote, how much surfaced as actionable inbox reports vs. got suppressed or dismissed? Resolve each run's emitted_report_ids via inbox-reports-retrieve and read the report statuses — across a window, the share of authored reports that are live and non-suppressed is the scout's hit rate.

  • Memory growth — a healthy scout accumulates pattern: / noise: / dedupe: entries over time. A scout with an empty scratchpad after many runs isn't learning.

Helper scripts

The skill bundles three pure formatters under scripts/ for the most common asks. They do no network I/O — they are the back half of an "agent fetches, script formats" split. The pattern is always the same:

  1. Fetch each payload with the MCP using call --json (raw JSON, not the pretty text format) and save it to a file. For the big ones (scout-runs-list, tasks-runs-session-logs-retrieve) this is mandatory anyway — they overflow inline and spill to a file you can point the script at.

  2. Run the script over those files.

All three are stdlib-only Python 3.11+ and print plain text to stdout (or --out) — designed to read well in a terminal, so save them as .txt.

scripts/render_run_report.py — drill into one run

Produces the kind of detailed write-up you'd want when inspecting a single run: header (status, duration, posture), a narrated timeline that interleaves the agent's narration with each tool call and its real input, the end-of-run summary, and any scratchpad memory.

# fetch (note --json), saving each to a file:
#   call --json scout-runs-retrieve { "id": "<run_id>" }            -> run.json
#   call --json tasks-runs-session-logs-retrieve { "id": "<task_run_id>", "task_id": "<task_id>", "limit": 5000, "offset": 0 }  -> log.json   (FULL, no exclude_types; page with offset until an empty page, then concatenate)
#   (optional) call --json scout-scratchpad-search { ... }          -> mem.json
#   (optional) call --json scout-config-list {}                     -> cfg.json
python scripts/render_run_report.py --run run.json --log log.json \
    --scratchpad mem.json --config cfg.json --out report.txt

Modes (--mode, default detailed):

ModeContains--log needed?
summaryheader + posture + close-out proseno
detailed+ narrated timeline with tool inputs + tool tally + scratchpadyes
full+ each tool call's (truncated) output inlineyes

Other flags: --show-output (outputs in detailed mode), --input-width / --output-width (truncation), --no-art (skip the hedgehog banner), --base-url (defaults to us.posthog.com).

scripts/fleet_survey.py — survey the whole fleet

One scannable table — scout, enabled, posture, cadence, last run, last outcome — with a "worth a look" section that flags never-run, stuck-in-dry-run, and last-run-failed scouts.

#   call --json scout-config-list {}                 -> cfg.json
#   (optional) call --json scout-runs-list { "limit": 30 }  -> runs.json   (small limit!)
python scripts/fleet_survey.py --config cfg.json --runs runs.json --now <current-ISO-time>

Pass --now (the current time, ISO-8601) to get relative "ago" columns; the last-outcome column reads what the run wrote straight off emitted_report_ids / edited_report_ids on the run row.

scripts/assess_health.py — health over a window of runs

Implements the "assess health and performance" workflow above: a per-scout table (runs, success %, report rate, cadence gap vs interval, adherence, median duration, memory growth) plus a "worth a look" section flagging all-failed scouts, timeout-shaped failures, cadence stalls, staleness, and empty scratchpads.

#   call --json scout-runs-list { "limit": 100, "date_from": "<ISO>" }  -> runs.json
#   (optional) call --json scout-config-list {}                          -> cfg.json
#   (optional) call --json scout-scratchpad-search {}                    -> mem.json
python scripts/assess_health.py --runs runs.json --config cfg.json \
    --scratchpad mem.json --now <current-ISO-time> [--skill signals-scout-general]

--config is what lets it score cadence adherence (the expected interval; a scout with a run_cron_schedule is marked cron and exempt from the interval-based adherence, stall, and staleness flags) and staleness (the authoritative last_run_at, which the windowed runs can miss when the 100-row cap truncates the newest runs). Without --scratchpad the memory column shows n/a and no memory flags fire. The report rate reads the run rows' emitted_report_ids / edited_report_ids directly, so it's exact — but it only counts writes; judge signal-to-noise by the resulting report statuses via inbox-reports-list.

Tips

  • Always surface the emit posture. "Running but in dry-run" is the single most common reason a user thinks a scout is broken when it isn't.

  • An empty close-out is success. Most runs should find nothing. Don't report a wall of clean, empty runs as a problem.

  • What a run wrote is a first-class run field. Read emitted_report_ids / edited_report_ids per run (or filter with runs-list?emitted=true) to find what was written, without parsing the prose summary. The source_product: "signals_scout" inbox filter lists the reports the fleet surfaced; an empty result there means it hasn't written anything yet (scouts hold a high bar), not that the filter is broken.

  • emitted_count: 0 does not mean "did nothing". emitted_count / emitted_finding_ids are legacy signal-channel fields — they stay 0 / empty on report-channel scouts, productive or not. Judge output by the report columns; a non-zero legacy tally means the run came from a scout still on the legacy channel, and is that run's real output.

  • A failed run that lasted ~15 minutes is usually a timeout, not a broken scout. The per-run budget is 15 minutes; completed runs finish in a couple. failure_reason names a timeout outright, so check it before reasoning from duration. Most often the scout over-investigated and ran the full budget (the fleet self-corrects by writing "tight-run recipe" scratchpad entries) — but some are false timeouts where the scout actually finished in a few minutes and the run then hung on a dropped close-out. The session log (above) tells them apart: real over-investigation shows tool calls right up to the wall; a false timeout goes silent long before it. Don't assume over-investigation from duration alone.

  • Lead with the run summary, then offer task_url for the full transcript — don't dump raw run rows at the user.

  • last_run_at: null means the coordinator has never dispatched the scout, not that it has never run: manual (scout-run-now) and workflow-triggered runs leave the stamp untouched, so check scout-runs-list before calling a scout never-run. Then check it's enabled and the project is enrolled (a cron scout also simply waits for its first slot after creation).

  • To explain a quiet scout, read the project profile. scout-project-profile-get shows whether the surface it watches is even in use — a logs scout on a project with no logs has nothing to do.

  • This skill is read-only. To change a scout's schedule, posture, or body, hand off to the authoring-scouts skill — it covers scout-config-update and the skills-store edit path.

Files

references/scout-data-model.md

Scout data model — what you're reading

Three records describe a scout's life on a project, plus one snapshot it orients from. This reference is the vocabulary for everything exploring-scouts returns.

SignalScoutConfig — the scout's settings

One row per (team, skill_name). Returned by scout-config-list. This is the scout's control surface, separate from its instruction body (the LLMSkill).

FieldMeaning
idConfig id: the handle scout-config-update takes to tune it.
skill_nameThe skill this config controls. Fixed; one config per skill per team. Usually signals-scout-*, but any skill name can hold a config.
display_nameThe name the UI shows. Blank means the default derived from skill_name.
descriptionWhat the scout watches, read live from the skill's frontmatter. Empty when the skill is gone from the team (an orphan config) or has no description.
scout_origin / ownerscanonical (PostHog seeded it) or custom (team-authored); owners lists the skill's owners. The value comes from the skill row's seed metadata, which survives an in-place edit, so a canonical scout the team has edited (diverged, no longer synced) still reads canonical here.
enabledfalse = paused. The coordinator skips disabled scouts entirely. Derived from status.
statusWho owns the pause: active, pending_pause (still runs; an ignored warning pauses after a week, a no_output warning only ever warns; any config edit clears it), paused_by_system (automatic, resumable with enabled: true), paused_by_user (a person switched it off; the system never overrides it).
pause_reasonWhy the system paused or warned: no_output, ignored, or repeated_failures. Null outside pending_pause / paused_by_system.
status_changed_atWhen status last changed: for pending_pause, when the warning was issued (an ignored warning pauses about a week later; a no_output one never does).
consecutive_failure_countCurrent streak of failed scheduled runs (manual and workflow-triggered failures don't count; a clean run from any trigger clears it). The failure breaker (repeated_failures) trips one failure past the runs the schedule fits in twelve hours, clamped to 5–25 (daily: 5, rolling hourly interval: 13, hourly cron: 15 because cron slots are counted over a DST-padded window); any config edit resets it.
auto_pause_exempttrue = the inactivity sweep never judges this scout (for a watchdog whose silence is the job, or output consumed where the inbox can't see it).
emitfalse = dry-run: the scout runs and reasons every tick but writes nothing to the inbox.
run_interval_minutesRolling cadence, 30–43200. Default 1440 (daily). The coordinator dispatches when due.
run_cron_scheduleOptional five-field cron in the project timezone. When set it takes precedence over the interval; a new or edited schedule waits for its next slot rather than catching up.
output_destinationsWhere reports go besides the inbox: slack (integration plus a channel or up to five DM targets, optional thread_reports) is the only destination Signals delivers to itself; a webhook entry is a pointer to a CDP destination the owning product manages, and says nothing about whether anything is delivered there.
network_accessWhat the run's sandbox can reach: trusted (default: the platform's trusted-domain allowlist: PostHog, GitHub, package registries) or full (any site, for skills that read external docs/papers).
mcp_gateway_server_idsMCP store servers the scout's runs may mount, from connections shared with the whole team. Empty = no shared servers (where gateway enforcement is not yet active, the acting user's personal connections may still mount). Security-relevant like network_access, and activity-logged.
write_scopesProject-wide write scopes this scout holds (notebook:write, dashboard:write, insight:write, annotation:write, alert:write, customer_task:write, llm_skill:write, warehouse_view:write, warehouse_table:write, replay_scanner:write, canvas:write, experiment:write). New scouts start with notebook:write. Empty = read-only beyond findings and memory. A dry run never holds them.
modelPinned model for this scout's runs, or null to follow the fleet default. Setting one needs the scouts-model-config early-access flag on the project.
structured_output_schemaOptional JSON Schema (object-rooted) describing one structured record the scout produces via scout-record-output. Null = the structured-output channel is off (it also needs emit on). When set, each run is shown the schema and its records are validated against it and recorded as $scout_structured_output events.
tagsFree-form kebab-case labels (up to 10) grouping the fleet; scout-config-list filters on them.
source_product / source_idSet when another product stood the scout up for one of its own objects (Replay Vision creates one per scanner) so it can find and clean up its scouts; null for a scout a person created directly.
last_run_atWhen the coordinator last dispatched it. null = never dispatched by the coordinator, not never run: manual (scout-run-now) and workflow-triggered runs leave it untouched, so check scout-runs-list before calling a scout never-run. Drives the due-check (with run_cron_schedule and creation time on a cron scout).

The replay_scanner:write grant permits scanner maintenance across the project. Scouts must set a credit limit on scanners they create, copy, or enable. Changes to targeting, sampling, or the model of an enabled scanner also require a limit. Scouts cannot remove a limit, delete a scanner, or start manual scans, retries, or backfills. They can disable a scanner and keep its observations. Shared ratings must record explicit user verdicts. Autonomous assessments belong in scout memory or reports. | updated_at | When the config last changed, including settings edits and system status changes. Scheduled dispatch updates last_run_at, not this field. |

A scout that is enabled: true, emit: false is alive and working — it just can't post reports. It is the extra-careful rollout posture for a chatty or high-stakes scout (the default is emit: true), and the most common cause of "my scout does nothing" reports.

SignalScoutRun — one execution

Returned by scout-runs-list (summary) and scout-runs-retrieve (detail; same shape). Each run is one sandboxed agent execution of one scout. The run is a thin bridge to a tasks.TaskRun — status, timing, and the full transcript live on the Task side.

scout-runs-retrieve takes the run id as id, not run_id — even though the list and the detail payload both name the field run_id. Pass the list's run_id value through as id.

FieldMeaning
run_idUUID of the run. Pass it to scout-runs-retrieve as id. Strictly team-scoped (404 across teams).
skill_nameWhich scout ran.
skill_versionThe body version that ran. If a scout was edited, older runs ran an older version — useful when comparing behavior before/after a change.
statusRun outcome, from the linked TaskRun (see below).
started_atISO-8601 — when the TaskRun was created.
completed_atISO-8601 — when it finished. null while in flight. Duration = completed_at - started_at.
emitted_report_idsThe reports the run authored via emit_report, in order. This (with edited_report_ids) is the run's output — resolve each id via inbox-reports-retrieve. Empty = the run authored nothing, except on a measurement scout, whose output is its $scout_structured_output records.
edited_report_idsThe reports the run edited via edit_report (title/summary rewrite, appended note, or reviewers set), deduped. Edits can target any inbox report, not just scout-authored ones.
emitted_countLegacy — how many weak findings the run emitted on the deprecated signal channel. Always 0 on report-channel scouts; populated only on runs from scouts still on the legacy channel. Don't infer "did nothing" from 0 — check the report id columns.
emitted_finding_idsLegacy — the finding_ids behind emitted_count, in emit order. Each maps to a Signal with source_id = run:<run_id>:finding:<finding_id>. Empty on report-channel scouts.
metadataTwo server-written regions. Top-level keys are runner-stamped at run start: harness_prompt_version / report_channel (none/emit/edit/both) / skill_origin / github_guidance (the provenance set — which prompt build, which report tools, canonical or custom skill, and whether the GitHub evidence section was rendered; segment on all four before comparing runs, and treat runs missing them as unknown rather than pooling them), plus model / runtime_adapter / reasoning_effort / service_tier only when routing overrode the default. metadata.derived is the harness's own map of booleans computed at finalize (has_emit_report, has_edit_report, has_self_improvement, has_chart, has_self_validation, has_structured_output, has_not_in_use_closeout) — the structured answer to "what kind of run was this?", so read it before parsing summary. Nothing here is scout-declared, so it can't contradict the run's actual output — except has_structured_output, which counts accepted batches rather than delivered events (see the structured-output section below). No derived map at all means unknown — the run predates the field (most historical runs), never reached finalize, or its stamp failed — never that every flag was false.
task_id, task_run_idIdentifiers on the Tasks side.
task_urlRelative deep-link to the Tasks UI for this run — the full transcript (every tool call and reasoning step) lives here, not in the run payload.
summaryThe scout's own one-paragraph end-of-run close-out. The primary thing to read and relay. Empty for runs that errored before close-out.
errorThe full TaskRun error text on a run that didn't complete cleanly; null otherwise.
failure_reasonA concise, derived one-line diagnosis of why the run failed (timeout at the budget, a tool erroring, a sandbox that never started). Read this before the transcript.
Run status values

status flows from the linked tasks.TaskRun: not_started, queued, in_progress, completed, failed, cancelled. Treat a completed run with an empty-handed summary as a healthy quiet run, not a failure — most runs should close out empty.

  • not_started / queued / in_progress: dispatched or running (completed_at null).

  • completed: finished cleanly. May or may not have written anything; check emitted_report_ids / edited_report_ids (both empty = quiet, unless it's a measurement scout — then check metadata.derived.has_structured_output). metadata.derived.has_not_in_use_closeout true means the quiet run was pointless: it wrote a not-in-use: entry because the product is off, rather than looking and finding nothing.

  • failed: the run errored before closing out. Its summary is empty; failure_reason says why in one line and error carries the full text, so the transcript (open task_url, or pull it as data with tasks-runs-session-logs-retrieve) is only needed for the sequence of calls that led there. In practice the common failure is a timeout at the 15-minute per-run budget, not a logic-broken scout; a failed run whose duration ≈ the budget is a timeout, and failure_reason names it. The usual cause is over-investigation (the scout ran to the wall), but some are false timeouts — the scout finished quickly and the run then hung on a dropped close-out; the session log distinguishes the two (tool calls up to the wall vs. silence long before it).

  • cancelled: the sandbox activity was cancelled mid-flight (a worker shutdown or deploy). Says nothing about the scout, so it doesn't count toward the failure breaker.

Read the summary and completed_at together rather than keying on one status string.

The run row tells you exactly what it wrote: emitted_report_ids lists the reports the run authored (bumped post-success on each emit_report; preflight-skipped / dry-run writes don't count) and edited_report_ids the reports it mutated. Resolve any id via inbox-reports-retrieve to read the report itself. Filter the list endpoint with emitted=true / emitted=false to slice runs by outcome without reading any prose — note emitted=true means "authored a report (or, legacy, emitted a finding)"; a run that only edited reads as emitted=false. A run with both columns empty closed out empty — expected and correct most of the time, since scouts only write when they clear a high bar. On a measurement scout the columns say nothing about output at all: it files no report on a normal run, so read metadata.derived.has_structured_output and the events themselves.

Each authored report's backing evidence persists as signal rows tagged source_product="signals_scout", so inbox-reports-list { "source_product": "signals_scout" } is the direct way to list the reports the fleet has surfaced.

Runs from scouts still on the legacy signal channel (no allowed_tools opt-in — old custom scouts, or a canonical scout not yet ported) emit weak findings instead: emitted_count is that tally and emitted_finding_ids lists the finding_ids behind it. Each finding went through emit_signal() with source_product="signals_scout" / source_type="cross_source_issue" and a deterministic source_id = run:<run_id>:finding:<finding_id> (stored at the top level of the signal's metadata, not inside metadata.extra). Grouping generated its own document_id and deduped on that — never on source_id — so a re-emitted finding_id produced a second signal. For these runs only, scout-runs-emission-reports maps each emitted finding to the inbox report its signal grouped into (or null). On report-channel scouts both fields are always 0 / empty.

$scout_structured_output events — schema-validated records (structured-output channel)

One event per record a run submitted via scout-record-output, recorded only when the scout's config carries a structured_output_schema (and emit is on — a dry-run scout has no channel). The queryable view of a measuring scout's output (judgments, scores, classifications), read like any events — trend them in insights or query the events table, filtering on the properties below.

PropertyMeaning
run_idThe run that recorded it.
skill_nameWhich scout produced it.
subjectOptional scout-chosen key naming what the record is about (a report id, URL, account key). Null for run-level.
outputThe full record, validated against the config's schema at submission time.
output_<key>Each scalar top-level payload key, flattened for direct breakdowns (e.g. output_verdict).

Event uuids are deterministic per (run, batch index, subject, payload), so a retried batch collapses at ingestion rather than double-counting. A run's metadata.derived.has_structured_output flag is a cheap per-run screen for "at least one batch was accepted", without querying events. It is not delivery confirmation: acceptance is counted before the events are forwarded, so a capture failure afterwards leaves the flag true with fewer or no events behind it. Count the events themselves whenever the number of records matters.

SignalScratchpad — durable fleet memory

Returned by scout-scratchpad-search. One row per (team, key); re-using a key upserts. This is the fleet's cross-run memory — prose entries scouts write so future runs are smarter and quieter.

FieldMeaning
keyAgent-chosen semantic key, unique per team. Carries a category prefix.
contentProse, read verbatim into a future run's prompt.
created_by_run_idWhich run wrote it (null if the run was later deleted).
created_by_skillThe original writer: a scout's skill name, or pipeline:report-research / pipeline:implementation for a report-pipeline stage. An upsert keeps the creator, so the last writer can differ.
created_at / updated_atWhen written / last rewritten.
expires_atOptional. Expired entries drop out of searches (unless include_expired=true) and a daily janitor deletes them two weeks past expiry.

The key prefix tells you the kind of learning: pattern: (baseline), watch: (a live issue tracked but still below the report bar), followup: (a validation probe a later run re-runs), noise: (ignore), addressed: (fixed/moved on), dedupe: (gate re-filing), allowlist: (never re-surface), not-in-use: (surface not used), mcp-gap: (tooling gap), report: (an authored report's report_id), reviewer: (a cached owner for reviewer routing). This vocabulary is open — scouts coin their own prefixes and <domain> labels, so treat an unfamiliar prefix as just another category. Entries link to each other with [[key]] wikilinks. The canonical prefix set and the four-state dedupe classifier the fleet reasons in terms of live in the authoring-scouts skill (references/dedupe-and-memory.md).

SignalProjectProfile — orientation snapshot

Returned by scout-project-profile-get. A deterministic, cached snapshot of "what's true about this project" — products in use, product intents, integrations, warehouse sources, signal source configs (split enabled/disabled), inbox report counts, and top events with reach/burst metrics. This is the ground truth every scout cold-starts from.

When exploring, reach for the profile to explain scout behavior: a scout watching a surface the profile shows as absent (no logs, no LLM events, no revenue source) has nothing to do, and its quiet runs are correct. The profile is ground truth from authoritative tables; the scratchpad is the fleet's inferred learnings — don't conflate them.

How the coordinator decides what runs

Useful context when a scout's runs are sparser than its schedule implies. A periodic Temporal coordinator ticks (~every 30 min) and, for each enrolled team, dispatches every enabled scout whose schedule is due, most-overdue first, capped per tick. On a rolling interval, due means last_run_at is None or now - last_run_at >= run_interval_minutes; on a run_cron_schedule, due means the first slot after the latest of last_run_at, the last schedule edit, and creation has passed, so a fresh cron scout waits for its first slot. Enrollment is via the signals-scout feature flag's allowlist. So a scout can be enabled yet run late if: the team was drained from the flag, the scout was disabled, or busy ticks hit the per-tick cap. There is no sampling — a due, enabled, enrolled scout runs. Separately, a scout paused by the failure breaker (repeated_failures) gets one probe run a day and resumes on a clean one, unless the project is at its enabled-scout cap, in which case the row stays paused_by_system until another scout is paused or deleted (enabling it by hand is refused at the cap too).

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
PostHog
Publisher
PostHog (Official)
Source
PostHog/skills · skills/omnibus/exploring-scouts · commit a5d30e7
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by PostHog.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download exploring-scouts.zip