Skip to content

Ask the docs

Answers come only from these docs and are written by an AI model (Gemini), so check the linked pages. Your question is not kept. Do not include personal details or keys.

Wake-ups: the agent comes back later

Wake-ups

Let 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.

Every agent’s model has two built-in tools for the conversation it is in:

ToolDoes
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.

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.

src/tools.ts
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:

src/agent.ts — the /answers route
await agent.cancelWakeUp(`remind:${id}`);
await agent.cancelWakeUp(`close:${id}`);

context.wakeUps is absent outside a conversation (a tool called over MCP), hence the ?..

CallResult
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, or unknown_person for 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.

  • Quiet hours. A wake-up due in their quiet hours waits for them to end. Pass respectQuietHours: false for 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.
LimitValue
Active wake-ups per person10
Active wake-ups per agent10,000
Furthest ahead90 days
Note500 characters
Key200 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.

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.