heartbeat: a memoryless agent on a timer¶
The problem¶
You want an agent to work on something big while you are away. One long session drifts, runs out of context, or dies halfway with nothing written down. Running it on a schedule is easy; the hard part is that every run starts blank, so it redoes work, wanders off, or trips over the run before it.
The method¶
Treat the blank start as the design. The agent's whole memory is a folder it reads first and writes last:
TASK.md the goal and the boundaries (the human writes this)
STATE.md done / next / blockers (the agent keeps it true)
PROTOCOL.md on waking: read, pick ONE chunk, do it, record, exit
journal/ one note per wake: Did, Learned, Next, Left open
work/ the output
The runner stays thin and does only what the agent should not think about:
- Gate before counting. Any command; non-zero skips the wake without using up the cap. Gate on battery, load, a usage budget, office hours.
- Cap. Stop after N wakes, so a forgotten timer cannot run forever.
- No overlap. A slow wake makes the next tick skip, never run twice.
- Distrust a quiet success. An agent can exit 0 having done nothing; the log says so.
- Tell it what it cannot know. The prompt carries the time (for the journal file name), whether the folder is shared, and the full path of the claim command. A solo agent told nothing tried to claim anyway and stalled on a tool it could not run.
Locks are mkdir, which is atomic: exactly one caller makes the directory. The
same trick lets two agents share one folder without doing the same chunk.
Because the brain is a Markdown file, you retune the agent by editing
PROTOCOL.md, not code.
Sample¶
The lock, in POSIX sh:
take() { # take LOCKDIR ID
if mkdir "$1" 2>/dev/null; then echo "$2 $(date +%s)" >"$1/owner"; return 0; fi
read -r _ when <"$1/owner" 2>/dev/null || return 1 # held, owner mid-write
[ $(( $(date +%s) - when )) -gt "$STALE" ] || return 1 # held, fresh
mv "$1" "$1.stale.$$" || return 1 # rename is atomic: one taker gets past
rm -rf "$1.stale.$$"; mkdir "$1" || return 1
echo "$2 $(date +%s)" >"$1/owner"
}
Use it:
heartbeat init ~/jobs/docs # then edit TASK.md
heartbeat wake ~/jobs/docs # one wake by hand
heartbeat schedule ~/jobs/docs 15 # launchd plist or systemd timer
Any agent CLI works: set HEARTBEAT_AGENT to a command that takes the wake
prompt as $1. Set HEARTBEAT_SHARED=1 when several agents share a folder.
Limits¶
- The agent must keep STATE.md honest; the journal is the backstop.
- One wake does one chunk. Size chunks in TASK.md.
- Claims stop honest agents duplicating work. They are not a security boundary.
- The cap stops wakes, not the timer; unload it when done.
Code¶
methods/heartbeat/ in the methods repository: the script, a README and a
test that drives it with a stand-in agent.