Wake
Wake lets you drive your own Primitive Functions on a schedule, or on demand, by sending them a typed command over signed email. A wake is just a structured email your function receives like any other inbound message, except it carries a command (for example process_inbox) and an optional JSON args object, and it is authenticated end to end with DKIM.
Two things make wake more than "email yourself on a cron":
- Typed delivery. A wake is delivered to your function as a typed
interaction.wake.dispatchedevent, not a raw message you have to parse. Your handler branches on the command and readsargs. - Default-deny authorization. A function is woken only by senders you have explicitly allowed. With no grants, nothing can wake it, not even your own other addresses.
Wake is built on the Primitive Interactions standard (wake.dispatch/1): every wake carries a DKIM-signed X-Primitive-Interaction classifier header, and inbound wakes run through the same trust engine as any other interaction.
Availability: Wake is enabled per organization. Until it is turned on for your org, the Wake API and dashboard surfaces return a 403.
The model
A wake moves through a small, typed lifecycle:
- dispatch sends a wake command to the function's address (on a schedule, or one-shot).
- awaiting_result means the function has received the command and is expected to act.
- result / reject / expire is the terminal state: the function reports a result, rejects the command, or the wake expires unanswered.
Each transition emits an event you can subscribe to:
| Event | When |
|---|---|
interaction.wake.dispatched | A wake was authorized and delivered to the function |
interaction.wake.completed | The function reported a result |
interaction.wake.rejected | The function (or the trust gate) rejected the wake |
interaction.wake.expired | The wake was not answered before it expired |
The interaction.wake.dispatched event is the load-bearing one: it is emitted only after authorization passes, and it is what your function endpoint actually receives.
Authorize who can wake a function
Wake is default-deny. Before a sender can wake a function, you grant it explicitly. A grant is scoped by:
- Sender domain (required): the domain the wake must come from, for example
agents.acme.dev. - Sender address (optional): narrow the grant to a single address, for example
scheduler@agents.acme.dev. Omit to allow any address on the domain. - Allowed commands (optional): restrict the grant to specific commands. Omit to allow any command. A command is matched exactly, so list each one you intend to allow.
Manage grants from the Wake tab on a function's page in the dashboard (which resolves the function's receiving endpoint for you), or with the CLI, where you pass that endpoint id directly:
# Allow scheduler@agents.acme.dev to send the process_inbox and refresh commands primitive wake authorizations create \ --endpoint <endpoint-id> \ --domain agents.acme.dev \ --address scheduler@agents.acme.dev \ --command process_inbox --command refresh primitive wake authorizations list --endpoint <endpoint-id>
To wake your own function from your own scheduler, grant the address your schedules send from (see below). A wake whose sender is not covered by an enabled grant is denied before delivery.
Schedule a wake
A schedule sends a wake command to a target address on a cron expression, in a timezone you choose.
| Field | Meaning |
|---|---|
from_address | The address the wake is sent from (must be authorized to wake the target). |
target_address | The function's inbound address that receives the wake. |
command | The typed command, for example process_inbox. |
args | Optional JSON object passed to the handler (capped at 16 KB). |
cron_expr | Standard cron expression for when to fire. |
timezone | IANA timezone the cron is evaluated in, for example America/New_York. |
from_address and target_address must differ; a schedule cannot wake the address it sends from.
Create and manage schedules from Settings then Wake in the dashboard, or with the CLI:
primitive wake schedules create \ --from scheduler@agents.acme.dev \ --to inbox@acme.dev \ --command process_inbox \ --args '{"limit": 50}' \ --cron "0 * * * *" \ --timezone America/New_York primitive wake schedules list primitive wake schedules run <schedule-id> # fire once now, off-schedule
Inspect dispatches
Every wake that fires is recorded. List recent dispatches and their outcomes:
primitive wake dispatches listIn the dashboard, the Wake surfaces show each schedule's next run and each dispatch's status (dispatched, completed, rejected, expired).
Handle a wake in your function
A woken function receives an interaction.wake.dispatched event. Branch on the command and read args:
export default async function handler(event) { if (event.event === "interaction.wake.dispatched") { const { command, args } = event.interaction; if (command === "process_inbox") { await processInbox({ limit: args?.limit ?? 100 }); } } }
The wake is signed and trust-checked before it reaches you: if the event arrived, the sender was authorized for that function and command.
API and SDKs
Everything above is available over the API and SDKs under /v1/wake:
GET/POST /v1/wake/schedules,GET/PATCH/DELETE /v1/wake/schedules/:id,POST /v1/wake/schedules/:id/runGET/POST /v1/wake/authorizations,PATCH/DELETE /v1/wake/authorizations/:idGET /v1/wake/dispatches
The CLI wraps the same operations: primitive wake schedules ..., primitive wake authorizations ..., and primitive wake dispatches list.