Skip to content
Draft
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
55 changes: 55 additions & 0 deletions openapi/openapiv2.json
Original file line number Diff line number Diff line change
Expand Up @@ -10715,6 +10715,35 @@
},
"description": "Target a worker polling on a Nexus task queue in a specific namespace."
},
"EventGroupMarkerInboundEventAttributes": {
"type": "object",
"properties": {
"inboundEventId": {
"type": "string",
"format": "int64"
}
},
"title": "The event ID of an event in the present workflow that triggered implicit\ncreation of this group marker. The target event's type must be one of the\nfollowing:\n - `WORKFLOW_EXECUTION_STARTED`\n - `WORKFLOW_EXECUTION_SIGNALED`"
},
"EventGroupMarkerInboundUpdateAttributes": {
"type": "object",
"properties": {
"inboundUpdateId": {
"type": "string"
}
},
"description": "The workflow-unique identifier of an inbound Update whose handler triggered\nimplicit creation of this group marker.\n\nUsed in place of `inbound_event_id` for Updates because the event ID of the\nUpdateAccepted history event is not known until the Workflow Task is\ncompleted and recorded by the server, which may be too late."
},
"EventGroupMarkerLabelAttributes": {
"type": "object",
"properties": {
"label": {
"$ref": "#/definitions/v1Payload",
"description": "This payload should be a \"json/plain\"-encoded payload that is a single\nJSON string for use in user interfaces. User interface formatting may not\napply to this text when used in \"label\" situations. The payload data\nsection is limited to 400 bytes by default.\n\nNote that it is valid to have distinct markers (i.e. distinct marker IDs)\nin a given workflow execution that carry the same label."
}
},
"description": "A user-defined short-form string value to be used as the group's label."
},
"LinkActivity": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -15319,6 +15348,24 @@
},
"description": "Target to route requests to."
},
"v1EventGroupMarker": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Opaque identifier assigned by the SDK.\n\nConstruction of this ID follows strict rules to ensure history\nconsolidation is possible. See below."
},
"labelAttributes": {
"$ref": "#/definitions/EventGroupMarkerLabelAttributes"
},
"inboundEventAttributes": {
"$ref": "#/definitions/EventGroupMarkerInboundEventAttributes"
},
"inboundUpdateAttributes": {
"$ref": "#/definitions/EventGroupMarkerInboundUpdateAttributes"
}
}
},
"v1EventType": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -15811,6 +15858,14 @@
"$ref": "#/definitions/v1Principal",
"description": "Server-computed authenticated caller identity associated with this event."
},
"eventGroupMarkers": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1EventGroupMarker"
},
"description": "Event group markers attached to this event."
},
"workflowExecutionStartedEventAttributes": {
"$ref": "#/definitions/v1WorkflowExecutionStartedEventAttributes"
},
Expand Down
59 changes: 59 additions & 0 deletions openapi/openapiv3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11840,6 +11840,60 @@ components:
type: string
description: Nexus task queue to route requests to.
description: Target a worker polling on a Nexus task queue in a specific namespace.
EventGroupMarker:
type: object
properties:
id:
type: string
description: |-
Opaque identifier assigned by the SDK.

Construction of this ID follows strict rules to ensure history
consolidation is possible. See below.
labelAttributes:
$ref: '#/components/schemas/EventGroupMarker_LabelAttributes'
inboundEventAttributes:
$ref: '#/components/schemas/EventGroupMarker_InboundEventAttributes'
inboundUpdateAttributes:
$ref: '#/components/schemas/EventGroupMarker_InboundUpdateAttributes'
EventGroupMarker_InboundEventAttributes:
type: object
properties:
inboundEventId:
type: string
description: |-
The event ID of an event in the present workflow that triggered implicit
creation of this group marker. The target event's type must be one of the
following:
- `WORKFLOW_EXECUTION_STARTED`
- `WORKFLOW_EXECUTION_SIGNALED`
EventGroupMarker_InboundUpdateAttributes:
type: object
properties:
inboundUpdateId:
type: string
description: |-
The workflow-unique identifier of an inbound Update whose handler triggered
implicit creation of this group marker.

