wake-on-event — an idle agent that actually sleepsYou want a Claude that's always there — ready the instant a message, a webhook, or a teammate needs it. The naive way to get that is a loop that keeps asking "anything yet?" — and a language model in a busy-loop burns tokens around the clock for the privilege of doing nothing. wake-on-event splits those two ideas apart: a dumb, free watcher stays awake so the expensive model doesn't have to.
"read this page and set the skill up for me") and it has the full spec, verbatim. No copy-paste, no PDF.cheap, always awake expensive, mostly asleep channel ──▶ watcher ──(only on new msg)──▶ Claude session │ │ └── polls a cursor, costs ~nothing └── wakes, acts, advances cursor, sleeps
sleep loop — effectively zero.This is the whole point, so it's worth being blunt about it:
| approach | idle cost | latency to react |
|---|---|---|
| Model in a poll-loop ("anything yet?") | continuous token burn, 24/7 | fast |
| Cron every N minutes | one wake per tick, whether or not there's work | up to N minutes late |
| wake-on-event | ~zero (a shell sleep) | instant — wakes on the event itself |
Run one agent this way and you save a little. Run a fleet of eight this way — most of them idle most of the time — and it's the difference between a hobby and a bill you can hand a CFO.
One command arms a watcher on a channel and hands it a wake target. The commands below are the shipping CLI — they'll work verbatim on release day:
# install the skill into your Claude Code setup $ npx wake-on-event init # arm a durable watcher: poll this channel, wake this session on anything new $ wake-on-event arm --channel inbox --session my-agent → watching. idle cost ≈ 0. next event wakes my-agent instantly.
The watcher re-arms itself across reboots and crashes (see robustness). From then on you don't manage it — you just send things to inbox.
# nothing is happening — the fleet is asleep, spending nothing $ wake-on-event status my-agent · armed · cursor=1042 · idle 3h12m · 0 tokens since last event # a message lands on the channel → the session wakes on its own, # handles it, advances the cursor to 1043, and goes back to sleep.
The discipline the skill teaches the woken Claude is deliberately small: read from the cursor, act, advance the cursor, return to wait. That last step is the one everybody forgets — and the reason naive setups reprocess the same event forever.
An "always-on" pattern is only as good as its worst night. The design assumes things fall over:
| failure | answer |
|---|---|
| The session crashes mid-task | The cursor only advances after the work is done. A crash re-delivers the same event on the next wake — at-least-once, never silently dropped. |
| The machine reboots | The watcher re-arms from a startup hook and reads the durable cursor. No lost events, no manual restart. |
| The watcher itself dies | It's supervised: a heartbeat re-launches it. The expensive part (the model) was never the thing holding the loop open. |
| A flood of events | The watcher coalesces: it wakes the session once and hands it the backlog by cursor range, instead of firing N times. |
You don't have to wait for the release. There's a way in right now:
Like the project? It's free and MIT. If it saves you a token bill and you want to chip in, ♥ support it — entirely optional.
| v0.1 | Public repo: watcher + startup hook + skill, this spec as README, adapters for bus / file / sedev |
|---|---|
| v0.2 | Webhook-queue adapter, coalescing tuning, per-session idle budgets |
| later | Multi-channel fan-in on one watcher · a tiny status endpoint fleet-status can read directly |
Not a concept — it's the loop this very fleet runs on, today. Left before the public release: lifting our channel adapters out of our internal bus, the startup-hook installer, docs. This page carries the repo link the day it's up.