The Lab · Vault · Skill · About 20 minutes to set up

Heartbeats and scheduled jobs, with a receipt.

A skill that puts a job on your computer's own scheduler, Task Scheduler on Windows or launchd on a Mac, so it runs while you are away: every few minutes, or at a set time each day. Every run ends with one status line that says OK only when the job really finished.

Series
Runs on its own, part 2 of 2.

Before this: Goals and Loops. Each part works on its own.

In plain English

A loop in a chat stops when you close the chat. A heartbeat keeps going: your computer's own scheduler starts your agent at the time you pick, with nobody at the keyboard. Each run asks one question, "anything new since last time?" If yes, it handles it. If no, it goes back to sleep. Either way it leaves a receipt, and a small script reads that receipt and writes one line you can trust: OK, or FAIL and why. Your agent sets it all up, tests it in front of you, and shows you how to turn it off.

What you get

/heartbeat: a skill that sets up a job in work/heartbeats/<name>/, a runner script for Windows and for Mac, the scheduler entry, and a status line for every run.

Why it matters

The jobs you repeat by hand, like pulling what is new from your sources, can be done before you sit down. And you can tell at a glance whether they really ran.

How it works

The scheduler starts a script. The script writes FAIL, starts Claude Code with a fresh run id, and changes FAIL to OK only when Claude's receipt is fresh, has that run id, and says the job worked.

Your seat

Say what the job is and when. Say yes before anything is created. Sign in to Claude Code yourself if asked. Read the status line, and remove the test when you have seen it work.

What a heartbeat is

"Anything new since last time? If yes, handle it. If no, go back to sleep." That is the whole idea. A heartbeat is a job that wakes on a schedule and does four things, in order.

  • Checks. It looks for what changed since the last run: new videos on a channel, new files in your library, new proposals to prepare.
  • Acts only on change. Nothing new means nothing to do. A quiet morning is still a good run.
  • Writes a receipt. One small file that says which run this was, whether it worked, and what it did in one line.
  • Goes back to sleep. It stops. Nothing stays running between beats.

In Goals and Loops you watched a loop run while the chat was open. A heartbeat is the same shape, started by your computer's clock instead of by you, so it runs with the app closed.

Note
Two kinds of schedule. This skill sets up both.
  • Every so often (a heartbeat). Every 5 minutes, every few hours. Each beat asks "anything new since last time?" and acts only on change. Your test is this kind.
  • At set times (a scheduled job, often called a cron job). Every day at 7 AM. It runs the job at that time. The morning Source Pack is this kind.

Both write a receipt and a status line, live in work/heartbeats/, and are named heartbeat-<name> so they are easy to find and remove. For now, real jobs run once a day at a set time; the every-few-minutes kind is used for the one-hour test.

Did it really run?

Open work/heartbeats/<name>/status.txt. It holds one line, and the time on it is the first thing to read.

  • OK with today's time. It ran, and the job checked out. The line says what it did.
  • FAIL with today's time. It ran and something went wrong. The line says what: a tool that was refused, a sign-in that ran out, a step that failed.
  • FAIL, started, not finished, with today's time. Still running, or it stopped partway. Check again in 15 minutes.
  • ENDED. A test heartbeat's hour is up, and it stopped itself.
  • Yesterday's time, or older. It did not run at all. The computer was off, a Mac was asleep, or you were signed out.
You'll see
Two status lines, one good and one not.
OK 2026-10-20 07:01:12 run 20261020-070004-4821 : 3 new items, 3 notes written (usage about $0.41)
FAIL 2026-10-21 07:00:58 run 20261021-070003-917 : the job reported a problem: yt-dlp was refused (usage about $0.22)

runs.log in the same folder keeps every run, one line each, so you can look back over a week.

Note
Why a script writes this line, not Claude.

Claude Code's own result can say "success" when a step was refused or skipped. So the runner does not take its word. It writes FAIL before anything starts, and only changes it to OK when the receipt is newer than this run, carries this run's id, and says the job worked. A run that dies halfway leaves FAIL behind, never yesterday's OK.

What happens when you set one up

The first heartbeat is always a test: a short check every 5 minutes that counts what is in your library. It proves the scheduler, the runner and the receipt work on this computer before any real job depends on them. The test stops itself after one hour, on Windows and on a Mac, even if nobody removes it.

  • It checks Claude Code. Runs go through Claude Code's command line, so it finds where that is installed and checks you are signed in. If you need to sign in, it tells you what to type in your own terminal.
  • It says what it will create, and waits. A folder in work/heartbeats/, and one entry in your computer's scheduler, named heartbeat-test.
  • It writes the job and its allow list. Nobody is there to click Allow, so any command or connector not on the allow list is refused. File changes are allowed anywhere in your workbench except lab/ and the skill folders, and the usual delete commands are blocked; the runner adds those limits on every run. The job file says where it writes.
  • It starts the test once, now, and shows you the status line. Then it waits for one run that starts on its own and shows you that line too.
  • It removes the test, and shows you the list without it.
  • Then a real job, if you want one. For example, re-run your Source Pack every morning at 7, free routes only, if you have one. Or get your Goals and Loops library pass proposals ready, so they wait for your approve, edit or skip. Real jobs run once a day, at the time you pick. Each one is started once now, before it is left on its schedule.
