Anysite Monitor
A one-shot Anysite call answers "what is the state right now?". This skill
answers the harder question — "what changed since I last looked?" — by
keeping a seen ledger of fingerprints in durable storage, subtracting it from
each fresh fetch, and reporting only what's new or edited.
Two things make a monitor good, and both are decided at setup, not at runtime:
- The right sources. This skill ships no catalog. Anysite has 566
sources and they change; a baked-in list rots and quietly points monitors at
endpoints that no longer do what their name suggests. Instead, onboarding
searches the live catalog for the user's actual goal, verifies each candidate
with
discover + one probe call, and compiles verified specs into the config.
- State that survives. Each scheduled run is a fresh session with no memory.
Anysite's request cache does persist for 7 days across sessions (find old
cache_keys with the free
search_requests), but it is a cache of fetches, not
a report state — 7-day retention is shorter than the ledger's rolling window,
and it can't record what was already reported. So the ledger must sit in
external storage a headless run can reach by token — OR, for daily-or-faster
monitors with stable-id sources, run ledgerless (ledger_mode: "cache_diff"): diff each fetch against the prior runs' fetches still in that
7-day cache. Zero storage and no persist step, at the price of no long memory
and silent loss after a failed delivery — mode selection rules in
references/storage-backends.md.
Two modes
- Setup mode — the user wants to create or change a monitor. Interview,
discover sources, compile the config, register the schedule, seed the baseline.
- Run mode — a scheduled task fired with a config in its prompt. Load ledger,
execute the config's sources, diff, deliver, persist. If the incoming prompt
already contains a monitor config, you are in Run mode — skip the interview.
Core concepts
- Fingerprint — a stable id for one item, chosen from the endpoint's real
response_fields (platform id/URN first, normalized URL only as fallback).
- New / changed / seen — new = fingerprint absent from the ledger; changed =
present but its content hash differs (only for hashed sources like pricing
pages); otherwise suppress.
- Baseline — on the first run everything looks new, so seed the ledger
silently and report one line: "monitoring initialized, tracking N items across
M sources". Real deltas start at run two. (
initial_snapshot overrides.)
- Rolling window — drop fingerprints unseen for
retention_days (default 45)
so the ledger stays bounded.
Setup workflow
Follow references/onboarding.md — it is the playbook, not a summary. The arc:
- Decision first. "What will you do differently when this fires?" Then
targets as resolvable handles (domain, slug, subreddit, URL), cadence,
delivery, volume tolerance.
- Signals. Restate the goal as testable signals: what item appears, and what
makes it worth reporting.
- Sources — discovered, never assumed. Per
references/source-discovery.md:
find candidates → discover(source, category) for exact params and
response_fields → one small execute probe on a real target → record the
verified spec. Two equally valid ways to find candidates: MCP-only (the
tools self-enumerate — a bogus source/category/endpoint returns
available_sources / available_categories / available_endpoints, free) or
catalog grep (docs.anysite.io/llms-full.txt via shell, adds keyword
search over descriptions). Use whichever the surface allows; never fetch the
12 MB openapi.json. Read the params for a time filter (posted_after,
date_posted, time_filter…) and use it when present.
- Size it out loud.
targets × signals calls per run, × cadence × 30 for the
month. Trim here, before the schedule exists.
- Pick the state mode, then check the run surface. Cadence ≤ 2 days +
stable-id sources + digest-tolerance → offer
ledger_mode: "cache_diff"
(no storage, no persist; state the trade-off out loud). Otherwise ledger,
backend per storage-backends.md. For ledger mode also verify: can the
scheduled run reach a shell, Python, and this skill's scripts/? Test during
the dry run; if not, record "diff_mode": "in_session" so the run doesn't
discover it at 7 a.m.
- Agree, dry-run, register, baseline. Show the monitor in plain language,
invite correction, optionally execute one pass to show the real digest, then
register the task and seed the ledger.
Scheduling
The schedule must outlive the session. Do not use CronCreate/CronList —
those are session-scoped (and expire in days). Use the host's durable
scheduled-task tools; in Cowork/Claude Code Remote these are create_trigger,
update_trigger, list_triggers, delete_trigger (load via ToolSearch if
deferred).
Verify the tools exist before promising a schedule. If none is available in
the current surface, do not fake it: finish the config, run the baseline, hand the
user the config plus a one-line instruction for creating the scheduled task
themselves in the UI, and say plainly that the schedule is not registered yet.
The task prompt must be a self-contained Run-mode instruction: "Invoke the
anysite-monitor skill in Run mode with the config below" + the config JSON +
the ledger JSON as a separate block. Store the returned id in
monitor.trigger_id. Set the task's notifications per the chosen delivery.
Run workflow
ledger_mode: "cache_diff"? Different loop, no ledger and no persist:
find prior fetches via search_requests (match endpoint + target handle, last
7 days), union their id sets via query_cache, diff the current fetch against
the union in-session, deliver; the current fetch's cache entry is next run's
history. No prior fetches → baseline (fetch + one-line init). Full recipe in
references/storage-backends.md. Everything below is the ledger-mode loop.
- Load config and ledger (backend per
references/storage-backends.md). No
ledger → baseline run.
- Collect. For each entry in
config.sources, execute exactly what the
spec says, capped by max_items. No re-discovery, no improvisation: if a
source errors, note it and continue (fail soft, never abort the digest).
Time window: if the spec has a time_filter, convert last_ok_run into
its value per source-discovery.md (unix_ts → minus one cadence of overlap;
enum → smallest bucket covering the gap). On a baseline run there is no
last_ok_run — use a deliberate seed window instead: for enum filters the
smallest bucket that matches the cadence (daily monitor → day/past-24h),
for unix_ts now − 7 days, and cap by max_items. Say the seeded window in
the init line, because anything older than it will surface as "new" on run 2.
- Diff deterministically. Shape items into
{fingerprint, content?, label, url} and run:
python3 scripts/ledger.py diff --ledger <file> --items <file> --today <YYYY-MM-DD> --retention-days <n> [--baseline].
It normalizes URLs, hashes content, buckets new/changed/seen, applies the
rolling window and returns the updated ledger. Don't hand-roll the diff — the
whole point is that every run diffs identically.
If the surface has no shell or no Python (or the skill's scripts/ isn't
available where the trigger fires — verify this at setup, not at 7 a.m.), fall
back to diffing in-session, following exactly the rules the script implements:
normalize URLs (lowercase host, strip utm_*/fbclid/gclid/ref, drop
fragment and trailing slash), compare fingerprints against ledger.items,
treat a differing content hash as changed, stamp s = today for everything
seen, drop entries older than retention_days. Note in the digest that the
fallback diff was used — it is the one step where drift can creep in.
- Persist, then deliver. Write the ledger back before sending the digest:
a run that reports deltas and then fails to save them repeats them next time.
Stamp
last_ok_run only on a successful write; if it lags last_run, say so
in the digest so repeats are explained. Recovery aid: search_requests (free)
can locate the previous run's fetches and their cache_keys for up to 7 days —
useful to reconstruct what a failed-persist run had already reported, or to
skip refetching a source this run already pulled.
- Deliver per the digest format below.
Digest format
Group by target, then signal. Lead with counts so the reader can triage.
📡 <Monitor name> — <date>
<N> new · <C> changed since last run
## <Target A>
### New job postings (2)
- Senior ML Engineer, Berlin — posted <date> — <url>
- Head of Sales, Remote — posted <date> — <url>
### Pricing change (1)
- Pro plan $99 → $79/mo — <url>
_No changes for: <Target B>, <Target C>_
_Source "<name>" returned an error this run — skipped._
Nothing new is a valid result: say it in one line. Match the channel — email can
be rich markdown, Telegram/Slack should be tighter. Honour focus when set.
Guardrails
- Cost.
discover and catalog greps are free; execute is not. Never
re-fetch history you've already fingerprinted, use time filters where they
exist, and keep max_items tight.
- Window overflow. If a source returns exactly
max_items new items, the
window may have overflowed and items were missed — warn in the digest and
suggest a tighter cadence or a higher cap. Silent loss is worse than noise.
- Stable fingerprints. Prefer platform ids over URLs; normalize URLs. An
unstable fingerprint turns the monitor into a spam machine on run two.
- Hash meaning, not markup. For pages, hash the extracted plan names/prices
or feature list — never raw HTML (nav, banners and tokens change constantly).
- cache_diff discipline. Ledgerless mode is only as good as the cache
window: if the gap since the last fetch approaches 7 days, re-seed quietly
instead of flooding, and never sell this mode for must-not-miss monitoring.
- No dead ends on discovery. If the shell or the docs fetch is unavailable,
the MCP self-enumeration path (
available_sources / available_categories /
available_endpoints) covers the same ground for free — never abandon a signal
because one browsing route was blocked.
- Headless reality. Only depend on hosted, token-authenticated tools. Anything
proxied through the user's desktop is unavailable when their laptop is closed.
- People data. Monitoring individuals' activity is personal-data processing:
keep it to what the stated decision needs, and don't quietly widen it.
Reference files
references/onboarding.md — the Setup-mode interview playbook.
references/source-discovery.md — how to find and verify sources in the live
catalog, and how to pick fingerprints and time filters from discover output.
references/config-schema.md — monitor config (incl. verified sources[]
specs) and ledger schema.
references/storage-backends.md — backend selection and read/write recipes.
scripts/ledger.py — deterministic diff, hashing, URL normalization, rolling
window.
1---2name: anysite-monitor3description: >-4 Build and run recurring monitoring on the Anysite MCP, delivering ONLY new or5 changed items each run by diffing against a persistent "seen ledger". Owns the6 onboarding: interviews the user about the decision the monitor should support,7 then searches the live Anysite catalog for endpoints that serve it, verifies8 each with a real call, and compiles them into a self-contained monitor config.9 No preset source list — sources are discovered per monitor. Use whenever10 someone wants to monitor, track, watch, configure monitoring, or get daily or11 weekly updates on anything Anysite can reach — companies, people, jobs, pricing12 pages, posts, news, reviews, listings, prices — e.g. "monitor X every day", "track changes in",13 "only show me what's new", "alert me when", "set up a recurring check",14 "help me configure monitoring", "настроить мониторинг", "мониторить конкурентов",15 "отслеживать изменения", "только новое", "раз в день".16 Also use it when a saved monitor fires on a schedule. For accounts that live in17 a connected CRM, prefer anysite-crm-signals (it writes signals back to the CRM);18 this skill covers everything outside the CRM and any non-account target.19---2021# Anysite Monitor2223A one-shot Anysite call answers *"what is the state right now?"*. This skill24answers the harder question — **"what changed since I last looked?"** — by25keeping a **seen ledger** of fingerprints in durable storage, subtracting it from26each fresh fetch, and reporting only what's new or edited.2728Two things make a monitor good, and both are decided at setup, not at runtime:29301. **The right sources.** This skill ships **no catalog**. Anysite has 56631 sources and they change; a baked-in list rots and quietly points monitors at32 endpoints that no longer do what their name suggests. Instead, onboarding33 searches the live catalog for the user's actual goal, verifies each candidate34 with `discover` + one probe call, and compiles verified specs into the config.352. **State that survives.** Each scheduled run is a fresh session with no memory.36 Anysite's request cache does persist for 7 days across sessions (find old37 cache_keys with the free `search_requests`), but it is a cache of fetches, not38 a report state — 7-day retention is shorter than the ledger's rolling window,39 and it can't record what was already *reported*. So the ledger must sit in40 external storage a headless run can reach by token — OR, for daily-or-faster41 monitors with stable-id sources, run **ledgerless** (`ledger_mode:42 "cache_diff"`): diff each fetch against the prior runs' fetches still in that43 7-day cache. Zero storage and no persist step, at the price of no long memory44 and silent loss after a failed delivery — mode selection rules in45 `references/storage-backends.md`.4647## Two modes4849- **Setup mode** — the user wants to create or change a monitor. Interview,50 discover sources, compile the config, register the schedule, seed the baseline.51- **Run mode** — a scheduled task fired with a config in its prompt. Load ledger,52 execute the config's sources, diff, deliver, persist. If the incoming prompt53 already contains a monitor config, you are in Run mode — skip the interview.5455## Core concepts5657- **Fingerprint** — a stable id for one item, chosen from the endpoint's real58 `response_fields` (platform id/URN first, normalized URL only as fallback).59- **New / changed / seen** — new = fingerprint absent from the ledger; changed =60 present but its content hash differs (only for hashed sources like pricing61 pages); otherwise suppress.62- **Baseline** — on the first run everything looks new, so seed the ledger63 silently and report one line: "monitoring initialized, tracking N items across64 M sources". Real deltas start at run two. (`initial_snapshot` overrides.)65- **Rolling window** — drop fingerprints unseen for `retention_days` (default 45)66 so the ledger stays bounded.6768## Setup workflow6970Follow `references/onboarding.md` — it is the playbook, not a summary. The arc:71721. **Decision first.** "What will you do differently when this fires?" Then73 targets as resolvable handles (domain, slug, subreddit, URL), cadence,74 delivery, volume tolerance.752. **Signals.** Restate the goal as testable signals: what item appears, and what76 makes it worth reporting.773. **Sources — discovered, never assumed.** Per `references/source-discovery.md`:78 find candidates → `discover(source, category)` for exact params and79 `response_fields` → one small `execute` probe on a real target → record the80 verified spec. Two equally valid ways to find candidates: **MCP-only** (the81 tools self-enumerate — a bogus source/category/endpoint returns82 `available_sources` / `available_categories` / `available_endpoints`, free) or83 **catalog grep** (`docs.anysite.io/llms-full.txt` via shell, adds keyword84 search over descriptions). Use whichever the surface allows; never fetch the85 12 MB `openapi.json`. **Read the params for a time filter** (`posted_after`,86 `date_posted`, `time_filter`…) and use it when present.874. **Size it out loud.** `targets × signals` calls per run, × cadence × 30 for the88 month. Trim here, before the schedule exists.895. **Pick the state mode, then check the run surface.** Cadence ≤ 2 days +90 stable-id sources + digest-tolerance → offer `ledger_mode: "cache_diff"`91 (no storage, no persist; state the trade-off out loud). Otherwise ledger,92 backend per `storage-backends.md`. For ledger mode also verify: can the93 scheduled run reach a shell, Python, and this skill's `scripts/`? Test during94 the dry run; if not, record `"diff_mode": "in_session"` so the run doesn't95 discover it at 7 a.m.966. **Agree, dry-run, register, baseline.** Show the monitor in plain language,97 invite correction, optionally execute one pass to show the real digest, then98 register the task and seed the ledger.99100## Scheduling101102The schedule must outlive the session. **Do not use `CronCreate`/`CronList`** —103those are session-scoped (and expire in days). Use the host's durable104scheduled-task tools; in Cowork/Claude Code Remote these are `create_trigger`,105`update_trigger`, `list_triggers`, `delete_trigger` (load via ToolSearch if106deferred).107108**Verify the tools exist before promising a schedule.** If none is available in109the current surface, do not fake it: finish the config, run the baseline, hand the110user the config plus a one-line instruction for creating the scheduled task111themselves in the UI, and say plainly that the schedule is not registered yet.112113The task prompt must be a self-contained Run-mode instruction: "Invoke the114anysite-monitor skill in Run mode with the config below" + the config JSON +115the ledger JSON as a **separate block**. Store the returned id in116`monitor.trigger_id`. Set the task's `notifications` per the chosen delivery.117118## Run workflow119120**`ledger_mode: "cache_diff"`?** Different loop, no ledger and no persist:121find prior fetches via `search_requests` (match endpoint + target handle, last1227 days), union their id sets via `query_cache`, diff the current fetch against123the union in-session, deliver; the current fetch's cache entry is next run's124history. No prior fetches → baseline (fetch + one-line init). Full recipe in125`references/storage-backends.md`. Everything below is the ledger-mode loop.1261271. **Load** config and ledger (backend per `references/storage-backends.md`). No128 ledger → baseline run.1292. **Collect.** For each entry in `config.sources`, `execute` exactly what the130 spec says, capped by `max_items`. No re-discovery, no improvisation: if a131 source errors, note it and continue (fail soft, never abort the digest).132 **Time window:** if the spec has a `time_filter`, convert `last_ok_run` into133 its value per `source-discovery.md` (unix_ts → minus one cadence of overlap;134 enum → smallest bucket covering the gap). **On a baseline run there is no135 `last_ok_run`** — use a deliberate seed window instead: for enum filters the136 smallest bucket that matches the cadence (daily monitor → `day`/`past-24h`),137 for unix_ts `now − 7 days`, and cap by `max_items`. Say the seeded window in138 the init line, because anything older than it will surface as "new" on run 2.1393. **Diff deterministically.** Shape items into140 `{fingerprint, content?, label, url}` and run:141 `python3 scripts/ledger.py diff --ledger <file> --items <file> --today <YYYY-MM-DD> --retention-days <n> [--baseline]`.142 It normalizes URLs, hashes content, buckets new/changed/seen, applies the143 rolling window and returns the updated ledger. Don't hand-roll the diff — the144 whole point is that every run diffs identically.145 **If the surface has no shell or no Python** (or the skill's `scripts/` isn't146 available where the trigger fires — verify this at setup, not at 7 a.m.), fall147 back to diffing in-session, following exactly the rules the script implements:148 normalize URLs (lowercase host, strip `utm_*`/`fbclid`/`gclid`/`ref`, drop149 fragment and trailing slash), compare fingerprints against `ledger.items`,150 treat a differing content hash as *changed*, stamp `s` = today for everything151 seen, drop entries older than `retention_days`. Note in the digest that the152 fallback diff was used — it is the one step where drift can creep in.1534. **Persist, then deliver.** Write the ledger back *before* sending the digest:154 a run that reports deltas and then fails to save them repeats them next time.155 Stamp `last_ok_run` only on a successful write; if it lags `last_run`, say so156 in the digest so repeats are explained. Recovery aid: `search_requests` (free)157 can locate the previous run's fetches and their cache_keys for up to 7 days —158 useful to reconstruct what a failed-persist run had already reported, or to159 skip refetching a source this run already pulled.1605. **Deliver** per the digest format below.161162### Digest format163164Group by target, then signal. Lead with counts so the reader can triage.165166```167📡 <Monitor name> — <date>168<N> new · <C> changed since last run169170## <Target A>171### New job postings (2)172- Senior ML Engineer, Berlin — posted <date> — <url>173- Head of Sales, Remote — posted <date> — <url>174### Pricing change (1)175- Pro plan $99 → $79/mo — <url>176177_No changes for: <Target B>, <Target C>_178_Source "<name>" returned an error this run — skipped._179```180181Nothing new is a valid result: say it in one line. Match the channel — email can182be rich markdown, Telegram/Slack should be tighter. Honour `focus` when set.183184## Guardrails185186- **Cost.** `discover` and catalog greps are free; `execute` is not. Never187 re-fetch history you've already fingerprinted, use time filters where they188 exist, and keep `max_items` tight.189- **Window overflow.** If a source returns exactly `max_items` new items, the190 window may have overflowed and items were missed — warn in the digest and191 suggest a tighter cadence or a higher cap. Silent loss is worse than noise.192- **Stable fingerprints.** Prefer platform ids over URLs; normalize URLs. An193 unstable fingerprint turns the monitor into a spam machine on run two.194- **Hash meaning, not markup.** For pages, hash the extracted plan names/prices195 or feature list — never raw HTML (nav, banners and tokens change constantly).196- **cache_diff discipline.** Ledgerless mode is only as good as the cache197 window: if the gap since the last fetch approaches 7 days, re-seed quietly198 instead of flooding, and never sell this mode for must-not-miss monitoring.199- **No dead ends on discovery.** If the shell or the docs fetch is unavailable,200 the MCP self-enumeration path (`available_sources` / `available_categories` /201 `available_endpoints`) covers the same ground for free — never abandon a signal202 because one browsing route was blocked.203- **Headless reality.** Only depend on hosted, token-authenticated tools. Anything204 proxied through the user's desktop is unavailable when their laptop is closed.205- **People data.** Monitoring individuals' activity is personal-data processing:206 keep it to what the stated decision needs, and don't quietly widen it.207208## Reference files209210- `references/onboarding.md` — the Setup-mode interview playbook.211- `references/source-discovery.md` — how to find and verify sources in the live212 catalog, and how to pick fingerprints and time filters from `discover` output.213- `references/config-schema.md` — monitor config (incl. verified `sources[]`214 specs) and ledger schema.215- `references/storage-backends.md` — backend selection and read/write recipes.216- `scripts/ledger.py` — deterministic diff, hashing, URL normalization, rolling217 window.