AbstractAbstractrunQueues a callback to execute in a separate worker context with a fresh request limit.
Creates a NEW execution with its own request limit of ~1000 requests (HTTP requests, tool calls, database operations). This is the primary way to stay under request limits when processing large datasets or making many API calls.
The callback will be invoked either immediately or at a scheduled time in an isolated execution environment. Each execution has ~1000 requests and ~60 seconds CPU time. Use this for breaking loops into chunks that stay under the request limit.
Key distinction:
this.run(callback) - Continues same execution, shares request countthis.runTask(callback) - NEW execution, fresh request limitCallback created with this.callback()
Optionaloptions: { runAt?: Date }Optional configuration for the execution
OptionalrunAt?: DateIf provided, schedules execution at this time; otherwise runs immediately
Promise resolving to a cancellation token (only for scheduled executions)
AbstractcancelCancels a previously scheduled execution.
Prevents a scheduled function from executing. No error is thrown if the token is invalid or the execution has already completed.
The cancellation token returned by runTask() with runAt option
Promise that resolves when the cancellation is processed
AbstractcancelCancels all scheduled executions for this tool/twist.
Cancels all pending scheduled executions created by this tool or twist instance. Immediate executions cannot be cancelled.
Promise that resolves when all cancellations are processed
AbstractscheduleSchedules a one-shot singleton task identified by key: scheduling
under a key that already has a pending task atomically cancels the existing
one and replaces it. At most one scheduled task per key is ever live.
Use this for one-shot keyed deferred work — a single future task whose pending occurrence should be atomically replaced if re-scheduled (e.g. a deferred cleanup, a one-time expiry action, a single future send).
For recurring/self-renewing jobs (watch renewals, polling loops, periodic syncs, self-heal checks), use scheduleRecurring instead. It owns the cadence on the platform side, so the chain survives dropped runs, suspensions, and deploys without the callback needing to reschedule itself.
Replacement is atomic on the server, so concurrent executions racing to schedule the same key converge on a single task rather than leaking.
Coalescing (coalesce: true): instead of replacing the pending task,
an existing one is KEPT — its fire time is pulled earlier when the new
runAt is sooner, but never pushed later. Use this for high-frequency
triggers (e.g. scheduling a sync pass from a provider webhook): a burst of
N calls collapses into a single pending task that fires at the earliest
requested time, where plain replacement would reset the timer on every
call and could starve under a continuous stream. With coalesce: true
the callback you pass may be discarded (when an existing task is kept),
so create a fresh callback for each call and do NOT store or reuse its
token elsewhere.
Stable identifier for this logical task. Scope it to what it
renews, e.g. `watch-renewal:${folderId}`.
Callback created with this.callback()
When to run. Required: keying only applies to scheduled tasks (immediate tasks go straight to the queue).
Optionalcoalesce?: booleanKeep an existing pending task for this key (earliest fire time wins) instead of replacing it.
Promise resolving to the cancellation token for the scheduled task
const cb = await this.callback(this.renewWatch, folderId);
await this.scheduleTask(`watch-renewal:${folderId}`, cb, { runAt });
// ...later, on disable:
await this.cancelScheduledTask(`watch-renewal:${folderId}`);
// Webhook-driven sync: collapse a notification burst into one pass
// that runs a few seconds from now.
const sync = await this.callback(this.incrementalSync);
await this.scheduleTask("incremental-sync", sync, {
runAt: new Date(Date.now() + 10_000),
coalesce: true,
});
AbstractcancelCancels the singleton task previously scheduled under key (if any).
No error is thrown if no task exists for the key or it has already run.
Pair this with scheduleTask in teardown paths (e.g.
onChannelDisabled, stopSync).
The same key passed to scheduleTask
Promise that resolves when the cancellation is processed
AbstractscheduleSchedules a durable recurring task identified by key. Unlike
scheduleTask (one-shot, deleted when it fires), a recurring task's
next occurrence is owned by the platform: the runtime re-arms it every
intervalMs automatically, so the chain survives a dropped queue message,
a suspension, a deploy/eviction, or a callback that throws before it could
reschedule. The callback just does the work, idempotently — it does NOT
need to reschedule itself.
intervalMs is a safety ceiling (the maximum gap between fires). For
data-dependent cadence (e.g. renew 24h before a provider-returned expiry),
pass firstRunAt for the precise next fire and re-call scheduleRecurring
with the same key on each run to keep tightening it; the ceiling guarantees
the chain still fires if a run is lost. firstRunAt can pull the next fire
earlier than the ceiling but never later.
Recurring tasks are keyed/singleton: re-scheduling under the same key atomically replaces the pending occurrence (one live task per key). Tear down with cancelScheduledTask.
Stable identifier, scoped to what it maintains, e.g.
`watch-renewal:${folderId}` or "mailbox-self-heal".
Callback created with this.callback().
Safety-ceiling cadence in milliseconds.
OptionalfirstRunAt?: DateOptional precise time for the next fire (clamped to no later than now + intervalMs).
// Fixed cadence (self-heal, polling): register once, never reschedule.
const cb = await this.callback(this.selfHealCheck);
await this.scheduleRecurring("mailbox-self-heal", cb, { intervalMs: 60 * 60 * 1000 });
// Variable cadence (watch renewal): precise firstRunAt + safety ceiling.
const renew = await this.callback(this.renewWatch, folderId);
await this.scheduleRecurring(`watch-renewal:${folderId}`, renew, {
intervalMs: 3.5 * 24 * 60 * 60 * 1000, // ceiling: half the 7-day watch
firstRunAt: new Date(expiry.getTime() - 24 * 60 * 60 * 1000),
});
Run background tasks and scheduled jobs.
The Tasks tool enables twists and tools to queue callbacks for execution in separate worker contexts. This is critical for staying under request limits: each execution has a limit of ~1000 requests (HTTP requests, tool calls, database operations), and running a task creates a NEW execution with a fresh request limit.
Key distinction:
this.run()) continues the same execution and shares the request countthis.runTask()) creates a NEW execution with fresh ~1000 request limitWhen to use tasks:
Note: Tasks tool methods are also available directly on Twist and Tool classes via
this.runTask(),this.cancelTask(), andthis.cancelAllTasks(). This is the recommended approach for most use cases.Best Practices:
this.callback()Example