diff --git a/docs/develop/dotnet/versioning.mdx b/docs/develop/dotnet/versioning.mdx
index 8b02cb5e54..83ea7f264b 100644
--- a/docs/develop/dotnet/versioning.mdx
+++ b/docs/develop/dotnet/versioning.mdx
@@ -183,3 +183,164 @@ public class MyWorkflow
}
}
```
+
+### Detailed Description of the Patched Function
+
+We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series:
+
+
+
+
+
+#### Behavior When Not Replaying
+
+If not replaying, and the execution hits a call to patched, it first checks the event history, and:
+
+- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true.
+ This happens in a given patch ID's first block.
+- If the patch ID is in the event history, it won't modify the history, and it will return true.
+ This happens in a given patch ID's subsequent blocks.
+
+> There is a caveat to the above, and we will discuss that below.
+
+#### Behavior When Replaying With Marker Before-Or-At Current Location
+
+If Replaying:
+
+- If the code has a call to patched, and if the event history
+ has a marker from a call to patched in the same place (which means it
+ will match the original event history), then
+ it writes a marker to the replay event history and returns true.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's first block*
+- If the code has a call to patched, and the event history
+ has a marker with that Patch ID earlier in the history,
+ then it will simply return true and not modify the
+ replay event history.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's subsequent blocks*
+
+#### Behavior When Replaying With Marker After Current Location
+
+If the Marker Event is after where the execution currently is
+in the event history, then, in other words,
+the patch is before the original patch, then the patch is too early. It will
+attempt to write the marker to the replay event
+history, but it will throw a non-deterministic
+exception because the replay and original event
+histories don't match
+
+#### Behavior When Replaying With No Marker For that Patch ID
+
+It will return false and not add anything to
+the event history. Furthermore, ***and this is the
+caveat mentioned in the preceeding section [Behavior When Not Replaying](#behavior-when-not-replaying)***, it will make all future calls to patched
+with that ID false -- even after it is done replaying
+and is running new code.
+
+Why is this a caveat?
+
+In the [preceding section](#behavior-when-not-replaying) where we discussed the behavior when not replaying , we said that if not replaying,
+the patched function will always return true, and if
+the marker doesn't exist, it will add it, and if
+the marker already exists, it won't re-add it.
+
+But what this
+is saying is that this doesn't hold if there was already
+a call to patched with that ID in the replay code, but not
+in the event history. In this situation, it won't return
+true.
+
+#### A Summary of the Two Potentially Unexpected Behaviors
+
+1. When Replaying, in the scenario of ***it hits a call to
+ patched, but that patch ID isn't before/on that point in
+ the event history***, you may not expect that
+ the event history *after* where you currently
+ are matters. Because:
+ 1. If that patch ID exists later, you get an NDE [(see above: Behavior When Replaying With Marker After Current Location)](#behavior-when-replaying-with-marker-after-current-location).
+ 2. If it doesn't exist later, you don't get an NDE, and
+ it returns false
+ [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id).
+
+2. When Replaying, if you hit a call to patched with an ID that
+ doesn't exist in the history, then not only will it return
+ false in that occurence, but it will also return false if
+ the execution surpasses the Replay threshold and is running new code.
+ [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id).
+
+#### Implications of the Behaviors
+
+If you deploy new code while a worker is down,
+any workflows that were in the middle of executing will replay
+using old code and then for the rest of the execution, they
+will either:
+
+1. Use new code if there was no call to patched in the replay code
+2. If there was a call to patched in the replay code, they will
+ run the non-patched code during and after replay
+
+This might sound odd, but it's actually exactly what's needed because
+that means that if the future patched code depends on earlier patched code,
+then it won't use the new code -- it will use the old code. But if
+there's new code in the future, and there was no code earlier in the
+body that required the new patch, then it can switch over to the new code,
+and it will do that.
+
+Note that this behavior means that the Workflow ***does not always run
+the newest code***. It only does that if not replaying or if
+surpassed replay and there hasn't been a call to patched (with that ID) throughout
+the replay.
+
+#### Recommendations
+
+Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block.
+
+
+
+```csharp
+if (patched('v3')) {
+ // This is the newest version of the code.
+ // put this at the top, so when it is running
+ // a fresh execution and not replaying,
+ // this patched statement will return true
+ // and it will run the new code.
+} else if (patched('v2')) {
+} else {
+}
+ ```
+
+
+The following sample shows how `patched()` will behave in a conditional block that's arranged differently.
+In this case, the code's conditional block doesn't have the newest code at the top.
+Because `patched()` will return `true` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions.
+
+
+
+```csharp
+if (patched('v2')) {
+ // This is bad because when doing a new execution (i.e. not replaying),
+ // patched statements evaluate to True (and put a marker
+ // in the event history), which means that new executions
+ // will use v2, and miss v3 below
+}
+else if (patched('v3')) {}
+else {}
+```
+
+
+
+
+### Best Practice of Using Classes as Arguments and Returns
+
+As a side note on the Patching API, its behavior is why Temporal recommends using a single object as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments/returns.
+The Patching API's main use case is to support branching in an `if` block of a method body.
+It is not designed to be used to set different methods or method signatures for different Workflow Versions.
+
+Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single object and returns a single object, so the method signature can stay constant, and you can do your versioning logic using `patched()` within the method body.
diff --git a/docs/develop/python/versioning.mdx b/docs/develop/python/versioning.mdx
index 86519e7baa..25e932f5b5 100644
--- a/docs/develop/python/versioning.mdx
+++ b/docs/develop/python/versioning.mdx
@@ -4,7 +4,7 @@ title: Versioning - Python SDK
sidebar_label: Versioning
description: Learn how to ensure deterministic Temporal Workflow execution and safely deploy updates using the Python SDK's patching and Worker Versioning APIs, for scalable long-running Workflows.
slug: /develop/python/versioning
-toc_max_heading_level: 2
+toc_max_heading_level: 4
keywords:
- best practices
- code sample
@@ -35,7 +35,17 @@ a non-deterministic issue if not handled correctly.
## Introduction to Versioning
-Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction: [https://www.youtube.com/watch?v=kkP899WxgzY](https://www.youtube.com/watch?v=kkP899WxgzY)
+Because we design for potentially long running Workflows at scale, versioning with Temporal works differently. We explain more in this optional 30 minute introduction:
+
+
+
+
## How to use the Python SDK Patching API {#python-sdk-patching-api}
@@ -115,7 +125,7 @@ Implementing patching involves three steps:
### Patching in new code {#using-patched-for-workflow-history-markers}
-Using `patched` inserts a marker into the Workflow History.
+Using `patched()` inserts a marker into the Workflow History.

@@ -199,6 +209,172 @@ class MyWorkflow:
)
```
+
+### Detailed Description of the Patched Function
+
+We take a deep dive into the behavior of the `patched()` function in this optional 37 minute YouTube series:
+
+
+
+
+
+#### Behavior When Not Replaying
+
+If not replaying, and the execution hits a call to patched, it first checks the event history, and:
+
+- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true.
+ This happens in a given patch ID's first block.
+- If the patch ID is in the event history, it won't modify the history, and it will return true.
+ This happens in a given patch ID's subsequent blocks.
+
+> There is a caveat to the above, and we will discuss that below.
+
+#### Behavior When Replaying With Marker Before-Or-At Current Location
+
+If Replaying:
+
+- If the code has a call to patched, and if the event history
+ has a marker from a call to patched in the same place (which means it
+ will match the original event history), then
+ it writes a marker to the replay event history and returns true.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's first block*
+- If the code has a call to patched, and the event history
+ has a marker with that Patch ID earlier in the history,
+ then it will simply return true and not modify the
+ replay event history.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's subsequent blocks*
+
+#### Behavior When Replaying With Marker After Current Location
+
+If the Marker Event is after where the execution currently is
+in the event history, then, in other words,
+the patch is before the original patch, then the patch is too early. It will
+attempt to write the marker to the replay event
+history, but it will throw a non-deterministic
+exception because the replay and original event
+histories don't match
+
+#### Behavior When Replaying With No Marker For that Patch ID
+
+It will return false and not add anything to
+the event history. Furthermore, ***and this is the
+caveat mentioned in the preceeding section [Behavior When Not Replaying](#behavior-when-not-replaying)***, it will make all future calls to patched
+with that ID false -- even after it is done replaying
+and is running new code.
+
+Why is this a caveat?
+
+In the [preceding section](#behavior-when-not-replaying) where we discussed the behavior when not replaying , we said that if not replaying,
+the patched function will always return true, and if
+the marker doesn't exist, it will add it, and if
+the marker already exists, it won't re-add it.
+
+But what this
+is saying is that this doesn't hold if there was already
+a call to patched with that ID in the replay code, but not
+in the event history. In this situation, it won't return
+true.
+
+#### A Summary of the Two Potentially Unexpected Behaviors
+
+1. When Replaying, in the scenario of ***it hits a call to
+ patched, but that patch ID isn't before/on that point in
+ the event history***, you may not expect that
+ the event history *after* where you currently
+ are matters. Because:
+ 1. If that patch ID exists later, you get an NDE [(see above: Behavior When Replaying With Marker After Current Location)](#behavior-when-replaying-with-marker-after-current-location).
+ 2. If it doesn't exist later, you don't get an NDE, and
+ it returns false
+ [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id).
+
+2. When Replaying, if you hit a call to patched with an ID that
+ doesn't exist in the history, then not only will it return
+ false in that occurence, but it will also return false if
+ the execution surpasses the Replay threshold and is running new code.
+ [(see above: Behavior When Replaying With No Marker For that Patch ID)](#behavior-when-replaying-with-no-marker-for-that-patch-id).
+
+#### Implications of the Behaviors
+
+If you deploy new code while a worker is down,
+any workflows that were in the middle of executing will replay
+using old code and then for the rest of the execution, they
+will either:
+
+1. Use new code if there was no call to patched in the replay code
+2. If there was a call to patched in the replay code, they will
+ run the non-patched code during and after replay
+
+This might sound odd, but it's actually exactly what's needed because
+that means that if the future patched code depends on earlier patched code,
+then it won't use the new code -- it will use the old code. But if
+there's new code in the future, and there was no code earlier in the
+body that required the new patch, then it can switch over to the new code,
+and it will do that.
+
+Note that this behavior means that the Workflow ***does not always run
+the newest code***. It only does that if not replaying or if
+surpassed replay and there hasn't been a call to patched (with that ID) throughout
+the replay.
+
+#### Recommendations
+
+Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block.
+
+
+
+```python
+if patched('v3'):
+ # This is the newest version of the code.
+ # put this at the top, so when it is running
+ # a fresh execution and not replaying,
+ # this patched statement will return true
+ # and it will run the new code.
+ pass
+elif patched('v2'):
+ pass
+else:
+ pass
+ ```
+
+
+The following sample shows how `patched()` will behave in a conditional block that's arranged differently.
+In this case, the code's conditional block doesn't have the newest code at the top.
+Because `patched()` will return `True` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions.
+
+
+
+```python
+if patched('v2'):
+ # This is bad because when doing a new execution (i.e. not replaying),
+ # patched statements evaluate to True (and put a marker
+ # in the event history), which means that new executions
+ # will use v2, and miss v3 below
+ pass
+elif patched('v3'):
+ pass
+else:
+ pass
+```
+
+
+
+
+### Best Practice of Using Python Dataclasses as Arguments and Returns
+
+As a side note on the Patching API, its behavior is why Temporal recommends using single dataclasses as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments.
+The Patching API's main use case is to support branching in an `if` block of a method body.
+It is not designed to be used to set different methods or method signatures for different Workflow Versions.
+
+Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single dataclass and returns a single dataclass, so the method signature can stay constant, and you can do your versioning logic using `patched()` within the method body.
+
## How to use Worker Versioning in Python {#worker-versioning}
:::caution
diff --git a/docs/develop/typescript/versioning.mdx b/docs/develop/typescript/versioning.mdx
index 406b4e7f8e..cc227d4038 100644
--- a/docs/develop/typescript/versioning.mdx
+++ b/docs/develop/typescript/versioning.mdx
@@ -238,6 +238,125 @@ export async function myWorkflow(): Promise {
`vFinal` is safe to deploy once all `v2` or earlier Workflows are complete due to the assertion mentioned above.
+
+#### Detailed Description of the Patched Function
+
+Here is a detailed explanation of how the `patched()` function behaves.
+
+##### Behavior When Not Replaying
+
+If not replaying, and the execution hits a call to patched, it first checks the event history, and:
+
+- If the patch ID is not in the event history, it will add a marker to the event history, upsert a search attribute, and return true.
+ This happens in a given patch ID's first block.
+- If the patch ID is in the event history, it won't modify the history, and it will return true.
+ This happens in a given patch ID's subsequent blocks.
+
+##### Behavior When Replaying With Marker Before-Or-At Current Location
+
+If Replaying:
+
+- If the code has a call to patched, and if the event history
+ has a marker from a call to patched in the same place (which means it
+ will match the original event history), then
+ it writes a marker to the replay event history and returns true.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's first block*
+- If the code has a call to patched, and the event history
+ has a marker with that Patch ID earlier in the history,
+ then it will simply return true and not modify the
+ replay event history.
+ *This is similar to the behavior of the non-replay case, and
+ just like in that case, this happens in a given patch ID's subsequent blocks*
+- If the code has a call to patched, and there no marker on or before that spot in the execution, it returns false.
+
+##### Implications of the Behaviors
+
+
+If you deploy new code, it will run the new code if it is
+not replaying, and if it is replaying, it will just do what
+it did the previous time.
+
+This means that if it has gotten through some of your code, then
+you stop the worker and deploy new code, then when it replays,
+it will use the old code throughout the replay, but switch over
+to new code after it has passed the replay threshold. This means
+your new code and your old code must work together. For example,
+if your Workflow Definition originally looked like this:
+
+```ts
+console.log('original code before the sleep')
+await sleep(10000); // <-- Stop the Worker while this is waiting, and deploy the new code below
+console.log('original code after the sleep')
+```
+
+Now we stop the Worker during the sleep, and wrap our original
+code in the else part of a patched `if` statement, and start
+our Worker again.
+
+```ts
+if (patched('my-change-id')) {
+ console.log('new code before the sleep')
+} else {
+ console.log('original code before the sleep') // this will run
+}
+await sleep(10000);
+if (patched('my-change-id')) {
+ console.log('new code after the sleep') // this will run
+} else {
+ console.log('original code after the sleep')
+}
+```
+
+In the first part, it will be Replaying, and it will run the old code,
+and after the sleep, it won't be Replaying, and it will run the new code.
+
+##### Recommendations
+
+Based on this behavior and the implications, when patching in new code, always put the newest code at the top of an if-patched-block.
+
+
+
+```ts
+if (patched('v3')) {
+ // This is the newest version of the code.
+ // put this at the top, so when it is running
+ // a fresh execution and not replaying,
+ // this patched statement will return true
+ // and it will run the new code.
+} else if (patched('v2')) {
+} else {
+}
+ ```
+
+
+The following sample shows how `patched()` will behave in a conditional block that's arranged differently.
+In this case, the code's conditional block doesn't have the newest code at the top.
+Because `patched()` will return `true` when not Replaying (except with the preceding caveats), this snippet will run the `v2` branch instead of `v3` in new executions.
+
+
+
+```ts
+if (patched('v2')) {
+ // This is bad because when doing a new execution (i.e. not replaying),
+ // patched statements evaluate to True (and put a marker
+ // in the event history), which means that new executions
+ // will use v2, and miss v3 below
+}
+else if (patched('v3')) {}
+else {}
+```
+
+
+
+### Best Practice of Using TypeScript Objects as Arguments and Returns
+
+As a side note on the Patching API, its behavior is why Temporal recommends using single objects as arguments and returns from Signals, Queries, Updates, and Activities, rather than using multiple arguments.
+The Patching API's main use case is to support branching in an `if` block of a function body.
+It is not designed to be used to set different functions or function signatures for different Workflow Versions.
+
+Because of this, Temporal recommends that each Signal, Activity, etc, accepts a single object and returns a single object, so the function signature can stay constant, and you can do your versioning logic using `patched()` within the function body.
+
### Upgrading Workflow dependencies
Upgrading Workflow dependencies (such as ones installed into `node_modules`) _might_ break determinism in unpredictable ways.