Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions openapi/openapiv2.json

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions openapi/openapiv3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10157,6 +10157,7 @@ components:
- EVENT_TYPE_NEXUS_OPERATION_CANCEL_REQUEST_FAILED
- EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED
type: string
format: enum
version:
Expand Down Expand Up @@ -10315,6 +10316,8 @@ components:
$ref: '#/components/schemas/WorkflowExecutionPausedEventAttributes'
workflowExecutionUnpausedEventAttributes:
$ref: '#/components/schemas/WorkflowExecutionUnpausedEventAttributes'
workflowExecutionTimeSkippedEventAttributes:
$ref: '#/components/schemas/WorkflowExecutionTimeSkippedEventAttributes'
description: |-
History events are the method by which Temporal SDKs advance (or recreate) workflow state.
See the `EventType` enum for more info about what each event is for.
Expand Down Expand Up @@ -11067,6 +11070,8 @@ components:
Calls are retried internally by the server.
(-- api-linter: core::0140::prepositions=disabled
aip.dev/not-precedent: "to" is used to indicate interval. --)
(-- api-linter: core::0142::time-field-names=disabled
aip.dev/not-precedent: "timeout" is an acceptable suffix for duration fields in this API. --)
nexusHeader:
type: object
additionalProperties:
Expand Down Expand Up @@ -12258,6 +12263,7 @@ components:
- EVENT_TYPE_NEXUS_OPERATION_CANCEL_REQUEST_FAILED
- EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED
type: string
description: The event type of the history event generated by the request.
format: enum
Expand Down Expand Up @@ -13571,6 +13577,10 @@ components:
allOf:
- $ref: '#/components/schemas/Priority'
description: Priority metadata
timeSkippingConfig:
allOf:
- $ref: '#/components/schemas/TimeSkippingConfig'
description: Time skipping configuration. If not set, automatic time-skipping is disabled.
SignalWithStartWorkflowExecutionResponse:
type: object
properties:
Expand Down Expand Up @@ -14033,6 +14043,10 @@ components:
allOf:
- $ref: '#/components/schemas/WorkerDeploymentOptions'
description: Deployment Options of the worker who will process the eager task. Passed when `request_eager_execution=true`.
timeSkippingConfig:
allOf:
- $ref: '#/components/schemas/TimeSkippingConfig'
description: Automatic time-skipping configuration. If not set, automatic time-skipping is disabled.
StartWorkflowExecutionResponse:
type: object
properties:
Expand Down Expand Up @@ -14436,6 +14450,35 @@ components:
TerminatedFailureInfo:
type: object
properties: {}
TimeSkippingConfig:
type: object
properties:
enabled:
type: boolean
description: "Enables or disables automatic time skipping for this workflow execution.\n By default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset) \n at the time they are started.\n Changes made after a transitively related workflow has started are not propagated."
disablePropagation:
type: boolean
description: If set, the enabled field is not propagated to transitively related workflows.
maxSkippedDuration:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
description: Maximum total virtual time that can be skipped.
maxElapsedDuration:
pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$
type: string
description: |-
Maximum elapsed time since time skipping was enabled.
This includes both skipped time and real time elapsing.
maxTargetTime:
type: string
description: |-
Absolute virtual timestamp at which time skipping is disabled.
Time skipping will not advance beyond this point.
format: date-time
description: |-
Configuration for automatic time skipping during a workflow execution.
When enabled, virtual time advances automatically whenever there are
no in-flight activities, child workflows, or Nexus operations.
TimeoutFailureInfo:
type: object
properties:
Expand Down Expand Up @@ -15859,6 +15902,7 @@ components:
- EVENT_TYPE_NEXUS_OPERATION_CANCEL_REQUEST_FAILED
- EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED
type: string
format: enum
description: EventReference is a direct reference to a history event through the event ID.
Expand Down Expand Up @@ -15929,6 +15973,7 @@ components:
- EVENT_TYPE_NEXUS_OPERATION_CANCEL_REQUEST_FAILED
- EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED
- EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED
type: string
format: enum
description: RequestIdReference is a indirect reference to a history event through the request ID.
Expand Down Expand Up @@ -16278,6 +16323,10 @@ components:
allOf:
- $ref: '#/components/schemas/Priority'
description: If set, overrides the workflow's priority sent by the SDK.
timeSkippingConfig:
allOf:
- $ref: '#/components/schemas/TimeSkippingConfig'
description: "Time-skipping configuration for this workflow execution.\n If not set, the time-skipping conf will not get updated upon request, \n i.e. the existing time-skipping conf will be preserved."
WorkflowExecutionOptionsUpdatedEventAttributes:
type: object
properties:
Expand Down Expand Up @@ -16309,6 +16358,10 @@ components:
description: |-
Priority override upserted in this event. Represents the full priority; not just partial fields.
Ignored if nil.
timeSkippingConfig:
allOf:
- $ref: '#/components/schemas/TimeSkippingConfig'
description: "Time skipping configuration upserted in this event. \n It means the time-skipping config is changed, and the full latest config is upserted."
WorkflowExecutionPauseInfo:
type: object
properties:
Expand Down Expand Up @@ -16614,6 +16667,28 @@ components:
identity:
type: string
description: id of the client who requested termination
WorkflowExecutionTimeSkippedEventAttributes:
type: object
properties:
targetTime:
type: string
description: The virtual time after time skipping is applied.
format: date-time
disabledAfterBound:
type: boolean
description: |-
when true, automatic time-skipping was enabled with a bound
and now that bound has been reached and time-skipping is disabled automatically.
(-- api-linter: core::0140::prepositions=disabled
aip.dev/not-precedent: "after" is used to indicate temporal ordering. --)
wallClockTime:
type: string
description: The wall-clock time when the time-skipping event happened.
format: date-time
description: |-
Attributes for an event indicating that virtual time was advanced for a workflow execution,
either via a manual time-skipping API call or automatic time skipping.
The worker_may_ignore field in HistoryEvent should always be set true for this event.
WorkflowExecutionTimedOutEventAttributes:
type: object
properties:
Expand Down
2 changes: 2 additions & 0 deletions temporal/api/enums/v1/event_type.proto
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,6 @@ enum EventType {
EVENT_TYPE_WORKFLOW_EXECUTION_PAUSED = 58;
// An event that indicates that the previously paused workflow execution has been unpaused.
EVENT_TYPE_WORKFLOW_EXECUTION_UNPAUSED = 59;
// An event that indicates that some duration was skipped for this workflow execution.
EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED = 60;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You know this just occurred to me and I don't think we've talked about it - if time skipping is enabled on a workflow with an old SDK version processing it, it's going to break on these new event types.

That's fine I think, we will tell people they need to upgrade, but we need to make sure that is clear in the docs & GTM material we create.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get it. the docs & GTM material mainly refer to API comments (here) and https://docs.temporal.io/ ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we always set worker_may_ignore for TimeSkippingEvents, it won't break workers right? because in the demo, I set worker_may_ignore = true and used current SDK which has no time-skipping feature and didnt break it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for sdk version old enough, it doesn't even know about the worker_may_ignore flag, hence the comment. but yeah this is a new feature so we can make it clear re. the sdk version required.

}
24 changes: 24 additions & 0 deletions temporal/api/history/v1/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,9 @@ message WorkflowExecutionOptionsUpdatedEventAttributes {
// Priority override upserted in this event. Represents the full priority; not just partial fields.
// Ignored if nil.
temporal.api.common.v1.Priority priority = 6;
// Time skipping configuration upserted in this event.
// It means the time-skipping config is changed, and the full latest config is upserted.
temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 7;
}

