* Add initial skill for testing, which is simply Steve's skill (#1) * Add initial skill for testing, which is simply Steve's skill * Rename skill to 'temporal-dev' and update version Updated skill name and version for Temporal Python. * Use claude to merge Steve's, Max's, and Mason's skills. (#2) * Use claude to merge Steve's, Max's, and Mason's skills. Did a review pass using claude's skill devlopment skills * Add missing things from Steve * trigger tweaks * Add in common gotchas from Johann * add simple feedback mechanism (#3) * Change skill name to kebab-case, for compatibility with Amp and Cline (#7) * Clean up references/core/ai-integration.md * Clean up references/core/common-gotchas.md * Clean up references/core/common-gotchas.md * Clean up references/core/determinism.md * Clean up references/core/determinism.md * Update error-reference.md * Update interactive-workflows.md * Clean up patterns.md * Cut shell scripts * Edit troubleshooting.md * remove interceptors for now * remove dynamic workflows * clarify on heartbeating of async activity completions, and prompt it a bit in relation to signals * Improve references/python/advanced-features.md * Use explicit namespace in connect * remove duplicated content from determinism.md, clean up * Improve references/python/data-handling.md * Prefer start_to_close_timeout * don't explicitely provide defaults for retry policies * error-handling.md cleanup * move idempotency patterns to patterns.md * remove multi-param activities * small edits * Unify sandbox stuff into one file * local activities aren't experimental * Clean up references/python/sync-vs-async.md * Cleanup observability.md, remove duplicated search attributes * Cut otel for now * cut a lot of duplicate stuff from python gotchas, address comments * de-duplicate content * Lots of improvements to testing * cleanup to top level of skill (like CLI install instructions), and to top-level of python * Improve patterns.md * clean up ai-patterns.md * Update readme with installation instructions * remove ts directory * De-couple core from python and TypeScript as much as possible * Remove TypeScript hints * add prompting for feedback at startup - wait for ethan on slack channel * shorten url * Update slack channel * Automated pass over on python cleanup & deduplication * Remove multi-patching from Python, since its obvious, dont waste tokens on it. (#34) * Add TypeScript (#31) Adds initial support for TypeScript to the skill --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> * Fix typos and reference links (#36) * Fix typos and reference links * 2 more typo fixes * quick edit to readme (#37) * Fix saga compensations to run under cancellation protection (#43) When a workflow is cancelled mid-saga, compensations must run in a cancellation-protected scope, otherwise they are immediately cancelled before they can execute. - Python: wrap compensation loop in asyncio.shield() so it runs even when the workflow receives a CancelledError - TypeScript: wrap compensation loop in CancellationScope.nonCancellable() so it runs even when the root scope is cancelled (per official docs: "Cleanup logic must be in a nonCancellable scope") - TypeScript: also fix compensation registration order — register BEFORE calling the activity (was already correct in Python) Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Update readme for public preview (#45) * a few more readme tweaks (#46) * Add MIT License to the project (#47) * Add Go (supersedes other PR) (#38) * progress on go * Go translation workflow completed. * missed a few spots * Manual edits * Address feedback * Add gotcha about anonymous local activities * Sample code for payload converter * clarify sdk protection mechanisms * Setup CODEOWNERS to AI SDK team (#48) * Align version number in SKILL.md and plugin.json. (#49) --------- Co-authored-by: James Watkins-Harvey <mjameswh@users.noreply.github.com> Co-authored-by: Chris Olszewski <chrisdolszewski@gmail.com> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
6.8 KiB
Python Workflow Sandbox
Overview
The Python SDK runs workflows in a sandbox that provides automatic protection against non-deterministic operations. This is unique to the Python SDK.
How the Sandbox Works
The sandbox:
- Isolates global state via
execcompilation - Restricts non-deterministic library calls via proxy objects
- Passes through standard library with restrictions
- Reloads workflow files on each execution
Forbidden Operations
These operations will fail in the sandbox:
- Direct I/O: Network calls, file reads/writes
- Threading:
threadingmodule operations - Subprocess:
subprocesscalls - Global state: Modifying mutable global variables
- Blocking sleep:
time.sleep()(useworkflow.sleep(timedelta(...)))
Pass-Through Pattern
Third-party libraries that aren't sandbox-aware need explicit pass-through:
from temporalio import workflow
with workflow.unsafe.imports_passed_through():
import pydantic
from my_module import my_dataclass
When to use pass-through:
- Data classes and models (Pydantic, dataclasses)
- Serialization libraries
- Type definitions
- Any library that doesn't do I/O or non-deterministic operations
- Performance, as many non-passthrough imports can be slower
Note: The imports, even when using imports_passed_through, should all be at the top of the file. Runtime imports are an anti-pattern.
Importing Activities
Activities should be imported through pass-through since they're defined outside the sandbox:
# workflows/order.py
from temporalio import workflow
with workflow.unsafe.imports_passed_through():
from activities.payment import process_payment
from activities.shipping import ship_order
@workflow.defn
class OrderWorkflow:
@workflow.run
async def run(self, order_id: str) -> str:
await workflow.execute_activity(
process_payment,
order_id,
start_to_close_timeout=timedelta(minutes=5),
)
return await workflow.execute_activity(
ship_order,
order_id,
start_to_close_timeout=timedelta(minutes=10),
)
Disabling the Sandbox
@workflow.defn
class MyWorkflow:
@workflow.run
async def run(self) -> str:
with workflow.unsafe.sandbox_unrestricted():
# Unrestricted code block
pass
return "result"
- Per‑block escape hatch from runtime restrictions; imports unchanged.
- Use when: You need to call something the sandbox would normally block (e.g., a restricted stdlib call) in a very small, controlled section.
- IMPORTANT: Use it sparingly; you lose determinism checks inside the block
- Genuinely non-deterministic code still MUST go into activities.
Customizing Invalid Module Members
invalid_module_members includes modules that cannot be accessed.
Checks are compared against the fully qualified path to the item.
import dataclasses
from temporalio.worker import Worker
from temporalio.worker.workflow_sandbox import (
SandboxedWorkflowRunner,
SandboxMatcher,
SandboxRestrictions,
)
# Example 1: Remove a restriction on datetime.date.today():
restrictions = dataclasses.replace(
SandboxRestrictions.default,
invalid_module_members=SandboxRestrictions.invalid_module_members_default.with_child_unrestricted(
"datetime", "date", "today",
),
)
# Example 2: Restrict the datetime.date class from being used
restrictions = dataclasses.replace(
SandboxRestrictions.default,
invalid_module_members=SandboxRestrictions.invalid_module_members_default | SandboxMatcher(
children={"datetime": SandboxMatcher(use={"date"})},
),
)
worker = Worker(
...,
workflow_runner=SandboxedWorkflowRunner(restrictions=restrictions),
)
Import Notification Policy
Control warnings/errors for sandbox import issues. Recommended for catching potential problems:
from temporalio import workflow
from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner, SandboxRestrictions
restrictions = SandboxRestrictions.default.with_import_notification_policy(
workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT
| workflow.SandboxImportNotificationPolicy.WARN_ON_UNINTENTIONAL_PASSTHROUGH
)
worker = Worker(
...,
workflow_runner=SandboxedWorkflowRunner(restrictions=restrictions),
)
WARN_ON_DYNAMIC_IMPORT(default) - warns on imports after initial workflow loadWARN_ON_UNINTENTIONAL_PASSTHROUGH- warns when modules are imported into sandbox without explicit passthrough (not default, but highly recommended for catching missing passthroughs)RAISE_ON_UNINTENTIONAL_PASSTHROUGH- raise instead of warn
Override per-import with the context manager:
with workflow.unsafe.sandbox_import_notification_policy(
workflow.SandboxImportNotificationPolicy.SILENT
):
import pydantic # No warning for this import
Disable Lazy sys.modules Passthrough
By default, passthrough modules are lazily added to the sandbox's sys.modules when accessed. To require explicit imports:
import dataclasses
from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner, SandboxRestrictions
restrictions = dataclasses.replace(
SandboxRestrictions.default,
disable_lazy_sys_module_passthrough=True,
)
worker = Worker(
...,
workflow_runner=SandboxedWorkflowRunner(restrictions=restrictions),
)
When True, passthrough modules must be explicitly imported to appear in the sandbox's sys.modules.
File Organization
Critical: Keep workflow definitions in separate files from activity definitions.
The sandbox reloads workflow definition files on every execution. Minimizing file contents improves Worker performance.
my_temporal_app/
├── workflows/
│ └── order.py # Only workflow classes
├── activities/
│ └── payment.py # Only activity functions
├── models/
│ └── order.py # Shared data models
├── worker.py # Worker setup, imports both
└── starter.py # Client code
Common Issues
Import Errors
Error: Cannot import 'pydantic' in sandbox
Fix: Use pass-through:
with workflow.unsafe.imports_passed_through():
import pydantic
Non-Determinism from Libraries
Some libraries do internal caching or use current time:
# May cause non-determinism
import some_library
result = some_library.cached_operation() # Cache changes between replays
Fix: Move to activity or use pass-through with caution.
Best Practices
- Separate workflow and activity files for performance
- Use pass-through explicitly for third-party libraries
- Keep workflow files small to minimize reload time
- Move I/O to activities always
- Test with replay to catch sandbox issues early