Skip to content

Add Serialization context for codecs and converters#2225

Open
yuandrew wants to merge 11 commits intotemporalio:masterfrom
yuandrew:serialization-context-codec-converter-new
Open

Add Serialization context for codecs and converters#2225
yuandrew wants to merge 11 commits intotemporalio:masterfrom
yuandrew:serialization-context-codec-converter-new

Conversation

@yuandrew
Copy link
Contributor

@yuandrew yuandrew commented Mar 10, 2026

A re-creation of #2149, as that did not properly capture the feature.

What was changed

Add SerializationContext — an opt-in mechanism that provides metadata (namespace, workflow ID, activity type,
etc.) to DataConverter, PayloadCodec, and FailureConverter implementations during serialization/deserialization.

New public API (converter package):

  • SerializationContext sealed interface with two implementations:
    • WorkflowSerializationContext — for workflow-level payloads (workflow input/result, child workflow, signal,
      query, update, memo, continue-as-new)
    • ActivitySerializationContext — for activity-level payloads (activity input/result, heartbeat, failure details)
  • Three opt-in interfaces: DataConverterWithSerializationContext, PayloadCodecWithSerializationContext,
    FailureConverterWithSerializationContext
  • WithDataConverterSerializationContext() / WithFailureConverterSerializationContext() helper functions
  • CodecDataConverter.WithSerializationContext auto-propagates context to both parent DC and codecs
  • CompleteActivityWithOptions / CompleteActivityByIDWithOptions / CompleteActivityByActivityIDWithOptions / RecordActivityHeartbeatWithOptions / RecordActivityHeartbeatByIDWithOptions - New client functions that allow users to pass in proper activity metadata to pass into codecs. The existing methods don't have sufficient context information.
Context is provided at every serialization site:
  • Workflow execution (input decode/result encode)
  • Activity and local activity (input encode/decode, result, heartbeat, failure)
  • Child workflow (input encode/decode, result)
  • Signal (including external signal)
  • Query (input/result)
  • Update (input/result/rejection)
  • Continue-as-new (input)
  • Side effect / mutable side effect (uses workflow-scoped DC)
  • Client operations (start, signal, query, terminate, update, describe)
  • Schedule client (create/describe actions)
  • Async activity completion by ID (complete, heartbeat)
  • Workflow task failure and query failure (FC wrapping)

Why?

Enables use cases like per-workflow encryption (using workflow ID as associated data for a key), context-dependent
encoding, or audit logging — without requiring custom interceptors or thread-local hacks.

Matches the serialization context feature already implemented in Python, .NET, and Java.

Checklist

  1. Closes Serialization context for codecs and converters #1352

  2. How was this tested:

    • Unit tests in converter/serialization_context_test.go — codec propagation matrix, signing round-trips, helper
      behavior
    • Workflow environment tests in internal/serialization_context_test.go — activity, local activity, child
      workflow, side effect, concurrent operations, signal external, query, update
    • Client mock tests in internal/serialization_context_client_test.go — StartWorkflow, SignalWorkflow,
      QueryWorkflow, TerminateWorkflow
    • Integration tests in test/serialization_context_test.go — end-to-end with signing codec against real server
      (side effect + activity + child workflow combined)
  3. Any docs updates needed?


Note

Medium Risk
Touches core payload/failure serialization across workflows, activities, child workflows, and client APIs; incorrect context propagation could cause encode/decode mismatches or subtle behavior changes in custom codecs.

Overview
Adds an opt-in converter.SerializationContext system (workflow- and activity-scoped) and wires it through DataConverter, PayloadCodec, and FailureConverter via new WithSerializationContext interfaces and helper wrappers.

Propagates these contexts throughout the SDK’s serialization sites (workflow/activity/local activity execution, child workflows, signals/queries/updates/continue-as-new, schedules, and client operations), including per-operation context-aware converters for decoding cancellations and failures.

Extends the public client API with *WithOptions variants for async activity completion/heartbeats so callers can supply activity metadata for context-aware codecs, and adds extensive unit/integration coverage to validate context propagation and round-trip behavior.

Written by Cursor Bugbot for commit 80d1311. This will update automatically on new commits. Configure here.

…Converter

Introduces an opt-in mechanism that provides metadata (namespace, workflow ID,
activity type, etc.) to converter implementations during serialization. This
enables use cases like per-workflow encryption using workflow ID as associated
data for a key.

Adds WorkflowSerializationContext (for workflow-level payloads) and
ActivitySerializationContext (for activity-level payloads), with opt-in
interfaces for DataConverter, PayloadCodec, and FailureConverter.

Context is provided at every serialization site: workflow execution, activity,
local activity, child workflow, signal, query, update, continue-as-new, side
effect, client operations, schedule client, and async activity completion.

Matches the feature already implemented in Python, .NET, and Java SDKs.

Closes temporalio#1352

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yuandrew yuandrew requested a review from a team as a code owner March 10, 2026 22:47
}

command := wc.commandsHelper.scheduleActivityTask(scheduleID, scheduleTaskAttr, startMetadata)
command := wc.commandsHelper.scheduleActivityTask(parameters.ScheduleID, scheduleTaskAttr, startMetadata)
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks correct for the Go SDK itself, because workflowEnvironmentInterceptor.ExecuteActivity generates the ID. But this affects the bindings API, so binding callers now need to call env.GenerateSequence() and set ScheduleID. As is, I think this may be a breaking change for bindings callers (other SKDs built on top of the Go SDK).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, this will need to be called out in the PR description/release notes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ended up adding in backwards compatible checks that will GenerateSequence() in the same places we do today if bindings callers don't mirror this new behavior, so we should be good, no longer a breaking change

…, require callers to explicitly wrap context themselves. Use wfInfo.Namespace for signal external workflow, wrap context for testWorkflowEnvironmentImpl.executeWorkflow
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor


// CompleteActivityByIDWithOptions reports workflow activity completed with full context options.
func (wc *WorkflowClient) CompleteActivityByIDWithOptions(ctx context.Context, opts CompleteActivityByIDOptions) error {
if opts.ActivityID == "" || opts.WorkflowID == "" || opts.Namespace == "" {
Copy link

Choose a reason for hiding this comment

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

Missing ensureInitialized call in new WithOptions methods

Medium Severity

CompleteActivityByIDWithOptions and CompleteActivityByActivityIDWithOptions are missing the wc.ensureInitialized(ctx) call that other WithOptions methods like CompleteActivityWithOptions (line 484), RecordActivityHeartbeatWithOptions (line 619), and RecordActivityHeartbeatByIDWithOptions (line 654) include. The original CompleteActivity delegates to CompleteActivityWithOptions which does call it, but CompleteActivityByID now delegates to CompleteActivityByIDWithOptions which doesn't, losing the initialization check that was previously handled by the shared code path.

Additional Locations (1)
Fix in Cursor Fix in Web

activityID = options.ActivityID
} else {
activityID = getStringID(scheduleID)
}
Copy link

Choose a reason for hiding this comment

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

Sequence consumption order changes break workflow determinism

High Severity

GenerateSequence() for activity schedule IDs and child workflow IDs is now consumed in the interceptor layer before other operations, whereas previously it was consumed inside the environment's ExecuteActivity/ExecuteChildWorkflow. This changes the deterministic sequence ordering for replaying existing workflow histories. No SDK flag guards this behavioral change, so workflows recorded with the old code may fail non-determinism checks on replay with the new code.

Additional Locations (1)
Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serialization context for codecs and converters

2 participants