Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"displayName": "PostHog",
"source": "./",
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from your AI coding tool. Optionally capture Claude Code sessions to PostHog LLM Analytics.",
"version": "1.1.63",
"version": "1.1.64",
"author": {
"name": "PostHog",
"email": "hey@posthog.com",
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "posthog",
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from your AI coding tool. Optionally capture Claude Code sessions to PostHog LLM Analytics.",
"version": "1.1.63",
"version": "1.1.64",
"author": {
"name": "PostHog",
"email": "hey@posthog.com",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog",
"version": "1.0.61",
"version": "1.0.62",
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Codex",
"author": {
"name": "PostHog",
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "posthog",
"displayName": "PostHog",
"version": "1.1.57",
"version": "1.1.58",
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Cursor",
"author": {
"name": "PostHog",
Expand Down
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "posthog",
"version": "1.0.59",
"version": "1.0.60",
"description": "Access PostHog analytics, feature flags, experiments, error tracking, and insights directly from Gemini CLI",
"mcpServers": {
"posthog": {
Expand Down
9 changes: 2 additions & 7 deletions skills/.sync-manifest
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ building-html-canvases
building-react-quill-canvases
building-workflows
checking-deploy-timing
checking-member-access
choosing-trend-or-slope-view
cleaning-up-stale-feature-flags
composing-grid-canvases
Expand All @@ -34,10 +35,8 @@ creating-box-plot-insights
creating-experiments
creating-online-evaluations
creating-replay-vision-scanners
creating-surveys
debugging-experiments
debugging-local-replay
debugging-mcp-analytics
debugging-signals-pipeline
debugging-surveys
designing-email-templates
diagnosing-ci-and-merge-bottlenecks
Expand Down Expand Up @@ -72,7 +71,6 @@ finding-replay-for-issue
finding-sessions-to-watch
formatting-insight-axes
grouping-noisy-errors
improving-mcp-tools
inbox-exploration
instrument-error-tracking
instrument-feature-flags
Expand All @@ -99,7 +97,6 @@ modeling-dimension-tables
modeling-product-usage-metrics
modeling-revenue-metrics
modeling-warehouse-foundations
organizing-conversations-code
planning-voice-agent-user-interviews
querying-canvas-data
querying-posthog-data
Expand All @@ -115,7 +112,6 @@ scanning-experiments-with-replay-vision
setting-up-a-custom-rest-source
setting-up-a-data-warehouse-source
setting-up-data-catalog
setting-up-support-slack-locally
setting-up-warehouse-properties
signals
signals-scout-ai-observability
Expand Down Expand Up @@ -150,7 +146,6 @@ skills-store
suggesting-data-imports
suggesting-path-cleaning-rules
suppressing-noisy-errors
testing-mcp-tools-locally
triaging-error-issues
triaging-visual-review-runs
tuning-incremental-sync-config
Expand Down
20 changes: 15 additions & 5 deletions skills/authoring-scouts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: authoring-scouts
description: >
How to author, edit, and adapt PostHog Signals scouts — the scheduled agents that
scan a project and write reports into the Signals inbox. Use to customize a
scan a project and file what they find. Use to customize a
canonical scout (narrow its scope, retune thresholds, add disqualifiers), tweak a
scout's schedule or dry-run posture, write a new scout for a surface the fleet
doesn't cover, build a measurement scout that records structured output (an
Expand All @@ -13,7 +13,8 @@ description: >
canonical in-repo path, and the test loop. Trigger on
"write/edit/customize a signals scout", "new scout for X", "tune my scout schedule",
"make a scout that watches <event>", "score/judge/measure X with a scout",
"structured output from a scout", "leave a note for / give feedback to a scout".
"structured output from a scout", "scout output to Slack",
"leave a note for / give feedback to a scout".
metadata:
owner_team: signals
---
Expand All @@ -26,11 +27,14 @@ This skill helps you and your agent **adapt those canonical scouts to a specific

A scout's output is the **report channel**: it lists `emit_report` / `edit_report` in its frontmatter `allowed_tools` and authors or edits full inbox reports 1:1 directly.
The canonical fleet runs this way, and **every new scout should too** — always include the `allowed_tools` opt-in when authoring one.
Where that output _lands_ is a separate, per-scout config decision: the report goes to the Signals inbox, and the same report can be delivered to a Slack channel or DM at the same time (`output_destinations` under Run posture) — so don't rule a scout out of a job because the user wants the result in Slack.
(A historical signal-emitting channel — weak `emit-signal` findings a pipeline consolidated — still exists in the harness for scouts that never opted in, but it is deprecated: don't author new scouts on it, and opt an old one in rather than extending it.)

A scout is just an `LLMSkill` whose name starts with `signals-scout-`.
The harness discovers scouts by globbing `signals-scout-*` over the project's skills, loads the body **verbatim** as the agent's system prompt, and progressively reads any bundled reference files on demand.
**The `signals-scout-` name prefix is load-bearing: a skill named anything else will never run as a scout.**
A scout is an `LLMSkill` that holds a `SignalScoutConfig`.
The harness loads the body **verbatim** as the agent's system prompt, and progressively reads any bundled reference files on demand.
**The config row is what makes a skill a scout.** Any valid skill name works, so the `signals-scout-` prefix is optional.
The prefix controls one thing: the coordinator globs `signals-scout-*` to auto-register a config for a skill that has none.
A skill with any other name needs its config created alongside it, which is what `scout-create-prepare` / `-execute` does.

## The job before the writing

Expand Down Expand Up @@ -136,6 +140,12 @@ For an **existing scout**, tune with `posthog:scout-config-update` (find the `id
A granted scout is told in its run prompt which objects it may change, and is asked to name every change in its close-out. The grant is an upper bound: the acting user's own permissions still apply to each object, and the scout reports a refused write rather than retrying it.
A dry run (`emit: false`) never holds the grant, so a scout can be previewed without it changing anything.
Applies from the scout's next run.
- `output_destinations` — defaults to none.
When adding Slack to an existing scout, first read `output_destinations`, then send the full object with every key preserved. Updates replace the object, so sending only `slack` removes an existing `webhook` pointer.
Set `slack` to deliver every report the scout emits to Slack as well as the inbox: an `integration_id` for the workspace, plus either a `channel` (`channel_id|#channel-name`) or up to five `users` to DM (`member_id|@display-name`), never both.
`thread_reports: true` posts a report as a short lead message with the rest split into replies at the summary's section labels, so a long report isn't clipped; it doesn't change how findings post.
Slack delivery is a firehose of that one scout's output — no priority filter, no reviewer routing — so it suits a scout whose bar is already tight rather than a chatty one you're still calibrating.
A Slack-delivered scout is also exempt from the ignored-reports auto-pause, since consumption there isn't measurable.
- `tags` — free-form labels grouping the fleet, e.g. `["revenue", "on-call"]`. Up to 10 per scout, normalized to lowercase kebab-case (`On Call` → `on-call`) and deduped.
Set them at create time: a scout that lands already grouped saves a follow-up edit, and the desktop app's scout list filters on them.
Prefer a tag that already exists on the fleet (`-config-list` shows every scout's tags) over minting a near-duplicate — `revenue` and `revenue-analytics` fragment the same group.
Expand Down
8 changes: 4 additions & 4 deletions skills/authoring-scouts/references/lifecycle-and-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ How scouts get discovered, scheduled, and dispatched; the two distribution paths

## How a scout runs

- **Discovery.** The harness globs `signals-scout-*` over the project's skills (`LLMSkill` rows).
Any matching skill is a scout.
No registration step.
- **Discovery.** A scout is a skill that holds a `SignalScoutConfig`, and the coordinator dispatches from those config rows.
The `signals-scout-` name prefix is optional; it controls only auto-registration, below.
- **Config.** Each scout has one `SignalScoutConfig` per `(project, skill_name)` carrying `run_interval_minutes` (default 1440), `enabled`, `emit`, `network_access` (`trusted` default, `full` for scouts that read arbitrary external sites), and a `last_run_at` stamp.
A config is **auto-registered** the first time the coordinator sees a `signals-scout-*` skill without one — authoring the skill is enough to get a scout.
A config is **auto-registered** the first time the coordinator sees a `signals-scout-*` skill without one, so authoring a prefixed skill is enough to get a scout.
A skill named anything else needs its config created with it.
Prepare a fresh per-team scout and its config together with `posthog:scout-create-prepare`; the nested `config` object sets its schedule, emit posture, and destinations before it can run.
Show the returned confirmation message, wait for the user to type `confirm`, then call `posthog:scout-create-execute` with the returned `confirmation_hash` and that literal confirmation.
The lower-level `posthog:scout-config-create` remains available when a skill already exists without a config.
Expand Down
23 changes: 13 additions & 10 deletions skills/authoring-scouts/references/report-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ Each entry identifies one reviewer by **`github_login`**, **`user_uuid`**, or bo
- **`github_login`** — a **bare, lowercase GitHub login** (e.g. `octocat`, not `@OctoCat`).
Internal assignment matches it against each user's linked GitHub login by exact, lowercased comparison, so a mis-cased handle, an `@`-prefix, a display name, a CODEOWNERS **team** slug, or an email won't set `is_suggested_reviewer` for anyone (autostart's PR-selection path is more lenient, but the assignment path is not).
- **`user_uuid`** — a **PostHog user UUID**.
The server resolves it to that org member's linked GitHub login for you (and it wins if you also pass a `github_login`).
Use this whenever your evidence already names a PostHog user — an account owner, an entity's `created_by`, a CSM — so you can route to them without ever looking up their handle.
A `user_uuid` that isn't an org member of this team **with a linked GitHub identity** is rejected (the whole call fails), so it never silently drops.
The server resolves it to that org member. It wins if you also pass a `github_login`.
Use this whenever your evidence already names a PostHog user. It works without a linked GitHub account.
A `user_uuid` that is not an org member of this team is rejected, so it never silently drops.

So you have two routes to a reviewer.
If you already hold a PostHog user UUID, prefer passing it as `user_uuid` — it's the most reliable.
Expand All @@ -219,7 +219,7 @@ Otherwise resolve a `github_login`, cheapest source first:
`.github/CODEOWNERS` for the owning path, or the last `git log` author for the file.
Neither usually hands you a usable login directly: CODEOWNERS entries are often **team** slugs (`@your-org/team-name`) and `git log` gives a name + email — both must be resolved to an **individual** GitHub login before you write the reviewer (a team slug or an email won't match any user).
4. **`scout-members-list`** — the in-run roster lookup, for the cold-start case where the cheaper paths above don't resolve an owner.
It returns this project's members, each with `user_uuid`, `email`, name, and a resolved `github_login` (pass `search=` to narrow); match the owner and route to their `github_login`, or hand the `user_uuid` straight through and let the server resolve it.
It returns this project's members, each with `user_uuid`, email, name, and a resolved `github_login`. Pass `search=` to narrow the result. Match the owner and route with `user_uuid`.
The org-scoped `org-members-list` / `org-member-get-github-login` tools are **not available in a scout run** — a scoped-team token can't reach the org-nested endpoint, so don't build a scout's reviewer recipe around them.

**If you can't confidently identify a reviewer, leave `suggested_reviewers` empty** — the report still surfaces for a human to grab.
Expand Down Expand Up @@ -261,20 +261,23 @@ Before authoring, list the team's existing reports so you reconcile against one
- `inbox-reports-list` — filter by title/summary free-text (`search`), `status`, `source_product`, or your own `task_id`; newest-updated first.
- `inbox-reports-retrieve` — fetch a single report by id (use the `report_id` you stashed in the scratchpad last run).

## Dedup: the channel is NOT idempotent
## Dedup: the retry is covered, the near-duplicate is not

`emit_report` is **not idempotent** — a retried call authors a _second_ report.
There is no server-side dedup key.
The dedup story is two-sided and the scout owns it:
`emit_report` carries an emit key, so resending a call that timed out returns the report the first one authored (`idempotent_replay` true) rather than a twin.
The key is the `idempotency_key` you pass, or the report's own content when you pass none, and it is scoped to your run.
Pass one when a retry might reword the report, since a reworded report is a different content key.

That barrier covers the transport failure and nothing else.
A report on a topic an earlier run already filed is a fresh emission with a fresh key, so the cross-run dedup is still two-sided and the scout owns it:

1. **Before authoring**, `inbox-reports-list` for a prior report on the same topic.
Found one?
`edit_report` it instead of authoring a new one.
2. **After authoring**, write a `report:<domain>:<entity>` scratchpad entry recording the `report_id` so the next run finds it (via `inbox-reports-retrieve`) without a title-search guess.
(This is the report-channel member of the scratchpad key-prefix vocabulary — see [`dedupe-and-memory.md`](dedupe-and-memory.md).)

**Never retry an `emit_report` / `edit_report` call that may have succeeded** a transport error after the write commits, retried, double-files.
If you're unsure whether a call landed, `inbox-reports-list` to check before retrying.
`edit_report` has no such barrier: **never retry an `edit_report` call that may have succeeded**, since a transport error after the write commits, retried, appends a second note.
If you're unsure whether an edit landed, `inbox-reports-retrieve` to check before retrying.

## The pipeline may rewrite what you authored (accepted)

Expand Down
8 changes: 5 additions & 3 deletions skills/authoring-scouts/references/scout-anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ Keep the body lean and push depth into references — every line of the body is

## Naming

The skill name **must** match `signals-scout-<scope>` — the harness discovers scouts by globbing `signals-scout-*`.
`<scope>` is lowercase kebab-case naming the surface or question the scout watches: `signals-scout-error-tracking`, `signals-scout-checkout-funnel`, `signals-scout-mcp-feedback`.
A skill named anything else is just a normal skill and never runs as a scout.
Any valid skill name works: lowercase letters, numbers, and hyphens.
The `SignalScoutConfig` row is what makes a skill a scout.
Name it in lowercase kebab-case after the surface or question the scout watches: `error-tracking`, `checkout-funnel`, `mcp-feedback`.
The canonical fleet keeps the `signals-scout-` prefix, and a per-team scout can use it too.
The prefix only controls whether the coordinator auto-registers a config for a skill that has none, so a scout named anything else comes in through `scout-create-prepare` / `-execute`, which writes the skill and its config in one call.

## Frontmatter

Expand Down
2 changes: 1 addition & 1 deletion skills/authoring-scouts/references/scout-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ Proven shapes: a daily LLM-analytics digest (latency / errors / clusters / cost
Score every section as the latest window vs the team's own trailing like-for-like baseline, lead with anything urgent, and keep steady-state items to one line.
(One exception to "always emittable": if the watched surface isn't in use at all, write a `not-in-use:<domain>` memory and skip the digest entirely — don't post an empty report.)
- **Channel + cadence:** the report channel (`emit_report`), **exactly one report per calendar day**.
Before emitting, check `dedupe:<domain>:{date}` in the scratchpad **and** `inbox-reports-list` — `emit_report` is not idempotent, so a same-day re-run must skip, and an emit that may have already landed must never be retried.
Before emitting, check `dedupe:<domain>:{date}` in the scratchpad **and** `inbox-reports-list` — the emit key only covers a retry of the same call within one run, so a same-day re-run must skip rather than file the finding again.
After emitting, record `report:<domain>:{date}` with the returned `report_id` and `dedupe:<domain>:{date}`.
- **Memory is what lets it speak in deltas.** A cursor (`pattern:<domain>:cursor` — the timestamp the last digest covered through) windows each run; baseline snapshots (`pattern:<domain>:cost-baseline`, `:latency-bands`, a cluster/state snapshot) let the digest say what moved rather than what is; `noise:` entries fold known recurring things (a nightly batch spike, a deliberate model swap) in as context instead of re-raising them.
- **Budget discipline is load-bearing.** The digest has a fixed section structure and a hard run budget, so query economically: one combined SQL returning several sections' numbers beats one query per section, and a shallow digest that posts beats a thorough one that times out.
Expand Down
6 changes: 6 additions & 0 deletions skills/building-canvases/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ That field is the only valid link to a canvas — never construct one yourself;
`canvases-actions-retrieve` tool and follow each verb's `usage` (payload/result shape,
behavior, and the confirmation copy it warrants) before wiring it.

- **`ph.connectors.call(provider, tool, args)`** — read live third-party data (GitHub, or any
MCP store server) with the VIEWER's own connection at view time. Never call GitHub, Calendly,
or another service yourself and paste the result into the source: that snapshot is stale on
publish and shows every viewer the author's data. Declare each provider and tool in
`capabilities.connectors`; discover them with the `canvas-connectors-retrieve` tool. See
`querying-canvas-data` for the result and not-connected handling.
- **`ph.agent.request(prompt)`** — ask the canvas's authoring agent for a change, with the viewer's
approval. Declare `agentRequests: true` in `capabilities.posthog`. Call it only from a direct
click or form submission — the host shows the exact prompt and asks the viewer to accept before
Expand Down
Loading