Runlane
Concepts

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(), and retry()

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):

OptionDefaultWhat it filters or controls
cursorNoneContinues a previous page from storage.
limitcontractDefaults.pagination.defaultLimitMaximum summaries to return, capped by storage/default pagination policy.
statusesAll statusesDurable run statuses to include.
taskIdsAll tasksTask ids to include.
queuesAll queuesQueue definitions to include; core resolves each against runtime.queues.
idempotencyKeyNoneRuns with this task-scoped idempotency key.
singletonKeyNoneRuns with this singleton key.
sourceRunIdNoneLinked children created from a source run.
createdAtNone{ from?, to? } inclusive created-time bounds.
updatedAtNone{ from?, to? } inclusive updated-time bounds.
runAtNone{ from?, to? } inclusive due-time bounds.
sortBycontractDefaults.sort.runs.fieldcreated_at, updated_at, or run_at.
sortDirectioncontractDefaults.sort.runs.directionasc or desc.

runlane.runs.events(runId, options):

OptionDefaultWhat it filters or controls
cursorNoneContinues a previous event page.
limitcontractDefaults.pagination.defaultLimitMaximum events to return.
typesAll event typesEvent types to include.
occurredAtNone{ from?, to? } inclusive event-time bounds.
sortBycontractDefaults.sort.runEvents.fieldoccurred_at or sequence. Sequence sorting is meaningful inside one run.
sortDirectioncontractDefaults.sort.runEvents.directionasc or desc.

findActive() and findCurrent():

MethodRequired optionsMeaning
runlane.runs.findActive({ task, idempotencyKey?, singletonKey? })task plus at least one keyAdvisory active-run lookup for UX. Correctness still belongs to storage-enforced trigger().
runlane.runs.findCurrent({ task, idempotencyKey })task, idempotencyKeyReads the current active or retained terminal idempotency owner.
runlane.idempotencyKeys.reset(task, { key })keyClears one retained terminal idempotency owner. Active owners reject.

Action Options

Operator actions share audit fields:

OptionDefaultUsed byWhat it records
actorcontractDefaults.actor.operator where applicablecancel, rerun, retry, pruneOperator identity recorded on audit events or prune commands.
metaNonecancel, rerun, retryJSON object with action context.
traceCarrierNone for cancel; source run trace carrier for linked runscancel, rerun, retryTrace context carried into linked run creation or audit events.

Action-specific options:

MethodOptionDefaultWhat it controls
cancel(runId, options)reasonNonePublic cancellation reason stored on the cancellation event.
rerun(runId, options)queueSource run queueQueue definition for the linked child run.
rerun(runId, options)runIdGenerated child run idCaller-supplied id for the linked child.
retry(runId, options)queueSource run queueQueue definition for the manual-retry child run.
retry(runId, options)runIdGenerated child run idCaller-supplied id for the manual-retry child.
prune(options)olderThanRequiredDuration string relative to runtime clock or explicit Date cutoff.
prune(options)statusesAll terminal statusesTerminal statuses to prune. Active statuses are rejected.
prune(options)limitcontractDefaults.pruning.batchLimitMaximum terminal runs storage prunes in one call.
prune(options)cursorNoneContinues a previous prune with the same frozen retention scope.

Cancellation

runlane.runs.cancel(runId, options) preserves history.

Run stateCancellation behavior
Queued, released, retrying, or scheduledThe run becomes terminally cancelled immediately.
Running in the same runtimeCore writes run.cancellation_requested, then aborts the active task context signal.
Running in another runtimeThe owner observes the request during lease monitoring and stops extending the lease.
Owner died or never cooperatestick() 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:

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.

On this page