Wake-ups: the agent comes back later
Wake-upsLet the agent give itself a turn later, with a note, to check on something no one will write about.
Sometimes the agent has to look again later, and no one will write to prompt it. For example, 24 hours after a buyer’s question it should check whether the seller answered, and remind them if not; at 48 hours it should close the question and tell the buyer. A wake-up handles this. It is a row in the agent’s store, never a timer in memory. At its time the agent gets a turn in that person’s conversation with the note it left itself, and decides what to do: look something up, message them, call a tool, or nothing.
Wake-ups need @flow-engineer/flow 0.8.0 or later.
Because a wake-up lives in the same store as the conversations, it survives restarts and deploys, and it runs once even when several copies of the agent run at the same time.
The model sets its own
Section titled “The model sets its own”Every agent’s model has two built-in tools for the conversation it is in:
| Tool | Does |
|---|---|
wake_me_up({ note, in_minutes | at, key? }) | Sets a wake-up. at is YYYY-MM-DD HH:MM, read in the time the agent is told it is (the person’s, else the business’s) |
cancel_wake_up({ id }) | Cancels one of its wake-ups in this conversation |
Each turn, the agent’s prompt lists the wake-ups it set in that conversation, so it knows what it already has. When one comes due, its turn starts with:
[Wake-up you set for yourself, not from the person: check whether the seller answered question Q7]Decide now what to do: check what you need, message them, or nothing. Nothing is sent unless you send it.To take both tools and the list away, set wakeUps: false on createAgent. Wake-ups your code
sets still run.
Set them from code
Section titled “Set them from code”A tool reaches the wake-ups of the person it runs for as context.wakeUps. This extends the
ask_seller tool from the agent’s own records. When a question is asked, it sets two
wake-ups, and a new close_question tool closes a question that is still waiting. Add "closed"
to the Question status type.
const HOUR = 3_600_000;
// In ask_seller, after context.records.put(...):await context.wakeUps?.add({ inMs: 24 * HOUR, note: `Check question ${id}. If it still waits, tell them the seller has not answered yet.`, key: `remind:${id}`,});await context.wakeUps?.add({ inMs: 48 * HOUR, note: `Check question ${id}. If it still waits, close it with close_question and tell them.`, key: `close:${id}`,});
// Inside the tools array:tool({ name: "close_question", description: "Close a buyer's question the seller has not answered.", input: z.object({ id: z.string() }), run: async ({ id }, context) => { const question = await context.records.get<Question>("questions", id); if (!question || question.status !== "waiting") return { status: question?.status ?? "unknown" }; await context.records.put("questions", id, { ...question, status: "closed" }); return { status: "closed" }; },}),When the seller answers, cancel both by key. In the /answers route from the records guide,
before the nudge:
await agent.cancelWakeUp(`remind:${id}`);await agent.cancelWakeUp(`close:${id}`);context.wakeUps is absent outside a conversation (a tool called over MCP), hence the ?..
| Call | Result |
|---|---|
context.wakeUps.add({ note, inMs | at, key?, respectQuietHours? }) | Sets one in this person’s conversation. at is milliseconds since the epoch |
context.wakeUps.cancel(key) | Cancels the active wake-ups with this key, in every person’s conversation. Resolves to how many |
context.wakeUps.list() | This person’s wake-ups still to come, soonest first |
agent.wakeUp({ to: { channel, address }, ... }) | The same as add, for someone by a channel identity they wrote from |
agent.cancelWakeUp(key) | The same as cancel |
agent.listWakeUps({ channel, address }) | The same as list |
add and agent.wakeUp resolve to one of:
{ status: "scheduled", wakeUp }: it is set.{ status: "exists", wakeUp }: an active wake-up of that person’s already has the key, and you get it back unchanged. Once it has run or been cancelled, the key is free again, so cancelling then adding moves it.{ status: "refused", reason }:too_many_for_person,too_many_for_agent, orunknown_personfor someone who never wrote.
A note that is empty or too long, or a time under a minute away or more than 90 days ahead, throws.
The person’s rules still apply
Section titled “The person’s rules still apply”- Quiet hours. A wake-up due in their quiet hours waits for them to end. Pass
respectQuietHours: falsefor work that messages no one. The model’s own wake-ups always wait. - STOP or a pause. After they texted STOP, or while they paused messages, the wake-up still runs so the agent can do its work, such as closing the question. Its note says so, and Flow refuses every send to them in that turn until they write.
- WhatsApp’s 24-hour window. Past the window it still runs. The agent is told that only a
template reaches them, and can send one of yours with
send_template. No fallback template goes by itself. - Late. A wake-up found late (the agent was down) still runs, and the note says when it was due. A scheduled message found late is skipped instead.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Active wake-ups per person | 10 |
| Active wake-ups per agent | 10,000 |
| Furthest ahead | 90 days |
| Note | 500 characters |
| Key | 200 characters |
They are exported as WAKE_UP_LIMITS. Past a limit, the model gets a plain error that says what
to do. agent.forgetPerson deletes the person’s wake-ups along with everything else about them.
See and move them in the playground
Section titled “See and move them in the playground”The playground’s Person view lists the agent’s wake-ups for the person, beside their scheduled
messages. With the playground clock connected, moving the clock brings them due. In
a business scenario, wait does the same.
Check it worked: ask a question as Maria, then open Person. You should see two wake-ups, one a day ahead and one two days ahead. Move the clock by one day twice: the agent takes its turn each time, and the second turn closes the question in Records.
Thanks, we read every comment.