Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0589a99
Clean up references/core/ai-integration.md
donald-pinckney Feb 13, 2026
22d90c5
Clean up references/core/common-gotchas.md
donald-pinckney Feb 13, 2026
a32152b
Clean up references/core/common-gotchas.md
donald-pinckney Feb 13, 2026
9ce8441
Clean up references/core/determinism.md
donald-pinckney Feb 13, 2026
519b2ea
Clean up references/core/determinism.md
donald-pinckney Feb 13, 2026
49c449a
Update error-reference.md
donald-pinckney Feb 17, 2026
d2a4af3
Update interactive-workflows.md
donald-pinckney Feb 17, 2026
e62865a
Clean up patterns.md
donald-pinckney Feb 17, 2026
324b532
Cut shell scripts
donald-pinckney Feb 17, 2026
5d3f79f
Edit troubleshooting.md
donald-pinckney Feb 17, 2026
19a9589
remove interceptors for now
donald-pinckney Feb 18, 2026
761b220
remove dynamic workflows
donald-pinckney Feb 18, 2026
4d032f4
clarify on heartbeating of async activity completions, and prompt it …
donald-pinckney Feb 18, 2026
8d77164
Improve references/python/advanced-features.md
donald-pinckney Feb 18, 2026
ceb40a0
Use explicit namespace in connect
donald-pinckney Feb 18, 2026
c61bc64
remove duplicated content from determinism.md, clean up
donald-pinckney Feb 18, 2026
236eab2
Improve references/python/data-handling.md
donald-pinckney Feb 18, 2026
43b1ed7
Prefer start_to_close_timeout
donald-pinckney Feb 18, 2026
cc593e3
don't explicitely provide defaults for retry policies
donald-pinckney Feb 18, 2026
24f0e3e
error-handling.md cleanup
donald-pinckney Feb 18, 2026
18cb1e8
move idempotency patterns to patterns.md
donald-pinckney Feb 18, 2026
03e8706
remove multi-param activities
donald-pinckney Feb 18, 2026
3f7dc9d
small edits
donald-pinckney Feb 18, 2026
9163fa2
Unify sandbox stuff into one file
donald-pinckney Feb 18, 2026
4c288cf
local activities aren't experimental
donald-pinckney Feb 18, 2026
b8b47ca
Clean up references/python/sync-vs-async.md
donald-pinckney Feb 18, 2026
9bc285f
Cleanup observability.md, remove duplicated search attributes
donald-pinckney Feb 19, 2026
f455d39
Cut otel for now
donald-pinckney Feb 19, 2026
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
Prev Previous commit
Next Next commit
remove duplicated content from determinism.md, clean up
  • Loading branch information
donald-pinckney committed Feb 18, 2026
commit c61bc64d648e5d1a8e5cc6eef778de81b56bb9c7
111 changes: 17 additions & 94 deletions references/python/determinism.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,26 @@ The Python SDK runs workflows in a sandbox that provides automatic protection ag

Temporal achieves durability through **history replay**. Understanding this mechanism is key to writing correct Workflow code.

### How Replay Works

1. **Initial Execution**: When your Workflow runs for the first time, the SDK records Commands (like "schedule activity") to the Event History stored by Temporal Server.

2. **Recovery/Continuation**: When a Worker restarts, loses connectivity, or picks up a Workflow Task, it must restore the Workflow's state by replaying the code from the beginning.

3. **Command Matching**: During replay, the SDK re-executes your Workflow code but doesn't actually run Activities again. Instead, it compares the Commands your code generates against the Events in history. If there's a match, it uses the stored result.

4. **Non-determinism Detection**: If your code generates different Commands than what's in history (e.g., different Activity name, different order), the SDK raises a `NondeterminismError`.

### Example: Why datetime.now() Breaks Replay
## Forbidden Operations

```python
# BAD - Non-deterministic
@workflow.defn
class BadWorkflow:
@workflow.run
async def run(self) -> str:
import datetime
if datetime.datetime.now().hour < 12: # Different value on replay!
await workflow.execute_activity(morning_activity, ...)
else:
await workflow.execute_activity(afternoon_activity, ...)
```
- Direct I/O (network, filesystem)
- Threading operations
- `subprocess` calls
- Global mutable state modification
- `time.sleep()` (use `asyncio.sleep()`)
- and so on

If this runs at 11:59 AM initially and replays at 12:01 PM, it will try to schedule a different Activity, causing `NondeterminismError`.
## Safe Builtin Alternatives to Common Non Deterministic Things

```python
# GOOD - Deterministic
@workflow.defn
class GoodWorkflow:
@workflow.run
async def run(self) -> str:
if workflow.now().hour < 12: # Consistent during replay
await workflow.execute_activity(morning_activity, ...)
else:
await workflow.execute_activity(afternoon_activity, ...)
```
| Forbidden | Safe Alternative |
|-----------|------------------|
| `datetime.now()` | `workflow.now()` |
| `datetime.utcnow()` | `workflow.now()` |
| `random.random()` | `rng = workflow.new_random() ; rng.randint(1, 100)` |
| `uuid.uuid4()` | `workflow.uuid4()` |
| `time.time()` | `workflow.now().timestamp()` |

### Testing Replay Compatibility
## Testing Replay Compatibility

Use the `Replayer` class to verify your code changes are compatible with existing histories:

Expand All @@ -73,64 +53,7 @@ The sandbox:
- Restricts non-deterministic library calls via proxy objects
- Passes through standard library with restrictions

## Safe Alternatives

| Forbidden | Safe Alternative |
|-----------|------------------|
| `datetime.now()` | `workflow.now()` |
| `datetime.utcnow()` | `workflow.now()` |
| `random.random()` | `workflow.random().random()` |
| `random.randint()` | `workflow.random().randint()` |
| `uuid.uuid4()` | `workflow.uuid4()` |
| `time.time()` | `workflow.now().timestamp()` |

## Pass-Through Pattern

For third-party libraries that need to bypass sandbox restrictions:

```python
with workflow.unsafe.imports_passed_through():
import pydantic
from my_module import my_activity
```

## Disabling Sandbox

```python
# Per-workflow
@workflow.defn(sandboxed=False)
class UnsandboxedWorkflow:
pass

# Per-block
with workflow.unsafe.sandbox_unrestricted():
# Unrestricted code
pass

# Globally (worker level)
from temporalio.worker import UnsandboxedWorkflowRunner
Worker(..., workflow_runner=UnsandboxedWorkflowRunner())
```

## Forbidden Operations

- Direct I/O (network, filesystem)
- Threading operations
- `subprocess` calls
- Global mutable state modification
- `time.sleep()` (use `asyncio.sleep()`)

## Commands and Events

Understanding the relationship between your code and the Event History:

| Workflow Code | Command Generated | Event Created |
|--------------|-------------------|---------------|
| `workflow.execute_activity()` | ScheduleActivityTask | ActivityTaskScheduled |
| `asyncio.sleep()` / `workflow.sleep()` | StartTimer | TimerStarted |
| `workflow.execute_child_workflow()` | StartChildWorkflowExecution | ChildWorkflowExecutionStarted |
| `workflow.continue_as_new()` | ContinueAsNewWorkflowExecution | WorkflowExecutionContinuedAsNew |
| Return from `@workflow.run` | CompleteWorkflowExecution | WorkflowExecutionCompleted |
See more info at `references/python/sandbox.md`

## Best Practices

Expand Down