Used in place of `inbound_event_id` for Updates because the event ID of the
UpdateAccepted history event is not known until the Workflow Task is
completed and recorded by the server, which may be too late.
EventGroupMarker_LabelAttributes:
type: object
properties:
label:
allOf:
- $ref: '#/components/schemas/Payload'
description: |-
This payload should be a "json/plain"-encoded payload that is a single
JSON string for use in user interfaces. User interface formatting may not
apply to this text when used in "label" situations. The payload data
section is limited to 400 bytes by default.

Note that it is valid to have distinct markers (i.e. distinct marker IDs)
in a given workflow execution that carry the same label.
description: A user-defined short-form string value to be used as the group's label.
ExternalWorkflowExecutionCancelRequestedEventAttributes:
type: object
properties:
Expand Down Expand Up @@ -12308,6 +12362,11 @@ components:
allOf:
- $ref: '#/components/schemas/Principal'
description: Server-computed authenticated caller identity associated with this event.
eventGroupMarkers:
type: array
items:
$ref: '#/components/schemas/EventGroupMarker'
description: Event group markers attached to this event.
workflowExecutionStartedEventAttributes:
$ref: '#/components/schemas/WorkflowExecutionStartedEventAttributes'
workflowExecutionCompletedEventAttributes:
Expand Down
5 changes: 5 additions & 0 deletions temporal/api/command/v1/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "temporal/api/common/v1/message.proto";
import "temporal/api/failure/v1/message.proto";
import "temporal/api/taskqueue/v1/message.proto";
import "temporal/api/sdk/v1/user_metadata.proto";
import "temporal/api/sdk/v1/event_group_marker.proto";

