Run.timeTravel()
The .timeTravel() method re-executes a workflow starting from any specific step, using either stored snapshot data or custom context you provide. Use it to debug failed workflows and test individual steps with different inputs. It can also recover from errors without re-running the entire workflow.
Usage exampleDirect link to Usage example
const run = await workflow.createRun()
const result = await run.timeTravel({
step: 'step2',
inputData: { value: 10 },
})
ParametersDirect link to Parameters
step:
inputData?:
resumeData?:
initialState?:
context?:
nestedStepsContext?:
requestContext?:
outputWriter?:
tracingContext?:
currentSpan?:
tracingOptions?:
rootSpanName?:
workflow run: '<id>' or agent run: '<id>' name. Use it to tell runs apart in trace lists.metadata?:
requestContextKeys?:
traceId?:
parentSpanId?:
nestUnderParent?:
true together with parentSpanId to attach this run as a child of that span inside the existing trace, for example an LLM judge under the run it scores. Exporters then keep the trace's own name, input, output, and tags. Leave unset when parentSpanId comes from your own tracing system. Ignored without a valid traceId and parentSpanId.tags?:
outputOptions?:
includeState?:
includeResumeLabels?:
ReturnsDirect link to Returns
result:
traceId?:
spanId?:
Extended usage examplesDirect link to Extended usage examples
Time travel with custom contextDirect link to Time travel with custom context
const result = await run.timeTravel({
step: 'step2',
context: {
step1: {
status: 'success',
payload: { value: 0 },
output: { step1Result: 2 },
startedAt: Date.now(),
endedAt: Date.now(),
},
},
})
Time travel to nested workflow stepDirect link to Time travel to nested workflow step
// Using dot notation
const result = await run.timeTravel({
step: 'nestedWorkflow.step3',
inputData: { value: 10 },
})
// Using array of step IDs
const result = await run.timeTravel({
step: ['nestedWorkflow', 'step3'],
inputData: { value: 10 },
})
Time travel with initial stateDirect link to Time travel with initial state
const result = await run.timeTravel({
step: 'step2',
inputData: { value: 10 },
initialState: {
counter: 5,
metadata: { source: 'time-travel' },
},
})
Time travel with nested workflows contextDirect link to Time travel with nested workflows context
const result = await run.timeTravel({
step: 'nestedWorkflow.step3',
context: {
step1: {
status: 'success',
payload: { value: 0 },
output: { step1Result: 2 },
startedAt: Date.now(),
endedAt: Date.now(),
},
nestedWorkflow: {
status: 'running',
payload: { step1Result: 2 },
startedAt: Date.now(),
},
},
nestedStepsContext: {
nestedWorkflow: {
step2: {
status: 'success',
payload: { step1Result: 2 },
output: { step2Result: 3 },
startedAt: Date.now(),
endedAt: Date.now(),
},
},
},
})
Concurrent time travel callsDirect link to Concurrent time travel calls
timeTravel() doesn't claim the run before it executes. It only rejects runs whose stored status is running. If two timeTravel() calls target the same run at the same time, both can proceed, and the target step and every step after it run once per call.
This matters whenever time travel can be triggered more than once for the same run, such as a "retry from step" button pressed twice or a retried API request. Make sure only one call proceeds per run in your application, for example with a per-run lock or an idempotency key:
const inFlight = new Set<string>()
async function retryFromStep(runId: string, step: string) {
if (inFlight.has(runId)) {
throw new Error(`Time travel already in progress for run ${runId}`)
}
inFlight.add(runId)
try {
const run = await workflow.createRun({ runId })
return await run.timeTravel({ step })
} finally {
inFlight.delete(runId)
}
}
The in-memory set above only protects a single process. If several server instances can handle the same run, use a shared lock instead, such as a database row lock.
Unlike resume(), timeTravel() has no WORKFLOW_RESUME_ALREADY_CLAIMED equivalent, and the HTTP endpoint doesn't return 409 Conflict for concurrent calls.
NotesDirect link to Notes
- Time travel requires storage to be configured since it relies on persisted workflow snapshots
- When re-executing a workflow, the workflow loads the existing snapshot from storage (if available)
- Step results before the target step are reconstructed from the snapshot or provided context
- Execution begins from the specified step with the provided or reconstructed input data
- If a step that precedes the target in the current workflow definition has no recorded entry in the snapshot (for example, the step was renamed since the run was recorded, or the run never reached it),
timeTravelrejects with a descriptive error before anything executes and the stored snapshot is left unchanged - The workflow continues to completion from that point forward
- Time travel can be used on workflows that haven't been run yet by providing custom context or input data for the step to start from.