Tasks and schedules
A Task is persisted configuration for running an Agent asynchronously; a Task run is one execution and owns the lifecycle state. Use Tasks for durable background work that runs on demand, once, at an interval, or from a cron schedule.
Task definitions and Task runs
A Task has a tk_… ID, Tenant and Agent ownership, a name, fixed prompt, optional Agent Version Pin, schedule, enabled state, and immutable userId Attribution. Its metadata and other mutable definition fields can be updated. Each submitted or scheduled execution creates a tr_… Task-run ID and copies the Task's Agent, resolved Version, prompt, userId, and metadata.
The Task is only the definition. activeRunId, latestRunId, and recent terminal summaries make run state visible without turning the definition itself into an execution.
Choose on-demand, once, interval, or cron
| Schedule | Configuration | Behavior |
|---|---|---|
| On demand | schedule: null | Runs only through createRun or submit: true during creation |
| Once | { kind: "once", config: { at } } | Runs at one ISO 8601 timestamp; an overdue fire found by recovery starts immediately |
| Interval | { kind: "interval", config: { everyMs } } | Runs on creation-anchored boundaries; minimum 60,000 ms |
| Cron | { kind: "cron", config: { expression, timezone, staggerMs? } } | Uses five numeric cron fields and an IANA timezone; timezone defaults to UTC |
Cron staggerMs optionally spreads load by a stable per-Task offset without crossing the next cron boundary. Cron scheduling disables automatic backfill, and interval reconciliation selects the next future creation-anchored boundary rather than replaying missed intervals.
Changing the schedule or enabled state advances its schedule generation so stale fires cannot create runs. Disabling a Task removes scheduled execution but does not disable its Agent, erase the definition, or prevent an explicit on-demand createRun; scheduled fires for a disabled Agent are skipped. Enabling allows the next eligible fire—skipped cron or interval fires are not added later as catch-up runs.
Run lifecycle and fresh Sessions
Public run statuses are:
| Group | Status | Meaning |
|---|---|---|
| Active | queued | Admitted as the Task's active run and awaiting execution |
| Active | running | Execution began and cancellation can be observed cooperatively |
| Terminal | blocked | Quota stopped the run before execution completed |
| Terminal | succeeded | The Turn completed successfully |
| Terminal | failed | Preparation or execution failed |
| Terminal | canceled | A queued or running cancellation request took effect |
An executing run receives one fresh ss_… Session, attached before the live Turn. Its messages persist incrementally and can be read through runMessages; a quota-blocked run can finish without a Session. The Version Pin stored on the Task is resolved when the run is enqueued; otherwise the Agent's then-current Version is recorded on the run and its usage.
Submit and inspect a run
Create an on-demand Task, submit a run with a stable idempotency key, then poll the run resource. Once terminal, read the fresh Session transcript through the Task-run operation.
const { task } = await client.tasks.create({
agentId,
name: "Account report",
prompt: "Prepare the account report as concise Markdown.",
});
const { runId } = await client.tasks.createRun(task.id, {
idempotencyKey: "account-report:2026-07-20",
});
const terminal = new Set<string>([
"blocked",
"succeeded",
"failed",
"canceled",
]);
let run = await client.tasks.getRun(task.id, runId);
while (!terminal.has(run.status)) {
await new Promise((resolve) => setTimeout(resolve, 1_000));
run = await client.tasks.getRun(task.id, runId);
}
if (!terminal.has(run.status)) {
throw new Error("Task run did not reach a terminal state");
}
const transcript = await client.tasks.runMessages(task.id, runId);
console.log(run.status, transcript.data);For a blocked run, runMessages returns an empty transcript, but sessionId depends on which quota gate stopped it. An early Task preflight blocks before Session creation, so sessionId is null; the ordinary Turn quota gate runs after the fresh Session is attached, so a run blocked there retains that Session ID with no messages. Inspect run.error and run.status to distinguish the outcome from execution failure.
Idempotency, overlap, and cancellation
An on-demand idempotency key is scoped to its Tenant and Task. Repeating the same key resolves to the same admitted run ID, including concurrent retries while that run is active. Task creation with submit: true is not idempotent because it creates a new Task and run.
At most one queued or running run exists per Task. While one is active, a new submission without an idempotency key or with a different key is rejected; a retry with the same key returns the already admitted run. A scheduled overlap is skipped without a Task-run row. Cancellation is cooperative: cancelRun records the request, queued cancellation terminalizes before execution, and a running worker stops when it observes the request.
Quotas, failures, and recovery
Task execution has an early quota preflight before Session creation or Sandbox admission. After the run attaches its fresh Session, the ordinary Turn gate checks quota again before the model and Tool loop. Either quota denial produces terminal blocked, distinct from execution failed; a Turn that starts execution is metered under the same usage model as interactive work.
Run submission directly enqueues a deterministic durable workflow. Reconciliation repairs schedule drift, and a separate repair scan re-enqueues a committed queued run if enqueue failed. Recovery returns an already recorded product outcome without rerunning the Turn; an interrupted, unrecorded attempt becomes failed. This is at-most-once Turn execution, not exactly-once execution or exactly-once external side effects.
See usage and quotas and limits and reliability for platform-wide behavior.
Related guides
- Run a background Task
- Schedule recurring work
- Monitor usage and quotas
- Sessions and Turns
- Versions and lifecycle