Hermes Agent alerts in Telegram only when a page changes
Three ways to watch a web page or feed with Hermes Agent cron and get a Telegram message only on change, from agent-only to a zero-token script check.
· ranx.cloud

This is for watching something that changes rarely but matters when it does: a vendor's pricing page, a project's release notes, a government notice board, a careers page. You end up with a Hermes Agent job that checks on a schedule and posts to Telegram only when there is something new, with one or two sentences on what changed. Quiet hours cost nothing or close to it.
Hermes offers three ways to build this, and they trade convenience for cost. All three are set up from the dashboard or by asking the agent in chat. Details below were checked against Hermes Agent v0.22.7.
| Approach | LLM calls per check | Good for |
|---|---|---|
A. Agent checks, replies [SILENT] if nothing changed |
every check | messy pages, "is this meaningful?" judgments |
| B. Script checks first, wakes the agent only on change | only on change | most pages and feeds |
C. Script only (no_agent), no model at all |
zero | exact signals: a number crossed a threshold, a new item appeared |
Before you start
You need Telegram connected and a home channel set. In the dashboard, open Channels → Telegram, use Create with QR or Manual setup with a token from @BotFather (/newbot) and your numeric user ID from @userinfobot, then restart the gateway from the banner. In your DM with the bot, send /sethome. The daily brief guide walks through this step by step.
Set a timezone in Config (search timezone, e.g. America/New_York) so schedules mean what you think they mean.
A. Let the agent check and stay quiet
The simplest version is a scheduled prompt that compares against the last run. Two cron features make it work:
[SILENT]: if the agent's final reply contains it, delivery is skipped. The output is still saved on disk.- continuity: each run receives the job's own most recent non-silent output, with an instruction not to repeat it. Silent runs are skipped when picking that context, so the comparison is always against the last thing you were actually told.
Message the bot (or use the Chat tab):
Every 2 hours, fetch https://example.com/pricing with web_extract and list
every plan with its monthly price and included limits. Compare with the
previous report you were given. If no plan, price or limit changed, reply
with only [SILENT]. If something changed, reply with: what changed, old value,
new value, and the URL. No other text. Deliver to Telegram.
Then open the new job on the Cron page, expand Advanced fields, and:
- tick continuity;
- under enabled_toolsets, select only
web. Every tool you leave enabled adds its schema to every model call, so a job that only reads one page shouldn't carry the terminal, browser, and delegation tools; - set Provider and Model to a small model (see costs below).
Press Trigger now once. The first run has no previous report, so it will send you the baseline. After that it should stay silent until the page changes.
The weakness of A: you pay for a model call on every check, including all the quiet ones.
B. Script gate: check first, wake the agent only on change
A cron job can run a pre-check script before the agent. If the script's last line of output is {"wakeAgent": false}, the run stops there: no model call, no delivery. Anything else wakes the agent, and the script's output is prepended to the prompt as a "Script Output" data block, so the agent sees exactly what changed.
You don't need a shell to install the script. Ask the agent to write it. Scripts must live in the scripts/ folder of the Hermes data directory, and the agent can write files there:
Write a Python script named watch_pricing.py in the Hermes scripts directory.
It should:
1. Download https://example.com/pricing with urllib and a normal browser
User-Agent.
2. Keep only the visible text between the "Plans" heading and the footer.
3. Compare it with the copy saved in scripts/.watch_pricing.last
(create it on the first run).
4. If unchanged, print exactly {"wakeAgent": false} and exit 0.
5. If changed, save the new copy and print a unified diff of old vs new
(at most 80 lines). Do not print a JSON line in that case.
Use only the Python standard library. Run it once to test it, and show me
the output.
Read the script it writes before you schedule it. A version of what you should get:
import difflib, json, pathlib, re, urllib.request
URL = "https://example.com/pricing"
STATE = pathlib.Path(__file__).with_name(".watch_pricing.last")
req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"})
html = urllib.request.urlopen(req, timeout=30).read().decode("utf-8", "replace")
text = re.sub(r"<script.*?</script>|<style.*?</style>", "", html, flags=re.S)
text = re.sub(r"<[^>]+>", "\n", text)
lines = [l.strip() for l in text.splitlines() if l.strip()]
start = next((i for i, l in enumerate(lines) if l.lower() == "plans"), 0)
current = "\n".join(lines[start:])
previous = STATE.read_text() if STATE.exists() else ""
if current == previous:
print(json.dumps({"wakeAgent": False}))
else:
STATE.write_text(current)
diff = difflib.unified_diff(previous.splitlines(), current.splitlines(), lineterm="")
print("\n".join(list(diff)[:80]))
Then create the job, either in chat:
Create a cron job named pricing-watch that runs every 30 minutes with the
pre-run script watch_pricing.py. Prompt: "The script output is a diff of the
pricing page. Summarize the change in two sentences: which plan, old value,
new value. Include https://example.com/pricing." Deliver to Telegram.
or on the Cron page: put the prompt in Prompt, watch_pricing.py in Advanced fields → Script, and pick Telegram in Deliver to. Leave no_agent unticked.
Now you can check every 30 minutes and only pay when the diff is non-empty.
C. No model at all
When the alert text can be computed directly, skip the agent. Tick no_agent on the job (or ask the agent for a no_agent job). The rules are simple:
| Script result | What happens |
|---|---|
| exit 0, output not empty | output is sent as-is |
| exit 0, empty output | nothing is sent |
| non-zero exit or timeout | an error alert is sent |
The last row matters: a broken watchdog tells you it's broken instead of going quiet.
A release watcher for a GitHub project, as a no_agent script. The public API allows 60 unauthenticated requests per hour per IP (GitHub rate limits), so one check every 30 minutes is well inside it:
import json, pathlib, urllib.request
REPO = "NousResearch/hermes-agent"
STATE = pathlib.Path(__file__).with_name(".release.last")
url = f"https://api.github.com/repos/{REPO}/releases/latest"
release = json.load(urllib.request.urlopen(url, timeout=30))
tag = release["tag_name"]
if STATE.exists() and STATE.read_text() == tag:
pass # empty output, nothing is sent
else:
STATE.write_text(tag)
print(f"{REPO} released {tag}\n{release['html_url']}")
Cron scripts run with a cleaned environment: your LLM keys and other Hermes-managed secrets are not passed to them. If a script needs a token of its own, add it in Keys and list its name under terminal.env_passthrough in Config (details).
Costs
Assumptions for one agent check in approach A: 3 model calls, 45,000 input tokens in total, 600 output tokens. Check the Analytics page after a day and adjust. Prices are list prices as of November 2026 (Claude pricing, OpenRouter models), with no cache discount.
Every 2 hours is 12 checks a day, 360 a month.
| Model | Per check | A: 360 checks/month |
|---|---|---|
| Claude Haiku 5.5 ($0.10 / $0.50 per MTok) | 0.045 × 0.10 + 0.0006 × 0.50 = $0.0048 | ≈ $1.73 |
| GPT-6 Luna via OpenRouter ($0.10 / $0.50) | same | ≈ $1.73 |
| Claude Sonnet 5.5 ($2 / $10) | 0.045 × 2 + 0.0006 × 10 = $0.096 | ≈ $34.56 |
With approach B, the script runs 1,440 times a month for free and the agent only runs when the page changed. If that happens 10 times a month on Haiku 5.5, it's about $0.05. Approach C costs nothing in model spend.
So the same alerts cost about $35 a month with A on Sonnet and a few cents with B on Haiku, and B checks four times as often. Use a big model only when deciding what counts as a change takes judgment.
What goes wrong
It alerts on every run. The page has content that changes on every load: timestamps, rotating testimonials, CSRF tokens, view counters. In B, narrow the text the script compares (the "between Plans and the footer" step). In A, tell the agent exactly which fields count as a change.
The fetch returns almost nothing. The page is rendered by JavaScript, so neither web_extract nor urllib sees the content. Look for the data the page loads instead: a JSON endpoint, an RSS feed, a GitHub API. Hermes's browser tools need a working Chromium in the container with enough shared memory, so don't build a scheduled job on them on a small hosted instance.
HTTP 403 or 429. The site blocks scripts or rate-limits you. Send a normal User-Agent, check less often, and respect the site's terms. Some sites can't be watched politely; pick another source.
The agent keeps reporting the baseline. continuity isn't on, or the job was recreated and lost its history. Each job's context is its own run history, so a new job starts fresh.
Nothing arrives and nothing is logged as failed. In A, check that the prompt doesn't make the agent answer [SILENT] too eagerly. Failed runs are always delivered, even with [SILENT], so silence means the agent thought nothing changed. Run the job with Trigger now after editing the prompt to say "report any difference at all" and compare.
The script isn't found. The Script field takes a path relative to the scripts folder (watch_pricing.py, not a full path). Paths that resolve outside that folder are rejected.
The cron job tried to create another cron job. It can't. Cron management is disabled inside cron runs to prevent runaway scheduling, so set up jobs from a normal chat.
On ranx.cloud, scripts can reach any public URL but not private IP ranges, so you can't point a watcher at a machine on your home network. Watch something with a public address instead.


