{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"the-monitor","title":"Pattern: watch something, say when it changes","url":"https://addaly.com/learn/agents-and-automation/the-monitor","position":51,"of":72,"minutes":9,"answer":"A monitor that dies is indistinguishable from a world where nothing is happening, so the first thing to monitor is that the monitor ran.","keyIdea":"A monitor that dies is indistinguishable from a world where nothing is happening, so the first thing to monitor is that the monitor ran."},"body":{"format":"markdown","text":"## The shape\n\nCheck a source on a schedule or subscribe to its events. Compare what you see with what you saw last time. Decide whether the difference is worth telling anyone. Tell them.\n\nPrice changes, competitor pages, regulatory notices, a supplier's stock levels, a slow-moving dataset, a folder that should receive a file every morning. The pattern is everywhere and it is simple in a way that hides two hard parts: deciding what counts as a change, and not becoming noise.\n\n## Diff structured state, never the raw page\n\nFetch the same web page twice and the bytes differ: session identifiers, timestamps, rotating advertisements, a \"3 people are viewing this\" counter. Diff the HTML and everything is a change, every time.\n\nExtract the fields you actually care about, store those, and compare those:\n\n```python\nstate = {\"price_minor\": 129900, \"in_stock\": True, \"sku\": \"AX-4\"}\nif state != last_state:\n    notify(diff(last_state, state))\n```\n\nHash the extracted state to detect change cheaply, but store the fields themselves, because a notification saying \"something changed\" makes the reader do the work you were supposed to do. Send the before and after values in the message.\n\n## The model's job here is small\n\nDetection must be deterministic. Never ask a model \"has this changed\": it will answer differently on identical inputs, and you will have built a monitor that fires at random.\n\nThe model is useful *after* detection: summarising what a fifteen-clause change to a policy document actually means, or judging whether a change is material enough to wake somebody. Deterministic detection, model-assisted interpretation. Keep the boundary sharp, because a stochastic detector produces the worst failure available to this pattern, which is a signal nobody can trust.\n\n## Noise is the failure mode\n\nAn alert nobody reads is worse than no alert, because it trains people to ignore the channel, including on the day it matters. Alert fatigue is the whole reason monitors get switched off.\n\nThree controls:\n\n**Hysteresis.** Do not alert every time a value crosses a threshold. Require the condition to hold for three consecutive checks, and require a return to normal before you will alert again. This removes flapping, which is most of the volume.\n\n**Batching.** Anything non-urgent goes into a digest at eight in the morning: one message, everything that changed overnight, sorted. Reserve immediate notification for things somebody would act on at 2am, and be honest about how short that list is.\n\n**Actionability.** Every alert states what changed, what it was before, why it might matter, and links straight to the thing. If the reader has to open three tabs to understand it, they will not, twice.\n\n## Watch the watcher\n\nThis is the part that gets missed and it is the most important paragraph in the lesson.\n\nA monitor that has crashed sends no alerts. A world in which nothing is changing also produces no alerts. From the reader's side these are identical, and the silence is reassuring in both cases, so a dead monitor can go unnoticed for months and be discovered on the day it was needed.\n\n> Figure: A price monitor that stopped, and the day it was missed. Timeline: 3 March, Deployed. Checks eleven supplier pages each morning and posts any change to a channel.; 9 March, First real alert. A supplier raises cement by four per cent and the team reprices the same day.; 22 April, The server is rebuilt. The cron entry is not restored. Nothing errors, because nothing runs.; May to July, Silence in the channel, read as a quiet market, which is exactly what silence looks like from outside.; 6 August, A quote is lost. The supplier moved prices in May and the monitor never saw it.; 7 August, A heartbeat is added: the job pings on success, and a separate service alerts when the ping is late.. A dead monitor and a world where nothing is happening produce exactly the same output, and the silence is reassuring in both cases. Alert on the absence of work, not only on the presence of problems.\n\nThe fix is a heartbeat, sometimes called a dead man's switch. Every successful run pings something; if the ping does not arrive on schedule, *that* raises an alert. Healthchecks.io has a free tier and self-hosts; or write a `last_run_at` timestamp somewhere and have a second, separate job complain when it goes stale. The two jobs must not share a machine or a scheduler, or a single failure silences both.\n\nAlert on the absence of work, not only on the presence of problems. This is the single most transferable idea in the operations half of this course.\n\n## The free path\n\n`cron` with `curl`, `jq` and a JSON file of last state does this properly in about twenty lines. **changedetection.io** self-hosts and handles page watching with a UI, including rendering JavaScript-heavy pages. RSS still exists on more sites than people assume and is a subscription rather than a poll, which is cheaper and kinder to the source.\n\nWhatever you use, respect the source: a sensible interval, a real user agent with contact details, and attention to `robots.txt`. A monitor polling every thirty seconds is indistinguishable from an attack from the other end, and getting your address blocked is the least bad outcome of that."},"check":{"question":"A price monitor ran reliably for months, then silently stopped after a dependency upgrade. Nobody noticed for six weeks. What design change would have caught it, and why is it necessary?","options":[{"text":"Alerting on exceptions from the monitoring script, so a crash is reported.","correct":false,"tempting":"Catches crashes that raise, but not a process killed by the operating system, a scheduler that stopped firing, or a machine that never came back."},{"text":"Increasing the polling frequency, so a failure surfaces sooner in the logs.","correct":false,"tempting":"More frequent runs produce more log lines nobody reads; the problem is that silence looks identical to no change, at any frequency."},{"text":"A heartbeat on a separate system that alerts when the expected ping does not arrive, because no alerts is also what a healthy quiet period looks like.","correct":true},{"text":"Sending a notification on every check, including ones with no change, so the absence would be obvious.","correct":false,"tempting":"Would technically reveal the silence, at the cost of training everyone to filter the channel, which guarantees the real alert is missed too."}]},"prev":{"slug":"the-research-agent","title":"Pattern: search, read, cite, and verify the citation","url":"https://addaly.com/learn/agents-and-automation/the-research-agent"},"next":{"slug":"the-batch-backfill","title":"Pattern: forty thousand rows, once","url":"https://addaly.com/learn/agents-and-automation/the-batch-backfill"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Pattern: watch something, say when it changes', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/the-monitor (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Pattern: watch something, say when it changes","courseTitle":"Agents and Automation","module":{"index":6,"slug":"automation-patterns","title":"Eight patterns that keep working"},"answer":null,"keyIdea":"A monitor that dies is indistinguishable from a world where nothing is happening, so the first thing to monitor is that the monitor ran.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"timeline","title":"A price monitor that stopped, and the day it was missed","events":[{"when":"3 March","what":"Deployed. Checks eleven supplier pages each morning and posts any change to a channel."},{"when":"9 March","what":"First real alert. A supplier raises cement by four per cent and the team reprices the same day."},{"when":"22 April","what":"The server is rebuilt. The cron entry is not restored. Nothing errors, because nothing runs."},{"when":"May to July","what":"Silence in the channel, read as a quiet market, which is exactly what silence looks like from outside."},{"when":"6 August","what":"A quote is lost. The supplier moved prices in May and the monitor never saw it."},{"when":"7 August","what":"A heartbeat is added: the job pings on success, and a separate service alerts when the ping is late."}],"caption":"A dead monitor and a world where nothing is happening produce exactly the same output, and the silence is reassuring in both cases. Alert on the absence of work, not only on the presence of problems."},"alt":"Figure: A price monitor that stopped, and the day it was missed. Timeline: 3 March, Deployed. Checks eleven supplier pages each morning and posts any change to a channel.; 9 March, First real alert. A supplier raises cement by four per cent and the team reprices the same day.; 22 April, The server is rebuilt. The cron entry is not restored. Nothing errors, because nothing runs.; May to July, Silence in the channel, read as a quiet market, which is exactly what silence looks like from outside.; 6 August, A quote is lost. The supplier moved prices in May and the monitor never saw it.; 7 August, A heartbeat is added: the job pings on success, and a separate service alerts when the ping is late.. A dead monitor and a world where nothing is happening produce exactly the same output, and the silence is reassuring in both cases. Alert on the absence of work, not only on the presence of problems."}},"blocks":[{"t":"h2","id":"the-shape","runs":[{"text":"The shape"}]},{"t":"p","runs":[{"text":"Check a source on a schedule or subscribe to its events. Compare what you see with what you saw last time. Decide whether the difference is worth telling anyone. Tell them."}]},{"t":"p","runs":[{"text":"Price changes, competitor pages, regulatory notices, a supplier’s stock levels, a slow-moving dataset, a folder that should receive a file every morning. The pattern is everywhere and it is simple in a way that hides two hard parts: deciding what counts as a change, and not becoming noise."}]},{"t":"h2","id":"diff-structured-state-never-the-raw-page","runs":[{"text":"Diff structured state, never the raw page"}]},{"t":"p","runs":[{"text":"Fetch the same web page twice and the bytes differ: session identifiers, timestamps, rotating advertisements, a “3 people are viewing this” counter. Diff the HTML and everything is a change, every time."}]},{"t":"p","runs":[{"text":"Extract the fields you actually care about, store those, and compare those:"}]},{"t":"code","lang":"python","label":"Python","text":"state = {\"price_minor\": 129900, \"in_stock\": True, \"sku\": \"AX-4\"}\nif state != last_state:\n    notify(diff(last_state, state))"},{"t":"p","runs":[{"text":"Hash the extracted state to detect change cheaply, but store the fields themselves, because a notification saying “something changed” makes the reader do the work you were supposed to do. Send the before and after values in the message."}]},{"t":"h2","id":"the-model-s-job-here-is-small","runs":[{"text":"The model’s job here is small"}]},{"t":"p","runs":[{"text":"Detection must be deterministic. Never ask a model “has this changed”: it will answer differently on identical inputs, and you will have built a monitor that fires at random."}]},{"t":"p","runs":[{"text":"The model is useful "},{"text":"after","i":true},{"text":" detection: summarising what a fifteen-clause change to a policy document actually means, or judging whether a change is material enough to wake somebody. Deterministic detection, model-assisted interpretation. Keep the boundary sharp, because a stochastic detector produces the worst failure available to this pattern, which is a signal nobody can trust."}]},{"t":"h2","id":"noise-is-the-failure-mode","runs":[{"text":"Noise is the failure mode"}]},{"t":"p","runs":[{"text":"An alert nobody reads is worse than no alert, because it trains people to ignore the channel, including on the day it matters. Alert fatigue is the whole reason monitors get switched off."}]},{"t":"p","runs":[{"text":"Three controls:"}]},{"t":"p","runs":[{"text":"Hysteresis.","b":true},{"text":" Do not alert every time a value crosses a threshold. Require the condition to hold for three consecutive checks, and require a return to normal before you will alert again. This removes flapping, which is most of the volume."}]},{"t":"p","runs":[{"text":"Batching.","b":true},{"text":" Anything non-urgent goes into a digest at eight in the morning: one message, everything that changed overnight, sorted. Reserve immediate notification for things somebody would act on at 2am, and be honest about how short that list is."}]},{"t":"p","runs":[{"text":"Actionability.","b":true},{"text":" Every alert states what changed, what it was before, why it might matter, and links straight to the thing. If the reader has to open three tabs to understand it, they will not, twice."}]},{"t":"h2","id":"watch-the-watcher","runs":[{"text":"Watch the watcher"}]},{"t":"p","runs":[{"text":"This is the part that gets missed and it is the most important paragraph in the lesson."}]},{"t":"p","runs":[{"text":"A monitor that has crashed sends no alerts. A world in which nothing is changing also produces no alerts. From the reader’s side these are identical, and the silence is reassuring in both cases, so a dead monitor can go unnoticed for months and be discovered on the day it was needed."}]},{"t":"p","runs":[{"text":"The fix is a heartbeat, sometimes called a dead man’s switch. Every successful run pings something; if the ping does not arrive on schedule, "},{"text":"that","i":true},{"text":" raises an alert. Healthchecks.io has a free tier and self-hosts; or write a "},{"text":"last_run_at","code":true},{"text":" timestamp somewhere and have a second, separate job complain when it goes stale. The two jobs must not share a machine or a scheduler, or a single failure silences both."}]},{"t":"p","runs":[{"text":"Alert on the absence of work, not only on the presence of problems. This is the single most transferable idea in the operations half of this course."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"cron","code":true},{"text":" with "},{"text":"curl","code":true},{"text":", "},{"text":"jq","code":true},{"text":" and a JSON file of last state does this properly in about twenty lines. "},{"text":"changedetection.io","b":true},{"text":" self-hosts and handles page watching with a UI, including rendering JavaScript-heavy pages. RSS still exists on more sites than people assume and is a subscription rather than a poll, which is cheaper and kinder to the source."}]},{"t":"p","runs":[{"text":"Whatever you use, respect the source: a sensible interval, a real user agent with contact details, and attention to "},{"text":"robots.txt","code":true},{"text":". A monitor polling every thirty seconds is indistinguishable from an attack from the other end, and getting your address blocked is the least bad outcome of that."}]}],"check":{"question":[{"text":"A price monitor ran reliably for months, then silently stopped after a dependency upgrade. Nobody noticed for six weeks. What design change would have caught it, and why is it necessary?"}],"options":[{"text":[{"text":"Alerting on exceptions from the monitoring script, so a crash is reported."}],"correct":false,"tempting":[{"text":"Catches crashes that raise, but not a process killed by the operating system, a scheduler that stopped firing, or a machine that never came back."}]},{"text":[{"text":"Increasing the polling frequency, so a failure surfaces sooner in the logs."}],"correct":false,"tempting":[{"text":"More frequent runs produce more log lines nobody reads; the problem is that silence looks identical to no change, at any frequency."}]},{"text":[{"text":"A heartbeat on a separate system that alerts when the expected ping does not arrive, because no alerts is also what a healthy quiet period looks like."}],"correct":true,"tempting":null},{"text":[{"text":"Sending a notification on every check, including ones with no change, so the absence would be obvious."}],"correct":false,"tempting":[{"text":"Would technically reveal the silence, at the cost of training everyone to filter the channel, which guarantees the real alert is missed too."}]}],"order":[0,2,3,1],"why":null},"prev":{"slug":"the-research-agent","title":"Pattern: search, read, cite, and verify the citation","minutes":10},"next":{"slug":"the-batch-backfill","title":"Pattern: forty thousand rows, once","minutes":9}}}