Growth Report
One skill, two cadences. The judgment is shared; only thresholds and depth differ.
Render through growth-report-format. Open it alongside this skill. It owns
the document frame, provenance grammar, table specs, and the empty case. This
skill owns what to pull, what counts as movement, and the section names inside
Findings — Moved, Watch, Changes shipped.
Pick the cadence from the traffic, not from habit
Monthly is the default. Run weekly only if the property clears the movement floors in a typical week — as a rule of thumb, **>500 organic clicks/week and
20 key events/week**. Below that, a weekly report is arithmetically incapable of finding anything: every metric sits under the floor, every run produces the empty case, and a report that always says "nothing moved" trains its reader to stop opening it.
Check before choosing. Pull 28 days with gsc_search_analytics and
ga4_key_events; divide by four. If the weekly numbers are near or below the
floors, say so and run monthly:
This property averages organic clicks and key events per week, below the weekly movement floors. Reporting monthly; a weekly run would report no movement by construction.
Cadence is otherwise an argument, never a guess — if the request doesn't say and traffic supports either, ask.
Target resolution
- Named property/site in the request wins.
- Otherwise the records skill (below) names the default property.
- Otherwise ask (GSC property + GA4 property ID).
Agentman defaults: GSC sc-domain:agentman.ai, GA4 property 471838641.
Tools this skill calls
| Need | Tool |
|---|---|
| Clicks, impressions, CTR, position by query/page/date | mcp__google_search_mcp__gsc_search_analytics |
| Sessions, users, key events, channel groups | mcp__google_search_mcp__ga4_run_report |
| Which events count as conversions | mcp__google_search_mcp__ga4_key_events |
| Index coverage per sitemap (monthly) | mcp__google_search_mcp__gsc_list_sitemaps |
When a pull is scoped to one page, filter it with dimension_filter_groups and
confirm responseAggregationType is byPage. An unfiltered query-dimension pull
returns property-wide rows that read convincingly as page data.
State model (what makes "moved" mean something)
Snapshots and records live in a private records skill, not on disk. In
claude.ai and Cowork the filesystem is session-scoped, so anything written to
data/ or /tmp/ is gone before the next run — which would make every report a
first run forever.
Find or create the store:
- Look for
growth-records-<property>(for this property,growth-records-agentman) withfind_skillsoropen_skill. - If it does not exist, create it with
create_skill: private (tenant scope), SKILL.md stating it is a durable record store holding real property IDs and never published. Thenpublish_skillso its files can be read back. - Write snapshots with
save_skill_resource, pathsnapshots/snapshot-<target>-<YYYY-MM-DD>.md.
Reading it back: open_skill on the records skill returns the file manifest —
that is how you enumerate what exists. read_skill_file returns one file's
contents, and takes a combined path: <skill-slug>/snapshots/<file>.md, not
a slug argument plus a relative path.
Comparisons only ever run against stored snapshots — never against memory or a doc.
First run on a target: pull everything, write the snapshot, and report "baseline established ; no comparison possible yet." That sentence is the correct output, not a failure.
The trust caveat (not a gate)
Conversion measurement breaks silently and often. Before reporting any key-event
number, call ga4_key_events and compare the starred list against the events
actually firing in ga4_run_report. Three states, three behaviours:
| State | What to do |
|---|---|
| Verified recently by the audit chain | Report normally |
| Not verified, no contradiction visible | Report the number, tagged (UNVERIFIED — measurement audit overdue) |
| Starred events are dead, or real intent events are unstarred | Report the number tagged, and open Findings with the defect |
Report the number and flag it. Do not withhold it. An earlier version of this skill blocked conversion reporting until three audit skills passed; that is the right instinct and the wrong workflow. A marketer who must run a three-layer audit before answering "how was last week" answers from the GA4 UI instead, and the discipline is lost entirely. A loud caveat keeps both the answer and the honesty.
The dead-measurement case is not hypothetical. A verified state on this property:
six events starred as conversions, five with zero occurrences in 28 days and one
with a single occurrence, while skill_cta_click (102), skill_try_now (70),
signup_click (10) and demo_request_open (6) fired unstarred. Conversion-
weighted analysis was running on a floor value. When you find this, it is the
lead finding — a 30-minute fix in GA4 admin is worth more than everything else
in the report.
Also watch for: a changed key-event list mid-window (silently redefines every
conversion number), and source fragmentation (linkedin and linkedin.com
counted separately understates a channel).
Deploy annotations (comparability protection)
Every deploy that touched titles, URLs, schema, or tracking gets a dated annotation in the snapshot AND the report. Renames, redirects, and analytics fixes all break period-over-period comparability; an unannotated jump gets misread as an organic win or loss.
— analytics shadow-module fix deployed; all custom-event counts before this date are invalid. Conversion comparisons start next period.
Monthly cadence (default)
Window: last full calendar month vs the prior month, plus same-month-last-year where GA4 retention allows. A metric down MoM but up YoY in a seasonal business is fine; say which frame applies and never present one month against the previous without the seasonality caveat.
Pulls:
gsc_search_analytics: clicks, impressions, CTR, position — totals and top 10 pages. Row sums are not totals; GSC withholds low-volume queries. Split brand vs non-brand before reporting CTR — a blended CTR is two different numbers averaged into a meaningless one.ga4_run_report: sessions, active users, key-event counts by event name; sessions bysessionDefaultChannelGroupwith engagement rate per channel. A channel spike with near-zero engagement is bots or a mis-tagged campaign, not a win.ga4_key_events: the trust caveat above.gsc_list_sitemaps: pages indexed vs submitted, new pages indexed, coverage errors, unexpected noindex, pages that fell out.
Movement thresholds: flag a metric only if it moved >20% vs the prior month AND beyond an absolute floor (200 clicks / 400 sessions / 20 key events). Both gates, or small numbers produce false alarms.
Also in monthly:
- Funnel-stage movement: acquisition (GSC clicks, GA4 organic sessions) → engagement (event volume on key surfaces) → conversion (key events). Name the stage that moved most; that is where next month's effort goes.
- Decay sweep: pages whose position or CTR fell vs their trailing 3-month self. Top 5 only.
- Overdue experiments:
open_skillthe records skill, listrecords/, and read any whoseRe-measuredate has passed and whoseStatusis not RE-MEASURED or CLOSED. Report each as OVERDUE, one line. An experiment nobody re-measures is a superstition. - Standing decisions: decision records carry a
Revisit ifcondition. When this period's data meets one, say so — that is the trigger firing, and it is how a "do not act" answer stays honest rather than becoming permanent.
Actions: at most five for monthly. This raises the format skill's default of three, and the reason is scope: a month covers index health, decay, and funnel movement alongside ranking work, and collapsing that to three drops real findings. Weekly stays at three.
Weekly cadence (only above the traffic bar)
Everything monthly does, minus index health, decay sweep, and YoY, over the last 7 full days vs the trailing 4-week same-weekday baseline. Week-over-week alone whipsaws on one good Tuesday. GSC lags ~2 days — never include partial trailing days.
Movement thresholds: >20% vs baseline AND beyond a floor of 50 clicks / 100 sessions / 5 key events.
Actions: at most three.
The action ledger (reports that get read)
- Last period's actions open Findings: each marked ✅ done or carried forward with a reason.
- An action carried more than two periods gets escalated or explicitly dropped — a report that accumulates stale actions stops being read.
- Every action names a target and, where one exists, the skill that executes it.
Honesty rules
- If a number contradicts the prior snapshot for the same window, re-pull before explaining; if it still differs, report the discrepancy itself (GSC restates data) and correct the stored snapshot with a note.
- Attribution language stays modest: "organic sessions rose after the title shipped" is an observation; "the title caused it" needs controls.
- Never fill a gap with an estimate. A missing pull is reported as missing.
- What this report cannot see: SERP features (whether an AI Overview or a competitor took the click), backlinks, and revenue. When movement is unexplained by the available data, say the explanation is outside what these tools return rather than inventing a plausible cause.
Publishing
Snapshots and records contain property IDs and event taxonomies. They live only in the private records skill. Never publish a records skill or copy its contents into this master skill — this file must stay portable across properties.