message ScheduleActivityTaskCommandAttributes {
string activity_id = 1;
Expand Down Expand Up @@ -294,6 +295,10 @@ message Command {
// * start_timer_command_attributes - populates temporal.api.history.v1.HistoryEvent for timer
// started where the summary is used to identify the timer.
temporal.api.sdk.v1.UserMetadata user_metadata = 301;

// Event Group Markers attached to the command by the workflow author.
repeated temporal.api.sdk.v1.EventGroupMarker event_group_markers = 302;
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.

Why here rather than in UserMetadata? Not necessarily opposed, just w/ UserMetadata I think we probably can avoid touching server at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Short story is that really depends on where we want to go with Event Groups. What we have in the MVP is really just an extension of the existing User Metadata concept, only providing minor enrichment to the workflow history. If we stop there, adding this to UserMetadata would indeed be a possibility (that was actually my initial direction).

However, envisioned goals for the proposal include some features that could benefit from Event Groups being their own thing rather than piggybacked on User Metadata. For example, one of the near-term goal is to allow requesting cancellation of an Event Group. Another one is to be able to follow Event Group across workflows. In both cases, there are some implicit requirements regarding the validity of the data stored in event groups, stronger than what we currently have on user metadata.

Even given the immediate MVP scope, the fact is that we accept UserMetadata on many APIs that can't accept Event Groups. So we'd need to spread server-side checks everywhere to confirm that supplied user metadata doesn't contain event groups. That's way more painful than adding this field here and add explicit copy assignments to the server in the very few places (three to be exact) where that's pertinent.

Copy link
Copy Markdown
Member

@Sushisource Sushisource Jun 2, 2026

Choose a reason for hiding this comment

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

the fact is that we accept UserMetadata on many APIs that can't accept Event Groups.

Yeah, that makes sense. The previous points I feel like we still have the data well formed even if it's in UserMetadata, but this makes a lot of sense to me.


// The command details. The type must match that in `command_type`.
oneof attributes {
ScheduleActivityTaskCommandAttributes schedule_activity_task_command_attributes = 2;
Expand Down
3 changes: 3 additions & 0 deletions temporal/api/history/v1/message.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "temporal/api/update/v1/message.proto";
import "temporal/api/workflow/v1/message.proto";
import "temporal/api/sdk/v1/task_complete_metadata.proto";
import "temporal/api/sdk/v1/user_metadata.proto";
import "temporal/api/sdk/v1/event_group_marker.proto";

// Always the first event in workflow history
message WorkflowExecutionStartedEventAttributes {
Expand Down Expand Up @@ -1193,6 +1194,8 @@ message HistoryEvent {
repeated temporal.api.common.v1.Link links = 302;
// Server-computed authenticated caller identity associated with this event.
temporal.api.common.v1.Principal principal = 303;
// Event group markers attached to this event.
repeated temporal.api.sdk.v1.EventGroupMarker event_group_markers = 304;
// The event details. The type must match that in `event_type`.
oneof attributes {
WorkflowExecutionStartedEventAttributes workflow_execution_started_event_attributes = 6;
Expand Down
65 changes: 65 additions & 0 deletions temporal/api/sdk/v1/event_group_marker.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
syntax = "proto3";

package temporal.api.sdk.v1;

option go_package = "go.temporal.io/api/sdk/v1;sdk";
option java_package = "io.temporal.api.sdk.v1";
option java_multiple_files = true;
option java_outer_classname = "EventGroupMarkerProto";
option ruby_package = "Temporalio::Api::Sdk::V1";
option csharp_namespace = "Temporalio.Api.Sdk.V1";


import "temporal/api/common/v1/message.proto";

message EventGroupMarker {
// Opaque identifier assigned by the SDK.
//
// Construction of this ID follows strict rules to ensure history
// consolidation is possible. See below.
string id = 1;

// Attributes attached to the group marker. They only needs to be set once per
// marker ID; further references to an existing marker ID reuse existing
// attributes of the referenced marker.
//
// In the case where a later group marker does carry attributes, then those
// attributes override the previous attributes of the same marker ID for all
// events carrying that marker ID, including preceding events.
oneof attributes {
LabelAttributes label_attributes = 2;
InboundEventAttributes inbound_event_attributes = 3;
InboundUpdateAttributes inbound_update_attributes = 4;
Comment on lines +30 to +32
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 get event/update being mutually exclusive, but, I think it'd be nice to be able to have explicit labels at the same time as it being auto-triggered? We could have some kind of getCurrentGroup API that you could then attach labels to?

Or, does the comment imply you could have two of these markers, one label, and one inbound, for the same ID and that enables this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does the comment imply you could have two of these markers, one label, and one inbound, for the same ID and that enables this?

Well, that would be technically feasible, though definitely not what I had in mind.

I think it'd be nice to be able to have explicit labels at the same time as it being auto-triggered? We could have some kind of getCurrentGroup API that you could then attach labels to?

An event can be attached to multiple groups, so getCurrentGroup() is not exactly possible. We could have getCurrentGroups(), but then that would be painful for users to do what you describe. Or we could have some form of getCurrentSignalUpdateOrWorkflowEventGroup() (just need to figure out a name), for which there is always one and only one at any point...

But at that point, I'd simply suggest the user to explicitly create a labelled group wrapping the complete signal/update handler's code.

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'd simply suggest the user to explicitly create a labelled group wrapping the complete signal/update handler's code.

Yeah, reasonable, and you're saying those commands would be part of both the auto and manual group. Makes sense.

}

// A user-defined short-form string value to be used as the group's label.
message LabelAttributes {
// This payload should be a "json/plain"-encoded payload that is a single
// JSON string for use in user interfaces. User interface formatting may not
// apply to this text when used in "label" situations. The payload data
// section is limited to 400 bytes by default.
//
// Note that it is valid to have distinct markers (i.e. distinct marker IDs)
// in a given workflow execution that carry the same label.
temporal.api.common.v1.Payload label = 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.

This is Payload just to allow for encryption I assume?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indeed. Basically, this follows the convention we used for summary and details.

}

// The event ID of an event in the present workflow that triggered implicit
// creation of this group marker. The target event's type must be one of the
// following:
// - `WORKFLOW_EXECUTION_STARTED`
// - `WORKFLOW_EXECUTION_SIGNALED`
message InboundEventAttributes {
int64 inbound_event_id = 1;
}

// The workflow-unique identifier of an inbound Update whose handler triggered
// implicit creation of this group marker.
//
// Used in place of `inbound_event_id` for Updates because the event ID of the
// UpdateAccepted history event is not known until the Workflow Task is
// completed and recorded by the server, which may be too late.
message InboundUpdateAttributes {
string inbound_update_id = 1;
}
}
Loading