Skip to content

Wait for time and events

A weekly routine has a rhythm: the manager answers by Monday evening, contacts go to the BDRs on Wednesday. Motions models time in four ways.

A motion’s calendar names the moments of its week. Each entry resolves to an instant when the batch opens, in the motion’s time zone:

calendar:
manager_answers: { weekday: mon, at: "18:00" } # the next Monday 18:00 after the batch opens
review: { in: 2d } # two days after it opens
launch: { at: "2026-12-01T09:00:00-03:00" } # a fixed instant

Use the keys as question deadlines and checkpoint times. Steps read them as ctx.calendar.manager_answers, in milliseconds since 1970.

A queue’s deadline is a duration from when a question was asked, or a calendar key:

queues:
owner_mismatch: { owner: ari, deadline: manager_answers, default: recommended }

At the deadline, every unanswered question in the queue takes the queue’s default, and the batch moves on. See Ask questions.

Some work finishes later: an enrichment provider that answers in a few minutes, a record someone is about to fill in. ctx.wait stops the step and runs it again after a while:

const job = await ctx.sources.fullenrich.enrich_bulk({ name: company.key, contacts });
const result = await ctx.sources.fullenrich.enrich_bulk_get({ enrichment_id: job.enrichment_id });
if (result.status === "pending") ctx.wait("10m", "waiting for FullEnrich");
  • The cell shows parked with the time it wakes, and the company sits in the parked lane until then.
  • When it wakes, the step runs again from the top, and its CRM reads are fresh.
  • A source call that answered before answers from the cache. A polling operation must not cache its “not yet” answers: FullEnrich’s poll doesn’t, and a custom source returns cache: false for them.
  • every is a duration: 30s, 10m, 4h, 1d.

A checkpoint step runs no code. It holds every company that reaches it until a condition is met, then lets them all through together:

- id: before_distribution
kind: checkpoint
title: Wait for Wednesday
until: { at: distribution }
until Releases when
nothing every company still moving has reached it; parked companies don’t hold it up
at: <key or duration> that time has passed, and no company is still on its way
event: <name> that event has happened, and no company is still on its way
manual: true an approver chooses Release in the batch’s More actions menu

at and event can be combined; both must hold.

An approver can always choose Release early.

An event is a named signal in a batch. A step emits one by returning it:

return { facts, events: [{ type: "gate_passed", data: { score } }] };

Someone with the approver role can also send one from outside, through the API: POST /api/batches/<batch id>/events with { "name": "gate_passed" } and an API token. A checkpoint with until: { event: gate_passed } releases once it has happened.

In tests, pass now and calendar to runStep to fix the clock. See Test a motion.

A batch can also run on a simulated clock that only moves when an approver advances it, so a week of deadlines plays out in minutes. It’s only for tenants in fixture mode on the test CRM, and can only be started through the API (clock: { start } when opening the batch). Once started, the batch screen has Advance the simulated clock.

Motions is built by Pruma. These docs describe what the platform does today; Limitations lists what it doesn't do yet. Open the app