// Not used anywhere. Use case is replaced by WorkflowExecutionOptionsUpdatedEventAttributes
Expand Down Expand Up @@ -965,6 +968,24 @@ message WorkflowExecutionUnpausedEventAttributes {
string request_id = 3;
}

// Attributes for an event indicating that virtual time was advanced for a workflow execution,
Copy link
Copy Markdown
Author

@feiyang3cat feiyang3cat Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⭐ highlight the change after review meeting

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add to the comment here that worker_may_ignore should always be set true for this event

bool worker_may_ignore = 300;

Copy link
Copy Markdown
Author

@feiyang3cat feiyang3cat Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added but I remember an early proposal was to break the workers and remind them to upgrade. So I guess our strategy changed?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant we should add this to the comment here, not on the field. Otherwise that field will have an ever-increasing list in the comment.

And I think that was before we realized workers don't need to handle this at all. Even old SDKs will be able to successfully replay histories with time-skipping, which could be useful if users want to try this without needing to upgrade SDKs.

Copy link
Copy Markdown
Author

@feiyang3cat feiyang3cat Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I located here correctly this time?

// either via a manual time-skipping API call or automatic time skipping.
// The worker_may_ignore field in HistoryEvent should always be set true for this event.
message WorkflowExecutionTimeSkippedEventAttributes {

// The virtual time after time skipping is applied.
google.protobuf.Timestamp target_time = 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The timestamp of this event will be the workflow (virtual) time before or after the skip? I guess it's before? and the difference between the event time and the target time is the duration skipped?

Copy link
Copy Markdown
Author

@feiyang3cat feiyang3cat Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emm this is after, right now at sometime for example 4:00 virtual time (which is 3:00 in real time) we are going to skip 1hour to 5:00 and the target_time is 5:00

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to this one but the name is changed

Image


// when true, automatic time-skipping was enabled with a bound
// and now that bound has been reached and time-skipping is disabled automatically.
// (-- api-linter: core::0140::prepositions=disabled
// aip.dev/not-precedent: "after" is used to indicate temporal ordering. --)
bool disabled_after_bound = 2;

// The wall-clock time when the time-skipping event happened.
google.protobuf.Timestamp wall_clock_time = 3;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to understand this field v.s. the event time of the event. I mean I understand that this is always wal clock and event time can be a virtual time, but want to see if you any use case where this field will help make it easier.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was discussed in the review-meeting only for better visibility for debugging purpose. for example, I am an user, and I am debugging something with my business system built on temporal, now I need to align temporal history (which is virtual time) with my own log (which is real time)

definitely, it will be better but heavier if we add all wallclock to all following events

}

