How an agent acts when no program is running
An agent that only answers when spoken to is a function call with a personality. This is what it took to make one act on its own.
An agent that answers when you talk to it is a function call with a personality. You send a message, it thinks, it replies, and in between it does not exist. Everything interesting I wanted to build needed the opposite: something that would notice the time was nine in the morning and start a conversation with me, or watch a value and speak up when it moved.
Motivation
The obvious way to build an agent that acts on its own is to leave a program running. You write a loop that wakes up, checks whether anything is due, does the work, and goes back to sleep. This works, and I nearly did it, and in March 2025 I stopped and asked myself why I kept avoiding it. The question I typed was plain: why am I naturally doing this tick-based approach instead of just having a TypeScript script running in the background?
The answer I gave myself a minute later is the design:
this is meant to exist completely serverless and event based transactions rather than a process running in memory. heartbeat is a virtual CPU
Deployment constraints
A program sitting in a loop has to be somewhere. Some machine holds it in memory, and that machine has to stay up, and if it restarts the agent forgets where it was. One agent is fine. A thousand agents is a thousand things in memory, all of them idle almost all of the time, because an agent that pings you at nine is doing nothing for the other twenty-three hours and fifty-nine minutes.
Serverless is the alternative arrangement, and it means the code does not live anywhere between runs. Nothing is held. Something outside calls the code, the code runs, it finishes, and it is gone again. Whatever must survive is written down rather than remembered.
That constraint sounds like a limitation and it is actually the whole design, because it forces the liveness out of the agent and into the platform. The agent stops being a thing that runs and becomes a thing that gets scheduled.
The heartbeat
A processor does not run your program continuously either. It has a clock, and on every tick it does the next thing, and the illusion of continuous execution is made out of a very large number of discrete steps. Borrowing that arrangement gives a scheduler that ticks, and on each tick asks which agents have something due, and runs only those.
Calling it a virtual CPU is the part I still like, because it names what the platform owns. Programs do not own the clock. The machine owns the clock, and programs are the things it advances. Move the loop into the platform and every agent on it becomes cheap, because an idle agent costs a row that gets checked rather than a process that gets kept.
The other half of the arrangement came from a comparison I made a few hours earlier:
the Idyll = description of behavior, sort of like a React component. and the virtual dom / runtime in our case is simply the reactor that routes events to it
A React component does not run. It describes what should be on screen given some state, and a runtime decides when to call it. An Idyll describes what should happen given some event, and the heartbeat decides when to call it. The behavior is a description and the aliveness is borrowed from underneath, which is why durability comes free: a description can be reloaded, and events that were not delivered can be delivered late.
Tick rate
The tick rate is the one number in this design that has a real derivation, and it is a nice one.
The worst case for a poller is that something becomes due immediately after a check, so the delay before anyone notices is the whole interval between checks. Check once a second and that delay approaches a second. Check twice a second and it stays under half of one, which is what makes it fair to call the resolution second-level: the error is bounded below the unit you are reporting in.
The reason I reached for a name is that this is the same shape as sampling a signal, where resolving detail at some interval means sampling at least twice as often, and I wrote it down that way at the time, that heartbeats activate every half a second giving second-level resolution due to Nyquist. The analogy points at the right answer. What is doing the work underneath is the plainer fact that the tick interval is the worst-case latency, and halving it halves the worst case.
Simulating a hundred thousand events at random offsets against three tick rates shows exactly what that does not buy:
| tick | worst-case lag | seen in a later whole second |
|---|---|---|
| 1.00s | 1.000s | 100% |
| 0.50s | 0.500s | 49.8% |
| 0.25s | 0.250s | 25.0% |
The left column is the guarantee and the right column is the thing people assume follows from it. A half-second tick still puts about half of all events into a later whole second than the one they became due in; something due at 9:00:00.7 is seen at 9:00:01. Halving the tick does not sharpen that, it only halves it, which means no tick rate ever buys same-second observation. What the number buys is a bound on the delay, and that is a different property from the one the sampling analogy suggests.
Two ticks a second, for every agent on the platform, is affordable precisely because a tick is a question and not a process.
Triggers and the work queue
A trigger that fires and immediately does the work is easy to write and hard to recover. If the work fails halfway, the trigger has already fired, and nothing remembers what was supposed to happen.
So the two get split. In March I wrote out the nine in the morning check-in step by step: at nine the scheduler heartbeat adds a function invocation request onto a queue, the way a job queue works, and the flow is then executed one step at a time. The trigger's only job is to enqueue. The queue's job is to survive.
The step-by-step execution has a reason of its own. The flow is stored as a tree, so the runtime can move one node at a time and recover at a node boundary rather than restarting the whole thing. A conversation that dies four steps into a morning check-in resumes at step five instead of asking you the first question again.
Event dispatch
The finer distinction underneath the queue is between calling something and telling everyone that something happened. Working through the Metronome behaviors, I put it this way: it is not scheduling a trigger, it is scheduling an event, and that event gets consumed or dispatched. If you make a call you get a result back and the caller waits for it. If you emit an event you are broadcasting, and nothing is tied to it.
Broadcasting is the right choice when the receiver might not be there yet, might take an hour, or might be three receivers instead of one. It costs you the return value, which is the thing you give up in exchange for not requiring both ends to exist at the same moment.
First Idyll: Metronome
The first Idyll built on all of this was a habit tracker, and renaming it Metronome came from noticing that the runtime metaphor and the product metaphor were the same metaphor.
we're calling the Habit Guardian Metronome now. track and discover your natural rhythm and leverage the rhythm metaphor to develop a cadence of habits in your life. super powerful framework based on my insights about computation and clock cycles, and music. problem with habits and streaks is you lose the momentum. but when you stop thinking in terms of streaks and start thinking in terms of keeping the beat (the music flows). you can feel the discord in rhythm when you try to onboard too many things at once, you can also adjust the BPM of your habit schedule intelligently with AI (stay in motion, play it slowly), the pain of missing beats (zero days), the desire to keep the music going (just get back on the next beat)
A streak is a counter that resets to zero, so one missed day destroys the record and the record was the motivation. A beat is a position in a cycle, so a missed beat is a gap in the music and the next beat is still coming. The platform ticks at two hertz and the habits tick at one a day, and both of them are clocked systems where the tempo is a parameter you can change.
The runtime
Six days before the tick rate got settled, I wrote down what the thing was, after ten months of building other things first:
not a builder, not a chat interface, not an agent framework. it's something that enables a new class of apps to actually be run. i see it now, it's the missing RUNTIME plus its attendant language. and we won't prescribe the language too early we'll discover it incrementally
The implementation call that followed was small and made on iteration speed rather than architecture. The Idyll could have been a JSON document that a runtime interprets, and instead it became a TypeScript class, because a class can be edited while a format has to be designed first. That trade is the right one early and the wrong one late, and knowing which end you are standing on is most of the skill.
Where behavior lives
The part I never settled is where behavior should live. A day after the heartbeat worked I was still writing that the system was done and I did not know what to do during the ticks, or what the intermediate representation should look like. The clock was easy. Deciding what a tick should find waiting for it is the hard part, and it is the same question as what an agent is made of.