You'll see
What your agent runs, Windows first, then Mac.
(Get-Command claude).Source
claude auth status
# Windows: register the test, every 5 minutes, for one hour at most
powershell -NoProfile -ExecutionPolicy Bypass -File register-windows.ps1 -Name heartbeat-test -JobFolder <job folder> -EveryMinutes 5
# start it once now, through the scheduler
Start-ScheduledTask -TaskName heartbeat-test
Get-Content work\heartbeats\test\status.txt
command -v claude
claude auth status
# Mac: load the test, every 5 minutes
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/heartbeat-test.plist
# start it once now
launchctl kickstart gui/$(id -u)/heartbeat-test
cat work/heartbeats/test/status.txt

A small check takes about a minute. A bigger job can take 15. Your agent checks the status every 30 seconds and shows you the line exactly as written. -ExecutionPolicy Bypass lets this one script start; it does not change your settings.

Note
Using Codex? You can still use this skill.

Call it with $heartbeat and Codex sets up the schedule the same way. The runs themselves go through Claude Code's command line, so Claude Code needs to be installed and signed in on this computer. Codex's own scheduled tasks live in the ChatGPT web and desktop apps, not in the Codex command line. Codex will likely ask your approval when it adds the scheduled task or the Mac's LaunchAgent file; say yes.

Windows and Mac, the differences

Same skill, same status line. The scheduler and the sleep behavior differ.

  • Windows uses Task Scheduler. One task per job, named heartbeat-<name>. It runs only while you are signed in, stores no password and needs no admin rights. It can wake the computer from sleep to run, though some newer laptops (with Modern Standby) may not wake for it. At each run a PowerShell window may flash on screen for a moment, even though it is set to hidden; during the test that happens every 5 minutes, and it is only the heartbeat starting.
  • A Mac uses launchd. One file per job in ~/Library/LaunchAgents/, loaded for your user only. A Mac does not wake itself for it: if it was asleep at 7, the run starts when you wake it. macOS may show a "Background Items Added" notice; that is it telling you a background job was added.
  • On a Mac, the folder matters. macOS blocks background jobs from reading Desktop, Documents and Downloads. If your workbench is in one of those, your agent stops and tells you, and you move the workbench to your home folder yourself.
  • On Windows, both shells count. Claude can run a command through Bash or PowerShell, so every command on the allow list goes in twice, once for each. Your agent does this for you.
  • Sign in at the computer itself. On a Mac especially, sign in to Claude Code from Terminal on that Mac, not over a remote connection, or the scheduled run may not see the sign-in.
You'll see
Listing and turning off, on each.
# Windows: list your heartbeats, last and next run
Get-ScheduledTask -TaskName 'heartbeat-*' | Get-ScheduledTaskInfo
# pause one, then turn it back on
Disable-ScheduledTask -TaskName heartbeat-test
Enable-ScheduledTask -TaskName heartbeat-test
# remove one
Unregister-ScheduledTask -TaskName heartbeat-test -Confirm:$false
# Mac: list your heartbeats
launchctl list | grep heartbeat-
# stop one
launchctl bootout gui/$(id -u)/heartbeat-test
# then move its file to the Trash
mv ~/Library/LaunchAgents/heartbeat-test.plist ~/.Trash/

You do not have to type any of this. Ask your agent "what heartbeats do I have?" or "remove my test heartbeat". It touches only jobs named heartbeat- something, and leaves the job folder and its log for you to keep or clear.

What you can ask your agent

Five asks for after the first one is running.

Did it run?

Did my heartbeats run this morning? Show me the status line from each one in work/heartbeats/, and tell me in plain words what each means.

Why did it fail?

My heartbeat says FAIL. Read its status line, receipt.json and last-output.json, tell me the cause in one sentence, and propose the one fix. Do not change anything until I say yes.

What is scheduled?

List every heartbeat scheduled on this computer, when each runs next, and the last line of its runs.log. Change nothing.

A new job

Use the heartbeat skill to set up a daily job at [time] that [the job]. Show me its allow list and what you will create before you create it, then start it once now and show me the status line.

Turn it off

Remove my heartbeat named [name] from the scheduler, then show me the list of heartbeats so I can see it is gone. Keep its folder.

