Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
eca74d6
Add initial skill for testing, which is simply Steve's skill (#1)
donald-pinckney Feb 2, 2026
5998c64
Use claude to merge Steve's, Max's, and Mason's skills. (#2)
donald-pinckney Feb 4, 2026
ff033bc
add simple feedback mechanism (#3)
donald-pinckney Feb 4, 2026
b8b74c0
Change skill name to kebab-case, for compatibility with Amp and Cline…
donald-pinckney Feb 13, 2026
d25073a
Clean up references/core/ai-integration.md
donald-pinckney Feb 13, 2026
b0fcfe3
Clean up references/core/common-gotchas.md
donald-pinckney Feb 13, 2026
e113399
Clean up references/core/common-gotchas.md
donald-pinckney Feb 13, 2026
2b6676f
Clean up references/core/determinism.md
donald-pinckney Feb 13, 2026
efc8ef2
Clean up references/core/determinism.md
donald-pinckney Feb 13, 2026
45497f4
Update error-reference.md
donald-pinckney Feb 17, 2026
f61d2ea
Update interactive-workflows.md
donald-pinckney Feb 17, 2026
1f4c53d
Clean up patterns.md
donald-pinckney Feb 17, 2026
3c19991
Cut shell scripts
donald-pinckney Feb 17, 2026
d237247
Edit troubleshooting.md
donald-pinckney Feb 17, 2026
5695854
remove interceptors for now
donald-pinckney Feb 18, 2026
e10cbb8
remove dynamic workflows
donald-pinckney Feb 18, 2026
d4d9921
clarify on heartbeating of async activity completions, and prompt it …
donald-pinckney Feb 18, 2026
1c185b6
Improve references/python/advanced-features.md
donald-pinckney Feb 18, 2026
e945f1d
Use explicit namespace in connect
donald-pinckney Feb 18, 2026
322f85a
remove duplicated content from determinism.md, clean up
donald-pinckney Feb 18, 2026
bb46cb4
Improve references/python/data-handling.md
donald-pinckney Feb 18, 2026
3dcccb8
Prefer start_to_close_timeout
donald-pinckney Feb 18, 2026
8b7dffa
don't explicitely provide defaults for retry policies
donald-pinckney Feb 18, 2026
f693cc7
error-handling.md cleanup
donald-pinckney Feb 18, 2026
3d43cd2
move idempotency patterns to patterns.md
donald-pinckney Feb 18, 2026
f0387e3
remove multi-param activities
donald-pinckney Feb 18, 2026
f36faca
small edits
donald-pinckney Feb 18, 2026
0bc69b2
Unify sandbox stuff into one file
donald-pinckney Feb 18, 2026
56dde75
local activities aren't experimental
donald-pinckney Feb 18, 2026
35c3bc5
Clean up references/python/sync-vs-async.md
donald-pinckney Feb 18, 2026
d61866b
Cleanup observability.md, remove duplicated search attributes
donald-pinckney Feb 19, 2026
ad4cc34
Cut otel for now
donald-pinckney Feb 19, 2026
f01763a
cut a lot of duplicate stuff from python gotchas, address comments
donald-pinckney Feb 20, 2026
cbfd857
de-duplicate content
donald-pinckney Feb 20, 2026
79ecaf3
Lots of improvements to testing
donald-pinckney Feb 20, 2026
663bbe4
cleanup to top level of skill (like CLI install instructions), and to…
donald-pinckney Feb 20, 2026
ec02f38
Improve patterns.md
donald-pinckney Feb 23, 2026
14da16a
clean up ai-patterns.md
donald-pinckney Feb 23, 2026
fd1af23
Update readme with installation instructions
donald-pinckney Feb 23, 2026
846cb44
remove ts directory
donald-pinckney Feb 23, 2026
0692311
De-couple core from python and TypeScript as much as possible
donald-pinckney Feb 23, 2026
83aa233
Remove TypeScript hints
donald-pinckney Feb 23, 2026
47ae2fd
add prompting for feedback at startup - wait for ethan on slack channel
donald-pinckney Feb 19, 2026
75c568d
shorten url
donald-pinckney Feb 19, 2026
3f972f9
Update slack channel
donald-pinckney Feb 23, 2026
99a2d4a
Automated pass over on python cleanup & deduplication
donald-pinckney Feb 27, 2026
87fe92e
Remove multi-patching from Python, since its obvious, dont waste toke…
donald-pinckney Mar 3, 2026
766f888
Add TypeScript (#31)
donald-pinckney Mar 7, 2026
8ad9233
Fix typos and reference links (#36)
donald-pinckney Mar 7, 2026
c73c9c0
quick edit to readme (#37)
donald-pinckney Mar 12, 2026
c013b87
Fix saga compensations to run under cancellation protection (#43)
donald-pinckney Mar 17, 2026
291f4e5
Update readme for public preview (#45)
donald-pinckney Mar 18, 2026
21d1d41
a few more readme tweaks (#46)
donald-pinckney Mar 18, 2026
6cd40f2
Add MIT License to the project (#47)
donald-pinckney Mar 18, 2026
e16c9b8
Add Go (supersedes other PR) (#38)
donald-pinckney Mar 19, 2026
b9a0728
Setup CODEOWNERS to AI SDK team (#48)
donald-pinckney Mar 19, 2026
29e4600
Align version number in SKILL.md and plugin.json. (#49)
donald-pinckney Mar 19, 2026
1cc4591
Merge branch 'main' into dev
donald-pinckney Mar 19, 2026
68ebe14
Fix typos and broken references across skill docs (#56)
jacksonlo Mar 31, 2026
b040ae1
Fix Python reference bugs: incorrect API name, syntax error, broken c…
trevoryao Mar 31, 2026
57c08ef
Add `@workflow.init` decorator to python.md Key Concepts (#57)
brianstrauch Mar 31, 2026
8369c65
Remove ASCII diagram, replace with prose. (#66)
donald-pinckney Apr 2, 2026
9b97cee
[fix] Add missing section to TS's observability (#65)
donald-pinckney Apr 2, 2026
0c8586b
Add Java SDK support (#42)
donald-pinckney Apr 2, 2026
7d5ae76
Reduce repetition in determinism sans sandboxing (#67)
donald-pinckney Apr 2, 2026
25eb553
Bump to 0.2.0 for Java release (#72)
donald-pinckney Apr 3, 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
cut a lot of duplicate stuff from python gotchas, address comments
  • Loading branch information
donald-pinckney committed Feb 23, 2026
commit f01763a45abc467df0d23dec5e034d01fd0ba690
5 changes: 4 additions & 1 deletion references/python/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class MyWorkflow:
return result
```

Only set options such as maximum_interval, maximum_attempts etc. if you have a domain-specific reason to.
If not, prefer to leave them at their defaults.

## Timeout Configuration

```python
Expand All @@ -101,7 +104,7 @@ class MyWorkflow:
my_activity,
start_to_close_timeout=timedelta(minutes=5), # Single attempt
schedule_to_close_timeout=timedelta(minutes=30), # Including retries
heartbeat_timeout=timedelta(seconds=30), # Between heartbeats
heartbeat_timeout=timedelta(minutes=2), # Between heartbeats
)
```

Expand Down
218 changes: 18 additions & 200 deletions references/python/gotchas.md
Original file line number Diff line number Diff line change
@@ -1,130 +1,6 @@
# Python Gotchas

Python-specific mistakes and anti-patterns. See also [Common Gotchas](../core/common-gotchas.md) for language-agnostic concepts.

## Idempotency

```python
@dataclass
class ChargePaymentInput:
order_id: str
amount: float

# BAD - May charge customer multiple times on retry
@activity.defn
async def charge_payment(input: ChargePaymentInput) -> str:
return await payment_api.charge(input.customer_id, input.amount)

# GOOD - Safe for retries
@activity.defn
async def charge_payment(input: ChargePaymentInput) -> str:
return await payment_api.charge(
input.customer_id,
input.amount,
idempotency_key=f"order-{input.order_id}"
)
```

## Replay Safety

### Side Effects in Workflows

```python
# BAD - Prints on every replay, notification runs in workflow
@workflow.defn
class NotificationWorkflow:
@workflow.run
async def run(self):
print("Starting workflow") # Runs on replay too
send_slack_notification("Started") # Side effect in workflow!
await workflow.execute_activity(...)

# GOOD - Replay-safe
@workflow.defn
class NotificationWorkflow:
@workflow.run
async def run(self):
workflow.logger.info("Starting workflow") # Only logs on first execution
await workflow.execute_activity(send_notification, "Started")
```

### Time-Based Logic

```python
# BAD - Different time on replay
if datetime.now() > deadline:
await cancel_order()

# GOOD - Consistent across replays
if workflow.now() > deadline:
await cancel_order()
```

### Other Non-Deterministic Operations

```python
# BAD - Different values on replay
random_id = str(uuid.uuid4())
random_value = random.random()

# GOOD - Deterministic alternatives
random_id = workflow.uuid4()
random_value = workflow.random().random()
```

## Query Handlers

### Modifying State

```python
# BAD - Query modifies state
@workflow.defn
class QueueWorkflow:
def __init__(self):
self._queue = []

@workflow.query
def get_next_item(self) -> str | None:
if self._queue:
return self._queue.pop(0) # Mutates state!
return None

# GOOD - Query reads, Update modifies
@workflow.defn
class QueueWorkflow:
def __init__(self):
self._queue = []

@workflow.query
def peek(self) -> str | None:
return self._queue[0] if self._queue else None

@workflow.update
def dequeue(self) -> str | None:
if self._queue:
return self._queue.pop(0)
return None
```

### Blocking in Queries

```python
# BAD - Queries cannot await
@workflow.query
async def get_data_with_refresh(self) -> dict:
if self._data is None:
self._data = await workflow.execute_activity(fetch_data, ...)
return self._data

# GOOD - Query returns state, signal triggers refresh
@workflow.signal
async def refresh_data(self):
self._data = await workflow.execute_activity(fetch_data, ...)

@workflow.query
def get_data(self) -> dict | None:
return self._data
```
Python-specific mistakes and anti-patterns. See also [Common Gotchas](references/core/common-gotchas.md) for language-agnostic concepts.

## File Organization

Expand Down Expand Up @@ -153,6 +29,8 @@ class MyWorkflow:
pass
```

`references/python/sandbox.md` contains more info about the Python sandbox.

### Mixing Workflows and Activities

```python
Expand Down Expand Up @@ -185,6 +63,8 @@ async def my_activity():

## Async vs Sync Activities

The Temporal Python SDK supports both async and sync activities. See `references/python/sync-vs-async.md` to understand which to choose. Below are important anti-patterns for both aysnc and sync activities.

### Blocking in Async Activities

```python
Expand Down Expand Up @@ -218,7 +98,7 @@ async def process_file(path: str) -> str:
### Missing Executor for Sync Activities

```python
# BAD - Sync activity without executor blocks worker
# BAD - Sync activity REQUIRES executor
@activity.defn
def slow_computation(data: str) -> str:
return heavy_cpu_work(data)
Expand All @@ -227,7 +107,7 @@ Worker(
client,
task_queue="my-queue",
activities=[slow_computation],
# Missing activity_executor!
# Missing activity_executor! --> THIS IMMEDIATELY RAISES AN EXCEPTION!
)

# GOOD - Provide executor
Expand All @@ -239,76 +119,10 @@ Worker(
)
```

## Error Handling

### Swallowing Errors

```python
# BAD - Error is hidden
@workflow.defn
class SilentFailureWorkflow:
@workflow.run
async def run(self):
try:
await workflow.execute_activity(...)
except Exception:
pass # Error is lost!

# GOOD - Handle appropriately
@workflow.defn
class ProperErrorHandlingWorkflow:
@workflow.run
async def run(self):
try:
await workflow.execute_activity(...)
except ActivityError as e:
workflow.logger.error(f"Activity failed: {e}")
raise # Or use fallback, compensate, etc.
```

### Wrong Retry Classification

```python
# BAD - Network errors should be retried
@activity.defn
async def call_api():
try:
return await http_client.get(url)
except ConnectionError:
raise ApplicationError("Connection failed", non_retryable=True)

# GOOD - Only permanent failures are non-retryable
@activity.defn
async def call_api():
try:
return await http_client.get(url)
except ConnectionError:
raise # Let Temporal retry
except InvalidCredentialsError:
raise ApplicationError("Invalid API key", non_retryable=True)
```

## Retry Policies
## Wrong Retry Classification

### Too Aggressive

```python
# BAD - Gives up too easily
result = await workflow.execute_activity(
flaky_api_call,
start_to_close_timeout=timedelta(seconds=30),
retry_policy=RetryPolicy(maximum_attempts=1),
)

# GOOD - Resilient to transient failures
result = await workflow.execute_activity(
flaky_api_call,
start_to_close_timeout=timedelta(minutes=10),
retry_policy=RetryPolicy(maximum_attempts=10),
)
```

Generally, prefer to use the default RetryPolicy.
**Example:** Transient networks errors should be retried. Authentication errors should not be.
See `references/python/error-handling.md` to understand how to classify errors.

## Heartbeating

Expand All @@ -318,13 +132,13 @@ Generally, prefer to use the default RetryPolicy.
# BAD - No heartbeat, can't detect stuck activities
@activity.defn
async def process_large_file(path: str):
for chunk in read_chunks(path):
async for chunk in read_chunks(path):
process(chunk) # Takes hours, no heartbeat

# GOOD - Regular heartbeats with progress
@activity.defn
async def process_large_file(path: str):
for i, chunk in enumerate(read_chunks(path)):
async for i, chunk in enumerate(read_chunks(path)):
activity.heartbeat(f"Processing chunk {i}")
process(chunk)
```
Expand All @@ -351,12 +165,14 @@ await workflow.execute_activity(

### Not Testing Failures

Below shows an example of how to test failure cases:

```python
# Test failure scenarios
@pytest.mark.asyncio
async def test_activity_failure_handling():
async with await WorkflowEnvironment.start_time_skipping() as env:
# Create activity that always fails
async with await WorkflowEnvironment.start_local() as env:
# An example activity that always fails
@activity.defn
async def failing_activity() -> str:
raise ApplicationError("Simulated failure", non_retryable=True)
Expand All @@ -377,6 +193,8 @@ async def test_activity_failure_handling():

### Not Testing Replay

Replay tests let you test that you do not have hidden sources of non-determinism bugs in your workflow code:

```python
from temporalio.worker import Replayer

Expand Down