A site for every sport. None of them knew about my week. So I built a small agent that sends me one message. Here is how, for someone who has never built one.
This page is the short version of a 30-minute O'Reilly session, Zero to Agent in 30 Minutes. It skips the code and keeps the shape: what already existed, what had to be built, what one run looks like, and what the process taught. Everything on it is real: the data, the run, the message.
The problem
I had built a website for each sport I follow: the World Cup first, then the WNBA, the NFL, the NBA, the Premier League, and more. Each one is good at its own game: every fixture in my time zone, on the channel I actually have. Then I had a new problem. I did not want that many tabs open. A hub that lists them all helped. What none of the sites could do was answer the only question I had on a Wednesday morning:
Listing games is a script. Deciding which four matter is judgment. Judgment is what the agent is for.
What already existed
Nothing on this list was made for the agent. The build was the connections between them.
Ten schedule sites
Each commits its full schedule to disk as a data file and refreshes nightly. No API to call, no key, no scraping.
Claude Code CLI
One command reads a prompt, calls only the tools it is allowed to call, and exits. There is no server to write.
launchd
The Mac's own scheduler. One file, one integer for the weekday. If the Mac is asleep, it runs at the next wake.
ntfy.sh
One web request to a topic lands as a push notification. Opt-in through a single environment variable.
How it fits together
Two halves. The grey half is deterministic: every question with one right answer (parsing, time zones, "is this a real fixture") is answered by a small tool, the same way every week. The amber half is judgment: a written policy the agent follows. Keeping the halves apart is the whole design.
Grey is the deterministic half and already existed or is plain scripting. Amber is the only thing that had to be authored with care: a policy, written in prose, that says what counts, what to ignore, and what one message looks like.
What one run looks like
- The clock fires. Wednesday, 7:00. launchd starts a shell script; nobody is at the keyboard.
- The script starts the agent with the policy as its prompt and a short allowlist: it may run two scripts and read one file. Nothing else.
- One tool call. The agent runs the schedule reader. A third of a second later it has the week: every game, every caveat flagged, about 7 KB of text.
- Judgment. Sixty to ninety seconds of quiet. Which games involve teams I follow (all of them go in), which one or two others are genuinely worth it, and one "why I care" per line, nine words or fewer.
- One delivery call. The notifier prints the message, raises a banner, writes it to disk, and pushes it to the phone.
- The check. The script looks for the file on disk. If it is not there, the run fails, whatever the agent said. Verify the effect, not the report.
A real message from a real run on September 14, 2026, abridged to fit. The full one is in the repo.
The policy, in seven sections
The policy is a Markdown file, about 1,100 words, written like a job description for a person at a desk who already knows what I like. Its headings run in the order the agent has to decide, and that order is the first best practice: facts before judgment, judgment before output, and the security boundary last.
What the process taught
Every rule in the policy was paid for by something going wrong first. These are the ones worth carrying to your own agent.
Give the agent one door to the data
Read literally, the World Cup file is 104 unplayed matches, a third of them between teams that do not exist. A small deterministic tool absorbs that once. The agent reads a summary and decides.
Write the policy as prose, not settings
A config can say "max 20 lines". It cannot say "zero picks is better than two weak ones" or define real stakes. The reader reasons like a person; write to it like one.
Name the traps anyway
The tool already handles the placeholders. The policy names them too, with examples, so the rule survives a change to the tool.
Verify the effect, not the report
A run once exited cleanly saying "sent" and left no message anywhere, because a tool's output never reaches the log. Now the notifier writes the message to disk and the runner checks for the file.
A UTC timestamp names the wrong day
A 5:20 PM Phoenix kickoff is stamped 00:20Z the next day. An early run reported the wrong evening. The tool now hands over the local date as a sentence, and the policy forbids reading dates off raw timestamps.
An honest empty week is a good week
The title promises never missing a game you care about. That is exactly why the agent must be willing to send two sentences and stop. Padding is the worst thing this job can do.
Keep the allowlist exact and read-only
Two scripts by name, plus Read. Not "any Node command", which would allow arbitrary code. The schedule sites stay untouchable, which is what makes the job safe to schedule.
Two runs will differ, and that is fine
On identical data, one run took an editor's pick and one did not. Both are inside the contract. A run that differs because the prompt drifted is not, so the policy is frozen and tested against the real thing.
Try it yourself
You need a Mac or Linux machine with Node, git, and the Claude Code CLI signed in. There is nothing else to install; the schedule data is fetched from the public repos behind the sports sites. Four commands, and the fourth takes about 90 seconds.
# get the kit and the schedule data git clone https://github.com/ismayc/never-miss-a-game.git cd never-miss-a-game && ./clone-viewers.sh # the data layer on its own, then the whole agent with the finished policy node read-schedules.mjs --days 7 --prefs preferences.json --all git checkout complete && ./run-concierge.sh
Then edit preferences.json: your teams, and a line on why you care about each. That file is the only thing the agent knows about you. If you would rather not edit JSON by hand, use the form: open your preferences.json in it, tick teams, and it writes the same file back (Chrome or Edge save it in place; any browser can download it). To get the message on your phone, install the free ntfy app, subscribe to a long random topic name, and export that name as NTFY_TOPIC before the run; the technical notes have the three steps and a one-line test.
main is the starting point; branch complete is the built-out result with the finished policy and every file the run produced.
How it fits togetherWhat existed, what was built, and each decision with its road not taken.
Writing the policyThe seven sections, the practice behind each, and a checklist for your own.
One real run, file by filePreferences in, tool read, transcript, message, delivery.
Technical notesScheduling, the phone, the safety boundary, the data traps.
The sports trackersThe sites the agent reads, all public.
The preferences formEvery team the ten sites know, as checkboxes over your preferences.json. Try the example without cloning anything.