Wake up to what changed.
Run one Python agent on a schedule. It compares against prior runs, keeps memory in SQLite, and leaves a timestamped finding before breakfast.
One concrete agent.
One concrete finding.
This is a real two-run transcript from a pricing monitor. Run 1 captured baseline. Run 2 detected a price change and persisted the finding to state. No hypothetical architecture.
from watchd import agent
@agent(name="pricing_watch")
def pricing_watch(ctx):
run = ctx.state.get("run", 0) + 1
previous = ctx.state.get("last_plan")
current = fetch_pricing_page()
if previous and previous["price"] != current["price"]:
ctx.log.info("pricing_change", old=previous["price"], new=current["price"])
return f"pricing_change detected: ${previous["price"]} -> ${current["price"]}"
ctx.state["run"] = run
ctx.state["last_plan"] = current
return "baseline captured"2026-02-23 20:35:02 [info ] agent_finished result='baseline captured' run_id=addcb912460d [success] pricing_watch (addcb912460d) in 1ms 2026-02-23 20:35:02 [info ] pricing_change new=79 old=49 plan=Starter run_id=332c200308a1 [success] pricing_watch (332c200308a1) in 1ms result: pricing_change detected: Starter $49 -> $79
{
"findings": [
{
"run": 2,
"finding": "pricing_change detected: Starter $49 -> $79"
}
],
"last_plan": {
"run": 2,
"plan": "Starter",
"price": 79,
"seats": 3
},
"run": 2
}Cron can schedule. It does not compound.
The activation energy is only worth it if you feel the difference on run two. watchd gives you state, history, and observability as defaults, so your agent can compare now vs then without extra plumbing.
Start with one painful signal.
Choose one thing you care about overnight: pricing drift, contract creep, stale docs, uptime checks. Keep the scope narrow.
$ watchd init $ watchd new compliance_check # point it at one source URL = "https://vendor.com/terms"
Make run two smarter than run one.
Use ctx.state to compare against prior runs. watchd stores the baseline, run history, and logs so your finding has context by default.
previous = ctx.state.get("last_terms")
current = scrape_terms_page()
if previous and previous["hash"] != current["hash"]:
return f"terms changed: {diff(previous, current)}"
ctx.state["last_terms"] = currentDeploy once, then let it run.
Push to any VPS in one command. You can inspect history, logs, and state at any time without stitching together extra tooling.
$ watchd run compliance_check $ watchd deploy # inspect later $ watchd history compliance_check $ watchd logs compliance_check $ watchd state compliance_check
You write agent logic. watchd handles the rest.
Scheduling, state, history, logs, deploy. Nine modules, zero external dependencies.