Operator APIs
Inspect, cancel, rerun, retry, and prune durable runs.
Operator APIs work on runs, not task definitions. They are the runtime surface for production maintenance tools, CLIs, dashboards, and support paths that need to inspect durable history or make an auditable control-plane change.
The core surface lives under runlane.runs:
const run = await runlane.runs.get(runId)
const page = await runlane.runs.list({ limit: 50, statuses: [RunStatus.Failed] })
const activeSync = await runlane.runs.findActive({ task: syncInvoices, singletonKey })
const currentRequest = await runlane.runs.findCurrent({ task: syncInvoices, idempotencyKey })
const attempts = await runlane.runs.attempts(runId)
const events = await runlane.runs.events(runId, {
sortBy: RunEventSortField.Sequence,
sortDirection: SortDirection.Asc,
})Reads are scoped to the runtime environment and use adapter-owned cursors. Core validates filters and passes them to storage; storage owns efficient indexes and cursor encoding.
The read APIs require both lane.capabilities.operatorReads and storage.capabilities.readsRunHistory:
get()list()events()attempts()findActive()findCurrent()- read-backed control actions:
cancel(),rerun(), andretry()
Core must inspect the current run before returning data or appending an operator-controlled change.
findActive() is an advisory UX helper over run filters. It requires a task plus an idempotency key or singleton key and returns an active run when one currently matches. It can race and should not be used as the correctness boundary; trigger() plus storage idempotency or singleton ownership is the correctness boundary.
findCurrent() is task-scoped idempotency lookup. It returns the active owner or the retained terminal owner for one idempotency key, following the same retention rules as duplicate trigger() calls.
runlane.idempotencyKeys.reset(task, { key }) is the idempotency escape hatch, not a force-run option. It requires storage.capabilities.enforcesIdempotency, clears one retained terminal owner for the runtime environment and task id, and rejects active owners.
attempts(runId) returns derived attempt summaries for dashboards and support tools. Raw events(runId) remains canonical history. RunSummary.failure and RunRecord.failure expose the current visible failure state, while attempts() shows previous retry/release/failure outcomes without embedding attempt history into every run projection.
Read Options
runlane.runs.list(options):
| Option | Default | What it filters or controls |
|---|---|---|
cursor | None | Continues a previous page from storage. |
limit | contractDefaults.pagination.defaultLimit | Maximum summaries to return, capped by storage/default pagination policy. |
statuses | All statuses | Durable run statuses to include. |
taskIds | All tasks | Task ids to include. |
queues | All queues | Queue definitions to include; core resolves each against runtime.queues. |
idempotencyKey | None | Runs with this task-scoped idempotency key. |
singletonKey | None | Runs with this singleton key. |
sourceRunId | None | Linked children created from a source run. |
createdAt | None | { from?, to? } inclusive created-time bounds. |
updatedAt | None | { from?, to? } inclusive updated-time bounds. |
runAt | None | { from?, to? } inclusive due-time bounds. |
sortBy | contractDefaults.sort.runs.field | created_at, updated_at, or run_at. |
sortDirection | contractDefaults.sort.runs.direction | asc or desc. |
runlane.runs.events(runId, options):
| Option | Default | What it filters or controls |
|---|---|---|
cursor | None | Continues a previous event page. |
limit | contractDefaults.pagination.defaultLimit | Maximum events to return. |
types | All event types | Event types to include. |
occurredAt | None | { from?, to? } inclusive event-time bounds. |
sortBy | contractDefaults.sort.runEvents.field | occurred_at or sequence. Sequence sorting is meaningful inside one run. |
sortDirection | contractDefaults.sort.runEvents.direction | asc or desc. |
findActive() and findCurrent():
| Method | Required options | Meaning |
|---|---|---|
runlane.runs.findActive({ task, idempotencyKey?, singletonKey? }) | task plus at least one key | Advisory active-run lookup for UX. Correctness still belongs to storage-enforced trigger(). |
runlane.runs.findCurrent({ task, idempotencyKey }) | task, idempotencyKey | Reads the current active or retained terminal idempotency owner. |
runlane.idempotencyKeys.reset(task, { key }) | key | Clears one retained terminal idempotency owner. Active owners reject. |
Action Options
Operator actions share audit fields:
| Option | Default | Used by | What it records |
|---|---|---|---|
actor | contractDefaults.actor.operator where applicable | cancel, rerun, retry, prune | Operator identity recorded on audit events or prune commands. |
meta | None | cancel, rerun, retry | JSON object with action context. |
traceCarrier | None for cancel; source run trace carrier for linked runs | cancel, rerun, retry | Trace context carried into linked run creation or audit events. |
Action-specific options:
| Method | Option | Default | What it controls |
|---|---|---|---|
cancel(runId, options) | reason | None | Public cancellation reason stored on the cancellation event. |
rerun(runId, options) | queue | Source run queue | Queue definition for the linked child run. |
rerun(runId, options) | runId | Generated child run id | Caller-supplied id for the linked child. |
retry(runId, options) | queue | Source run queue | Queue definition for the manual-retry child run. |
retry(runId, options) | runId | Generated child run id | Caller-supplied id for the manual-retry child. |
prune(options) | olderThan | Required | Duration string relative to runtime clock or explicit Date cutoff. |
prune(options) | statuses | All terminal statuses | Terminal statuses to prune. Active statuses are rejected. |
prune(options) | limit | contractDefaults.pruning.batchLimit | Maximum terminal runs storage prunes in one call. |
prune(options) | cursor | None | Continues a previous prune with the same frozen retention scope. |
Cancellation
runlane.runs.cancel(runId, options) preserves history.
| Run state | Cancellation behavior |
|---|---|
| Queued, released, retrying, or scheduled | The run becomes terminally cancelled immediately. |
| Running in the same runtime | Core writes run.cancellation_requested, then aborts the active task context signal. |
| Running in another runtime | The owner observes the request during lease monitoring and stops extending the lease. |
| Owner died or never cooperates | tick() records terminal cancellation after the cancellation-requested lease expires. |
If cooperative task code stops cleanly, core records run.cancelled. If task code throws during cancellation or cleanup, the attempt remains a failure.
await runlane.runs.cancel(runId, {
actor: { type: ActorType.Operator, id: 'ops@example.com' },
reason: 'operator_requested',
})Task code must cooperate by checking or passing context.signal:
const syncAccount = task({
id: 'accounts.sync',
schema: accountSyncSchema,
async run(payload, context) {
for await (const page of provider.listPages(payload.accountId, { signal: context.signal })) {
if (context.isCancellationRequested()) {
return
}
await persistPage(page, { signal: context.signal })
}
},
})Cancellation is not a hard process kill. If user code never observes the signal and never returns, the durable run remains cancellation_requested until the active lease expires and maintenance terminally cancels it.
Repeating cancel() for a cancellation_requested run re-notifies local user code when possible and returns the current run. It does not force an actively leased attempt into a terminal state. Terminal runs cannot be cancelled again.
Rerun And Manual Retry
runlane.runs.rerun(sourceRunId) creates a new linked child run from a terminal source run. It requires the source task to be registered in the current runtime, uses the persisted payload, validates it against the currently registered task schema, and links the new run with source.type = RunSourceType.Rerun.
runlane.runs.retry(sourceRunId) is narrower: the source run must be failed. It creates a linked recovery child with source.type = RunSourceType.ManualRetry.
Neither operation mutates the source run.
const replay = await runlane.runs.rerun(sourceRunId)
const recovery = await runlane.runs.retry(failedRunId)Linked child runs intentionally do not reuse task idempotency keys because the operator is asking for new work. Task singleton keys still apply when the lane can enforce them, so recovery actions do not overlap active work for the same logical resource.
Linked child runs use the runtime dispatch policy. With the default dispatch.onTrigger: TriggerDispatchMode.Eager, rerun() and retry() publish the child run's wakeup immediately. With dispatch.onTrigger: TriggerDispatchMode.Deferred, they persist the child run and outbox row, and tick() or another maintenance runner publishes later.
Queue overrides use registered queue definitions:
await runlane.runs.retry(failedRunId, { queue: recoveryQueue })Pruning
runlane.runs.prune() delegates retention work to storage adapters that report storage.prunesRuns: true. Core always sends terminal statuses to storage, even when the caller omits statuses, and rejects active statuses before the adapter is called.
await runlane.runs.prune({
actor: { type: ActorType.Operator, id: 'ops@example.com' },
olderThan: '30d',
statuses: [RunStatus.Succeeded, RunStatus.Cancelled],
})olderThan may be a duration string relative to the runtime clock or an explicit Date. When limit is omitted, storage uses contractDefaults.pruning.batchLimit.
When pruning returns nextCursor, pass that cursor back with the same olderThan and statuses inputs. Duration cutoffs are frozen into the continuation cursor so the next call does not drift forward in time.
Pruning is irreversible through the public API. Use explicit bounded limit and cursor values when deleting large histories.
CLI Commands
The CLI uses the same runtime-backed operator APIs after loading the configured runtime. See CLI configuration for runtime loading and JSON output, then use the command sections for the shipped operator surfaces:
runlane runs listmaps torunlane.runs.list().runlane runs getmaps torunlane.runs.get()and can includerunlane.runs.events()with--events.runlane cancelmaps torunlane.runs.cancel().runlane retrymaps torunlane.runs.retry().runlane rerunmaps torunlane.runs.rerun().runlane prunemaps torunlane.runs.prune().
There are no CLI commands for findActive(), findCurrent(), attempts(), or idempotencyKeys.reset() yet; call the runtime APIs directly from an operator tool when those controls are needed.