// Event marking that an operation was scheduled by a workflow via the ScheduleNexusOperation command.
message NexusOperationScheduledEventAttributes {
// Endpoint name, must exist in the endpoint registry.
Expand All @@ -982,6 +1003,8 @@ message NexusOperationScheduledEventAttributes {
// Calls are retried internally by the server.
// (-- api-linter: core::0140::prepositions=disabled
// aip.dev/not-precedent: "to" is used to indicate interval. --)
// (-- api-linter: core::0142::time-field-names=disabled
// aip.dev/not-precedent: "timeout" is an acceptable suffix for duration fields in this API. --)
google.protobuf.Duration schedule_to_close_timeout = 5;
// Header to attach to the Nexus request. Note these headers are not the same as Temporal headers on internal
// activities and child workflows, these are transmitted to Nexus operations that may be external and are not
Expand Down Expand Up @@ -1199,6 +1222,7 @@ message HistoryEvent {
NexusOperationCancelRequestFailedEventAttributes nexus_operation_cancel_request_failed_event_attributes = 62;
WorkflowExecutionPausedEventAttributes workflow_execution_paused_event_attributes = 63;
WorkflowExecutionUnpausedEventAttributes workflow_execution_unpaused_event_attributes = 64;
WorkflowExecutionTimeSkippedEventAttributes workflow_execution_time_skipped_event_attributes = 65;
}
}

Expand Down
47 changes: 47 additions & 0 deletions temporal/api/workflow/v1/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,53 @@ message WorkflowExecutionOptions {

// If set, overrides the workflow's priority sent by the SDK.
temporal.api.common.v1.Priority priority = 2;

// Time-skipping configuration for this workflow execution.
// If not set, the time-skipping conf will not get updated upon request,
// i.e. the existing time-skipping conf will be preserved.
TimeSkippingConfig time_skipping_config = 3;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sushisource @yycptt updated this part especially the bound part according to discussions. With the new fine-grained bound, I think the 1h-signal can be easily implemented by setting max_elapsed_duration to 1hour or max_target_time to some specific time in 1h hour.

For server, a EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED will be created to indicate time-skipping is disabled by bound even if no duration skipped.

For SDK can poll this event, but I think there are two things to consider right now

  1. we may need to add a version/ID for the time-skipping config if the user resets this config multiple times so the poller can differentiate EVENT_TYPE_WORKFLOW_EXECUTION_TIME_SKIPPED easily
  2. right now there is one get-api to read history events, it may be easier if we build a dedicated polling api (but this one may be added later?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for me the confusing part is those two options didn't say it will, like, create a new timer at the specified bound. and I am under the impression that time skipping will only skip to existing timers within the bound.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it only skip timers, but we allow this to be turned off automatically without finding anything to skip
do you have recommendations how to change ?

// Configuration for automatic time skipping during a workflow execution.
// When enabled, virtual time advances automatically whenever there are
// no in-flight activities, child workflows, or Nexus operations.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need to block auto skipping when there's pending signal/cancel external workflow operations in the transfer queue as well.

Copy link
Copy Markdown
Author

@feiyang3cat feiyang3cat Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these are good scenarios for testing, I think I can record these in testing planning.

For desgin,
(1) last time I learned signal/update, they come with workflow tasks so covered by workflow tasks ?
(2) for cancel task in transfer queue, the ms state becomes completed no? and I think our poc already check this state together with isAutoSkippable
pls feel free to correct me if I misunderstand how the sys works right now

message TimeSkippingConfig {
Copy link
Copy Markdown
Member

@yycptt yycptt Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to add this in to child workflow command?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, I don't imagine we will need to set childWorkflow time-skipping config thru command when users use SDK testing env. And in general, I think we may keep this as complex as the current version and only consider adding more when SDK really needs it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Wonder if we need to call this AutoTimeSkippingConfig in case we add manual skipping support in the future.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am kind of yes and no for different reasons. Guts-feeling is we will not get there that adding manual skipping.
what do you think @Sushisource ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, I’m leaning against renaming it. In my mind, 'TimeSkipping' implies an automatic process. Let’s keep it simple and only add a 'Manual' prefix when we need to distinguish the manual version?


// Enables or disables automatic time skipping for this workflow execution.
// By default, this field is propagated to transitively related workflows (child workflows/start-as-new/reset)
// at the time they are started.
// Changes made after a transitively related workflow has started are not propagated.
bool enabled = 1;

// If set, the enabled field is not propagated to transitively related workflows.
bool disable_propagation = 2;

// Optional bound that limits how long time skipping remains active.
// Once the bound is reached, time skipping is automatically disabled.
// It can later be re-enabled via UpdateWorkflowExecutionOptions.
//
// This is particularly useful in testing scenarios where workflows
// are expected to receive signals, updates, or other events while
// timers are in progress.
//
// This bound is not propagated to transitively related workflows.
// When bound is also needed for transitively related workflows,
// it is recommended to set disable_propagation to true
// and configure TimeSkippingConfig explicitly for transitively related workflows.
oneof bound {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⭐ highlight the change after review meeting

Copy link
Copy Markdown
Member

@yycptt yycptt Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great!

max_elapsed_duration may be a bit hard to use due to real time part of it.

For the timer + signal case we talked about, which option should caller use? max_elapsed_duration?
sorry I see you answered this question in the other comment.


// Maximum total virtual time that can be skipped.
google.protobuf.Duration max_skipped_duration = 4;

// Maximum elapsed time since time skipping was enabled.
// This includes both skipped time and real time elapsing.
// (-- api-linter: core::0142::time-field-names=disabled --)
google.protobuf.Duration max_elapsed_duration = 5;

// Absolute virtual timestamp at which time skipping is disabled.
// Time skipping will not advance beyond this point.
google.protobuf.Timestamp max_target_time = 6;
}
}

// Used to override the versioning behavior (and pinned deployment version, if applicable) of a
Expand Down
6 changes: 5 additions & 1 deletion temporal/api/workflowservice/v1/request_response.proto
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ message StartWorkflowExecutionRequest {
temporal.api.common.v1.Priority priority = 27;
// Deployment Options of the worker who will process the eager task. Passed when `request_eager_execution=true`.
temporal.api.deployment.v1.WorkerDeploymentOptions eager_worker_deployment_options = 28;
// Automatic time-skipping configuration. If not set, automatic time-skipping is disabled.
temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 29;
}

message StartWorkflowExecutionResponse {
Expand Down Expand Up @@ -836,6 +838,8 @@ message SignalWithStartWorkflowExecutionRequest {
temporal.api.workflow.v1.VersioningOverride versioning_override = 25;
// Priority metadata
temporal.api.common.v1.Priority priority = 26;
// Time skipping configuration. If not set, automatic time-skipping is disabled.
temporal.api.workflow.v1.TimeSkippingConfig time_skipping_config = 27;
}

message SignalWithStartWorkflowExecutionResponse {
Expand Down Expand Up @@ -2951,4 +2955,4 @@ message DeleteActivityExecutionRequest {
}

message DeleteActivityExecutionResponse {
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add new line

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

emm it seems not to be a new line >?

Loading