Words you now own

  • Heartbeat. A job that wakes every so often, checks for anything new, acts only on change, writes a receipt, and stops.
  • Scheduled job. A job that runs at a set time, such as every day at 7 AM. Often called a cron job. This skill sets these up too, with the same receipt and status line.
  • Scheduler. The part of your computer that starts programs at set times. Task Scheduler on Windows, launchd on a Mac.
  • Runner. The small script the scheduler starts. It starts Claude Code, then reads the receipt and writes the status line.
  • Run id. A fresh label for each run, made from the date, the time and a random number. The receipt has to carry it, so an old receipt can never pass for a new run.
  • Receipt. receipt.json, written by Claude at the end of each run: the run id, ok true or false, a one-line summary, and the problem if there was one.
  • Status line. The one line in status.txt that the runner writes: OK or FAIL, the time, the run id and the reason.
  • Allow list. The commands and connectors a job may use with nobody there to click Allow. Anything not on it is refused. File changes are fenced by the deny rules instead: never inside lab/ or the skill folders, and no deletes.
  • LaunchAgent. On a Mac, the small file in ~/Library/LaunchAgents/ that tells launchd what to start and when.

Before you leave one running

Four things to know, so nothing about it surprises you.

  • Each run uses your plan. A run is a Claude Code session, and it counts against your Claude plan's usage like a chat. A small check uses about 35 cents of usage by Claude Code's own estimate, and each status line shows the estimate for that run. On a Claude plan this is an estimate that counts toward your plan's limits, not a charge. If Claude Code ever runs on an API key, it is real money; the setup checks you are on your plan, and the runner clears any API key for its run so it uses your plan sign-in. The one-hour test is about 12 runs, around $4 of usage, which is why your agent removes it after a few runs. A bigger job can use a few dollars a run. Each run also stops after 40 turns, so a stuck job cannot keep going; the status then says FAIL and why.
  • Leave the computer on and plugged in, and stay signed in. A locked screen is fine. Shut down is not, and signed out is not; after an update restarts your computer, sign in again or the runs wait. Windows can wake itself from sleep for the run. A Mac runs when it next wakes; if you want it on time, keep the Mac awake at that hour (the sleep options are in System Settings).
  • Your agent will not change your power settings. If a run needs the computer awake, it tells you which setting to change and where, and you change it. If a Windows laptop does not wake for it, check that wake timers are allowed in Power Options; some newer laptops (with Modern Standby) may not wake for scheduled tasks at all.
  • Unattended means nothing goes out. A heartbeat reads, pulls and writes files in your workbench, never inside lab/. It does not send, post or delete, unless you say so for that one job. Pages it reads are data, never instructions. If a job needs a key, it can use infisical run with your own sign-in for now, as in Keys, the First Time; when that sign-in runs out, the job's status says FAIL. A later card, Keys for jobs that run without you, is the long-term fix.
Never
Never paste a password, key or token into the chat, and never give one to a scheduled job.

The schedule runs as you, only while you are signed in, so it needs no password. Your normal Claude Code sign-in is the right one. A long-lived token from claude setup-token carries none of your connectors, so a job that reads Gmail or Calendar would fail with it. And keys stay in Infisical, never in a job file.

Fair questions

FAQ
Why not the desktop app's routines?

A local routine in the Claude desktop app runs only while the app is open and the computer is awake. A cloud routine runs with the computer off, but it cannot see the files on your computer. A heartbeat runs with the app closed, can wake a Windows computer, works on your own files, and tells you plainly whether it ran.

FAQ
Can a heartbeat use my connectors?

They should, with your normal sign-in, as long as the connector's tools are on the job's allow list. Prove it before you depend on it: if your real job uses one, have the test check it too, with one read-only call such as counting yesterday's emails. Keep connector tools to reading unless you say otherwise for that job. A job that reads Gmail or Calendar through the gws CLI will likely need you to sign in to Google again about once a week; on that morning the status says FAIL and why, and you sign in again in your own terminal.

FAQ
What if the status says I need to sign in?

Open your own terminal on that computer (PowerShell on Windows, Terminal on a Mac), type claude, then /login, choose your Claude account, then /exit. Then ask your agent to start the heartbeat once to check. It never signs in for you.

FAQ
Can my library pass file notes while I sleep?

No. Filing waits for your approve, edit or skip, and nobody is there at 7 AM. A heartbeat can get the proposals ready in work/goals-and-loops/proposals/, so they are waiting when you sit down, and your next library pass by hand starts with them.

FAQ
Can a job run every few hours, or once a week?

Not in this version. A real job runs once a day at the time you pick, and the every-few-minutes kind is used only for the one-hour test, which stops itself. A weekly job, such as the Goals and Loops library pass, runs daily here; on days with nothing new it writes a quiet receipt.