Actor Workflow pattern definition
An Actor Workflow is an Entity Workflow that does things, not just holds state. It extends the Entity Workflow pattern by adding behavioral distinction: an entity is a thing, an actor is a thing that does things. An Entity Workflow is analogous to a distributed data cache (stores and retrieves state). An Actor Workflow is analogous to a distributed object with operations (stores state and executes side effects). An Entity Workflow that takes action becomes an Actor Workflow. Examples include: a Workflow that scales a database when load increases, starts a car engine when a driver authenticates, submits an order when a customer confirms checkout, or manages a player session by joining rooms and executing combat moves.
Actor model mapping to Temporal concepts
The actor model formalizes computation in terms of autonomous units that communicate through asynchronous messages. Each actor has encapsulated state, processes messages one at a time from an inbox, can send messages to other actors, can create new actors, and can change internal state in response to messages. Temporal maps naturally to this model: Actor identity = Workflow Id, Actor mailbox = Workflow Event History (Signals or Updates), Processing a message = Signal handler or Update handler, Sending a message to another actor = Signaling an external Workflow, Creating a new actor = Starting a new Workflow Execution, Actor supervision = Retry Policies and parent-child relationships, Actor state persistence = Durable Execution (automatic via Event History).
Event History limits and Continue-As-New for actor workflows
Every Workflow Execution in Temporal produces an append-only Event History limited to 50,000 Events or 50 MB. For actor Workflows that run indefinitely, Event History growth must be managed proactively. Continue-As-New atomically completes the current Workflow Execution and starts a new one with the same Workflow Id, carrying forward any state provided as arguments. From the perspective of external callers, the Workflow Id remains the same. Signals sent during the transition are not lost; the Temporal Service buffers them for the new execution. Before executing Continue-As-New, all in-progress Signal and Update handlers must finish processing. The workflow.all_handlers_finished predicate provides this guarantee.
Signal volume limits per Workflow Execution
Two hard limits govern Signal volume per Workflow Execution: 10,000 total Signals per Execution (Continue-As-New resets this counter, so an entity that transitions regularly is not constrained in practice), and 2,000 pending Signals (unprocessed Signals buffered by the server) at any one time (new Signals are rejected if this limit is reached). The practical throughput ceiling is Worker-side: each Signal triggers a Workflow Task, and a Workflow processes one Workflow Task at a time, yielding a few Workflow Tasks per second per Workflow Execution for typical short tasks.
High-frequency event streaming patterns for actor workflows
If a use case requires higher Signal ingestion rates than typical (for example, streaming real-time game telemetry), consider these approaches: (1) Batch events into a single Signal payload—instead of one Signal per game event, batch several events into a list and send them as a single Signal, with the handler appending the entire batch to the queue in one shot. (2) Use an aggregation layer—route high-frequency event streams through a service such as a message broker or aggregator that batches events before forwarding them as Signals, decoupling the producer's throughput from the Workflow's processing rate.
Signal deduplication in actor workflows
Temporal delivers Signals at least once: the same Signal may arrive more than once under certain failure conditions. Without deduplication, a duplicate Signal could cause incorrect behavior (for example, double-processing a move). Deduplication strategies: (1) For idempotent operations (like join_room), check if the operation is already queued or completed and drop duplicate Signals silently. (2) For non-idempotent operations (like execute_move), use an event_id field as an idempotency key, track seen identifiers in an in-memory set, and ignore Signals with duplicate event_ids. Callers must generate and provide a unique event_id for every Signal requiring deduplication. If a Signal has no event_id, duplicates cannot be detected. For operations where the caller needs strict at-most-once semantics or confirmation, use a Workflow Update instead of a Signal.
Message queue pattern for Signal processing in workflows
Signal handlers should not execute Activities directly. Instead, append to internal queues and have the main workflow loop drain these queues. This serializes side effects and prevents concurrent Activity executions that could conflict with each other. For example, if two join_room Signals arrive simultaneously, processing through a queue guarantees that the player leaves the first room before joining the second. This pattern ensures deterministic ordering and makes state transitions atomic.
Workflow state management across Continue-As-New cycles
Continue-As-New atomically completes the current Workflow Execution and starts a new one with the same Workflow Id. Any state that must persist across the transition must be explicitly passed as arguments to the new execution. State that does not need to persist (such as a temporary deduplication set for Signals in the current execution) can be reset in the new execution. Session-level counters can be reset while lifetime counters are preserved. Before continuing, ensure all in-progress Signal and Update handlers have finished by awaiting workflow.all_handlers_finished.
Search Attributes for operational visibility of actor workflows
Search Attributes enable operators to query running Workflows by custom criteria. Update Search Attributes whenever actor state changes (status, location, level, etc.). For example, to find all players in a specific room: PlayerStatus = 'in_room' AND CurrentRoom = 'dungeon-7'. To find all players above a certain level: PlayerLevel >= 10 AND PlayerStatus = 'online'. Search Attributes allow live operations tasks like finding all players in a room that needs maintenance, identifying high-level players for special events, or monitoring the distribution of player statuses.
Player Actor Workflow lifecycle pattern for game sessions
A player Actor Workflow represents a single player's game session with the following lifecycle: (1) Start when a player logs in, passing initial PlayerState. (2) Enter a main event loop waiting for Signals and Updates. (3) Process queued room joins, combat moves, and notifications through Activities. (4) Periodically check Event History size and trigger Continue-As-New when approaching limits. (5) Carry forward PlayerState across Continue-As-New boundaries. (6) End when the player logs out. The Workflow remains independently addressable by player ID and survives Worker crashes.
Player session example using Python Actor Workflow
The durable-gaming-sessions guide provides a complete Python implementation of a player session Actor Workflow system. Key files: models.py (PlayerState, CombatMoveRequest, RoomInfo dataclasses), activities.py (join_game_room, leave_game_room, process_combat_move, update_leaderboard Activities), player_workflow.py (PlayerSessionWorkflow with join_room and execute_move Signal handlers, get_player_state Query, main event loop), worker.py (starts a Worker on task queue 'player-session-queue' with ThreadPoolExecutor), starter.py (starts sessions and sends Signals/Queries). The implementation demonstrates Signal queuing, deduplication, Continue-As-New, Search Attributes, and cross-actor communication via Activities.
Workflow.wait_condition for polling in actor workflows
Use workflow.wait_condition with a lambda predicate and timeout to efficiently poll for pending actions in actor workflows. Example: await workflow.wait_condition(lambda: (bool(pending_joins) or bool(pending_moves) or shutdown_requested), timeout=timedelta(minutes=5)). This waits for any pending action or timeout expiration without busy-polling. The timeout ensures periodic checks of Event History growth and Continue-As-New conditions even during idle periods. On timeout, the condition is not satisfied but execution continues, allowing the Workflow to check Continue-As-New thresholds.
Entity Workflow vs Actor Workflow distinction
An Entity Workflow is a long-running Workflow that represents a thing: it holds state, responds to messages, and exposes that state through Queries. It is analogous to a distributed data cache. An Actor Workflow is an Entity Workflow that does things: it holds state and executes side effects through Activities. The distinction is behavioral—if an Entity Workflow takes action, it becomes an Actor Workflow. Both patterns use the same Temporal features (Signals, Queries, Updates, Activities); the difference is whether the Workflow delegates side effects to Activities.
Audit trail recording for compliance
Every action in the approval Workflow is recorded in an audit trail: document submission, storage, approval requests, signals received, reminders sent, escalations, decisions, rejections, and withdrawals. Each audit entry includes timestamp (ISO 8601, via workflow.now()), workflow_id, action name, actor (email or 'system'), and detailed description. The audit trail is maintained both in-memory within the Workflow state and persisted externally via the record_audit_entry Activity using upsert semantics keyed on Workflow Id and action to ensure idempotency.
Python worker logging with activity.logger
Activities and Workflows should use structured logging via activity.logger (in Activities) and workflow.logger (in Workflows) instead of print() or standard Python logging. This ensures logs are replay-safe in Workflows and properly integrated with Temporal's observability. Example: activity.logger.info('Sending notification', extra={'recipient': request.recipient_email, 'subject': request.subject}). The extra dict provides structured context for better filtering and searching in logs.
Task Queue naming best practice
Always define Task Queue names as constants in a shared module that both Workflows and Workers import. If a Workflow references Task Queue 'gpu-processing' but a Worker polls 'gpu-procesing' (typo), two separate queues are created and the Worker never receives Tasks.
Provisioning and deprovisioning workflow separation best practice
By separating the provisioning and deprovisioning steps into distinct Workflows, the pattern adheres to the best practice of returning control to the caller as soon as the provisioning command succeeds. The provisioning Workflow completes after confirming the Child Workflow has started, preventing the client from waiting for the long deprovisioning operation.
Claim check pattern for large payloads
To handle large payloads and reduce the risk of exceeding payload size limits, use the claim check pattern: store large payloads in an object store and pass references to the stored payloads within the Workflow instead of the actual data. Retrieve the payloads from the object store when needed during execution. The claim check pattern is built into SDKs as External Storage or can be implemented using a custom Payload Codec. This is the most reliable way to avoid hitting payload size limits.
Compression for large payloads
Use compression with a custom Payload Codec for large payloads. This may address the immediate issue, but if payload sizes continue to grow, the problem can arise again. The claim check pattern is preferred for a more reliable long-term solution.
gRPC message size limit resolution: Break larger batches into smaller batches
To resolve gRPC message size limit errors, break larger batches of commands into smaller batch sizes. At the Workflow level, modify the Workflow to process Activities or Child Workflows in smaller batches and iterate through each batch, waiting for completion before moving to the next. At the Workflow Task level, execute Activities in smaller batches within a single Workflow Task and introduce brief pauses or sleeps between batches.
Temporal Developer Skill for AI agents
The Temporal Developer Skill gives AI coding agents expert-level knowledge of Temporal's programming model, including workflow determinism rules, activity patterns, retry policies, error handling, testing strategies, worker configuration, versioning, and common gotchas. It is available at https://github.com/temporalio/skill-temporal-developer and works with Claude Code, Codex, Cursor, and any agent that supports Skills.
Install Temporal Developer Skill in Claude Code
To install the Temporal Developer Skill in Claude Code: (1) Add the Temporal skills marketplace with `/plugin marketplace add temporalio/claude-temporal-plugin`, (2) Install the Temporal Developer Skill with `/plugin install temporal@temporal-marketplace`. Restart Claude Code after installing.
Install Temporal plugin in Cursor
Install the Temporal plugin in Cursor from the Cursor Marketplace at https://cursor.com/marketplace/temporal, or run `/add-plugin temporal` in Cursor's agent chat.
Install Temporal skill with npx
To install the Temporal Developer Skill with npx (works with Claude Code, Codex, Cline, and other agents), run: `npx skills add https://github.com/temporalio/skill-temporal-developer`. Restart your coding agent after installing.
Manually install Temporal Developer Skill
To manually install the Temporal Developer Skill, clone the repository into your Claude skills directory: `git clone https://github.com/temporalio/skill-temporal-developer.git ~/.claude/skills/temporal-developer`. For agents other than Claude, change the target directory accordingly. Restart your coding agent after installing.
Temporal Knowledge Base MCP Server
The Temporal Knowledge Base MCP Server provides AI tools real-time access to best practices compiled from Temporal's documentation, educational materials, community forum responses, and Slack channels. It is publicly available but requires one-time login with a Google or GitHub account to enforce rate limits and prevent abuse. Only an opaque user ID is used for rate limiting; personal data is not accessed or collected. The server URL is https://temporal.mcp.kapa.ai.
Register Temporal Knowledge Base MCP Server in Claude Code globally
To register the Temporal Knowledge Base MCP Server globally in Claude Code (available in all projects), run: `claude mcp add --scope user --transport http temporal-docs https://temporal.mcp.kapa.ai`. Then restart Claude Code and run `/mcp` to authenticate with your Google account.
Register Temporal Knowledge Base MCP Server in Claude Code for a specific project
To register the Temporal Knowledge Base MCP Server for a specific project only in Claude Code, run: `claude mcp add --transport http temporal-docs https://temporal.mcp.kapa.ai` (omit the `--scope user` flag). This stores the configuration in the project's `.mcp.json` file.
Add Temporal Knowledge Base MCP Server to Claude Desktop
To add the Temporal Knowledge Base MCP Server to Claude Desktop: (1) Open Claude Desktop settings, (2) Navigate to Settings > Connectors, (3) Add a new MCP server with the URL https://temporal.mcp.kapa.ai.
Eager Workflow Start best practice: combine with Local Activities
Combine Eager Workflow Start with Local Activities for the greatest total latency reduction. Eager Workflow Start eliminates the Matching overhead on the first Workflow Task, and Local Activities eliminate server round-trips within each Workflow Task.
Eager Workflow Start best practice: Worker timing
Start the Worker before executing the Workflow so it has an available slot. In Go, use w.Start() and defer w.Stop(). In Python, use async with Worker(...). In Java, call factory.start() before creating the workflow stub.
Eager Workflow Start best practice: do not rely on eager dispatch
Do not rely on eager dispatch always firing. The server falls back to normal dispatch if no local slot is available (for example, the Worker is at capacity). Design the Workflow to work correctly in both cases.
Eager Workflow Start best practice: shared client connection
The Worker and the workflow starter must use the same WorkflowClient instance (Java), client.Client (Go), or Client (Python). A Worker using a different connection cannot receive eager tasks from another client.
Eager Workflow Start best practice: resource sharing in co-located deployments
When a Worker runs in the same process as a request handler, they share CPU, memory, and failure domains. A spike in activity execution can slow request handling, and vice versa. Monitor Worker CPU, Workflow Task execution latency, and task queue depth to ensure Worker load does not affect client-facing latency.
Resumable Activity best practice: log and record state transitions
Log and record state at every transition, as AWAITING_CORRECTION and AWAITING_APPROVAL states can last hours or days. Structured log lines at each transition make the audit trail clear. Update a Search Attribute at each transition (for example, a Keyword attribute storing the current status) so operators can filter and query workflows by state directly from the Temporal UI or CLI.
Resumable Activity best practice: proactive operator notification
The AWAITING_CORRECTION transition is a good point to send an alert — an email, a Slack message, or a ticket — rather than waiting for the operator to notice in the Temporal UI.
Best practice: use no more than five priority levels
The PriorityKey range is 1–5. Keep levels coarse—for example, 1 = urgent, 3 = normal, 5 = batch—rather than mapping fine-grained business importance to many values.
Best practice: reserve priority 1 for genuinely urgent work
If high priority is the fallback when no priority is specified, the highest level fills with routine work and the feature provides no benefit. Priority 1 should be reserved for truly urgent tasks only.
Best practice: set PriorityKey at Workflow start, not inside Workflow code
Set the priority in the start options before execution begins. Workflow code cannot change its own priority after it starts.
Best practice: override Activity priority deliberately
Activities inherit the parent Workflow's priority by default. Override only when a specific Activity must run at a different level than its Workflow.
Best practice: monitor queue depth per priority level
Sustained backlog growth at a priority level signals that Worker capacity is insufficient for the submitted load at that level.
Encrypt sensitive values on Lambda
Encrypt sensitive values like TLS keys or API keys. Refer to AWS documentation for options.
Namespace naming conventions - case sensitivity
Temporal Cloud Namespace names are case-insensitive, so MyNamespace and mynamespace refer to the same Namespace. In open source Temporal, Namespace names are case-sensitive, so MyNamespace and mynamespace are different Namespaces. To avoid confusion across environments, always use lowercase.
Namespace naming pattern and character limits
Use a pattern like <use-case>-<domain>-<environment> to name Namespaces. Use case should be max 10 characters (examples: payments, fulfill, orders). Domain should be max 10 characters (examples: checkout, notify, inventory). Environment should be 3 characters (examples: dev, stg, prd). Examples: payments-checkout-dev, fulfill-notify-prd, orders-inventory-stg. Temporal Cloud Namespace names are limited to 39 characters total.
Namespace boundaries affect Temporal Cloud operations
In Temporal Cloud, a Namespace boundary affects APS limits and rate limiting, access control and credential scope, blast radius for misconfigured or overloaded Workers, observability boundaries for dashboards and alerts, and operational overhead for provisioning, tagging, and lifecycle management.
Namespace per use case and environment pattern
Pattern 1 is for simple configurations without multiple services or team boundaries. Naming convention: <use-case>-<environment>. Examples: payments-prd, orders-dev. Choose this pattern when one team owns the use case, environments need clean separation, and workload volume and criticality do not yet require further isolation.
Namespace per use case, service, and environment pattern
Pattern 2 is for multiple services that are part of the same use case communicating externally to Temporal via API (HTTP/gRPC). Naming convention: <use-case>-<service>-<environment>. Examples: payments-gateway-prd, payments-processor-prd. Choose this pattern when services need separate credentials or access policies, one service can exhaust APS or operational limits independently of the others, or teams want separate ownership of deployment, alerting, or on-call boundaries.
Namespace per use case, domain, and environment pattern
Pattern 3 is for multiple services that need to communicate with each other using Temporal Nexus to connect Workflows across Namespace boundaries, providing better security, fault isolation, and modularity than sharing a Namespace. Naming convention: <use-case>-<domain>-<environment>. Examples: payments-checkout-prd, payments-refunds-prd. Choose this pattern when multiple teams or domains need independent release cadence and ownership, failures in one domain should not affect the others, you want a stronger permission boundary between capabilities, or you plan to expose cross-Namespace contracts through Nexus. When multiple teams share a Namespace, prefix each Workflow ID with a service-specific string to ensure uniqueness, and Task Queue names must also be unique within the Namespace.
Namespace per tenant pattern
Pattern 4 uses a separate Namespace per tenant only when each tenant needs a true isolation boundary. This is usually appropriate only for a small number of high-value tenants that require dedicated credentials and access control, tenant-specific rate limits or capacity decisions, strict compliance or data-isolation boundaries, or independent debugging, alerting, and operational ownership. For most SaaS use cases, a shared Namespace with per-tenant Task Queues is simpler and more scalable.
When to split a Namespace
Revisit Namespace topology when one workload is consuming enough APS that it regularly threatens others in the same Namespace, one team needs tighter access controls or dedicated credentials, production troubleshooting requires clearer dashboards, alerts, or ownership boundaries, one application or domain is business-critical enough that its blast radius must be reduced, or a tenant or regulated workload needs stronger separation than Task Queue isolation can provide. Splitting a Namespace increases safety, but it also adds overhead for provisioning, tagging, credentials, and cross-Namespace coordination. Use Nexus where possible instead of sharing Temporal primitives across team or domain boundaries.
Open source production safeguard - use Authorizer
Use a custom Authorizer on your Frontend Service to set restrictions on who can create, update, or deprecate Namespaces. If an Authorizer is not set, Temporal uses the nopAuthority authorizer that unconditionally allows all API calls. On Temporal Cloud, role-based access controls provide namespace-level authorization without custom configuration.
Temporal Cloud production safeguard - deletion protection
Enable deletion protection for production Namespaces in Temporal Cloud to prevent accidental deletion.
Temporal Cloud production safeguard - High Availability
For business-critical use cases with strict uptime requirements, enable High Availability features for a 99.99% contractual SLA.
Temporal Cloud production safeguard - Infrastructure as Code
Use the Temporal Cloud Terraform provider to manage Namespaces. If Terraform is not suitable, scripting against the Cloud Ops API or tcld is a good alternative. This provides documentation of each Namespace's purpose and owners, prevention of infrastructure drift, and version-controlled configuration changes. Use prevent_destroy = true in Terraform configuration to prevent accidental Namespace deletion via Terraform. This is separate from Temporal Cloud deletion protection, which prevents deletion through any interface.
Temporal Cloud Namespace tagging
Tags are key-value metadata pairs that help organize, track, and manage Namespaces in Temporal Cloud. Tags complement naming conventions by adding metadata that does not fit in the Namespace name. Recommended tag categories include: environment (deployment stage: dev, staging, production), team (owning team: platform, payments, identity), division (business unit: engineering, finance, ops), criticality (business importance: high, medium, low), data-sensitivity (data classification: pii, pci, public), and latency-sensitivity (performance tier: realtime, batch, async).
SDK Client Namespace configuration
Set Namespaces in your SDK Client to isolate your Workflow Executions. If you do not set a Namespace, all Workflow Executions started using the Client will be associated with the default Namespace. You must register a Namespace before setting it in your Client.
Problems without proactive rate limiting for downstream APIs
Without proactive rate limiting when calling downstream services, users may experience: HTTP 429 errors where Activities overwhelm APIs causing 'Too Many Requests' errors, account suspension from repeated violations leading to temporary or permanent bans from the downstream service, failed Workflows that cannot continue to make progress without proper retry handling, wasted execution where Activities that will fail due to rate limits consume Worker resources, and cascading failures where one Workflow's excessive API calls could affect other Workflows.
Common external API rate limit examples
Common rate limits enforced by external APIs include: SendGrid 100 emails/minute on free tier and 1000/minute on paid, Stripe 100 requests/second globally, OpenAI 60 requests/minute and 90,000 tokens/minute, Twilio 1 request/second per phone number. These limits vary by provider service and plan.
Monitoring and adjustment for rate-limited Task Queues
Monitor API usage and track actual API calls versus configured rate limits to adjust configured rate limits in Temporal Task Queues. API providers may change rate limits, so monitor and update Worker configuration accordingly. Some APIs allow short bursts above stated limits; test to determine safe Temporal limits. Set Temporal rate limits to 90% of API limits to leave a safety buffer.
Using separate API keys and Task Queues for multiple environments
Use separate API keys and Task Queues for dev/staging/prod environments to prevent one environment from affecting another and to manage rate limits independently per environment.