zepto-claw · Episode 5

I made my AI agent text me first

Every agent waits for you. This one starts the conversation.

Building your own claw — a series where we build a tiny AI agent one small piece at a time.

▶ Watch the build on YouTube

My claw could think, remember, talk on WhatsApp, and even reach into the real world with tools. But it had one thing in common with every agent before it: it only ever spoke when spoken to. It waited.

So I gave it a heartbeat. Now, out of nowhere, my phone buzzes: "💧 Water break! How much did you drink?" I reply "a glass", it estimates ~250ml, logs it, and tells me 1,250 / 3,000 ml for the day. Same brain, same memory as the last four episodes — it just learned to start the conversation.

The problem

Every agent in this series so far has been reactive. You text it, it answers. Useful, but passive — it's a thing you poke. A real assistant doesn't just sit there waiting for you to remember it exists. It checks in. It nudges. It starts the conversation when something matters. The claw couldn't do any of that, because nothing in it ever ran on its own.

The code

The whole lesson is one timer. In whatsapp.ts, when the client is ready, we grab our own chat id and set up an interval that messages it — unprompted.

// whatsapp.ts — a heartbeat: a timer that messages YOU, unprompted.
client.on("ready", () => {
  const me = client.info.wid._serialized;
  onScheduleReminder((hours) => {
    const ms = hours * 60 * 60 * 1000;
    setInterval(() => {
      client.sendMessage(me, "💧 Water break! How much did you drink?");
    }, ms);
    return ms;
  });
});

That's the heartbeat. The other half lives in claude.ts: a tool that runs when you reply, estimating millilitres and reporting the running total against a 3,000ml goal.

// claude.ts — when you reply, the agent logs it and cheers you on.
tool("log_water", "Record water the user drank (estimate ml from 'a glass' ~250).",
  { ml: z.number() },
  async ({ ml }) => {
    addWater(ml);
    return text(`logged ${ml}ml — today: ${waterToday()} / 3000ml`);
  });

There are two siblings to log_water: set_water_reminder (so you can turn the heartbeat on just by asking) and water_today (so you can ask "how much today?" any time). All three read and write a tiny SQLite water table.

How it works

This is the flip. Before today, code only ran in response to a message — a handler woke up, called ask(), and replied. A heartbeat doesn't wait for anyone: setInterval fires on its own and client.sendMessage drops a normal WhatsApp message into your own chat. The agent speaks first.

When you reply — "a glass", "500ml", "done" — that's just an incoming message, so the usual handler runs ask() as always. The model reads "a glass", decides to call log_water with ml: 250, the tool adds it to the water table, and you get back 1,250 / 3,000 ml. You never wrote a parser for "a glass" — the model did the estimating, the tool did the math.

And one timer can do more than nag you about water. The same heartbeat also pushes me a daily news briefing in the morning — one pulse, many nudges.

One caveat — the timer is in memory

This setInterval lives in the running process. Restart the claw and the schedule is gone — you'd have to ask it to remind you again. That's fine for a demo, but a real version persists the schedule: a row in the database it reloads on boot, or a proper cron. The heartbeat should survive a restart the same way the memory does. Small first, though — one timer teaches the whole idea.

Try it yourself

  1. Add the setInterval heartbeat to whatsapp.ts and the log_water / set_water_reminder / water_today tools to claude.ts, backed by a water table.
  2. Run it: npm run whatsapp, then text yourself remind me to drink water every 3 hours.
  3. Wait for the nudge, reply with how much you drank, then ask how much today?

Don't want to wait three hours to see it work? Set WATER_MS=15000 and the heartbeat fires every few seconds instead — perfect for a demo. Watch it text you first, log your reply, and walk your total toward 3,000ml.

Watch the full build: the zepto-claw E05 Short, and subscribe on YouTube for E06. Catching up? Start with E01 · E02 · E03 · E04.